Skip to content

Agent Container Runtime Interface (ACRI)

The Agent Container Runtime Interface (ACRI) is the standard interface contract between the Orchestrator and any agent harness. It defines how an agent is created, started, observed, interrupted, and terminated independently of which harness is underneath.

The name is deliberate. Kubernetes defines a Container Runtime Interface (CRI) that decouples the kubelet from any specific container runtime (containerd, cri-o, etc.). The kubelet does not know or care whether it is talking to containerd or cri-o it speaks CRI. ACRI applies the same principle one layer up: the Orchestrator does not know or care whether it is talking to OpenCode, ForgeCode, Hermes, or a remote Coder workspace it speaks ACRI.

Without a standard like ACRI, every harness integration is bespoke. The Orchestrator accumulates a driver per harness, each with its own lifecycle model, log format, and interruption mechanism. That is not a composable system it is a collection of adapters held together by integration debt. ACRI makes harnesses interchangeable, which is the architectural prerequisite for routing tasks to the right agent for the job rather than the agent that happens to be already integrated.


Existing harnesses each have their own runtime model:

  • OpenCode exposes a client/server API, but the programmatic interface for headless orchestrator-driven operation is not yet stable and is designed primarily around developer interaction.
  • ForgeCode supports one-shot CLI invocation and event dispatch, but with no standard for streaming execution logs back to the Orchestrator or injecting runtime messages.
  • Coder ships an Agent Message Runtime (AMR) for structured message exchange with a running workspace agent but it is Coder-specific, tightly coupled to the Coder workspace model, and not available to any harness running outside Coder infrastructure.
  • Hermes (and other long-running agents) are reachable via MCP or SSE, but with no standard for lifecycle management, log correlation, or controlled interruption from an external Orchestrator.

These are not criticisms each harness is solving for its own use case. The problem is that there is no shared vocabulary across them. ACRI provides that vocabulary.


ACRI defines six operation groups. A conformant harness driver implements all six. Harnesses that cannot support a given operation natively (e.g. a CLI-only tool with no streaming) implement a compatibility shim that emulates the semantics.

Create(AgentSpec) → AgentHandle
Start(AgentHandle) → RunHandle
Stop(RunHandle, reason: string)
Kill(RunHandle, reason: string)
Status(RunHandle) → AgentStatus

Create provisions an agent instance from a spec. The spec declares: harness type, model, context manifest (see Context Layers), tool access, sandbox constraints, and resource limits. For an ephemeral agent this may mean spawning a local process or making a network call to a remote factory (Coder workspace, k8s job, etc.). For a long-running agent this returns a handle to the already-running instance.

Start begins execution of a task on a provisioned agent. Returns a RunHandle that identifies this specific execution for log correlation and interruption.

Stop requests a graceful halt. The agent is allowed to reach a safe checkpoint, emit a final status, and exit cleanly. Use when the task is complete, cancelled, or superseded.

Kill is an immediate termination. No checkpoint. Use when the agent is in a runaway state or producing harmful output and cannot be trusted to self-terminate.

Status queries the current state of a run: pending, running, waiting (blocked on a tool call), completed, stopped, or failed.


Logs(RunHandle, since?: Cursor) → Stream<LogEvent>

Every agent run emits a structured log stream. LogEvents carry a timestamp, run ID, event type (tool_call, tool_result, reasoning_step, message, error), and payload. The stream is the primary observability surface it is how the Orchestrator tracks progress, how human operators monitor execution, and how post-run audits are produced.

Log streams are pull-based with a cursor so that a consumer that disconnects and reconnects does not lose events. A harness that does not natively emit structured logs (e.g. a CLI tool writing to stdout) wraps its output in a passthrough LogEvent for compatibility; structured parsing is a harness-specific enhancement.

For long-running agents that were already running before the current task was dispatched, the stream begins at the StartRun call. Prior session history is not included unless explicitly requested via the since cursor.

