Skip to main content
  1. The Scheduler fires; agent-api asks the Runtime to spawn (it touches no docker itself).
  2. The container mounts only the granted workspaces — one bind per mount, no clone; another tenant’s workspace is not in the filesystem at all — + brokers creds.
  3. The agent resumes from the session file in the rw folder (continuity is a file), runs the runner, and commits freeform changes.
  4. It emits events on its Stream; the container is reaped when idle.
The workspace folder (+ its session file) is the only durable state; the container is disposable.

Implemented — the live path

A dispatch is the in-container worker agent_api.worker. The Runtime injects everything it needs as env + a bind-mount, and the worker drives the runner over the mounted folder:
Continuity is files in the workspace. Both the session id (.claude/.session) and the chat transcript are saved in the folder — claude-code’s transcript dir is symlinked into <workspace>/.claude/projects — so a fresh container resumes the same conversation from the durable git folder, no warm container needed.
Proven end-to-end on docker: a unit.v1 dispatch → isolated vexa-agent-<id> container → a real claude turn → a workspace.v1-governed commit → events on unit:<id>:out. Code: core/agent/services/agent-api/src/agent_api/worker.py (the harness), core/runtime/src/runtime_kernel/docker_backend.py (mount + credential brokering).

The same run, any substrate

The flow above is the Docker path (the open-core default). agent-api never spawns anything itself — it hands the Runtime a runtime.v1 workload, and the kernel’s pluggable backend turns that same workload into a child process, a container, or a Kubernetes Pod (RUNTIME_BACKEND=k8s) — same lifecycle, same dispatch, same worker. Only how the container is created changes; the run does not. The per-backend mechanics — and what the k8s path does and does not wire up yet — are in Runtime → Where it runs.