Found while re-sweeping #3649's remaining scope. Sibling to #3834/#3835/#3836 — those tested and merged the grant-binding and continuation-entry mechanism; this issue is about the fact that nothing in production ever exercises it.
Problem
AgentCheckpoint (src/bernstein/core/persistence/agent_checkpoint.py:136) is never constructed for writing anywhere in src/. Every hit for its constructor is a read, deserializing an existing file:
src/bernstein/core/persistence/agent_checkpoint.py:192: return AgentCheckpoint(**json.loads(path.read_text()))
src/bernstein/core/persistence/agent_checkpoint.py:210: candidate = AgentCheckpoint(**json.loads(path.read_text()))
src/bernstein/core/persistence/agent_checkpoint.py:247: orphans.append(AgentCheckpoint(**json.loads(checkpoint_path.read_text())))
save_checkpoint (agent_checkpoint.py:171) — the only function that writes one — has zero callers in src/. scan_orphaned_checkpoints (agent_checkpoint.py:222) has zero callers anywhere. The module's only production consumer is src/bernstein/cli/commands/resume_cmd.py, which imports find_checkpoint_for_task and is_checkpoint_recoverable — both read-only — and never imports save_checkpoint.
Consequence: #3834's grant-comparison logic and #3835's continuation-entry logic (once #3835 lands) are both correct and unit-tested in isolation, but on a real run find_checkpoint_for_task always returns None — there is no checkpoint to check, because nothing ever wrote one with the new fields (or at all). The grant-binding feature #3649 asks for does not activate outside tests.
Separately, src/bernstein/core/tasks/suspension.py (#2552) already implements a more mature suspend/resume system for a different checkpoint concept — Merkle-chained journal rows (record_task_suspension_row), HMAC audit-chain receipts (record_task_suspension), and its own continuity verification (verify_suspension_continuity, suspension.py:1366) that does part of what #3649's ContinuationEntry is trying to do. Whether agent_checkpoint.py's heartbeat-based crash recovery is still live scope, or has been superseded by suspension.py without anyone updating this module's docstring, is not established anywhere.
Proposal
Decide between:
(a) Find or add the minimal correct production call site that constructs an AgentCheckpoint — with role, grant_hash, parent_run_id, chain_head_at_suspend populated — at the moment a real agent's work is actually suspended, and calls save_checkpoint. suspension.py's park_task (suspension.py:878) is the most likely existing suspend entrypoint to check first, since it already calls record_task_suspension_row at exactly the moment this issue is looking for.
(b) Determine that agent_checkpoint.py's crash-recovery path is superseded by suspension.py (#2552) and document that explicitly — a module docstring correction, not a new subsystem — so #3834/#3835/#3836 are understood as "correct and tested in isolation, not wired to a live run" rather than silently assumed active.
Why this shape
Building a full heartbeat-writer from scratch is not evening-sized and risks duplicating suspension.py's already-shipped continuity mechanism for a second, parallel checkpoint concept. A comment-only outcome is legitimate here if the decision comes back "superseded" — this issue's job is to force the decision with evidence, not presume the answer either way.
Scope
Does NOT include: redesigning suspension.py, building general heartbeat/crash-recovery infrastructure beyond the single suspend call site (a), or resolving #3834/#3835/#3836's own remaining acceptance criteria (tracked separately, unaffected by this issue's outcome).
Brief for a coding agent
GOAL: Decide whether AgentCheckpoint (agent_checkpoint.py) still needs a
production writer, and either add the minimal one or document that the
module is superseded. Read before guessing — this repo has two competing
suspend/resume subsystems and picking the wrong one wastes the work.
## Read first, in this order
1. src/bernstein/core/persistence/agent_checkpoint.py:1-25 (module docstring)
and :171-256 (save_checkpoint, load_checkpoint, find_checkpoint_for_task,
scan_orphaned_checkpoints) — confirms the read/write surface described
above.
2. src/bernstein/cli/commands/resume_cmd.py:75-116 (prepare_resume) — the
only production caller, and it is read-only:
`find_checkpoint_for_task` at line 108, `is_checkpoint_recoverable` at
line 110. No `save_checkpoint` import anywhere in this file.
3. src/bernstein/core/tasks/suspension.py:1-40 (module docstring — read the
whole thing, it explains the Merkle-chained-journal-row design) and
:878 (`park_task`) — the likely real suspend entrypoint in the *other*
subsystem. Also read :1008 (`decide_resume`), :1061 (`resume_task`),
:1366 (`verify_suspension_continuity`) to see how much of #3649's
continuation-entry ask this module already does for its own checkpoint
type.
4. `gh pr view 3723 --repo sipyourdrink-ltd/bernstein --json body --jq .body`
— the merged PR that built the grant-binding mechanism this issue found
has no production entry point. It does not mention suspension.py at all —
worth confirming with its author (or the commit history) whether the two
systems' relationship was considered.
## Current missing shape
`git grep -n "AgentCheckpoint(" origin/main -- 'src/**'` returns exactly 3
hits, all inside agent_checkpoint.py itself, all deserializing JSON — never
constructing a fresh instance for writing. Verify this yourself; if a writer
has been added since this issue was filed, the fix might already be partial
or complete.
## What currently depends on this (not "call sites" in the usual sense —
## nothing calls a writer that doesn't exist; these are the consumers
## waiting on one)
- resume_cmd.py:108, :110 — currently always see `find_checkpoint_for_task`
return `None` in production, so `is_checkpoint_recoverable` never runs on
a real checkpoint.
- #3835 (append the continuation entry) has nowhere to fire in production
until a suspend-time writer exists — its own unit tests are unaffected
since they construct checkpoints directly, but its end-to-end value is
zero until this issue resolves.
## Existing helper to reuse
If (a): reuse `save_checkpoint` (agent_checkpoint.py:171) itself as the
write primitive — it already calls `write_atomic_json`
(persistence/atomic_write.py) correctly. Do not hand-roll a second JSON
writer. Compute the four grant fields with `compute_grant_hash`
(agent_checkpoint.py:48), which already exists and is tested.
If (b): mirror how `suspension.py`'s own module docstring documents its
design (read it — it's a good model for a documentation-only PR) rather
than inventing new prose conventions.
## Test matrix
If (a):
| Test | Protects | File |
|---|---|---|
| `test_suspend_writes_checkpoint_with_populated_grant_fields` | the real suspend path produces a checkpoint.json with non-empty role/grant_hash/parent_run_id/chain_head_at_suspend | wherever the suspend call site's own tests live — depends on what (a) turns out to touch, find it before writing a new file |
| `test_resume_after_real_suspend_finds_and_validates_checkpoint` | find_checkpoint_for_task + is_checkpoint_recoverable see a real, non-None checkpoint end to end | same |
If (b): no new test is required for a documentation change; instead confirm
#3833's existing tests (tests/unit/test_grant_bound_checkpoint.py) still
pass unchanged, since a docs-only PR must not touch behavior.
## The trap
Don't assume "no writer" means "build a cron-like heartbeat poller from
scratch." Check whether `suspension.py`'s `park_task` already fires at
exactly the lifecycle moment this issue wants (task suspension) — if so,
the fix may be as small as calling `save_checkpoint` from the same call
site that already calls `record_task_suspension_row`, not inventing a new
lifecycle hook. Conversely, don't assume they're interchangeable just
because both fire "at suspend" — `suspension.py`'s checkpoint concept and
`agent_checkpoint.py`'s `AgentCheckpoint` are different shapes with
different fields; read both fully before concluding either way.
## Decision
(a) vs (b) above. This is the one thing that must be decided, not guessed —
guessing wrong means either wiring a writer that immediately becomes a
second, redundant suspend-tracking system next to `suspension.py`, or
leaving #3834/#3835/#3836's already-reviewed, already-tested logic
permanently unreachable from a real run. State the decision and the
evidence for it in the PR.
## Verify
git -C . grep -n "AgentCheckpoint(" origin/main -- 'src/**'
uv run pytest tests/unit/test_agent_checkpoint.py tests/unit/test_grant_bound_checkpoint.py -v
uv run mypy src/bernstein/core/persistence/agent_checkpoint.py
Found while re-sweeping #3649's remaining scope. Sibling to #3834/#3835/#3836 — those tested and merged the grant-binding and continuation-entry mechanism; this issue is about the fact that nothing in production ever exercises it.
Problem
AgentCheckpoint(src/bernstein/core/persistence/agent_checkpoint.py:136) is never constructed for writing anywhere insrc/. Every hit for its constructor is a read, deserializing an existing file:save_checkpoint(agent_checkpoint.py:171) — the only function that writes one — has zero callers insrc/.scan_orphaned_checkpoints(agent_checkpoint.py:222) has zero callers anywhere. The module's only production consumer issrc/bernstein/cli/commands/resume_cmd.py, which importsfind_checkpoint_for_taskandis_checkpoint_recoverable— both read-only — and never importssave_checkpoint.Consequence:
#3834's grant-comparison logic and#3835's continuation-entry logic (once #3835 lands) are both correct and unit-tested in isolation, but on a real runfind_checkpoint_for_taskalways returnsNone— there is no checkpoint to check, because nothing ever wrote one with the new fields (or at all). The grant-binding feature #3649 asks for does not activate outside tests.Separately,
src/bernstein/core/tasks/suspension.py(#2552) already implements a more mature suspend/resume system for a different checkpoint concept — Merkle-chained journal rows (record_task_suspension_row), HMAC audit-chain receipts (record_task_suspension), and its own continuity verification (verify_suspension_continuity,suspension.py:1366) that does part of what #3649'sContinuationEntryis trying to do. Whetheragent_checkpoint.py's heartbeat-based crash recovery is still live scope, or has been superseded bysuspension.pywithout anyone updating this module's docstring, is not established anywhere.Proposal
Decide between:
(a) Find or add the minimal correct production call site that constructs an
AgentCheckpoint— withrole,grant_hash,parent_run_id,chain_head_at_suspendpopulated — at the moment a real agent's work is actually suspended, and callssave_checkpoint.suspension.py'spark_task(suspension.py:878) is the most likely existing suspend entrypoint to check first, since it already callsrecord_task_suspension_rowat exactly the moment this issue is looking for.(b) Determine that
agent_checkpoint.py's crash-recovery path is superseded bysuspension.py(#2552) and document that explicitly — a module docstring correction, not a new subsystem — so #3834/#3835/#3836 are understood as "correct and tested in isolation, not wired to a live run" rather than silently assumed active.Why this shape
Building a full heartbeat-writer from scratch is not evening-sized and risks duplicating
suspension.py's already-shipped continuity mechanism for a second, parallel checkpoint concept. A comment-only outcome is legitimate here if the decision comes back "superseded" — this issue's job is to force the decision with evidence, not presume the answer either way.Scope
Does NOT include: redesigning
suspension.py, building general heartbeat/crash-recovery infrastructure beyond the single suspend call site (a), or resolving #3834/#3835/#3836's own remaining acceptance criteria (tracked separately, unaffected by this issue's outcome).Brief for a coding agent