11 KiB
L10/L11 Lived-Spine Lookback Review (#567-#573)
Date: 2026-06-05
Branch: audit/l10-l11-lookback
Base reviewed: origin/main at ea503f6d5177
Scope: PRs #567-#573, the L10/L11 lived-spine stack.
Executive Finding
The stack landed the intended Shape B+ resume spine: bit-exact field/vault/session
serialization, schema-v2 state loading, reboot-transparent L10 traces, EngineIdentity
lineage stamping, and proposal-only idle learning. The strongest wrong=0 hazards
requested in the brief are solid: idle_tick() does not ratify or bypass HITL,
vault restore does not reproject, and persistence restore adds no hidden field
repair at a forbidden site.
One live crash-consistency hazard remains: the new multi-file checkpoint treats
manifest.json as the final commit marker but loads session_state.json
unconditionally. A power loss after session_state.json is atomically replaced
and before manifest.json is replaced can pair an old manifest with newer lived
state on reboot. Fix before the next phase.
Hazards
H1 - Multi-file checkpoint can restore mixed-generation state
Category: hazard
Evidence:
ChatRuntime.checkpoint_engine_state()writes recognizers, candidates, thensession_state.json, thenmanifest.jsonas the stated commit marker (chat/runtime.py:779-803).EngineStateStore.save_session_state()explicitly says session state is saved before the manifest so the manifest is the last durable act (engine_state/__init__.py:225-236).- On load,
_load_engine_state()reads the manifest turn count, then restoressession_state.jsonwheneverpersist_session_state=Trueand the file exists; there is no generation, digest, or turn-count check tying the loaded session snapshot to the manifest (chat/runtime.py:719-755,engine_state/__init__.py:238-251). - The P4 tests cover an orphan temp file and
os.replacefailure formanifest.json, but they only assert recoveredturn_countfrom the manifest; they do not simulate a newer committedsession_state.jsonwith an older manifest (tests/test_l10_continuity.py:204-238).
Impact:
A kill between save_session_state() and save_manifest() can next-boot into a
hybrid checkpoint: old manifest/turn count plus newer field/vault/referent state.
That violates the L10 P4 claim that recovery lands on a valid prior checkpoint.
It is not a versor-condition failure, but it is a lived-spine continuity and
deterministic replay hazard.
Fix before next phase:
Make the checkpoint generation atomic at the composed-state level. Minimal repair:
stamp session_state.json with the same committed turn_count/generation and
load it only when it matches the manifest. Stronger repair: write a generation
directory or content-addressed bundle and have the manifest point to hashes for
recognizers, candidates, and session state. Add a P4 regression test that creates
old manifest plus newer session state and proves restore refuses/falls back to
the valid prior generation.
Drift
D1 - P5a recall precision did not land
Category: drift
The L10 continuity spike design specified P5 as the T-experience gate, including
P5a recall precision@k stability. The landed contract explicitly records P5a as
not_covered (evals/l10_continuity/contract.md:33-38), while P5b/P5c landed
as anchor/coherence catastrophe checks (evals/l10_continuity/predicates.py:291-380).
This is honest in the contract, but it is still a design-vs-implementation cut.
Fix before next phase: Do not claim T-experience or long-horizon recall stability from #567-#573 alone. Add the held-out exact-CGA recall probe set and gate P5a before using the lived spine as evidence of continuous experiencing memory.
D2 - EngineIdentity scope differs from the L10 scoping recommendation
Category: drift
The L10 scoping brief recommended an EngineIdentity hash over
schema_version + canonical_bytes and a lineage chain recording schema migration.
The shipped EngineIdentity is instead a content hash over ratified identity,
safety, ethics, register, anchor-lens packs plus code revision
(core/engine_identity.py:1-20, core/engine_identity.py:85-127).
The manifest stamps this identity and its parent (engine_state/__init__.py:161-189).
This shipped model is internally coherent and tested, but it is not the same
identity primitive described in the scoping record. Lived experience remains in
Shape B+ session_state.json; the identity hash names the ratified substrate.
Fix before next phase:
Amend the L10/L11 design record or ADR scope to say EngineIdentity deliberately
means ratified substrate identity, not checkpoint-content identity. If checkpoint
content is still desired, add a separate checkpoint_identity/digest and link it
from the manifest rather than overloading EngineIdentity.
Gaps
G1 - P4 is sampled at the manifest/orphan boundary, not arbitrary interruption
Category: gap
The design brief elevated power-loss-at-arbitrary-instruction as a primary driver.
The landed runner simulates an orphan .manifest.json.*.tmp and the tests patch
os.replace for manifest writes (evals/l10_continuity/runner.py:107-119,
tests/test_l10_continuity.py:204-238). That is useful, but it does not enumerate
or sample every write boundary in the multi-file checkpoint.
Fix before next phase: After H1 is fixed, extend P4 to cover crashes after each durable file in the checkpoint sequence: recognizers, candidates, session state, and manifest.
G2 - Idle learning backpressure remains unresolved
Category: gap
The roadmap/scoping notes called out absent-reviewer saturation as a real operator
condition. The proposal path has a pending cap (DEFAULT_PENDING_CAP = 256) and
emits a queue-full report when saturated (teaching/proposals.py:48,
teaching/proposals.py:551-586). idle_tick() treats proposal refusals as
non-created proposals and continues (chat/runtime.py:858-865). This is safe for
wrong=0, but it means learning can stall silently from the runtime result shape:
IdleTickResult has no refused/capacity count (chat/runtime.py:508-514).
Fix before next phase:
Expose refusal counts/reasons in IdleTickResult or idle telemetry so operators
can distinguish "no determined candidates" from "HITL queue full."
Solid
S1 - idle_tick() does not bypass HITL
Category: solid
idle_tick() contemplates pending candidates, calls propose_from_candidate(),
and never calls accept_proposal() or writes to the corpus (chat/runtime.py:815-873).
propose_from_candidate() first builds a proposal through check_eligibility(),
which rejects polarity="undetermined" (teaching/proposals.py:165-181), then
records a created event and replay evidence; acceptance remains a separate
operator path (teaching/proposals.py:623-676). Tests cover undetermined refusal
and determined-but-pending behavior (tests/test_continuous_learning_idle.py:72-99).
Answer to the audit question: an undetermined candidate cannot reach pending
through idle_tick(). A determined candidate can reach pending, but not
accepted; that is the intended HITL boundary.
S2 - Vault restore does not reproject
Category: solid
VaultStore.from_dict() decodes persisted arrays, rebuilds _exact_index, leaves
_matrix_cache derived, and never calls store() or reproject()
(vault/store.py:444-469). The bright-line test asserts byte-identical restored
versors and exact recall (tests/test_vaultstore_codec.py:41-84). The existing
live store() path still auto-reprojects every N stores, but the restore path is
pure deserialize plus derived-index rebuild.
Answer to the audit question: engine-state restore does not reproject a vault.
S3 - Persistence adds no forbidden field repair
Category: solid
The array codec is raw dtype/shape/base64 bytes; FieldState.from_dict() rebuilds
from decoded bytes via the constructor, which copies and validates shape/dtype but
does not normalize (field/state.py:117-132, field/state.py:173-183).
SessionContext.restore() composes component restore functions and explicitly
keeps vocab/persona outside session state (session/context.py:373-405).
No new persistence load path calls unitize, normalize, null_project, or a
field repair operator.
Answer to the audit question: no new persistence restore path normalizes or repairs field state at a forbidden site.
S4 - Schema-v2 compatibility/refusal is implemented
Category: solid
_SCHEMA_VERSION = 2 is present (engine_state/__init__.py:74). load_manifest()
tolerates old/equal versions and refuses newer versions with
IncompatibleEngineStateError (engine_state/__init__.py:191-209). Tests cover v2
manifest write and v1 no-session-state fallback
(tests/test_engine_state_session_persistence.py:40-54).
S5 - persist_session_state is default false and opt-in
Category: solid
RuntimeConfig.persist_session_state defaults to False with comments explaining
the per-turn O(n) snapshot cost and always-on-runtime scope (core/config.py:279-285).
ChatRuntime only loads/saves the lived session snapshot when the flag is true
(chat/runtime.py:752-755, chat/runtime.py:789-795). The L10 soak runner forces
the flag on for the resume lane (evals/l10_continuity/runner.py:93-104).
S6 - Referent slot/history aliasing is preserved on restore
Category: solid
ReferentRegistry.to_dict() persists history as source of truth and slots as
slot-to-history-index so from_dict() reuses the same restored ReferentEntry
objects (session/referents.py:173-204). The test confirms the active slot object
is the same object as the corresponding history entry
(tests/test_session_context_codec.py:52-63).
S7 - Source-tree invariant scans exclude .claude/
Category: solid
The whole-tree architectural scans filter .claude in both walk/rglob styles
(tests/test_architectural_invariants.py:240-243,
tests/test_architectural_invariants.py:648-658). This satisfies the audit note
that invariant source-tree scans must exclude agent worktrees.
LOC Accounting
GitHub/merge-commit accounting for #567-#573:
| PR | Title | Files | Additions | Deletions | Net |
|---|---|---|---|---|---|
| #567 | L10 continuity spike | 8 | 1305 | 0 | +1305 |
| #568 | Shape B+ Phase A | 4 | 319 | 1 | +318 |
| #569 | Shape B+ Phase B | 2 | 138 | 0 | +138 |
| #570 | Shape B+ Phase C | 8 | 413 | 5 | +408 |
| #571 | Shape B+ Phase D+E | 9 | 215 | 40 | +175 |
| #572 | L11 identity continuity | 10 | 549 | 5 | +544 |
| #573 | Continuous learning idle | 2 | 203 | 0 | +203 |
| Total | 43 | 3142 | 51 | +3091 |
This was not a net-removal stack. It added a new eval lane, persistence codecs, state wiring, identity lineage, and idle-learning tests/substrate.
Validation
Requested baseline:
core test --suite smoke -q
The bare core command was not initially on PATH in the fresh worktree. After
running through the local editable project entrypoint:
uv run --with-editable . core test --suite smoke -q
Result:
87 passed in 116.32s (0:01:56)
No source code was changed during this audit. Only this report was added.