ResultRef handle to its full output on disk. The lead’s window grows by a
sentence per task, not by the task. Long children surface a content-free
progress signal every ~30 seconds so you can watch them advance without them
completing, and every child runs isolated under an attenuated lease in its own
jailed workspace — so this longevity is never bought with a loss of safety.
Coordinator vs. profile.
autonomy.role is a sibling of
autonomy.profile, not a replacement. The profile decides
how much an agent may do (its capability set and guard rails). The role
decides how it spends its own window — worker (the default) does the work
inline; coordinator narrows the lead to an orchestration-only tool surface so
heavy work must be delegated. The role never widens a capability; it only
narrows the tool surface. The two dials are orthogonal.The run-for-days pattern
A normal agent does its work in its own context: it reads the files, runs the searches, and accumulates every intermediate result in the window it is reasoning in. That is fine for a short task and fatal for a long one — the window fills, compaction kicks in, and the lead starts losing the thread of what it was coordinating. The coordinator pattern inverts this. The lead does not do heavy work inline. For each heavy task it spawns an isolated child (viasessions_spawn), the child does the work in its own
context window, and it returns:
- a bounded summary sized for the lead’s context, and
- a
ResultRef— a handle to the child’s full, unredacted output on disk.
Tier-0 micro-compaction (every turn)
Underneath the periodic compaction, a Tier-0 micro-compaction pass runs on every turn — no LLM, no cost. It evicts stale tool results for the read-only tool set (read, grep, find, ls, exec, web_fetch,
web_search), keeping the last K (the
observationKeepWindow knob, default 25). It never
touches user or assistant messages, thinking blocks, or recalled memory — only
the ephemeral tool output beyond the keep window.
Unlike the TTL-expiry and token-ceiling triggers (which only fire after an idle
gap or once the context crosses a size ceiling), this runs unconditionally so the
long-running coordinator’s context stays flat continuously, not only after an
idle period. The replacement placeholder is cache-stable: frozen by
tool-call-id and protected behind the cache fence, so a cleared result is
byte-identical turn-over-turn and never busts the prompt cache (see
the cache-stable prompt architecture).
autonomy.role: coordinator
Set the role in config — it is a single field on the
autonomy block:
~/.comis/config.yaml -- a long-running coordinator lead
role defaults to worker, which is exactly today’s behavior — existing
agents are unchanged, and a default install is byte-identical. Setting
coordinator does one thing: it narrows the lead’s tool surface to the
orchestration set, so the lead cannot do heavy work inline and must delegate.
The coordinator surface. A coordinator lead is stripped to:
- Orchestration —
sessions_spawn,pipeline,cron,message, plus the session-management tools (sessions_list,sessions_history,session_status,session_search). - Read-only drill-in —
read,grep,find,ls,memory_search,memory_get,extract_document. These are how the lead inspects a child’sResultRefon demand (next section) without ingesting it. - Observability — the
obs_querytool, so the lead can ask about the health and outcome of the children it is coordinating.
exec, edit, write, and
browser are not on a coordinator’s surface. That exclusion is the
delegate-then-synthesize doctrine made structural — the lead has no inline way
to run a command, edit a file, or browse, so a heavy task has to become a child
spawn, and the lead’s job becomes routing the work out and synthesizing the
results back.
Narrows the tool surface; never widens a capability.
role: coordinator
changes only which tools the lead may call. The agent’s resolved
capabilities (the orch:* permission tokens from its
profile) are byte-identical with and without the role —
a no-escalation invariant pinned by an architecture test. obs_query is a tool
name on the coordinator surface, not a new capability: there is no
obs:read capability to grant. The role can only ever take tools away, never add
authority.Window isolation
The reason a coordinator stays context-flat is that each child runs in a genuinely isolated context loop — not a fork.- Isolated, not fork-mode. A forked child copies the parent’s prefix into its own window, which is the opposite of what longevity needs. A coordinator’s children are isolated spawns: each gets its own fresh context budget and its own message list. The child does not start from the lead’s transcript and, critically, the child loop never writes back into the lead’s message list.
- Only the summary crosses back. The single thing that enters the lead’s
window from a finished child is its bounded return summary (built from the
child’s condensed result, not its raw transcript). The child’s full
conversation, tool calls, and intermediate output stay in the child —
reachable later via the
ResultRef, never injected. - Parent history defaults to none. When the lead spawns a child it passes
include_parent_history: "none"by default, so the child does not even start with the lead’s history. (You can opt into a condensed parent summary per spawn, but never the raw transcript — see Subagent Context Lifecycle.)
Summary + ResultRef
A child can produce megabytes — a long file dump, a full crawl, a giant
analysis. The point of the ResultRef is that none of that has to enter the
lead’s window for the lead to have it.
When a child finishes, Comis materializes its full output to the child’s
own jailed workspace and hands the lead two things in the completion
announcement:
- the bounded summary (for context), and
- a
ResultRefhandle — a reference plus the byte size and kind of the stored output.
ResultRef carries a full query engine — five in-jail extraction methods,
all on the coordinator’s read-only surface:
sql/jsonpath methods run the DuckDB CLI daemon-side (the jail stays
--unshare-net), hardened to a read-only slicer: --readonly :memory:, extension
auto-load disabled, and any extension/file-write/network statement refused before
DuckDB is ever spawned. DuckDB is a prerequisite the daemon host provides; if it is
absent, sql/jsonpath honest-degrade and read/grep/jq keep working. See
the orchestrate tool reference
for the full extraction API and the hardening details.
Measured savings
The run-for-days pattern keeps a lead context-flat by construction; anorchestrate run additionally puts a measured number on the
saving. When a run materializes a high-volume tool return as a ResultRef on disk instead of
re-entering it into context, Comis records how many tokens that avoided — a labeled
estimate:
- Materialized-only. Only bytes actually written to the run’s
results/directory count — a payload the script streamed past without materializing is never credited. - Post-bounce actual. The subtracted term is what the script actually re-entered (its
console.logstdout, after in-jail slicing), so a run that re-logs its wholeResultRefnets ~zero saving, honestly. - Never negative.
estSavedTokensclamps at0;savedRatio— the saved fraction of the would-be cost — is reported only when there was a would-be cost to divide by.
- Per run —
comis explain <sessionKey>carries it assavings: { estSavedTokens, savedRatio }on each entry of itsorchestratesection. - Daemon-wide —
comis fleetrolls it into anorchestrate_efficiencyfinding: the run count and the summed estimated tokens saved across the window.
estSavedTokens, never savedTokens), it
reads as a directional efficiency signal — “this run kept ~30k tokens of fetched data out of
context” — not a billing figure.
Resumable runs
A genuinely long pipeline should survive a daemon restart mid-run instead of starting over. That is opt-in and nested under the durability posture: withautonomy.durability.orchestrateResume
on (off by default, inside the already-off-by-default autonomy.durability
block), an orchestrate run gains three durability affordances.
- Checkpoint / resume inside the script. The in-jail SDK exposes
checkpoint(stateJson)andresume(): a run persists a small progress blob (a longer-TTLResultRef, same per-file/per-run caps as any result) and reads the last one back on a later attempt.resume()returns that state wrapped as untrusted data — it re-enters as data the script inspects, never as control it executes. - Timeout → a resumable row. When a resume-enabled run hits its wall-clock
timeout, instead of only being killed and rejected it records a content-free
resumable row
{ runId, scriptRef, checkpointRef }and skips the run-end cleanup so the pinned script + checkpoint survive.orchestrate({ resumeRunId })then re-spawns the pinned original bytes (thescriptargument is ignored on resume — no new bytes are ever accepted) andresume()hands back the last checkpoint, so the run picks up where it left off. - Restart survival. The resumable row rides the durable-runs boot sweep: after a
restart the daemon surfaces the run as resumable once it verifies the pinned
script + checkpoint are still on disk (it does not re-execute the bytes on
boot — resuming is an explicit
resumeRunIdcall). If the checkpoint is gone, the run is surfaced as an honest orphan (a closed-enum reason) rather than silently lost, and its leftover workspace artifacts are reclaimed.
orchestrateResume off, a timed-out run is killed
and cleaned exactly as before, and no durable row is written. For debugging, an
operator can deterministically re-run a durable run’s pinned bytes against a
separate replay socket with comis orchestrate replay <runId>.
30-second progress fork
A delegated task can run for a long time, and an isolated child by design does not stream its work into the lead’s window. So that a long child does not look stuck, Comis attaches a read-only progress fork to it. Roughly every 30 seconds, while a child is still running, the fork emits a content-freesession:sub_agent_progress event:
- It is an advance signal, not a result. The
progressLineis a short status (a few words likerunning, step N); the payload carries no child output, message body, or tool result — it is content-free by design (the same counts-and-ids-only observability rule the rest of the platform follows). - It is read-only. The fork does not re-run the child, call a tool, or spawn anything — it only reads the child’s elapsed time and step count and emits the event. It is read-only by construction: the fork has no execution channel at all, so it cannot burn budget or fan out.
- It is lifecycle-bound. The fork starts when the child is spawned and stops on the child’s terminal boundary, so it never outlives the child and never leaks a timer.
Security: isolation is never escalation
Spinning up a fresh-window child must not become a way to gain authority. It does not.- Each child inherits an attenuated lease. A child’s capabilities are the
intersection of the parent’s capabilities and what the child requested
(
child_caps = parent_caps ∩ requested_caps) — a child can only ever be a subset of its parent, never a superset. A narrow lead spawns only narrow children. - Each child writes to its own jailed workspace. A child’s output is materialized to its own per-child jailed workspace, resolved from the child’s agent id — never the lead’s workspace and never a shared, un-jailed location.
- The longevity machinery does not touch the lease. Window isolation, the
ResultRefmaterialization, and the progress fork are all built on top of the existing attenuated-lease and jailed-workspace primitives — they read and surface, they never mint or broaden. A coordinator that runs for days spawns hundreds of children, and every one of them is bounded by the same intersection rule as the first.
Related
Autonomy
Named profiles, the capability set the role narrows around, and the
attenuated lease that bounds every child.
Subagent Context Lifecycle
How a child is prepared, run, condensed, and announced back to the lead.
Context Management
How the lead’s own window is budgeted — the layer the longevity invariant
sits on top of.
Compaction
The periodic compaction term in the longevity equation.
Config YAML Reference
The autonomy.role field and every other autonomy.* knob.
Sessions Tool
sessions_spawn and the session tools a coordinator orchestrates with.
