Skip to content
Harness reference

Harness reference

Every knob of a run, layer by layer. How to use them is on Harness tuning.

Pins on main: Maku v0.32.3, composed by crossplane-configuration v0.9.31. Values below are examples: the reference host’s configuration.

StatusMeaning
default
On by default
experimental
Built, on for a few classes only; its benefit is still measured
off
Shipped, switched off
planned
Specified, not built

Factory: tiers

config.tiers in tooling/base/agent-factory/helm-values-configmap.yaml. Triage fixes one tier per task; every run of the task inherits it.

default

TierrunUSD / taskUSDrunMinutesrunTokens / taskTokens
light0.51 / 1.0220300 k / 600 k
standard2.55 / 5.10451.5 M / 3 M
frontier6.80 / 13.60904 M / 8 M
FieldWhat it doesBounds
modelThe logical model the run asks for. Every tier asks for agent-default today, so the tier changes caps, not the modelA logical name
runUSDThe meter revokes the run at this cost (BudgetExhausted, limit run-usd). Cache reads are priced at their own ratePositive; needs a meter.prices row
runTokensThe claim’s budget.maxTokens. Caps the run only when the meter cannot price it1 … 5 M
taskUSD, taskTokensThe whole task, every run and resume. A new run starts only if a whole run still fits≥ the run cap
runMinutesThe pod’s deadline. A kill at the deadline is PodFailed and never resumed1 … 480

A priced run is held to USD, not tokens: kk2egjta passed 12.5 M tokens against a 4 M maxTokens at $4.46 of $6.80, since 95% were cache reads. The gateway’s per-run budget B1 is the backstop (gateways).

Factory: templates and triage

config.templates: the roles that run, in order, on one branch.

default

TemplateRolesReview rounds
soloimplementer—
pairimplementer, reviewer2
trioimplementer, reviewer: pair’s shape until the bench; the tester runs first through testFirst, not the template2
investigatetriager: proposes an issue text, never writes—
  • maxReviewRounds: every revision or restarted review uses one. Out of rounds, the task escalates.
  • reviewerModel: the reviewer’s model whatever the tier says. Unset here: ADR-0058 keeps the public reviewer on the implementer’s model.

Triage picks the template from the class and the tier:

TaskTemplate
A RunLore findinginvestigate, forced internal
docs-linkssolo; pair on frontier
Any other classpair; trio on frontier

The tier would come from the complexity classifier, which is

planned
: every task falls back to standard, except one in ten (triage.controlPercent: 10), the control group, on frontier. defaults.template and defaults.tier choose nothing at run time, but the factory still validates them at startup, as it requires triage.classifierURL: they leave the config only with an Maku release that drops them.

The reviewer runs on the other tier: frontier for a standard or light implementer, standard for a frontier one. The reviewer’s tier’s USD cap applies; its tokens and minutes stay the implementer’s tier’s. The tester keeps the implementer’s tier.

Factory: caps, resume and suspend

KeyValue hereWhat it does
caps.activeTasks3Tasks worked at once
caps.concurrentRuns4Sandboxes at once
caps.tasksPerDay20Past it, a labelled issue is refused with a comment
caps.maxTextBytes14,336The longest task text accepted
caps.maxPendingMinutes30 (5 … 480)A run still Pending escalates as run_unschedulable
resume.maxPerTask2 (1 … 5)Runs lost to infrastructure are resumed; never a PodFailed one.
default
sandbox.suspend.enabledtrue (example)An attended run waiting on an approval gives up its pod after 5 min.
default
budgets.*factoryDailyUSD $42.50, humanDailyUSD $8.50Daily caps in USD at meter.prices (the USD gauges since Maku v0.16.0). The fleet’s and each person’s count in shadow while enforcePrincipal is false
loops.<class>default: {scope: off}; docs (scoper, loop policy, search) and scripts (scoper, splitter)The loop policy and the scope, off for every class without a row. scope is off, steering or scoper (what a brief holds). A row sets stallSteps (default 80, 1 … 500), continuations (2, 0 … 5) and judge (one of the four model aliases, default tier-light, or none); any one of them switches the class on. Loop policy
scoper.nudgeAt, scoper.maxToolCalls25 and 40 (Maku’s defaults; this repository sets no scoper: key)The scoper’s exploration bound: a nudge to wrap up at 25 tool calls, a stop at 40 with the end budget_calls; any end without a handoff leaves the implementer to run unscoped
loops.<class>.searchtrue on docs onlyThe implementer’s search tool; never switches the loop on. Search

Factory: what a brief holds

loops.<class>.scope picks the mode per class; loops.default covers a class with no row and review. docs and scripts are scoper here; every other class is off.

Briefoffsteeringscoper
Scoper (a triager run)——preamble · steering · snapshot · how to scope, ask or assume, and hand off
First implementerpreamble · snapshot (today)preamble · steering · snapshotpreamble · steering · scope · snapshot
Revision, CI fix, retry, resume (≤ 13 KiB)todaysteering before the room logsteering and scope before the room log
Judge’s continuationthe issue’s acceptance sectionthe samethe scope’s acceptance checks
Reviewerthe roomthe room; steering once the reviewer’s brief carries itthe room (the scope is a handoff); steering and scope once it does

