Skip to main content

Harnesses

What decides how an agent talks to a model, and what each harness adds on top of the others.

An Agent manifest says what an agent must produce. A harness decides how it talks to a model. The harness is a plugin behind the harnesses port, resolved by Agent.spec.harness. basic is the default.

The basic harness​

basic is a native agent loop on the model port, with no LangChain. It repeats model call, tool calls and their results until the model calls submit:

  1. it sends the system prompt, the declared inputs and any feedback.
  2. it runs the tools the model asks for and feeds the results back.
  3. it validates the submit artifact against the agent's writes Schema and finishes.

The tools a basic agent may use are the built-in workspace tools and any mcp: server (see Plugins). The submit tool is always offered, built from the output Schema.

Submit, artifact and route​

submit carries one artifact, valid against the Schema the agent declares in writes. When the node chooses among several targets (choose), the submit also carries route: one or more of the offered targets, at least the edge's min.

An invalid artifact or a bad route is sent back to the model as a corrective tool result, up to limits.maxSubmitRetries. Past that, the turn fails and the ticket goes to needs_human.

A reply with neither a tool call nor a submit gets the same corrective treatment.

The budget block​

Before every model call, the engine appends a <budget> block after the history, as the last message of that call only, never to the saved history. It lists each limited dimension with what is used, its limit and the percentage, per agent and, for cost and time, per ticket. It is built from the ledger, so it survives a pause or a restart.

The request order is the system prompt, the tools in a deterministic order, the history and then the volatile block (the budget and the todo list). Nothing volatile sits inside the prefix, so it stays byte-identical across the calls of a turn and the provider can cache system prompt, tools and history.

Summarization​

Past summarizeTokens, one call to the agent's model replaces the older messages with a short summary. The system prompt, the first user message and the last summarizeKeepMessages messages stay. The summary call goes through the ledger and counts against the budget.

Model fallbacks​

A Model alias may name fallbacks. When a provider call fails with an error, not a verdict, the harness retries the same call on the next alias, in order, and only then fails the turn. Every attempt goes through the ledger.

Before that, the provider itself retries an answer that may go away on its own: 429 Too Many Requests, 500, 502, 503, 504, or no connection. It retries up to 3 times, waiting the Retry-After the endpoint sends (at most 30 s), or 1, 2 and 4 s. A 4xx is not retried, and neither is a read timeout, which the provider may already have billed. A turn that still fails hands over to a person as HarnessFailed: retry, take over or close.

The deep harness​

deep is the basic loop plus a plan and subagents. It keeps every basic guarantee (submit, verdicts, budget block, token and save, summarization, fallbacks), and adds:

  • the file tools (ws.write, ws.edit, ws.delete) when workspaceAccess: write.
  • ws.exec and ws.test when execute: true.
  • write_todos: the plan lives in the turn state, and is shown to the model next to the budget block on the next call only.
  • task(subagent, instructions): runs a subagent in a sub-turn of its own, and returns its final answer to the parent.

Subagents​

Agent.spec.subagents declares the subagents the agent may hand work to:

spec:
harness: deep
workspaceAccess: write
tools: ["ws.read", "ws.write"]
subagents:
- name: tester
description: runs the project's tests
systemPromptFile: tester.md
tools: ["ws.read", "ws.exec"]
model: cheap # optional; inherited from the agent when absent

tools must be a subset of the agent's own tools. The prompt file and the model alias must exist, checked when the configuration loads. A subagent has no submit, and can call neither task nor write_todos. Its calls go through the same ledger, so they count against the activation's budget. A pause or a crash inside a sub-turn resumes inside it: the token holds the whole stack.

The acp harness​

acp runs an external coding agent inside the node's sandbox, and drives it over the Agent Client Protocol. The engine is the client, and offers no filesystem or terminal, so the agent uses its own tools in the container.

kind: AcpAgent
metadata: {name: opencode}
spec:
command: [opencode, acp]
env: [OPENROUTER_API_KEY] # names only: the value comes from the engine's environment
model: deepseek/deepseek-v4-flash
reportsCost: true

The Agent names it with harness: acp and agent: opencode, and the project's sandbox runs it (sandbox.egress limits it to the model's host). Every turn, tool call and reported cost goes through the ledger like any agent. A budget threshold pauses it, the limit stops it, and a repeated tool call triggers the loop guard.

Permissions​

Agent.spec.permissions maps ACP tool kinds (read, edit, execute, fetch, …) to allow, deny or ask. The default is ask. allow and deny answer the agent at once. ask pauses the node with a human request (allow or deny), and the answer is replayed to the agent when the turn resumes.

The artifact​

The artifact is read from the workspace diff and the agent's final message. Fields named like files (files, paths) take the changed files. Fields named like a summary (summary, message, …) take the final message. Any other field goes through one extraction call on the agent's model alias, through the ledger.

An invalid artifact sends a corrective prompt to the agent, up to maxSubmitRetries. An acp agent whose Schema has fields beyond files and summary must declare model.

A pause or a crash resumes the ACP session. The session id is saved with the activation, and the engine asks the agent to session/load it. When the agent cannot, it starts a new session with the saved transcript. A paid turn already recorded is never repeated.

See also​