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:
- it sends the system prompt, the declared inputs and any feedback.
- it runs the tools the model asks for and feeds the results back.
- it validates the
submitartifact against the agent'swritesSchema 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) whenworkspaceAccess: write. ws.execandws.testwhenexecute: 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.