The steering note and the scope are never clipped. The snapshot is inlined whole while the brief stays within 16 KiB, else the brief tells the implementer to read it with room_read. The scope is an agent’s text drawn from an untrusted issue: sanitised and fenced as the snapshot is. The steering note is fenced as trusted. Example (the reference host): on for docs and scripts.

Run API: runs started by hand

task agent:run -- --role … --class … --task … asks the factory for one run.

FlagDefaultNote
--max-tokens2,000,000 (≤ 5 M)A hand-started run has no tier, so it is capped in tokens
--modelagent-defaultOne of the four logical names
--profilesnoneEgress profiles: pypi, npm, golang, crates
--minutes, --sizerefusedFixed at 120 min and small

AgentRun claim

The XRD agentruns.cloud.ogenki.io (definition). Only budget.maxTokens can change on a live run. The factory always writes size: small.

FieldDefaultEffect
rolerequiredimplementer writes and pushes; reviewer and triager read only; the tester writes tests the harness signs and pushes
dataClassrequiredpublic: Z.ai, GitHub egress. internal: Claude, read-only clone, no egress, cluster read tools, prompt caching
modelagent-defaultThe harness’s MODEL
budget.maxTokens2 M (1 … 5 M)The token cap when the run is not priced
budget.maxMinutes120 (1 … 480)The pod’s deadline and the TTL of its tokens
sizesmallsmall 1→2 CPU, 2→4 GiB; medium ×2; large ×4
egress.profiles[]Package registries on top of GitHub; refused on internal
roomRef—Adds the maku-bridge sidecar (container room-bridge) and the room rules
suspendable—/workspace on a PVC, so the run can be suspended
loop.checks—Runs the repository’s checks in the sandbox: AGENT_CHECKS=1, refused on internal
loop.search—The search tool for an implementer: AGENT_SEARCH=1. Alone, no loop policy

Composition

apis/agentrun/kcl/main.k turns the claim into the pod. Nothing here is set per run.

SettingValue
Harness imageghcr.io/smana/maku-harness:v0.32.3@sha256:d6ec677262d10342dee4e8ba53af087e7d6f227502c0cc7c2ecfe4d6636c040e (the release’s image, composed by crossplane-configuration v0.9.31, where the composition pin lives): a harness change reaches runs only through the composition’s digest, so the harness roles reach runs with that bump
EnvironmentMODEL, DATA_CLASS, ROLE, LLM_BASE_URL, MCP_URL, TASK_FILE, RULES_FILE, tracing; AGENT_CHECKS and AGENT_RUN_SECONDS with loop.checks; AGENT_SEARCH with loop.search. LLM_REASONING_EFFORT is not set
rules.mdOne branch, one PR, never .github/workflows/, “a 429 means the budget is spent: stop”. Each rule is also enforced outside the sandbox
task.mdThe task text: the biggest driver of a run’s length
Egressgithub.com and its API, codeload, content and release-asset hosts, plus each profile’s registries (golang includes dl.google.com)
Grace period30 s, 45 s with a room: checkpoint, final read, stop, revoke
Read-only MCP toolsFlux, VictoriaMetrics and VictoriaLogs reads and the room_* tools; any other needs an approval in a room

Harness

Set in agent_run.py and sitecustomize.py, or left at the SDK v1.49.6 defaults. None is a claim field.

KnobValueEffectStatus
reasoning_efforthighUnsent, GLM-5.3 thinks at maximum: 150 s per reply instead of 11 s
default
Model timeout300 s per callA call that timed out before its first token is retried once when the run has time left; a second timeout ends the run
default
Retries3 attempts, 10 s then 20 s apartOn a 503 or a reset. Never a 429; a timeout has its own single retry (above)
default
max_iterations500Past it the run ends error; the USD cap and deadline usually end it first
default
Stuck detectionlast 20 eventsEnds the run stuck on 4 identical cycles, 3 repeated errors or 3 messages with no action
default
Condenser80 events, keep first 4Summarises history with the same model, on the run’s budget
default
Event size30,000 charsA huge output is truncated, not refused
default
Toolsterminal, file editor, task tracker, MCPNo browser; MCP tools filtered per role by the gateway
default
search40 iterations, 300k tokens, 300 s, 6 a run, effort low, 8,000 characters backA read-only child (grep, glob, read_file, list_directory) on the run’s token; its steps are the run’s; ledger /workspace/.agent-search.jsonl
experimental
Prompt cachingon for internal85% of input from cache: $0.48 instead of $2.13
default
autotitleoffSaves a model call per title
default
agent-checksthe CI’s fast subset from .github/agent-checks.yamlBefore every push; the harness re-runs it on a finished implementer’s head and notes the result
default
Step logevery 15 sLines arrive in batches
default
Progress notes5 milestones, ≤ 8 a runFixed texts in the room, nothing the model wrote
default
Checkpoint on SIGTERMimplementer onlyA reclaimed run’s work survives on its branch
default
Tracingspan per model and MCP callNo prompt or output content
default
# One run's tokens by model and type, as the meter prices them
sum by (gen_ai_request_model, gen_ai_token_type) (
  max_over_time(gen_ai_client_token_usage_sum{ar_agent="system:serviceaccount:agents:xplane-run-<id>"}[1d]))