harnessie.com / docs / User guide

Harnessie user guide

The complete guide to running, configuring, and extending Harnessie. If you are new, read Getting started first; it gets you to a green run in five minutes. This guide is the reference behind it.

Contents

What Harnessie is

Harnessie is a brain-agnostic multi-agent harness for getting reliable work out of language models. A goal enters. An orchestrator (a capable model at high effort) decomposes it into task packets with acceptance criteria and out-of-scope fences. Workers (cheaper models) execute those packets inside a jailed workspace with allowlisted tools, owning the files they create. Every worker phase exits through a gate that runs deterministic checks first, then an independent fresh-context verifier that never sees the worker's reasoning and fails closed. Contested decisions fan out to an adversarial panel whose positions and objections land in a decision record only a human may arbitrate. Everything is journaled, budgeted, resumable, and hash-chain audited.

The operating thesis: the harness structure carries the quality floor, the model carries the ceiling. The gates, jails, budgets, ownership rules, and retry ladders keep output honest no matter which model you run underneath them. That is what "brain-agnostic" means here, and it is a testable claim, not a slogan: a model earns its place in a tier by passing a scorecard.

Core concepts

Roles. There are three role kinds. The orchestrator decomposes and integrates. Workers execute task packets and own their files. Verifiers judge a phase's output in fresh context and can only fail it closed. Each role is a prompt file in agents/ plus machine-owned boundary text the harness appends so no prompt can opt out of policy.

Phases. A workflow is an ordered list of phases. Each phase runs one role against one task and, for worker phases, exits through a gate. Prior phases' reports are available to later phases through {phase_name} placeholders in the task template.

Task packets and consent. A task packet is an offer, not a command. Before any side-effecting tool unlocks, a worker must call accept_task, or it can decline_task with a reason and an optional counter-proposal. Declining is a first-class, non-punished outcome.

Gates. A gate runs the phase's deterministic checks (shell commands that must exit 0), then an independent verifier in fresh context. The verifier never sees the worker's reasoning, only the task and the artifacts. If either fails, the phase does not pass.

Routing and budget. Task classes map to model tiers and effort levels in config. The orchestrator does not pick its own model; routing is config, not model self-assessment. A run has a token and dollar budget; hitting it stops the run cleanly.

Memory. Project memory is a set of dated, provenance-stamped facts under memory/, maintained under approval gates. Nothing is deleted; expired facts move to an archive.

Audit. Every run writes a hash-chained event log. harnessie audit re-verifies the chain and renders one composite timeline of agent and operator actions.

Installation and requirements

Python 3.11 or newer and PyYAML. Install from PyPI:

pip install harnessie   # or: pipx install harnessie / uv tool install harnessie
                        # or: brew install snapsynapse/tap/harnessie

Developing on the harness itself (or wanting the test suite), install from source:

git clone https://github.com/snapsynapse/harnessie.git
cd harnessie
pip install -e ".[dev]"

The model adapters are standard-library only and need no vendor SDK. Shell-using workflows require an OS sandbox: macOS uses native sandbox-exec (Seatbelt); Linux uses bubblewrap, firejail, or docker in that order of preference. Every backend is admitted only after a startup smoke test proves it can confine on your host. On any platform with no usable backend (Windows, or a host where the smoke test fails), shell-using workflows fail closed rather than run unconfined. See Security model.

The CLI

All commands are subcommands of python3 -m harness.cli (or harnessie once installed). The --root flag sets the project root; it defaults to the current directory.

