ch03 state #2: bootstrap state core expansion - #66
Closed
ericleepi314 wants to merge 1 commit into
Closed
Conversation
Grows src/bootstrap/state.py from 52 LOC / 2 fields to ~33 fields with
~50 accessors, the core of Chapter 3's process-singleton tier.
New field groups (mirrors typescript/src/bootstrap/state.ts grouping):
* Identity & paths — original_cwd, project_root, cwd, session_id (UUID),
parent_session_id, session_project_dir. All path setters NFC-normalize.
* Session flags — session_trust_accepted, session_persistence_disabled,
is_remote_mode, has_exited_plan_mode (plus pre-existing is_interactive,
client_type).
* Cost & timing — total_cost_usd, total_api_duration{,_without_retries},
total_tool_duration, total_lines_added/removed, has_unknown_model_cost,
model_usage (dict of ModelUsage), start_time, last_interaction_time.
* Cache optimization — cached_claude_md_content, system_prompt_section_cache,
pending_post_compaction (with mark/consume one-shot helpers),
additional_directories_for_claude_md.
* Model — main_loop_model_override, initial_main_loop_model.
* API correlation — prompt_id, last_main_request_id,
last_api_completion_timestamp, last_emitted_date.
Key architectural moves:
* switch_session() is the only mutator for session_id + session_project_dir,
preserving the CC-34 single-setter discipline. It emits the
_session_switched signal (from PR #65's createSignal) so subscribers
like concurrentSessions PID-file sync can react without bootstrap
needing to import them. regenerate_session_id() is the /clear path
and deliberately does NOT emit.
* SessionId is a NewType for static type discipline (TS analogue of the
branded `type SessionId = string & {__brand}`).
* ModelUsage is a small dataclass holding the per-model breakdown
consumed by add_to_total_cost_state / set_cost_state_for_restore.
* reset_state_for_tests() is gated by PYTEST_CURRENT_TEST so production
cannot accidentally wipe accumulated state; also clears signal
subscribers for test isolation.
* All pre-existing accessors (get_is_interactive, set_is_interactive,
get_is_non_interactive_session, get/set_client_type) preserved exactly.
contextvars-based per-query isolation (SdkContext / run_with_sdk_context)
is deferred to the next PR in the stack — the synchronous global is the
foundation, the async overlay layers on top.
Tests: 38 passing — defaults, NFC normalization (real NFD round-trip),
session lifecycle (switch_session emits, regenerate does NOT emit, parent
lineage), post-compaction one-shot, cost accumulators, model override,
test-reset gating.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced May 11, 2026
3 tasks
singlaamitesh
pushed a commit
to singlaamitesh/clawcodex
that referenced
this pull request
Jul 7, 2026
Consolidates Python's two CostTracker classes (the stub at src/cost_tracker.py and the test-only 283-LOC class at src/services/cost_tracker.py) onto the bootstrap-singleton-backed accounting added in PR agentforce314#66. New files: * src/services/pricing.py — single source of truth for per-model pricing. Pure functions and constants: PRICING, DEFAULT_PRICING, get_pricing(model), compute_cost(model, usage). No state, no class. * src/services/cost_restore.py — restore orchestrator analogue of TS cost-tracker.ts:149. restore_cost_state_for_session(sid) reads the persisted snapshot at ~/.clawcodex/sessions/<sid>.json and dispatches set_cost_state_for_restore into the bootstrap singleton. The gate is the persisted file's session_id field (matches TS exactly): works whether the caller called switch_session(sid) first or not, and also defends against renamed/hand-edited files. _sessions_dir() is exposed as a function so tests can monkeypatch it with tmp_path. Modified files: * src/cost_tracker.py — refactored from 13-LOC stub to a facade over the bootstrap singleton. record(label, units) is preserved for back-compat with costHook.apply_cost_hook. New record_usage(model, usage) routes through compute_cost + add_to_total_cost_state. total_cost_usd is a property that reads through to bootstrap, so two CostTracker instances share state — the "two trackers disagree" problem the gap analysis flagged is gone. * src/services/cost_tracker.py — annotated DEPRECATED in the docstring. Pricing tables/functions removed; now re-exports PRICING, DEFAULT_PRICING, compute_cost, get_pricing from src/services/pricing.py. The 283-LOC CostTracker class is preserved because 23 unit tests depend on it; production code should use the new facade or the bootstrap accessors directly. Tests: 40 passing — 17 new facade/pricing/restore tests + 23 preserved legacy services-CostTracker tests. The restore tests use tmp_path + monkeypatch (no real-home pollution) and verify the file-side gate works without switch_session(sid) being called first. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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
PR 2/6 in the ch03 state stack. Stacked on top of #65 (Signal + Store primitives).
Grows
src/bootstrap/state.pyfrom 52 LOC / 2 fields → ~33 fields with ~50 accessors. Mirrors the field groupings intypescript/src/bootstrap/state.ts:original_cwd,project_root,cwd,session_id(UUID, brandedSessionIdNewType),parent_session_id,session_project_dir. Every path setter NFC-normalizes (matches macOS HFS+/APFS expectations).session_trust_accepted,session_persistence_disabled,is_remote_mode,has_exited_plan_mode, plus pre-existingis_interactive/client_type.total_cost_usd,total_api_duration{,_without_retries},total_tool_duration,total_lines_added/removed,has_unknown_model_cost,model_usage,start_time,last_interaction_time.cached_claude_md_content,system_prompt_section_cache,pending_post_compaction(withmark/consumeone-shot helpers from chapter §3.6),additional_directories_for_claude_md.main_loop_model_override,initial_main_loop_model.prompt_id,last_main_request_id,last_api_completion_timestamp,last_emitted_date.Architectural choices preserved from chapter
switch_sessionis the only mutator forsession_id+session_project_dir(CC-34 single-setter discipline) and emits the_session_switchedsignal.regenerate_session_idis the/clearpath and deliberately does NOT emit.SessionId = NewType("SessionId", str)for static type discipline (TS analogue of the branded type).reset_state_for_tests()gated byPYTEST_CURRENT_TESTso production code cannot accidentally wipe state. Also clears signal subscribers between tests.get_is_interactive,set_is_interactive,get_is_non_interactive_session,get/set_client_typecontinue to work; verified byTestExistingAccessorsStillWork.Out of scope (next PR in stack)
SdkContext+run_with_sdk_context/ contextvars per-query isolation.Session.create()migration to useget_session_id().Both land in
feat/ch03-state-3-contextvars(next).Test plan
pytest tests/test_bootstrap_state.py— 38 passing🤖 Generated with Claude Code