The agent runtime
How a run actually executes: one inbound event spawns one process, durable resume gives it memory of the last run on the same task, and it fans out sub-agents, renders its own UI, and re-runs its dependents when an upstream changes.
A run is one execution of your employee: one inbound event wakes one process, it works the folders, and it exits. Continuity, sub-agent fan-out, live UI, and dependency-aware re-run all build on that single mechanism. The runtime is a native Claude Code spine. Jetsyn wraps it with provisioning, credential isolation, and transport, and the stock claude process assembles its own prompt from the cell's files.
The lifecycle of a run#
The employee is not a daemon. There is no long-lived agent to attach to. The cell persists on its volume; the employee is summoned per event and exits when the turn is done. Every run walks the same path:
- 1
Trigger
An inbound event reaches the cell: a chat message, a task assignment, a channel webhook, or a re-run from an upstream change.
- 2
Spawn
The runtime spawns exactly one
claudeprocess bound to the cell, keyed to a deterministic session id for that task. - 3
Work
The process reads the folders, dispatches sub-agents, and streams output. It rewrites its live panel to
ui.jsonand enqueues any side effects as intents. - 4
Drain
A privileged parent drains the intents queue every few seconds, so progress streams to the customer while the run is still working.
- 5
Exit
The process finishes the turn and exits. The transcript, the Brain memory, and the run files stay on the cell volume.
- 6
Resume
The next event on the same task resumes the recorded transcript by id, and the employee continues where it left off.
run_task() is the single public entry point for a headless run. Because every caller reaches it, a gateway, the strategy engine, a re-run driver, the transport stays runtime-agnostic: the caller hands off a task and never needs to know which runtime executed it. A first touch on a task triggers a fresh run; a follow-up on the same task resumes the existing one.
Availability
Available now: the default cli runtime, a bare claude subprocess, proven in production. Coming soon: an Agent-SDK runtime selectable with JETSYN_AGENT_RUNTIME=sdk. Because run_task() is the only entry point, switching runtimes changes no caller. An unrecognized value falls back to cli.
Note
The intents queue is the credential seam: the employee proposes side effects, a privileged parent executes them, and the employee never holds your tool or provider tokens. That mechanism is covered in Gateways.
Durable resume#
Continuity comes from a deterministic session id. It is derived from the workspace and the task as a uuid5 of slug:task_id, so the same task always maps to the same id, across processes and across cell restarts. The first run on a task pins that id; a later run on the same task resumes the recorded transcript by id.
The runtime never forks the session (a fork mints a new id and loses the thread) and never disables session persistence (which would discard the transcript). Those two invariants are what make a follow-up feel like a coworker who remembers yesterday's work rather than a fresh stranger.
Resume also self-heals. If a resume points at a transcript that no longer exists, the runtime retries once with a fresh id and clears the stale transcript and any pending intents, so a missing transcript recovers instead of failing the run.
Tip
The mental model: memory is keyed to the task. A follow-up on the same task resumes and remembers; a genuinely new task starts a clean session. Reuse a task_id when you want continuity, use a new one when you want a fresh start.
Sub-agent fan-out#
A run does not do everything in one context. The main employee dispatches specialist sub-agents through its Task tool, each with its own tool scope, and composes their results. Fan-out is one level deep: sub-agents never carry the Task tool themselves, so a sub-agent cannot recursively spawn more.
Interactive: explore and worker
The interactive runtime ships two generic, composable sub-agents. The main agent picks one by intent: explore to investigate without touching anything, worker to make changes.
| Sub-agent | Tool scope | Use it for |
|---|---|---|
| explore | Read, Glob, Grep (read-only) | Reading the folders and gathering context with zero side effects. |
| worker | adds Write, Edit, Bash | Producing files and making changes once the plan is clear. |
Headless: workflow personas
In a workflow-driven run, sub-agent personas are declared in the workflow and passed to the process as validated --agents definitions. Schema validation is a hard gate: a malformed persona stops the run rather than launching a half-defined agent. Named steps run in order behind a completion gate, so a step does not begin until the prior one emits its completion signal, and the run stops rather than advancing out of order.
The agent-to-UI contract#
An employee renders its own UI as it works. It writes a ui.json file and one generic frontend renderer draws it. There is no per-agent React tile: add a new employee and its panel appears for free. The agent writes .claude/agents/<id>/ui.json, rewriting the whole file at each step (queued, working, blocks, done) so the panel fills live. Each write is atomic (temp file plus rename), so a reader never catches a half-written file.
The envelope is a small, closed object. Unknown top-level keys are rejected.
| Field | Required | Notes |
|---|---|---|
v | yes | Schema version. Currently 1. |
agent | yes | Role id, e.g. enrichment. |
title | yes | Panel title, e.g. Enrichment. |
status | yes | queued | working | done | error. |
headline | yes | One line: what the agent is doing right now. |
progress | no | Integer 0 to 100 for an optional progress bar. |
blocks | yes | Ordered render primitives (may be empty). |
updated_at | yes | UTC ISO-8601, whole seconds, Z suffix. |
blocks is a closed set of seven primitives. Each block is a type plus exactly the fields listed, and no others.
| Block type | Fields |
|---|---|
text | text |
markdown | markdown |
list | items (string array) |
kv | items of { key, value } |
link | label, href |
preview | url, optional label |
action | action, label, optional task_id |
{
"v": 1,
"agent": "enrichment",
"title": "Enrichment",
"status": "working",
"progress": 40,
"headline": "Searching acme.com",
"blocks": [
{ "type": "kv", "items": [ { "key": "Company", "value": "Acme Inc" } ] },
{ "type": "list", "items": [ "Founded 2019", "B2B SaaS" ] },
{ "type": "link", "label": "Website", "href": "https://acme.com" }
],
"updated_at": "2026-07-02T12:34:56Z"
}Note
The shape is deliberately closed so one renderer can draw every agent. To add a new render block, extend the block-type set in the shared schema and keep it closed. The generic frontend picks it up with no per-agent tile.
Dependency-aware re-run#
Employees form a dependency graph: some read what others produce. When an upstream output changes, the dependents must re-run, and in the right order. The runtime models this as a DAG of employees. Changing an upstream node computes the dirty set (that node plus everything downstream of it), and the re-run driver runs the dirty set in topological order.
Every re-run is a resume, not a fresh start. Each dependent employee picks up its own durable session and revises its prior work rather than redoing it from scratch, so a change ripples through the org chart while every employee keeps its memory.
Re-run from a chat message
A re-run can start from a plain chat message. The durable orchestrator session is itself the classifier: it decides whether an incoming message is a re-run request. A wide, strategy-tier cascade is gated behind an explicit confirmation before it fans out, so a casual message never triggers a company-wide rebuild by accident.
Tip
Recipe: add an employee to the re-run graph. Add an entry to the employee graph with its upstream edges declared, its identity flag, and a one-line description of its substeps. The re-run driver and the chat classifier pick it up as data, with no driver change, and its durable session is derived the same way, a uuid5 of slug:employee_slug.
Tuning a run#
A run reads a small set of environment variables. The stall watchdog is the one to internalize: the only kill condition is a silent stdout, and there is no total-runtime cap, so a long, legitimate job is never cut off mid-stream.
| Variable | Default | Effect |
|---|---|---|
JETSYN_AGENT_RUNTIME | cli | Selects the headless runtime. cli is proven; an unknown value falls back to cli. |
JETSYN_MODEL / JETSYN_EFFORT | claude-opus-4-8 / xhigh | Model and effort for a CLI run. |
JETSYN_STALL_TIMEOUT_SECONDS | 600 | The only kill condition: stdout silent this long. No total-runtime cap. |
JETSYN_AGENT_{MODEL,EFFORT,CWD,PERMISSION} | claude-opus-4-8 / xhigh / /workspace / bypassPermissions | Model, effort, working directory, and permission mode for the interactive REPL. |
# Run a headless task on the default cli runtime, with a longer stall budget
export JETSYN_AGENT_RUNTIME=cli
export JETSYN_MODEL=claude-opus-4-8
export JETSYN_EFFORT=xhigh
export JETSYN_STALL_TIMEOUT_SECONDS=900Architecture
How the runtime is built: the cell, the spawn-per-event employee, durable resume, and credential isolation.
Read →Build an AI employee
Author an employee as plain Claude Code files: the folder layout, the sub-agent contract, and a minimal example.
Read →Folders & memory
The cell filesystem the runtime reads and writes: the Brain vault, durable state, and what survives a process death.
Read →Gateways
The channels a run arrives on and the credential-isolated intents seam it acts through.
Read →