CommandWhat it does
run <workflow> --goal "..."Run a workflow from a goal. Prints a pre-run cost preview first (LIVE vs MOCK, ceilings, worst case) and refuses a live run with no budget ceiling; ends with a plain-language summary and the run id.
resume <run_id> <workflow> --goal "..."Resume a run from its journal. Re-runs only phases that did not pass.
report <run_id>Plain-language run summary: outcome, per-phase status, and on a halt the one named next action. --raw appends the raw journal, events, and proof listing.
audit <run_id>Verify the hash chain and render the governance timeline. Exit 0 clean, 1 broken chain, 2 run not found.
eval [suite]Run the deterministic eval scorecards (optionally one suite YAML).
eval --liveRun opt-in live provider scorecards; skipped visibly unless HARNESSIE_LIVE=1 and provider configuration are present.
verify-manifest [manifest]Verify the trust-bundle manifest. Defaults to docs/MANIFEST.yaml.
init [path]Scaffold a minimal project layout, then run the guided readiness check: Python version, sandbox backend detection, API-key guidance, and a zero-dollar mock run that must be green. --no-verify skips the guided check for scripted scaffolding.

What governs a run

A run's behavior is not in one file. Each decision has exactly one owner. To predict or change what a run does, edit the owner, not the prompt.

DecisionGoverned by
Which model runs each task class, and how to swap brainsconfig/models.yaml (tiers plus the routing table)
Token and dollar ceilings, effort per task classconfig/models.yaml (budget plus routing)
Which phases run, in what order, with which gates and verifiersthe workflow YAML in workflows/
Which files each agent may writeOWNERSHIP.yaml (lanes plus first-writer claims)
What each role may do (tools, shell allowlist, approval)the tool registry (harness/tools/builtin.py) plus role prompts in agents/

Writing a workflow

A workflow is a YAML file with a name, a description, and an ordered list of phases. Here is the built-in build-and-verify workflow, annotated.

name: build-and-verify
description: Plan a code change, implement it, and gate it on tests plus a verifier.
phases:
  - name: plan
    agent: orchestrator
    task_class: plan          # -> routes to the frontier tier at high effort
    max_steps: 15
    task: |
      Goal from operator: {goal}
      Produce an implementation plan: subtasks with acceptance criteria,
      inputs, and out-of-scope fences. Inspect first; do not write files.
  - name: implement
    agent: implementer
    task_class: implement
    max_steps: 40
    task: |
      Implement the following plan in the workspace:
      {plan}
    verify:
      max_attempts: 3
      checks:
        - name: tests
          command: pytest -q      # deterministic gate: must exit 0
      verifier: code-verifier     # independent, fresh-context judge
      task_class: verify
      criteria: |
        Every criterion in the plan is met. Tests are meaningful and pass.
        Nothing outside the plan's scope was modified.
  - name: integrate
    agent: orchestrator
    task_class: integrate
    task: |
      Original goal: {goal}
      The implement phase finished with: {implement}
      Write the final operator summary with evidence and follow-ups.

Phase fields:

Prior-phase reports are treated as untrusted model output: before substitution they pass through the same quarantine filter that scans tool results, so injection attempts inside a report are fenced as data rather than followed. The operator's goal is never fenced.

Adversarial phases. A phase with mode: adversarial runs a panel instead of a single worker. Each positions entry is an agent on a task class (choose different task classes to get genuinely different brains, which is what earns the record its independent-positions claim). After rebuttal_rounds of objections, arbitration: convergence passes only on unanimous agreement with zero open objections; anything else halts as needs_arbitration with a decision record. See Governance.

Approval policy. Approval-gated tools deny closed unless authorized. A workflow may use approve_tools for phase-local recorded pre-approval, or an operator can pass --approval-policy approvals.yaml with allow and deny lists. Each rule names a tool and may name a phase; explicit deny wins. --approve-interactive prompts on a TTY when no policy rule matches.

Verification options. Start with the offline deterministic path: pytest, harnessie eval, and harnessie verify-manifest. When a local OpenAI-compatible endpoint such as Ollama is already running, HARNESSIE_LIVE=1 HARNESSIE_OPENAI_COMPAT_BASE_URL=http://localhost:11434/v1 harnessie eval --live gives a live-local scorecard without external provider calls. External provider scorecards are attended operations. CLI fan-out, local model review, or a separate model-family review can strengthen a change, but it is review evidence; the merge proof remains the repo's tests, evals, manifests, and audit records.

