Roadmap
The live build list β measured status per workstream, and the seams left.
Preamble β "live build list"
Everything between today and a full-fledged general-purpose agent runtime; status is measured, not aspirational. Legend: [x] done, [~] partial, [ ] not started, B blocks other work, ! known-false claim shipping today. Overall ~70% of the list (72 of 103 enumerated items), re-derived 2026-07-28 by executing every claim against the tree rather than reading the commit log β the percentage is the fraction of enumerated items verified done, this project's own definition of scope and not the industry's. At that point: pytest β 1,381 passed, 10 deselected; ruff check . clean; the wheel imports all 94 submodules in a clean virtualenv. Four subsystems were once built, tested and reachable by nothing (planner/, policy/, the CLI's memory store, cost on trace events); all four are closed and one seam is left β server/ still ships its own in-process session runtime instead of the durable session/ one (Β§12.3).
Next five things
In order: (1) Put the HTTP API on the real session layer (Β§12.3) β the last seam; two session layers, one seam. (2) Let admission constrain arguments (Β§5.6) β ! the gap most likely to be over-read; a rule reaches a node's kind and never its args. (3) Route the tool plane through the document (Β§7.5 remainder) β nothing calls permission_policy(), so grapharc agent is still governed by Python objects rather than the TOML file. (4) ~~Publish to PyPI~~ (Β§11.1) β done, 0.1.0 is live; next is 0.1.1 to correct the __version__ the wheel carries, built only from a clean tree via the tag-driven release.yml rather than a local uv build. (5) ~~Decide the version~~ β decided: 0.1.0; a 1.0 would imply API stability that several days-old subsystems do not have.
0. Correctness debt β ship-blockers, section clear
Every item verified by running code; all shipped.
- [x] 0.1 β
ctypesescape closed, plussqlite3.connect,_posixsubprocess.fork_exec, and compiled-extension imports from outside the runtime paths. - [x] 0.2 β Child environment scrubbed to an allowlist.
- [x] 0.1b β Runtime paths are read-only; a sandboxed tool writing
evil.pthraisesSandboxViolationwhile stdlib reads still succeed. - [x] 0.3 β
max_secondsinterrupts a running node (SIGALRM on the main thread, async-exception injection elsewhere) and re-arms. - [x] 0.4 β Tokens charge automatically via a usage callback, deduplicated by call identity;
max_tokensenforced aton_llm_end. - [x] 0.5 β Types validated at write time (annotation constraints bite; a model's own
@field_validatordoes not run at write time). - [x] 0.6 β README claims corrected, each disproof re-run.
- [x] 0.7 β Neo4j fiction removed; a real
ClaimStoreprotocol exists. - [x] 0.8 β
pytestdefaults fixed:addoptscarries-m 'not live'. - [x] 0.9 β
LICENSEcopyright and README clone URL corrected. - [x] 0.10 β Gateway tempfile leak fixed via an
ExitStack-managedTemporaryDirectorywithignore_cleanup_errors=True.
1. Graph kernel β [~] ~85%
No longer amputates LangGraph; async, Command returns and state access all came through, which unblocked the HTTP API and the session layer.
- [x] Typed state, per-node declared writes, deep-copy isolation
- [x] Budgets (iterations/tokens/seconds/concurrency), convergence guards, DAG mode, cycle detection
- [x] JSONL traces, checkpoint resume, fail-closed run context
- [x] Bounded fan-out with worker isolation and dedup
- [x] 1.1 β Async
ainvoke/astream/astream_events; sync entry points refuse async graphs withAsyncNodeError;v3refused. - [x] 1.2 β
Commandreturns accepted (updategoes through the write allowlist,gotovalidated at the node boundary); aCommandas input stays unsupported deliberately. - [x] 1.3 β
get_state/update_state/get_state_historypassthrough plusa*twins;update_staterejects unknown fields and type-checks. - [x] 1.7 β
TraceRecorder.thread_summaryis incremental: O(events) over a thread's life rather than O(eventsΒ²). - [~] 1.5 β
interrupt()half-works: suspend is real, no supported resume; still writes a spuriousphase="error"line carrying theGraphInterrupt. - [ ] 1.4 β Passthrough
retry_policy,cache_policy,durability, subgraphs. - [ ] 1.6 β Offer a decorator form so discipline composes with LangGraph instead of replacing it.
- [ ] 1.8 β Make deep-copy opt-out-able for large states (currently unconditional).
2. Model gateway β [~] ~80%
- [x] Claude Code CLI adapter (tools disabled, argv array, stdin prompt); correct cache-token accounting
- [x] 2.1 β
bind_toolsworks on OpenRouter; stillNotImplementedErroron Claude-CLI (inherent toclaude -p). - [x] 2.2 β
with_structured_outputworks on OpenRouter. - [x] 2.3 β
_streamand_agenerateboth work on OpenRouter. - [x] 2.5 β
RetryPolicy: 3 attempts, 0.5s initial, Γ2, capped 20s, 25% shrinking jitter, explicit transient/deterministic split; streaming not retried. - [x] 2.6 β Routing rules: provider
order/sort/max_price/require_parametersplus model-levelfallback_models. - [x] 2.7 β Enforced cost ceilings via
SpendMeter(ensure_headroombefore, raise after charging); unpriced calls land inunpriced_calls. - [x] 2.9 β Backend registry:
claude-cli,openrouter,openai,ollama,mock;openaiis both a backend and an OpenRouter author slug and the backend wins. - [~] 2.4 β Provider adapters: OpenRouter, Claude CLI, OpenAI API, local Ollama,
mock; the three OpenAI-wire backends share one base class. Direct Anthropic API and vLLM pending; theapiextra exists with nothing importinganthropic. - [ ] 2.8 β Prompt caching support and per-run model pinning.
3. Tool plane β [~] ~55%
- [x] Registry, denyβaskβallow permissions, hooks, approval gates; audit-hook executor (path confinement, network gating, spawn refusal, SIGKILL escalation)
- [x] 3.1 β Wired to an agent:
AgentNodedrives registry, permissions and executor;grapharc agent <task>drives the core toolset;examples/agent_fixit.pyis a shipped graph that calls tools. - [x] 3.2 β Container executor at
grapharc/harness/container.py; live-runtime tests skip when no runtime or image is present and never pull one (exercised against Docker withpython:3.12-slim). - [x] 3.3 β Core tools:
read_file,write_file,edit_file,list_dir,glob,grep,run_command. - [ ] 3.4 β Browser tool and HTTP/network tool.
- [ ] 3.5 β MCP client (the
mcpextra exists with nothing importing it). - [ ] 3.6 β Progressive disclosure / tool search for large tool sets (
ToolRegistry.visible()filters by permission, a different thing). - [ ] 3.7 β Idempotency keys for side-effecting tools.
- [ ] 3.8 β Large-output offloading (
ToolLimitstruncates at 20,000 chars; nothing is offloaded).
4. Agent node β [~] ~60%
- [x] 4.1 β
AgentNodebuilt: observe β model β tool request β permission check β sandboxed execute β repeat, budgeted and traced. A denied tool is fed back to the model rather than killing the run; malformed tool JSON is reported back; stall detection keys on the tool result; onlytarget_metfills the answer field, otherwise the last utterance stays inpartial_output. - [ ] 4.2 β Context management (compaction, just-in-time retrieval).
- [ ] 4.3 β Subagent spawning with context isolation and summary-only return.
- [ ] 4.4 β Skills / instruction packs loaded on demand.
- [ ] 4.5 β Per-node model and effort tiering.
5. Planner & admission β [~] ~85% β the crux, and it closed
- [x] 5.1 β Planner node that proposes rather than acts;
Subgraph/ProposedNodeforbid extra fields so abody=key is aValidationError. - [x] B 5.2 β Admission checker: five checks (registry, policy, budget, depth, acyclicity), all run on every proposal; nothing executes during a check; every decision keys on
kind, nevername; worst-case cost summed from the registry. - [x] 5.3 β Rejections are first-class traced
phase="admission"events carrying status, fingerprint, checks run and failed codes. - [x] 5.4 β Replanning with loop protection:
AdmissionResult.feedback()becomes the planner's next input; nine stop reasons includingno_progress,max_rounds,admission_refused,planning_failed; the loop cannot fall out of the bottom. - [x] 5.4b β Materialisation binds to the authorisation by fingerprint (
NotAdmitted);Command(goto=β¦)confined to the admitted edge set withUnadmittedTransition. - [ ] 5.5 β Decomposition strategies (map-reduce, specialist fan-out) as reusable planner presets.
- [ ] ! 5.6 β Admission cannot constrain arguments;
forward_args=Truehands the raw dict to a factory unchecked. Admission authorises the verb, not the object.
6. Session runtime β [~] ~85%
- [x] 6.1 β Long-lived sessions with a status lifecycle in a
SessionStore. - [x] 6.2 β Resume across process restart, verified across two interpreters; append-only log shows each node exactly once; resuming process must register the graph or gets
UnknownGraphError. - [x] 6.3 β Interrupt and steering at superstep boundaries.
- [x] 6.4 β Event queue for multi-turn input, durable in the store.
- [x] 6.5 β Human approval as a suspending graph node via LangGraph
interrupt_before; every gated node on a superstep boundary held separately with its own request id β a signature onsend_emailis not a signature ondelete_records. - [x] 6.6 β Concurrent sessions with isolation, each on its own thread.
- [ ] 6.7 β Async turns (
run()is synchronous and occupies its caller until the session stops). - [ ] 6.8 β A real runner lease; nothing reclaims a session whose runner died holding it. That is a claim, not a lease.
7. Policy engine β [~] ~75% built, 0% wired
- [x] 7.1 β Declarative TOML policy over nodes, edges, tools and spend; tiered rather than positional evaluation (every deny before every ask before every allow); example at
grapharc/policy/example.toml. - [x] 7.2 β Approval routing:
askrules carry a requiredapprover_role;engine.approval_router(handlers, tenant=β¦)andengine.permission_policy(tenant=β¦). - [x] 7.3 β Policy versioning and decision audit in JSONL naming resource, subject, tenant, effect, rule id, reason, policy version and document digest.
- [x] 7.4 β Multi-tenant scoping; a rule scoped to an unknown tenant is a load error and a request naming one is a recorded denial.
- [x] 7.5 β The document reaches the gate via
edge_policy(tenant=β¦), withgrapharc plan --policyas a shipped caller.
8. Memory & artifacts β [~] ~85%
- [x] Claims with provenance, supersession instead of overwrite, Unicode-safe entity normalization
- [x] 8.1 β
SQLiteMemoryStoreverified durable across genuinely separate processes. - [x] 8.2 β
SQLiteArtifactStoreon the same file: append-only with versions, mandatory provenance, content-addressed blobs written before the referencing row;nameis metadata and never used to build a path. - [x] 8.3 β Real retrieval: BM25F over subject+predicate+object, optional vector channel with a similarity floor, graph traversal with per-hop score decay; every result list sorted by a total order so both backends rank identically.
- [x] 8.4 β Automatic contradiction detection (
detect_contradictions,add_and_detect) β structural, not semantic; reports and never resolves. - [x] 8.5 β Token-budgeted context rendering:
render_contexttakesmax_tokens,max_dead_endsand acount_tokenscallable. - [x] 8.7 β
grapharc run --memory PATHgivesstage6andcapstonetheSQLiteMemoryStore; in-process remains the default. - [ ] 8.6 β Per-tenant/user memory scoping.
9. Triggers & surfaces β [~] ~55%
- [x] CLI (
run/trace/metrics/viz) - [x] 9.1 β HTTP API: seven FastAPI routes (create, list, get, post event, SSE stream, NDJSON trace, healthz), verified end to end including 404 on unknown session/graph and 422 on input failing the state schema; behind the
serverextra. - [x] 9.2 β Real CLI: ten commands β
run/plan/agent/serve/models/replay/diff/trace/metrics/vizβ every one with--json; exit codes0/1/2are part of the interface. - [x] 9.6 β Streaming output to clients via SSE with a
last-event-idcursor so a reconnect skips what it already saw. - [ ] 9.3 β Cron schedules; 9.4 β webhook triggers.
- [ ] 9.5 β Chat channels (Slack / Discord).
10. Operations β [~] ~60%
- [x] JSONL traces, metrics summaries, Mermaid path rendering
- [x] 10.1 β Replay a run from its trace: a reconstruction, not a re-execution;
diff_runs/diff_tracealign two runs and report where they diverged. - [x] 10.3 β OpenTelemetry export, one root span per run and one child per node execution, dependency confined behind a Protocol; verified this pass against
opentelemetry-sdk1.44.0 with spans reaching anInMemorySpanExporter. - [~] 10.4 β Cost attribution per run, thread (session) and node; price recorded not guessed,
recorded_cost_usdandestimated_cost_usdnever mix. One gap left: no tenant on a trace event. - [ ] 10.2 β Rollback and versioned graph/prompt configs.
- [ ] 10.5 β Alerting on budget, failure, and verifier-drift.
11. Product & distribution β [~] ~35%
- [x] Builds a clean wheel; 1,534 tests; CI; ruff clean; a bare wheel install imports most of the 103 submodules (
gateway.openrouterand the wholeserverpackage need their extras). - [x] 11.6 β Classifiers,
[project.urls], contribution guide; every URL names a file that exists and theallextra is self-referential. - [x] 11.7 β The source is on the public remote;
git clone β¦ && uv sync --group devworks. This was the single most consequential false claim in the tree for most of the project's life, because it was the first one a reader hit. - [x] 11.1 β Publish to PyPI:
0.1.0is live, verified in a clean virtualenv through tographarc demo stage0. - [~] 11.3 β Live-model examples behind the
livemarker: 10 tests, deselected by default; CI wiring still pending (needs a key in secrets). - [~] 11.2 β Docs site: a cookbook is landing under
docs/cookbook/; no published site. - [ ] 11.2 β Ship
0.1.1to correct the__version__; build only from a clean tree (git statusempty, thenuv build). - [ ] 11.4 β Benchmarks, including published losses.
- [ ] 11.5 β External security review (the audit-hook sandbox is defense in depth; Β§3.2's container executor is the boundary to review).
12. Seams β [~] ~80%
Each item is two working subsystems that do not know about each other; none is research, and all are worth more than another feature. Four of the five are closed.
- [x] 12.1 β A surface for the governed loop:
grapharc plan <goal>drives propose β admit β materialise β execute β replan and prints every round, its admission status and rejection codes;--model SPEC,--registry module:attr(plusSTATE_SCHEMAandWRITES);tests/test_cli.pypins rounds, stop reason and exit codes. - [x] 12.2 β
PolicyDocumentβEdgePolicyviaPolicyEngine.edge_policy(tenant=β¦); pinned tocheck_edgeacross an edge Γ tenant matrix, an undeclared tenant compiling to a policy that permits nothing;grapharc plan --policy PATH --tenant NAMEis the caller, and with a*->deploydeny rule round 1 is refused while removing the rule admits it. - [x] 12.4 β A durable store for the shipped graphs (
grapharc run --memory PATH), proved across a real process boundary. - [x] 12.5 β
cost_usdonto trace events (see Β§10.4). - [ ] 12.3 β The HTTP API on the real session layer:
create_app(runtime=β¦)already takes anySessionRuntime; missing is the implementation backed bygrapharc.session, which would give durable sessions, cross-process resume, and approval events that are delivered rather than merely recorded.
Milestones
Five milestone rows, each with scope, a gate stated as a real task against a real model, and a measured status. V0 (Β§0 + Β§2.1 + Β§4.1 β an agent edits a file and runs tests, permission-gated and budgeted) passed. V1 (Β§5 + Β§1.1β1.2 β "Refactor this repo and run tests" plans its own fan-out; an over-budget plan is rejected with a recorded reason) mechanism done, gate not run. V2 (Β§6 + Β§8.1 β a session survives restart; a human approves a destructive action mid-run) mechanism done, gate not run, verified across two processes with a scripted model. V3 (Β§7 + Β§9 β incident response runs from a webhook, remediation gated on approval) blocked on Β§12.2 and Β§9.4. V4 (Β§10 + Β§11 β replay any production run; a stranger pip installs it) replay works; nobody can install it (dated against Β§11.7).