Repository navigation
Description
Umbrella for making TokenOps hard-dependent on the HTTP control plane for the
ledger: remove the local-file / embedded path from the agent, move run state to the
plane, keep per-turn latency sane. Absorbs #55 and #60; coordinates with
agentplane-control-plane (see its epic).
Full working plan + wire contract: scratch/remote-only-control-plane-plan.md and
scratch/phase-0-contract.md in the repo (contract moves to
control-plane/docs/api-contract.md when Phase 1 starts).
Decisions (locked)
POST /v1/ledger/precheck— one consolidated read per governed call.POST /v1/ledger/events:batch— one batched write; single-op ledger routes become wrappers.- Idempotency key required on every write event; plane dedupes.
- Run state (
steps/window/velocity) moves to the plane (run_statetable); Tier-1LocalRunStatestays as a per-process cache. - Policies carry
data_scope ∈ {local, global};localreads Tier-1 only,globalreads the plane viaprecheck. - No client-side event buffer yet —
apply_events(list)is the only write path so a buffer can wrap it later. - No accuracy-loss bounding — budgets stay a soft cap.
trajectory_hintdisabled (done); notrajectory/*routes.RunState→LocalRunState(rename).- Budget fan-out = one
spent_addevent with alltargets; plane applies in one txn. - Inflight-counter safety (call-id set) is out of scope → Inflight ledger is a bare per-segment counter — not safe under async/batched/retried admit·complete #116.
run_registrationsmerged intoruns; identity columns write-once;create_runremoved from the agent path → Merge run_registrations into runs; make identity columns write-once; stop agents calling create_run #117.- One
runsrow perrun_id— no per-agent participation rows. span_idremoved from TokenOps entirely; cross-hop link isX-TokenOps-Run-Idonly.- No
TOKENOPS_EMBEDDED— unit tests use an in-memoryFakeLedgerBackend, kept honest by a contract suite parametrized against the real plane overhttpx.ASGITransport. - UI lives in the plane — delete
src/tokenops/ui/and thestreamlitdependency.
Update (0.3.0, 2026-09-12): the hard HTTP dependency landed — no more local ledger
fallback, from_env() requires a URL, Ledger(backend=...) is wired end to end. Epic
stays open: data_scope-based detector grouping, span_id removal, server/ui
teardown, and Phase 3/4 are still outstanding. See checklist below.
Phases
Phase 0 — contract & decisions ✅
- Wire contract (
scratch/phase-0-contract.md) - Plane-unreachable behavior settled (3 modes + circuit breaker)
-
trajectory_hintdisabled in code
Phase 1 — agentplane-control-plane → 0.2.0 ✅ merged
control-plane#12 merged (closes control-plane#10): precheck, events:batch,
run_state, data_scope, run-scoped halt, PRAGMA user_version migrations,
registered_at, PATCH narrowing, start/stop/status CLI, Dockerfile. Additive —
0.1-era clients still work. Destructive fold deferred → control-plane#11 (0.3.0, not
required for this epic).
Phase 2 — tokenops SDK
-
from_env()requires a URL; delete embedded branch; reworkdemo.pyto launchcontrol_plane.appin-process — shipped in 0.3.0:ControlPlaneClient.from_env()raises withoutCONTROL_PLANE_URL/TOKENOPS_URL,TOKENOPS_EMBEDDEDis gone,tokenops.demolaunchestokenops.control.dev_planein-process - Remove bundled
tokenops.server(src/tokenops/server/), rewire Docker/compose/Makefile/examples tocontrol-plane serve, collapseshould_mount_run_registration/mount_run_registration, preserve therun_id↔trace_idbinding (Reconcile divergent 'run' vs 'trace' (and 'span') terminology across TokenOps / Chronicle / control-plane #113) —src/tokenops/server/still present;should_mount_run_registration()now always returnsFalse(0.3.0) but the module itself isn't removed yet - Add
agentplane-control-planeas a[contract]optional dep (kept out of[dev]until 0.2.0 is on PyPI; plane-backed testsimportorskip) — LedgerBackend protocol + HttpLedgerBackend (remote-only, Phase 2.1) #120 -
LedgerBackendprotocol;HttpLedgerBackend(direct) +FakeLedgerBackend(tests). Only write path =apply_events(list)with a# TODO(buffering)seam.register_run/resolve_run/governance_config_for/patch_run_record(dropssteps/cost_micros) — LedgerBackend protocol + HttpLedgerBackend (remote-only, Phase 2.1) #120 -
Ledger(backend=...)wired intoLedger/ControlPlaneClient(0.3.0): every write routes throughLedgerBackend.apply_events, every spend/inflight/halt read throughread_state(precheck);RunState→LocalRunStaterename shipped (refactor: rename RunState to LocalRunState; add PolicyInstance.data_scope #131) -
RemoteLedgerViewoverprecheck; Governor groups detectors bydata_scope, oneprecheckper call —PolicyInstance.data_scopeis persisted and round-tripped (refactor: rename RunState to LocalRunState; add PolicyInstance.data_scope #131) but not yet consumed bybuild_governor - Drop
span_id(Add Planner→Researcher→Writer triad TokenOps bench #14): no minting, noX-TokenOps-Parent-Span-Id, strip fromSpanContext/Observation/BoundaryStep -
Ledger.record:if cost > 0guard on budget fan-out — Ledger.close_run() + skip zero-cost spend writes (remote-only prep) #121 -
register_runreturns fullRunRegistration— kill register-then-resolve -
governance_cachere-key(base_url, agent)+ two-level lazy TTL (Governance config: one-time bootstrap load + two-level lazy TTL (stop fetching GET /v1/governance/{agent} on every run start) #114); no-TTL per-run_idregistration cache - Plane-unreachable:
TOKENOPS_PLANE_UNREACHABLE=fail-fast|passthrough|local-policies-only+ circuit breaker +TOKENOPS_FALLBACK_RUN_CAP_MICROS -
Ledger.close_run()fromtokenops_runfinally(Ledger.runs (per-run RunState) is never evicted — unbounded memory growth for long-lived / shared-governor processes #115) — Ledger.close_run() + skip zero-cost spend writes (remote-only prep) #121 (note: Ledger.runs (per-run RunState) is never evicted — unbounded memory growth for long-lived / shared-governor processes #115 also wants a TTL/max-entries sweep backstop for the shared-governor case — not done, issue stays open) - Remove
src/tokenops/ui/+streamlitdep (Fix triad.yaml: tool_reject → tool_fix #16); decideexamples/ui/ - Compat matrix in README (Part 13); docs pass
Phase 3 — tests & CI
-
FakeLedgerBackend(tests/fakes.py) + conftest fixture — LedgerBackend protocol + HttpLedgerBackend (remote-only, Phase 2.1) #120 - ASGI backend fixture (
http_backend/any_backend, FastAPITestClient—httpx.ASGITransportis async-only) againstcontrol_plane.app— LedgerBackend protocol + HttpLedgerBackend (remote-only, Phase 2.1) #120 -
test_ledger_backend_contract.pyparametrized over both backends (24 tests) — LedgerBackend protocol + HttpLedgerBackend (remote-only, Phase 2.1) #120 -
tests/examples/(e2e-marked) moved into default CI; CI installsagentplane-control-planefrom source for[contract]/e2e coverage (e2e/live tests excluded from CI, and 3 of 11 are currently broken on main #127, fix: bring e2e tests into default CI, fix 3 stale against attribution hardening #130) — these now exercise real HALT/MUTATE decisions against a real in-process plane, not just plumbing - Triage ~22
Store(...)test files per the Part 14 taxonomy; drop allTOKENOPS_EMBEDDED - Round-trip regression gate (≤2/call; →1 with buffering)
- Plane-unreachable tests (all 3 modes + fallback cap)
- compose: agents get
TOKENOPS_URLonly, no DB volume
Phase 4 — follow-ups
-
BufferedLedgerBackend(gated on Inflight ledger is a bare per-segment counter — not safe under async/batched/retried admit·complete #116) - real
Durability: queuedqueue - SSE/websocket halt push
- server-side pricing → derive ledger from
envelopes:batch(Part 8) - per-run authz (control-plane #6)
-
trajectory_hintremote re-enable
Sub-issues
#113 (run/trace/span terminology) · #114 (governance-config TTL) · #115 (LocalRunState leak) · #116 (inflight counter) · #117 (run_registrations/runs merge)
Activity
susheem-k commentedon Sep 10, 2026
Phase 1 (control-plane) tracked in theagentplane/control-plane#10.
refactor: rename RunState to LocalRunState; add PolicyInstance.data_s…
feat: wire Ledger onto LedgerBackend (precheck/apply_events) alongsid…
feat: eliminate the local ledger — tokenops is now hard-dependent on …
docs: fix Quickstart to reflect the hard dependency on a running cont…
refactor: rename RunState to LocalRunState; add PolicyInstance.data_s…
feat: wire Ledger onto LedgerBackend (precheck/apply_events) alongsid…
feat: eliminate the local ledger — tokenops is now hard-dependent on …
docs: fix Quickstart to reflect the hard dependency on a running cont…
refactor: rename RunState to LocalRunState; add PolicyInstance.data_s…
chore: release 0.3.0 (#136)
feat(control): LedgerBackend protocol + HttpLedgerBackend (remote-only,
feat(ledger): close_run() + skip zero-cost spend writes (remote-only …