Agents operate on more than text. A coding agent may read a screenshot of a UI to understand a visual bug, produce a compiled binary as an output artifact, diff a generated image against a reference, or write a PDF report. These are first-class outputs of the execution, and the log stream must represent them without degrading them to opaque references or base64-encoded text fields.

ACRI log payloads are typed. A LogEvent’s payload field carries one of:

Payload typeUse
textReasoning steps, messages, structured JSON, stdout
artifact_refA reference to a binary artifact stored in the run’s artifact store path, content hash, MIME type, and size. The stream carries the reference; the artifact is fetched separately via ArtifactFetch.
artifact_inlineA small binary artifact embedded directly in the event (max size is a harness capability flag). Use for thumbnails, short audio clips, or small diffs where a round-trip to the artifact store is wasteful.
multipartA compound payload combining text and one or more artifact references in a single event used when a reasoning step and its associated output artifact need to be correlated atomically.

ArtifactFetch is a companion operation on the ACRI stream:

ArtifactFetch(RunHandle, artifact_id: string) → Stream<bytes>

It returns the raw binary content of a referenced artifact, streamed in chunks. This keeps the log stream lightweight the event log is structured metadata, the artifact store holds the bytes while making the full content available to any consumer that needs it.

Artifact metadata recorded in an artifact_ref includes: content type (MIME), encoding, originating tool call ID (so the artifact can be traced back to the exact step that produced it), and an optional human-readable label. For image artifacts, width and height are included so a monitoring UI can render thumbnails without fetching the full payload.

Injection supports multimodal content too. An operator injecting a correction mid-run may attach a screenshot (“this is the UI state you should be targeting”), a reference file, or a diff. InjectionMessage payloads follow the same typing model as log payloads text, artifact_ref, artifact_inline, or multipart.

Harnesses that do not natively support binary artifact tracking emit artifact_ref payloads pointing to files written to the run’s working directory. The ACRI driver is responsible for registering those files in the artifact store and rewriting the references. This means a harness that writes binary files to disk during execution is automatically multimodal-compatible at the driver level, even if the harness itself has no explicit artifact API.


Inject(RunHandle, message: InjectionMessage) → InjectionAck

Input injection is the mechanism for sending a message to a running agent mid-execution. This is a first-class operation, not a workaround.

Use cases:

  • Human-in-the-loop correction. An operator observing the log stream sees the agent heading in the wrong direction. They inject a corrective message “stop, you are modifying the wrong service” without killing the run. The agent receives the message as a high-priority instruction and can course-correct without losing its task context.
  • Orchestrator-driven signals. The Orchestrator injects updated context (e.g. a concurrent task has just merged a change that affects this run), a pause instruction, or a checkpoint request.
  • Escalation responses. An agent that has raised a clarification request blocks until an injection resolves it, rather than guessing or failing.

InjectionMessage carries: message content, priority (normal, high, interrupt), and an optional directive type (correction, context_update, pause, resume, clarify_response).

Harnesses that support true async message reception (Coder AMR, Hermes MCP, OpenCode server API) implement this natively. Harnesses that do not (pure CLI tools) receive injections via stdin at a defined delimiter, with the same structured envelope.

The Coder Agent Message Runtime is the clearest existing precedent for this operation. ACRI generalises it: the same injection semantics apply regardless of whether the agent lives in a Coder workspace, a k8s pod running OpenCode, or a long-running Hermes instance.


ContextState(AgentHandle) → ContextManifest
InjectContext(AgentHandle, layers: ContextDelta)

These operations are the runtime face of Context Layers dispatch resolution.

ContextState queries what context the agent currently holds which layers, at which versions. For long-running agents this is essential: the Orchestrator must know what the agent already has before deciding what to inject. For newly created ephemeral agents this returns an empty manifest.

InjectContext pushes a context delta to the agent. This may be a full context package (for an ephemeral agent receiving its initial load) or a targeted delta (for a long-running agent receiving an updated organisation context version or a new project context package).

