Skip to content

feat: wire Ledger onto LedgerBackend (precheck/apply_events) - #132

Merged
susheem-k merged 1 commit into
feat/local-run-state-and-data-scopefrom
feat/ledger-backend-wiring
Sep 12, 2026
Merged

susheem-k merged 1 commit into
feat/local-run-state-and-data-scopefrom
feat/ledger-backend-wiring

Conversation

@susheem-k

Copy link
Copy Markdown
Collaborator

Summary

Stacked on #131 (needs LocalRunState) — the core Ledger/LedgerView piece of tokenops#118 Phase 2. Adds a backend= mode to Ledger, additive alongside the existing store=/in-memory modes (mutually exclusive with store=), so nothing that constructs Ledger/build_governor today is touched.

  • record() batches the step + spent_add (when cost > 0) into one apply_events call sharing an idempotency seq, so the ack's totals covers the run-total cum spend without a separate read_state round trip for the common case.
  • admit/complete/mark_halted/clear_halt are one-shot apply_events writes. Their idempotency keys are random (uuid4), not the control-plane 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 (verified: every current call site is actually always run:-scoped, but the API is more general). 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 — 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 (locked decision #6).
  • velocity/recent/window/step_count are unchanged — always Tier-1 LocalRunState, never a backend round trip. Those are inherently per-process reads.

Not yet wired into build_governor/ControlPlaneClient — that swap, and migrating the ~18 Store-dependent test files, are the next increments (staged deliberately, matching the epic's own Phase 2 vs. Phase 3 split).

Test plan

  • tests/test_ledger_backend_mode.py — 9 new tests against FakeLedgerBackend: pricing/cum_spent, zero-cost crossings write no spend, budget_left (incl. unlimited + unknown budget), admit/complete/inflight, mark_halted/clear_halt, and two Ledger instances sharing one backend for halt visibility + inflight counting (the actual cross-process scenario this rewire targets) and confirming Tier-1 reads stay local.
  • python -m pytest -q → 246 passed, 16 skipped, 11 deselected (9 more than refactor: rename RunState to LocalRunState; add PolicyInstance.data_scope #131's baseline, all new)
  • ruff check/ruff format --check clean
  • mypy src/tokenops/control/ledger.py clean

🤖 Generated with Claude Code

@susheem-k
susheem-k force-pushed the feat/local-run-state-and-data-scope branch from 5d26170 to 93e7678 Compare September 12, 2026 11:16
…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>
@susheem-k
susheem-k force-pushed the feat/ledger-backend-wiring branch from 90ee638 to 3c81d40 Compare September 12, 2026 11:17
@susheem-k
susheem-k merged commit 90fbd93 into feat/local-run-state-and-data-scope Sep 12, 2026
3 checks passed
@susheem-k
susheem-k deleted the feat/ledger-backend-wiring branch September 12, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant