refactor: rename RunState to LocalRunState; add PolicyInstance.data_scope - #131
Merged
Conversation
4 tasks
susheem-k
force-pushed
the
feat/local-run-state-and-data-scope
branch
from
September 12, 2026 11:16
5d26170 to
93e7678
Compare
tishachawla-jg
previously approved these changes
Sep 12, 2026
susheem-k
added this pull request to stack #135
September 12, 2026 12:24
susheem-k
dismissed
tishachawla-jg’s stale review
September 12, 2026 12:26
The merge-base changed after approval.
…cope (#118) First increment of tokenops#118 Phase 2 — two small, independent, mechanical pieces from the locked decisions, landed ahead of the larger Ledger/LedgerView backend rewire: - Ledger.RunState -> LocalRunState (decision #9): makes the two-tier model explicit — this is the per-process Tier-1 cache, not the plane's authoritative run_state. Pure rename, no behavior change. - PolicyInstance.data_scope: local | global, default local (decision #5). Persisted in the local Store (additive migration, mirrors the pattern already used for dims/parent_span/governance_events) and round-tripped through governance_config_for. Not yet consumed by build_governor/Governor — grouping detectors by data_scope is part of the LedgerBackend rewire. Full suite: 237 passed, 16 skipped, 11 deselected (matches main's current baseline pre-#130). ruff check/format and mypy clean on touched files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e Store (#118) Adds a backend= mode to Ledger, additive alongside the existing store=/in-memory modes (mutually exclusive with store=) — the core Ledger/LedgerView piece of the remote-only rewrite's Phase 2 checklist. build_governor/ControlPlaneClient don't construct it yet; that swap, and migrating the ~18 Store-dependent tests, are the next increments (staged deliberately, per the epic's own Phase 2/3 split). - record(): batches the step + spent_add (when cost > 0) into one apply_events call sharing an idempotency seq, so the ack's totals cover the run-total cum spend without a separate read_state round trip for the common case. - admit/complete/mark_halted/clear_halt: one-shot apply_events writes. Their idempotency keys are random (uuid4), not the contract's deterministic {run_id}:...:{seq} recipe — that recipe needs a real run_id to stay collision-free across processes for a non-run-scoped segment key (e.g. an agent-dimension concurrency cap shared across runs), which isn't always available at these call sites. A random key is correct for a one-shot, non-retried write either way; the deterministic recipe only earns its keep once a buffered backend needs a retried flush to regenerate the same key (the existing # TODO(buffering) seam in ledger_backend.py). - cost_micros/budget_left/is_halted/inflight: read via backend.read_state (precheck). No per-call batching across detectors yet (one read per method call) — Governor grouping detectors by data_scope into a single precheck per governed moment is a separate, later optimization, not required for correctness given there's no client-side write buffer yet either (decision #6). - velocity/recent/window/step_count are unchanged: always Tier-1 LocalRunState, never a backend round trip — those are inherently per-process reads. tests/test_ledger_backend_mode.py: 9 new tests against FakeLedgerBackend, including two Ledger instances sharing one backend (the actual cross-process scenario this whole rewire is for) for halt visibility and inflight counting. Full suite: 246 passed, 16 skipped, 11 deselected. ruff check/format and mypy clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…the control plane (#118) The core swap for tokenops#118: ControlPlaneClient.from_env() now requires CONTROL_PLANE_URL/TOKENOPS_URL and raises if neither is set. There is no more TOKENOPS_EMBEDDED and no code path left that silently falls back to a local SQLite ledger. build_governor/tokenops_run construct Ledger(backend=...) (an HttpLedgerBackend, i.e. real precheck/events:batch traffic) for every live run. - client.py: from_env() requires a URL; new .backend property (lazy HttpLedgerBackend); should_mount_run_registration() always False (no embedded mode left to self-host POST /v1/runs under). The store= constructor kwarg remains as an explicit, visible test-only escape hatch — never reachable from from_env(), so no env misconfiguration can select it. - config.py/run.py: build_governor/build_governance_stack take backend=; run.py picks store vs backend off client_obj.embedded (not the local store= param, which was a real bug in an earlier draft of this change — a client= constructed with store= wasn't routing to its own Store). - http_store.py: HttpStore accepts an injectable httpx.Client (mirrors HttpLedgerBackend) so tests can point it at an in-process app. - dev_plane.py (new): launches a real agentplane-control-plane on a real localhost TCP port, in-process. Used by tokenops.demo (still zero-setup: it launches its own throwaway plane and configures it over HTTP) and by tests/conftest.py::live_plane_url for tests that must exercise ControlPlaneClient.from_env() itself (an in-process ASGI app isn't reachable that way since from_env() builds its own plain httpx.Client). - CI now installs agentplane-control-plane from the control-plane repo (git+.../control-plane@main) alongside .[dev,contract] — the [contract] tests and tests/examples/ e2e suite now actually run a real control plane in CI instead of importorskip-ing past it. They're load-bearing coverage now. - Migrated every TOKENOPS_EMBEDDED-dependent test (test_tokenops_run.py, test_control_plane_client.py, test_control_plane_app.py, test_governance_config_cache.py, test_demo.py, and the two tests/examples/ e2e files) onto either the explicit store= escape hatch or live_plane_url. - tests/examples/test_bench_e2e.py and test_triad_e2e.py now configure their policies/budgets on a real, in-process control plane over its own HTTP API (PUT /v1/budgets, PUT /v1/policies) instead of a local Store — this is the concrete demonstration that governance policies configured on the plane reach a live, multi-agent run and HALT/steer it (step_cap, cost_budget, output_runaway CANCEL+RETRY, tool_output_cap deep swap). - Removed the now-dead TOKENOPS_EMBEDDED convenience shims from the three example CLI clients (a2a, brief, triad) — nothing reads that var anymore. - README/onboarding/control-plane-deploy docs: removed the stale TOKENOPS_EMBEDDED documentation and precedence warnings. Full suite: 258 passed, 16 skipped, 1 deselected. ruff check/format clean. mypy clean on every touched file. Not in this change (separate, later Phase 2/3 checklist items): removing the bundled src/tokenops/server + src/tokenops/ui/streamlit, dropping span_id, Governor batching detectors by data_scope into one precheck per call, and migrating the ~18 policy-logic unit tests that construct Ledger()/Store() directly for white-box testing (not a production bypass — never reachable from ControlPlaneClient.from_env()). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rol plane Two active falsehoods left over from the previous pass on this file (docs/env-var table only, not the Quickstart copy itself): - Section 1's manual snippet called ControlPlaneClient.from_env() with no mention a control plane must already be running — it now raises immediately without one. - The Quickdeploy tip claimed "a single-process agent doesn't need it running at all", which was true under the old embedded-ledger fallback and is false now that tokenops has no ledger of its own (#118). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
susheem-k
force-pushed
the
feat/local-run-state-and-data-scope
branch
from
September 12, 2026 12:26
c06c73b to
005f7a5
Compare
tishachawla-jg
approved these changes
Sep 12, 2026
31 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First increment of the tokenops#118 Phase 2 "swap work" — two small, independent, mechanical pieces from the epic's locked decisions, landed ahead of the larger
Ledger/LedgerViewbackend rewire so that change stays reviewable on its own.Ledger.RunState→LocalRunState(locked decision Add control-plane API split on top of latest main. #9): makes the two-tier model explicit — this is the per-process Tier-1 cache, not the plane's authoritativerun_statetable. Pure rename, no behavior change.PolicyInstance.data_scope:local|global, defaultlocal(locked decision #5). Persisted in the localStore(additive migration, following the existingdims/parent_span/governance_eventspattern) and round-tripped throughgovernance_config_for. Not yet consumed bybuild_governor/Governor— grouping detectors bydata_scopeand routingglobal-scope reads throughprecheckis part of the upcomingLedgerBackendrewire.No runtime behavior changes for existing configs — every current policy defaults to
data_scope="local", which is exactly howLedger/Governoralready behave today.Test plan
python -m pytest -q→ 237 passed, 16 skipped, 11 deselected (matchesmain's current baseline)ruff check src tests examplescleanruff format --check src tests examplescleanmypyclean on touched files (ledger.py,models.py,store.py)🤖 Generated with Claude Code