Configuring brains

config/models.yaml is the only file you edit to swap models. It has three sections.

Tiers name model endpoints, ordered cheap to capable (local, cheap, mid, frontier). Each tier declares a provider, a model id, the environment variable holding its key (never the key itself), token limits, whether it supports an effort dial, and cost-per-million-token rates for budgeting.

tiers:
  frontier:
    provider: anthropic
    model_id: claude-fable-5
    api_key_env: ANTHROPIC_API_KEY
    supports_effort: true
  local:
    provider: openai-compat
    model_id: qwen3.6:35b-mlx
    base_url: http://localhost:11434/v1
    api_key_env: ""          # local endpoints usually need no key

Routing maps each task class to a tier and effort. Workflow phases declare a task_class; the router resolves it here. This keeps model choice a policy decision, not something a model asserts about itself.

routing:
  plan:      { tier: frontier, effort: high }
  implement: { tier: mid,      effort: medium }
  verify:    { tier: mid,      effort: high }
  default:   { tier: mid,      effort: medium }

Budget sets the run-wide ceilings:

budget:
  max_usd: 10.0
  max_tokens: 2000000

To run everything on a local open-source model, point the task classes you use at the local tier and leave ANTHROPIC_API_KEY unset. To swap one provider for another, change the tier's provider, model_id, and base_url. Nothing else in the harness changes.

The escalation ladder walks the tier order. When a phase fails its gate, the harness first reformulates the task with the verifier's evidence, then raises effort, then raises tier, before halting for a human. That ladder is why a cheap worker can carry bulk execution safely: a capable verifier and an automatic escalation stand behind it.

Ownership lanes

OWNERSHIP.yaml at the project root declares who may write what. It sits outside the workspace jail and no agent can reach it. Three lane kinds:

Paths not covered by any lane use first-writer-owns: the first agent to write a file claims it, and later cross-agent writes are refused. Claims and denials are logged as events and show up in the audit timeline. An agent that needs a file it does not own calls request_change to record the need rather than overwriting.

Harnessie treats agent work as governed, not merely executed. Three mechanisms carry that.

Consent. A task packet is an offer. Before side-effecting tools unlock, the worker calls accept_task, or decline_task with a reason and an optional counter-proposal. A decline ends the phase cleanly and is never punished with a bigger model; escalation is for capability failures, not disagreement. A side-effecting call before acceptance is refused at dispatch with a structured refusal the model can read.

Contested decisions. Some questions are decisions, not artifacts. An adversarial phase runs two or more agents on different brains, each forming an independent position from the workspace evidence, then objecting to each other's. If they converge (unanimous recommendation, zero open objections) the phase passes. Otherwise it halts as needs_arbitration and writes an AIDR-style decision record to runs/<id>/decisions/DR-<phase>.md capturing each position, the objections, and the dissent.

Arbitration. Only a human arbitrates a contested decision. You open the decision record, write your arbitration into it, and re-run; the run resumes from that record rather than re-running the contest (which would overwrite the recorded dissent). The record carries structurally earned claims: for example, independent-positions is earned only when the positions genuinely came from different providers.

Everything above emits events into the hash-chained log. harnessie audit <run_id> renders them as one timeline: consents granted and declined, ownership claims and denials, structured refusals, change requests, injection flags, gate verdicts, approval grants and denials, operator arbitration, memory facts saved and expired, and decision records with their earned claims. See GOVERNANCE.md for the full model.

When a run halts

Every run ends in a named stop condition, and each maps to one operator action. Resuming is harnessie resume <run_id> <workflow> --goal ...; resume re-runs only phases that did not pass, so fixing the cause and re-running is safe.

