Long-running agents are not just prompts with tools. A production agent runtime must keep a durable record of what happened, call a model in a loop, route tool calls, provision execution environments, protect credentials, stream events to users, and recover after failures. The early version of many systems puts all of those responsibilities in one container or process because it is simple: the model loop, filesystem, terminal, credentials, and session state all live together.
That coupled design becomes fragile as agents run for longer periods and as they touch more environments. If the container dies, the session can disappear with it. If the sandbox hangs, the harness may be indistinguishable from a network failure. If generated code shares an environment with credentials, prompt injection can escalate from bad output to credential exfiltration. If every session must provision a full container before the first model token, time-to-first-token suffers even when the task does not need a sandbox yet.
A decoupled agent runtime separates the agent into stable interfaces: a durable session log, a stateless harness or “brain” that runs the model loop, and one or more “hands” that execute actions through narrow tool contracts. The key design move is not a specific vendor API. It is the same abstraction strategy that operating systems used for hardware: expose small interfaces that can outlive the current implementation, then let the underlying harnesses, sandboxes, context managers, and tool providers evolve independently.
The session should be the authoritative record of user messages, model outputs, tool calls, tool results, status events, interrupts, and lifecycle transitions. It is append-only or append-mostly, queryable by event position, and independent of any one harness process. If a harness crashes, a replacement can reload the session, rebuild the relevant context slice, and resume from the last committed event.
The harness owns the model loop: select context, call the model, parse tool calls, route actions, emit events, and decide whether to continue. It should not need to be a pet process whose local memory is the only copy of state. Treating harnesses as cattle lets the system scale many brains, roll out new context-management strategies, and recover from crashes without losing the session.
Execution environments, MCP servers, file tools, browsers, shells, phones, and domain-specific services can all look like hands. The harness does not need to know how each hand is implemented. It only needs a capability contract that accepts a name and input and returns a bounded result or error.
Keep recoverable state in the session log and keep credentials outside the sandbox. The model can request actions, but tool proxies and sandbox initialization code should apply the authority. This preserves debuggability, recoverability, and least privilege as models become more capable.
A coupled runtime has one lifecycle: the container is alive or dead. A decoupled runtime has at least three. Sessions persist until explicitly deleted or expired. Harness workers can be restarted, scaled horizontally, or swapped for a different implementation. Hands can be provisioned lazily, replaced after failure, and scoped to the resources needed for a task.
type SessionStore interface {
Append(sessionID string, event Event) error
Read(sessionID string, span EventSpan) ([]Event, error)
}
type Harness interface {
Wake(sessionID string) error
Step(sessionID string) error
}
type Hand interface {
Execute(name string, input map[string]any) (ToolResult, error)
}
func StepAgent(store SessionStore, model Model, tools map[string]Hand, sessionID string) error {
events, err := store.Read(sessionID, RecentEvents(200))
if err != nil {
return err
}
msg, err := model.Next(BuildContext(events))
if err != nil {
return err
}
if err := store.Append(sessionID, Event{Kind: "model", Payload: msg}); err != nil {
return err
}
for _, call := range msg.ToolCalls {
hand := tools[call.Target]
result, err := hand.Execute(call.Name, call.Input)
store.Append(sessionID, Event{Kind: "tool_result", Payload: ToolEnvelope{Call: call, Result: result, Err: err}})
}
return nil
}
The runtime should not clone a repository, boot a container, mount files, or install dependencies before knowing whether the model needs that hand. A session can begin inference from the durable event log and ask for a sandbox only when a tool call requires execution. This reduces startup latency and separates short conversational work from expensive environment setup.
The session log is not the prompt. A harness can read event slices, summarize old spans, preserve recent tool outputs verbatim, and optimize prompt-cache boundaries without mutating the underlying history. This distinction lets future harnesses use better context engineering while the durable log remains the shared source of truth.
Coding agents often need shell access, file edits, dependency installation, repository credentials, and hours of event history. A decoupled design lets the model loop recover after worker restarts, lets the sandbox be recreated from a known environment recipe, and prevents generated code from directly reading provider or repository tokens.
Some tools live in a customer’s VPC or on a private network. If the harness assumes every resource sits inside its own container, deployment requires network peering or a customer-hosted harness. If each external resource is a hand behind a narrow endpoint, the brain can stay in one runtime while hands live wherever data and policy require.
Advanced agents increasingly need more than one execution environment: one repository sandbox, one browser, one database tool, one issue tracker, and one specialized simulation service. Decoupling turns these into separately named capabilities rather than forcing all tools into a single giant environment.
Write every user event, model decision, tool request, tool result, interrupt, timeout, and compaction marker before acting on subsequent state. Include sequence numbers or monotonic offsets so a replacement harness can resume deterministically and so the UI can stream events without guessing whether it missed a result.
{
"sequence": 1842,
"session_id": "sess_01J...",
"kind": "tool_call",
"created_at": "2026-04-23T19:05:12Z",
"payload": {
"target": "repo_sandbox",
"name": "execute",
"input": {"cmd": "npm test -- --runInBand"}
},
"caused_by": 1841
}
Do not pass raw OAuth tokens, API keys, or repository secrets into generated-code sandboxes. For repository operations, initialize the environment with a remote that can authenticate through a scoped helper. For external APIs, call a proxy that maps session identity to vault-held credentials and enforces policy before forwarding the request.
Expose hands through a registry that names each capability, describes input schemas, declares timeouts and resource limits, and records where execution actually happens. The harness should reason over the registry, not over hidden ambient access. This makes multi-hand routing observable and auditable.