Context injection over ACRI is the mechanism that makes the Declare / Diff / Inject resolution strategy operational. Without a standard injection interface, context management is ad-hoc per harness.


Capabilities(harness: HarnessType) → CapabilitySet

Not all harnesses support all ACRI operations at the same fidelity. A harness declares its capability set so the Orchestrator can make informed routing decisions and apply the appropriate compatibility shims.

Capability flags include:

FlagMeaning
structured_logsEmits typed LogEvents natively (vs. raw stdout)
async_injectionSupports mid-run message injection without stdin
context_stateCan report current context manifest
graceful_stopDistinguishes Stop from Kill (checkpoint before exit)
subagent_spawnCan itself create subordinate agents
headlessCan operate without a human at a terminal
streaming_contextCan receive context updates mid-run
multimodal_inputCan accept image, audio, or binary content in injection payloads
multimodal_outputProduces typed artifact_ref or artifact_inline log payloads natively
artifact_inline_max_bytesMaximum binary payload size for artifact_inline events (0 = not supported)

A harness advertising headless: false should not be used as an Orchestrator target for unattended execution. A harness advertising async_injection: false can still receive injections but they arrive as stdin messages on the next read cycle rather than as async interrupts.


Subscribe(RunHandle, filter?: EventFilter) → Stream<RuntimeEvent>

Beyond log events, ACRI defines a runtime event bus for coarser-grained signals that the Orchestrator and human operators subscribe to. These are not the full execution trace they are the key moments.

Standard event types:

EventTrigger
run.startedAgent has begun task execution
run.waitingAgent is blocked tool call, clarification, or resource
run.resumedAgent unblocked and continuing
run.completedTask finished successfully
run.stoppedGraceful halt completed
run.failedUnrecoverable error
run.killedForced termination
injection.receivedAgent acknowledged an injection
injection.appliedAgent acted on the injection
context.updatedContext delta was applied to agent state
escalation.raisedAgent has raised a question requiring external resolution

Events are the integration point for the Orchestrator’s task tracker, for human monitoring dashboards, and for webhook callbacks to external systems (GitHub status checks, workflow state, etc.).


ACRI operations are transport-agnostic at the interface level. Drivers implement the operations over whatever the underlying harness supports. Common transport patterns:

  • Local process operations are system calls and pipe I/O (for CLI harnesses run in-process or as a subprocess)
  • HTTP + SSE Create/Start/Stop/Kill/Inject are HTTP calls; Logs and Events are SSE streams (for harnesses with a local or network server, including OpenCode’s server mode and Hermes MCP)
  • Network / workspace API operations map to the remote harness’s API (Coder workspace API, k8s job API)
  • MCP tool calls for harnesses that expose themselves as MCP servers, ACRI operations are MCP tool invocations with structured result schemas; log streaming uses the MCP notification channel

The Orchestrator sees one interface. The driver handles translation.


Harnesses are the implementations that run behind an ACRI driver. A harness is evaluated on benchmark performance, model support, and observability. ACRI is the contract that makes a harness pluggable the Orchestrator routes to whichever harness is appropriate for the task without changing its own logic.

The Orchestrator is the primary consumer of ACRI. It uses lifecycle operations to provision and terminate agents, log streaming and events for observability, and injection for runtime correction. The Orchestrator is also responsible for deciding when to Stop vs Kill the distinction matters for context preservation.

Context Layers and dispatch resolution depend on ACRI’s ContextNegotiation operations to be operational at runtime. Without ContextState and InjectContext in a standard form, Context Layers resolution remains a per-harness concern.

Bifrost (internal AI gateway) sits at a different layer it unifies LLM inference and MCP tool calls. ACRI sits above it: a harness speaks to Bifrost for model calls; the Orchestrator speaks ACRI to manage the harness. These are complementary, not overlapping.