Stop conditionWhat it meansWhat to do
complete / phase passedtask done, gate satisfiednothing; the next phase proceeds
declinedthe worker declined the offered task packetread the counter-proposal in the report; revise the packet or accept the objection, then re-run
needs_humana gate's checks or verifier failed after the retry ladder exhaustedread the proofs under runs/<id>/proofs/ and the report; fix the task or the acceptance criteria; re-run
needs_arbitrationa contested phase produced dissentopen runs/<id>/decisions/DR-<phase>.md, record your decision, then re-run (resume keys on that record)
stuckthe model repeated an identical failing or refused callinspect the refusal via harnessie audit <id>; fix the tool grant, allowlist, or task; re-run
budgetthe run hit its token or dollar ceilingraise the ceiling in config/models.yaml or narrow the goal; re-run
max_stepsthe loop hit its step ceiling without completingraise max_steps for the phase or simplify the task
model_errorthe provider errored twice in a rowcheck the endpoint and API key; re-run
no_actionthe model produced no tool call even after a nudgeusually a role-prompt or model-fit issue; check the role prompt in agents/

Security model

Harnessie's guarantees live in code at the tool and registry layer, so no role prompt can opt out of them. The layers, in brief:

The full threat model, the honest limits of each layer, and the per-platform backend table are in SECURITY.md.

Extending the harness

Adding a tool. Tools are registered with a name, a JSON-schema signature, an effects class (read, write, or execute), the roles allowed to call it, and whether it requires approval. Registration is in harness/tools/builtin.py. The effects class and role grant are enforced at dispatch, so a new tool cannot bypass policy. The built-in grants are a useful template:

ToolRolesEffects
read_file, list_files, task_completeorchestrator, worker, verifierread
run_shellworker, verifierexecute
write_file, accept_task, decline_task, save_fact, expire_fact, request_changeworkerwrite or read

Adding a role. A role is a markdown prompt file plus its kind. Orchestrators live at agents/orchestrator.md, workers under agents/workers/<name>.md, verifiers under agents/verifiers/<name>.md. The harness appends machine-owned boundary text per kind, so the prompt file defines the role's job while policy stays in the harness. Reference the role by file name as a phase's agent.

Adding a workflow. Write a new YAML file under workflows/ following the schema in Writing a workflow. Nothing about the harness needs to change; workflows are declared, not coded.

Evaluation

Harnessie ships deterministic eval scorecards that run against a mock brain with no network, so behavior is reproducible. harnessie eval runs the default suites; pass a suite YAML to run one. Scenarios come in three shapes: golden cases that must pass, risky cases that must fail closed, and recovery cases that must route through the declared ladder. The governance suite scores consent, ownership, contest, and audit behavior specifically, so those guarantees are measured rather than assumed. harnessie eval --live is the separate, operator-opted-in provider scorecard; it skips visibly without HARNESSIE_LIVE=1 and provider configuration. See EVALS.md for the scenario schema and how to add cases.

FAQ

Do I need an API key to try it? No. The test suite and harnessie eval run against a mock brain offline. You only need a key (or a local endpoint) to run a real workflow.

Can I run it fully offline on open-source models? Yes. Point the task classes you use at the local tier in config/models.yaml and use any OpenAI-compatible server (Ollama, vLLM, llama.cpp). Leave the key env unset.

Does it run on Linux? Yes, with a sandbox backend present (bubblewrap preferred, then firejail, then docker). Without a usable backend, shell-using workflows fail closed by design. Windows should use WSL2, which presents as Linux.

What happens if a worker goes off the rails? The gate is the backstop: deterministic checks plus an independent verifier that fails closed, then an automatic reformulate-effort-tier ladder, then a human halt. Ownership lanes and the sandbox bound what a single worker can touch in the meantime.

Where is my run's data? Under runs/<run_id>/: journal.jsonl (the resume ledger), events.jsonl (the hash-chained audit log), proofs/ (check outputs), and decisions/ (contested-phase records). This directory is gitignored.

How do I make an agent stop overwriting another agent's files? Declare lanes in OWNERSHIP.yaml, or rely on first-writer-owns. Cross-agent writes are refused with a request_change remedy.

See also