Skip to content

ch03 state #2: bootstrap state core expansion - #66

Closed
ericleepi314 wants to merge 1 commit into
feat/ch03-state-1-foundationfrom
feat/ch03-state-2-bootstrap
Closed

ch03 state #2: bootstrap state core expansion#66
ericleepi314 wants to merge 1 commit into
feat/ch03-state-1-foundationfrom
feat/ch03-state-2-bootstrap

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

Summary

PR 2/6 in the ch03 state stack. Stacked on top of #65 (Signal + Store primitives).

Grows src/bootstrap/state.py from 52 LOC / 2 fields → ~33 fields with ~50 accessors. Mirrors the field groupings in typescript/src/bootstrap/state.ts:

  • Identity & paths: original_cwd, project_root, cwd, session_id (UUID, branded SessionId NewType), parent_session_id, session_project_dir. Every path setter NFC-normalizes (matches macOS HFS+/APFS expectations).
  • 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, start_time, last_interaction_time.
  • Cache optimization: cached_claude_md_content, system_prompt_section_cache, pending_post_compaction (with mark/consume one-shot helpers from chapter §3.6), 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.

Architectural choices preserved from chapter

  • switch_session is the only mutator for session_id + session_project_dir (CC-34 single-setter discipline) and emits the _session_switched signal. regenerate_session_id is the /clear path and deliberately does NOT emit.
  • SessionId = NewType("SessionId", str) for static type discipline (TS analogue of the branded type).
  • reset_state_for_tests() gated by PYTEST_CURRENT_TEST so production code cannot accidentally wipe state. Also clears signal subscribers between tests.
  • All pre-existing accessors preserved exactlyget_is_interactive, set_is_interactive, get_is_non_interactive_session, get/set_client_type continue to work; verified by TestExistingAccessorsStillWork.

Out of scope (next PR in stack)

  • SdkContext + run_with_sdk_context / contextvars per-query isolation.
  • Session.create() migration to use get_session_id().

Both land in feat/ch03-state-3-contextvars (next).

Test plan

  • pytest tests/test_bootstrap_state.py — 38 passing
  • Defaults, NFC round-trip with actual NFD input, switch_session/regenerate semantics, post-compaction one-shot, cost accumulator math, model override clear/set, test-reset gating + signal-subscriber cleanup.
  • No regressions in the full suite (verified locally; 8 pre-existing failures unrelated).

🤖 Generated with Claude Code

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>
@ericleepi314
ericleepi314 deleted the branch feat/ch03-state-1-foundation May 11, 2026 18:08
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>
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