- The Scheduler fires; agent-api asks the Runtime to spawn (it touches no docker itself).
- The container mounts the granted workspaces at
/workspace(no clone) + brokers creds. - The agent resumes from the session file in the rw folder (continuity is a file), runs the runner, and commits freeform changes.
- It emits events on its Stream; the container is reaped when idle.
Implemented — the live path
A dispatch is the in-container workeragent_api.worker. The Runtime injects everything it needs as
env + a bind-mount, and the worker drives the runner over the mounted folder:
.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 aruntime.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.