Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Agent Harness

A zero-dependency, self-adjusting Python agent harness: LangGraph-style state-machine orchestration plus LangChain-style composability, implemented entirely on the standard library. At startup it ingests the project it lives in (metadata, directory structure, goal), asks Fable 5 to design its own execution graph — nodes, edges, routing rules, and a tailored tool selection — then executes that graph under a hard cost cap with full live observability.

harness/
  engine.py      state-machine executor, dynamic graph topology, cost gateway
  prompts.py     project-context ingestion + self-bootstrapping architect contracts
  dashboard.py   local observability server (stdlib HTTP + SSE) with embedded UI
  tools.py       modular tool registry with sandboxed execution
  jsonspec.py    minimal JSON-schema validator / extractor / fabricator
  __main__.py    CLI: python -m harness
tests/
  smoke_test.py  offline end-to-end tests (mock transport)

The four pillars

  1. Internal component. The harness is a plain package inside your codebase. Graphs are native Python objects (Graph, Node) executed by Engine; you can hand-build them fluently or let the model generate them.
  2. Context-based self-adjustment. ProjectContext.gather() snapshots the repo (manifests, tree, language mix); the architect prompt turns that plus your goal into a JSON graph spec — validated, compiled, and run. Invalid specs get bounded repair rounds; if the architect still fails, a stock fallback graph boots so the run never dies at startup.
  3. Observability dashboard. python -m harness --open serves a live page (127.0.0.1 only): the execution graph as SVG with per-node status, agent execution paths, active sub-agents, token/cost totals with a budget meter, step-latency chart, per-node usage table, and the full event log — streamed over SSE. Implemented with http.server, honoring the zero-dependency constraint (the HTTP layer is isolated in dashboard.py; swapping in FastAPI is a drop-in change if you prefer it).
  4. Fail-safe & cost gateway. Every model call passes ModelGateway: budget preflight + hard USD cap shared across sub-agents, transport retries with retry-after respect, refusal handling with server-side fallback to Opus, and structured-JSON verification gates. A step that fails validation N times (default 3) routes the run to a manual intervention state: the dashboard shows the question and the operator answers (retry / skip / raise budget / abort). Tools run under a sandbox policy: reads confined to the project root, writes confined to the run workspace, subprocesses isolated (python -I, scrubbed env, hard timeouts), network deny-by-default.

Quickstart

No install needed — it runs in place on Python 3.10+ (3.11+ enables pyproject.toml name sniffing via tomllib).

# Offline demo: full pipeline incl. self-generated graph, sub-agent, dashboard
python -m harness --mock --open

# Real run against Fable 5 (set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN)
python -m harness --goal "Find and rank the top risks in this repo" --budget-usd 3 --open

# Tests
python tests/smoke_test.py

Useful flags: --budget-usd, --model, --effort low|medium|high|xhigh|max, --fallback-model none, --allow-network, --max-steps, --human-timeout, --port, --no-dashboard, --exit-when-done, --print-graph (bootstrap only, emit Mermaid, exit), --resume <run-id>, -v.

Every run writes a full trace to .harness_runs/<run-id>/: events.jsonl, graph.json (topology + Mermaid), spec.json (the resumable graph spec), checkpoint.json, state.json, report.json, meta.json, summary.md, plus the sandboxed workspace/ the run's write-tools used.

Durable runs: suspend & resume

The engine checkpoints state and position before every step. If a manual intervention times out with no operator present, the run parks as suspended instead of failing — resume it later and the operator gate reopens exactly where it left off, with prior spend counted against the same budget cap:

python -m harness --resume 20260706-153000-a1b2c3   # or a path to the run dir

Programmatically: Harness.resume(run_dir) (pass graph= for hand-built graphs that wrap Python callables — declarative graphs round-trip from the persisted spec automatically). Crashed, failed, and aborted runs resume the same way; a resumed run re-enters the checkpointed node with fresh step/loop counters.

Programmatic use

from harness import Graph, Harness, HarnessConfig, Node

# Self-adjusting path: the model designs the graph
harness = Harness("summarize this codebase", root=".",
                  config=HarnessConfig(budget_usd=2.0))
report = harness.run()
print(report.status, report.summary)

# Hand-built path: LangGraph-style explicit state machine
graph = (
    Graph("review")
    .add(Node(name="ask", kind="human",
              config={"question": "Ship it?", "options": ["approve", "abort"]}))
    .add(Node(name="done", kind="terminal", config={"summary_from": "human_decision"}))
    .add_edge("ask", "done")
    .set_entry("ask")
)
Harness("gated release", graph=graph, config=HarnessConfig(mock=True)).run()

Node kinds: llm (one structured call), agent (model+tool loop), tool (deterministic call), router (state-driven branch), parallel (concurrent fan-out with reducer merge), subgraph (nested sub-agent with its own graph), human (operator gate), terminal, and func (any Python callable — state -> delta — for hand-built pipelines). State channels support replace / append / merge / add reducers.

LangChain-style composition drops into func nodes via Pipe:

from harness import pipe

enrich = pipe(load_meta, score) | summarize   # each stage: state -> delta
graph.add_node("enrich", enrich)              # later stages see earlier deltas

Graph.to_mermaid() renders any graph as a Mermaid flowchart (also saved into every run's graph.json; preview a goal's generated design with python -m harness --mock --print-graph).

Design notes

  • Model calls hit POST /v1/messages directly over urllib (the zero-dependency constraint rules out the official SDK; the transport is one class, so migrating to anthropic later is trivial). Fable 5 rules are honored: no thinking param (always on), no sampling params, refusal stop_reason handled, fallbacks + beta header sent by default so a safety-classifier false positive degrades to Opus 4.8 instead of failing.
  • Structured output uses forced tool_choice (emit tool) so arbitrary JSON-schema contracts work; the local validator is the enforcement gate, with repair feedback sent back as an errored tool_result.
  • Cost is computed from response usage (cache-aware) against a pricing table; the preflight projects worst-case spend before each call, so the cap cannot be blown mid-flight. The cap is shared by all sub-agents.
  • Mock transport (--mock) deterministically exercises the whole system offline — architect, agent tool-loops, sub-agent spawn, review/route, dashboard — with zero credentials.
  • The dashboard binds loopback only; mutating endpoints require a per-session token injected into the page, so other local web pages cannot drive a run.

About

Zero-dependency, self-adjusting Python agent harness: dynamic execution graphs, live observability dashboard, fail-safe cost gateway, durable runs

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages