Autonomy vs. capabilities. A profile is the operator-facing dial. It
resolves to a set of capabilities (the
orch:* permission tokens an agent
holds) plus guard rails (budget ceiling, spawn-rate limit, message quota).
Capabilities are enforced by a single gate on every privileged action — see
the Capability Model for the enforcement side.
This page is about choosing and reading the posture.The Zero-Config Default
If you set noautonomy block at all, every agent resolves to the standard
profile. That is the great-out-of-the-box posture: the agent can orchestrate
(spawn sub-agents, author cron jobs, run execution graphs, use skills, read and
analyze content, write to its jailed workspace) under always-on guard rails, and
it messages only its own channel under an hourly quota.
~/.comis/config.yaml -- nothing to set; standard is the default
standard and holds exactly those surfaces, capability-gated. It
is an explicit grant via a named profile, not a compatibility shim.
Named Profiles
Setautonomy.profile to pick a posture. There are four:
unattended is active; max ships later. unattended’s capability set
stays standard-equivalent (the structural floor still bounds every cap — no
over-grant), but its mode behaviors are on in this release: a run that would
ask a human resolves to deny + escalate (never a blocking prompt), a
denial breaker aborts a run that keeps hitting a floor block, and an operator
can evict an in-flight run back to default — see
The unattended profile below. max’s extra surface
(sandbox auto-allow) is not in this release: selecting it resolves to
standard’s capability set and attaches a not-yet-available notice (logged at
boot). Comis never silently grants a capability whose
enforcement floor is not yet built, so you are told rather than over-granted.
(Durable runs are not one of the deferred surfaces — they are enabled by
default and remain orthogonal to the profile; see
Durability & Resume below.)The unattended profile
unattended is for a run with no human present — a long-lived cron pipeline,
an overnight batch, a background coordinator. Its capability set is exactly
standard’s (the structural floor bounds every cap; there is no over-grant), but
its mode changes how the run behaves when it reaches a decision a human would
normally make. The whole point is that such a run never stalls waiting on a
person and never burns its budget retry-looping a thing it is not allowed to
do.
- Never-hang: a would-ask becomes deny + escalate. Under
standard, an action that needs approval pauses for a human. Underunattendedthere is no human to pause for, so that same action does not block — it resolves to a denial and the platform escalates it to your operator out-of-band. The run keeps going on what it is allowed to do; it does not sit on a prompt forever. - Outward actions still escalate, never auto-send.
unattendeddoes not loosen the outward floor. A send to a non-origin endpoint must already carry exact endpoint authority and the configured grant; browser actions and other hard-to-reverse outward steps are still proposed for approval, never auto-decided — exactly as understandard. Never-hang changes waiting into escalation; it does not mint routing authority or change propose into send. - The denial breaker stops a retry-loop. A run that keeps trying a blocked
capability would otherwise spin — each attempt denied, the budget draining for
nothing. After
denialBreakerNconsecutive floor blocks (default 5) on one run, the denial breaker trips: the run is aborted (anexecution:abortedwith reasondenial_breaker) and the operator is told, rather than letting it retry-loop the budget away. An actual allowed step in between resets the counter, so a single stray denial inside productive work never accumulates to a trip. - An operator can evict an in-flight run to
default. If anunattendedrun misbehaves, an admin can callautonomy.evictwith itsrootRunId. This is demote-but-continue: the run is not killed — it keeps running, but underdefaultfrom its next gated decision onward (so it reverts to the conservative posture mid-flight, no waiting for a restart or the next spawn). This is distinct fromlease.revoke(cooperative stop) andrun.kill(hard stop). - Fail-closed: an unresolvable mode is treated as
default. Mode resolution is fail-closed. If the run’s autonomy mode cannot be determined for any reason, it is treated asdefault(the safe profile) — never something broader. This posture is controlled byevictOnPolicyUnreachable(default true), and it is the same safe target theautonomy.evictdemotion lands on.
unattended knobs are denialBreakerN (default 5) and
evictOnPolicyUnreachable (default true); both live in the
autonomy.* config table and
apply to every profile, but they are the dials that matter most for a no-human run.
What standard turns on
The standard profile resolves these nine floor capabilities:
orch:message is on by default but scoped to the agent’s origin channel
only — it answers where it was spoken to. The cap covers the genuinely-outward
send subset (message.send/reply/react); editing, deleting, fetching, and
attaching stay admin-only. Every call must also match the complete endpoint
bound to the current turn. message.channels controls whether a non-origin
endpoint already selected by a trusted scheduled run is grantable; it does not
discover an endpoint or authorize arbitrary channel_type/channel_id
coordinates.
Two surfaces are off in every default profile because they are outward and
hard to reverse: orch:browse (a real browser) and non-origin messaging beyond
the endpoint already bound to the run. Opt into the browser explicitly with
browse: true; even then it always escalates for approval — it is never
auto-decided.
Learned procedures stay advisory
When an agent solves a task with anorchestrate run and that
approach proves out across verified successes, the learning layer distils it into an advisory
procedure doc — a learned skill that records the run’s tool footprint. It surfaces into the
agent’s <available_skills> listing like any other learned skill, bounded by a per-agent
budget (learning.reflect.maxProcedureDocsSurfaced,
default 10) that, when exceeded, keeps the most-corroborated procedure docs in a stable
listing order, so a burst of procedures can never bloat every prompt. Crucially, surfacing a
procedure doc grants no new capability and no new execution path: it is guidance the model
re-authors into a fresh orchestrate script, run under the same capability gate and jail
as any other run. A learned procedure can never widen the agent’s autonomy posture — the profile
and its guard rails still bound every tool the re-authored script may touch.
Overriding Individual Knobs (Progressive Disclosure)
Most operators set onlyprofile: (or nothing). When you need a single
adjustment, set just that field — an explicit field overrides the profile,
so you do not have to restate the whole posture.
~/.comis/config.yaml -- standard, but with a tighter budget and the browser on
web, analyze, write, browse) each map to the
matching orch:* capability. The scalar guard rails (aggregateBudgetUsd,
maxConcurrentSelfAgents, maxSelfSpawnRatePerMin, cronSelfMax), the
lease.leaseMaxTtlMin renewal ceiling (in minutes — the maximum a jailed-surface
capability lease can renew up to, so revoke actually stops renewal), and the
message block (channels, maxPerHour) are all individually overridable.
The always-on bounds are also exposed as nested blocks for finer control, each
defaulting safe so a runaway agent is bounded out of the box:
budget.{ aggregateUsd, tokens, wallClockMs }— the per-root-run ceiling.aggregateUsdis the priced 0) model the $ limb would count as free.rate.{ perRootCallsPerSec, perSocketCallsPerSec, connectionChurnPerMin }— the call-rate ceiling that bounds a call storm without tripping a legitimate burst.spawn.{ maxConcurrentSelfAgents, maxSpawnDepth, maxChildrenPerAgent }— the tree-wide spawn shape (maxConcurrentSelfAgentsis the alias of the flat field; depth defaults to 3, fan-out to 5).outward.{ originOnly, perTargetGrants, volumeCap }— outward-send governance.originOnlykeeps sends to the agent’s own channel by default; a new target needs an explicitperTargetGrantsentry;volumeCapbounds a mass-recipient send.
Connected MCP Tools
Theorch:mcp
capability — which lets a jailed orchestrate
script reach connected MCP server tools — is a floor capability held by
default on the autonomy-bearing profiles (standard/unattended/max), like
orch:write. Holding the cap opens no server, though: reachability is gated
by the per-server autonomy.mcp.allow allowlist, which is empty by default.
So a fresh agent holds orch:mcp yet reaches no MCP tool until you allowlist the
exact {server -> tools} it may call.
~/.comis/config.yaml -- expose two allowlisted MCP tools to one agent
autonomy.mcp defaults to { enabled: false, allow: {} }. On standard/unattended/max
the orch:mcp cap is floor-held regardless of enabled, so a fresh agent’s MCP
surface is dark at exactly one operative layer — the empty allowlist. Setting
allow is the only step needed to make a tool reachable:
allowis the per-server inbound allowlist — the operative gate. Each key is a connected server’s name; itstoolsis the explicit list of tool names reachable on that server (there is no*wildcard — you name each tool), andclassificationis the inbound tier. Absent fromallow⇒ denied by absence.enabledgrantsorch:mcpto theassistantprofile (which has autonomy off and no floor caps) as an explicit opt-in. On standard/unattended/max the cap is already floor-held, soenabledis not required there (it is a harmless no-op for the grant). There is no separate booleanmcp:toggle besideweb/analyze/write/browse; the nested block’s ownenabledfield is the assistant-opt-in signal, mirroringdurability.enabled.
classification is a three-tier inbound gate:
Reachability is thus unlisted ⇒ unreachable at every step: an unlisted
server, an unlisted tool on a listed server, or a listed server with no inbound
classification are all denied. Enforcement is the daemon’s, at the capability
socket — the config only ever widens from the dark default, and never past
what the operator explicitly names. The connected MCP tool runs daemon-side
so the jail stays
--unshare-net, and the management plane (connecting or
authenticating a server) is never reachable from a jailed script. This inbound
allowlist is a separate control from the outbound /mcp/v1 export policy
that governs which of this daemon’s tools are exposed to external MCP clients;
turning one on never affects the other.
One-shot auto-repair
When anorchestrate script fails on a recoverable
error — a bad import, a misused comis_tools call, or a TypeError — the runner
does one utility-model re-prompt that regenerates the script and re-runs it
once. It turns a small model’s near-miss into a working run in the same turn,
without bouncing the failure back to the main model for another inference turn.
This is class-gated off the model’s capability class — it is not a profile knob
and there is no config toggle:
The class is resolved from the agent’s model (an operator capability-class override on
the provider, otherwise the provider family). There is no
autonomy.autorepair
field to set: the model tier is the whole control, so there is no dead toggle to
drift out of sync. When the class is absent or unresolved it defaults to the
repair-eligible side (small), so an unknown-tier or keyless local deployment still
self-repairs rather than silently not.
The repair is bounded to exactly one attempt — a repaired script that fails again
surfaces the original error, never a retry loop. It runs on a cheap utility model (the
same keyless-safe path other utility passes use) and rides normal spend/budget
accounting, so the agent’s budget bounds it like any other call. The regenerated
script re-runs in the identical jail, capability, and lease envelope as the
original — it gains no new privilege.
Durability & Resume
Durability is enabled by default for autonomy-bearing agents. A long-running run is resumable across a daemon restart, with durable duplicate suppression and explicit uncertainty parking for outward sends. Setautonomy.durability.enabled
to false only when you deliberately want in-flight runs to remain
process-local.
~/.comis/config.yaml -- opt out of durable autonomy runs
- Resume or honest orphan — never a silent vanish. As a run advances it
stamps a checkpoint (its spawn tree, the consumed budget, a heartbeat) to
memory.db. On restart the daemon scans the live runs (after channels are up) and resumes each one, or — if its record cannot be trusted — orphans it and notifies the operator out-of-band. A run is never left in limbo. A daemon-wide watchdog sweeps any run whose heartbeat has lapsed paststaleHeartbeatMs(the same fate: resume or orphan-and-notify). - Duplicate suppression with honest crash uncertainty. Every
agent-originated outward send is wrapped in a durable, closed five-state
ledger (
send_attempt_started,unknown_after_send,committed,failed, orunresolved) keyed by one stable logical operation identity and its(rootRunId, step-index)pair. Repeating a retained committed operation returns its prior platform receipt without another send; every other existing state blocks another execution. If a call may have crossed the platform boundary but no durable receipt proves its outcome, the row is atomically parkedunresolvedand escalated. Startup applies that same rule to everysend_attempt_startedorunknown_after_sendrow: it does not query channel history and does not replay the operation. This covers both the directmessage.send/reply/reactpath and the sub-agent completion-announcement path — the announcement dead-letter queue uses the same retained operation identity and ledger. The typedcomis_tools.message_send/reply/reactoutward methods dispatch that samemessage.*path, so they ride the same protection. This protection is active only under durability. With durability off (an explicit opt-out), an outward send is a best-effort pass-through — still delivered, but without a retained operation record across a crash. Even with durability enabled, Comis does not claim universal exactly-once delivery: an ambiguous platform outcome requires operator verification before the operation can be attempted again. - A resumed run never broadens its authority. On resume the lease is
re-minted from the run’s persisted, already-attenuated capabilities — never
re-attenuated from a live parent (which could be broader), and never a superset
of what the run originally held. A
lease.revoke/run.killpoisons the persisted record (flips it torevoked), so a later boot can never resurrect pre-revoke capabilities — a revoked record is orphaned on resume, not re-minted. - DAGs resume at node-boundary granularity. A cron-fired multi-node execution graph interrupted by a restart resumes its incomplete nodes; already-completed nodes are not re-run. An outward operation repeated with the same retained identity is short-circuited when committed and blocked when uncertain; it is not blindly replayed. (Intra-node LLM/tool state is not checkpointed — resume is per node, not mid-node.)
- Plain sub-agents resume from a protected restart boundary. The durable checkpoint pins the child conversation, task declaration, model, budget, workspace-policy hash, and attenuated capabilities. Recovery validates that descriptor and then re-executes the declared task in the same child conversation. It does not resume a half-finished provider turn.
autonomy.durability):
The uncertainty rule is channel-independent. Comis does not treat a content
and time-window match in platform history as proof that one unique operation was
sent, and absence from a history window does not prove it was not sent. Recovery
therefore never converts an interrupted send into a
sent or not_sent
verdict. It parks the operation unresolved, blocks automatic replay, and
escalates with content-free identifiers so an operator can verify the platform
effect. This is deliberately narrower than exactly-once delivery.Resume scope. A resumed run is re-anchored under the bounded-autonomy
meter (bounded, killable, and with uncertain outward rows parked across the
restart) from its persisted checkpoint. DAG work resumes at an incomplete node
boundary, a plain sub-agent restarts its declared task from its validated
descriptor, and
orchestrate resumes only through an explicit
resumeRunId against verified pinned artifacts. None of these resumes a
half-finished provider turn or promises that every platform side effect occurs
exactly once.Legible Boot Logging
At startup Comis logs one INFO line per agent stating the resolved posture, so you can read what every agent was granted — and how to change it — from the boot logs alone, without turning on debug logging:changeField names the one config field that changes all of the above:
autonomy.profile. The line also carries an m1Notice whose text is
profile-specific: for an unattended agent it states that the never-hang mode
behaviors are active (deny + escalate, the denial breaker, operator evict) and
that the capability set stays standard-equivalent; for a max agent it states
that the extra surface (sandbox auto-allow) is not yet available.
Honest, Legible Degrade
Autonomy needs a jail to run its orchestration surfaces safely. On Linux, that jail is built from an unprivileged user namespace. If the host preflight for that namespace fails, Comis does not quietly run the agent unjailed. Instead it downshifts that agent to theassistant profile (no orchestration
surfaces, the agent only replies) and says so, two ways:
- A WARN at boot naming the downshift, with an actionable hint and
errorKind: "precondition". - A doctor finding an operator can read, naming the same remediation.
sysctl kernel.unprivileged_userns_clone=1, and on AppArmor-restricted distros
kernel.apparmor_restrict_unprivileged_userns=0) and restart. If you intend to
run an assistant-only agent, set autonomy.profile: assistant explicitly to
silence the warning.
Related
The agent reads its own copy of this guidance. A bundled
autonomy skill
(packages/daemon/bundled-skills/autonomy/) auto-seeds into every agent’s
workspace and is surfaced to the model: it is the on-demand, model-facing
decision guide for when to reach for an orchestration surface and how to
route a multi-step task through orchestrate. A
one-paragraph always-on doctrine carrying the same contract and routing rule
also rides every run’s system prompt, so the floor applies even when the model
never opens the skill.Capability Model
How capabilities are enforced by a single gate, and why no capability ever
implies admin access.
Safety
The budget cap, circuit breaker, and rate limits that bound an autonomous
agent.
Config Reference
Every
autonomy.* field, its type, and its default.Security Model
The orthogonal capability axis and the deny-by-origin control-plane boundary.
