Problem
Four operator surfaces report parked sessions. None of them can ever report one.
SpawnSupervisor.spawn() (src/bernstein/core/agents/spawn_supervisor.py:276)
is the only path to _consume_respawn (:348), _park (:361-369) and
SupervisorState.PARKED. Grepping SpawnSupervisor and spawn_supervisor
across src/ finds the module itself, docstring cross-references, and three
read-only CLI call sites: src/bernstein/cli/commands/status_cmd.py:428-430
and src/bernstein/cli/commands/agents_cmd.py:579-581 and :601-603, which
call parked_sessions() and resume(). Nothing in the orchestrator or the
spawner ever calls .spawn().
So the respawn budget cannot be consumed in production, and
bernstein agents resume <id> can only resume a session parked by a unit test.
The aggregator has the same shape independently.
load_parked_sessions() reads .sdd/runtime/spawn_supervisor/parked.json
(src/bernstein/core/orchestration/supervisor_aggregator.py:168-179). Grepping
parked.json across src/ and tests/ returns only that reader. Its
fallback branch keys on .sdd/runtime/failures/*.json entries with
kind == "respawn_exhausted" (supervisor_aggregator.py:193); grepping
respawn_exhausted across src/ and tests/ finds only that reader and one
test name. The function structurally returns the empty set.
Everything built on it inherits that: bernstein status, bernstein supervisor, bernstein fleet and the TUI status bar
(src/bernstein/tui/status_bar.py:465) report "nothing parked" unconditionally
and always, on every run, whatever happened.
This is the worst shape a recovery path can have. It is not absent, so nobody
notices it is missing; it is not broken in a way that errors; it answers, and
the answer is always the reassuring one.
Proposal
Two things, in order.
Wire the writer. The spawner's failure path calls SpawnSupervisor.spawn()
so respawn attempts are budgeted and exhaustion parks the session, and _park
persists to the path the aggregator already reads. The read surfaces do not
change; they start being fed.
Then make the silence impossible to repeat. A surface that reports the
absence of something must be able to distinguish "none occurred" from "the
producer never ran". load_parked_sessions() returns a result that carries
whether the store has ever been written in this run, and the surfaces render
0 parked and parked state unavailable differently.
The second half is the part that generalises. An empty recovery surface should
be a claim the run can support, not a default.
Why this shape
Every other guarantee in this project is checkable rather than assumed, and this
is a surface that has been quietly asserting a fact for its whole life with
nothing behind it. Making the unavailable case distinct from the empty case is
what turns the status bar from a decoration into evidence, and it is cheap: the
store either has a write for this run or it does not.
Scope
- Call
SpawnSupervisor.spawn() from the spawner's failure and respawn path so
the budget is consumed and exhaustion parks.
_park persists to .sdd/runtime/spawn_supervisor/parked.json, the path
supervisor_aggregator.load_parked_sessions already reads.
load_parked_sessions() distinguishes empty from unavailable.
bernstein status, bernstein supervisor, bernstein fleet and the TUI
status bar render the two cases differently.
bernstein agents resume <id> resumes a session parked by a real run.
Done means
Start here
Picking this up cold? Land this first and stop.
Step 1 - make the empty answer honest, before wiring anything. Change
load_parked_sessions() in
src/bernstein/core/orchestration/supervisor_aggregator.py:168-179 to return a
result that distinguishes three cases: the store file is absent (unavailable),
present and empty (zero parked), present with entries (the list). Render the
unavailable case distinctly in src/bernstein/cli/commands/status_cmd.py:428-430.
Do not touch the spawner yet.
Proves it: a test named for the property that absent and empty are different
answers. Assert the unavailable result with no file on disk; write an empty
store and assert the zero-parked result; write one entry and assert it is
returned. Then assert the status output text differs between the first two -
that assertion is the whole point of the step, because it is the one that would
have caught this.
Not in step 1: calling SpawnSupervisor.spawn() from the spawner. That is
the larger half and it changes live failure handling, so it wants its own PR
with its own failure-path tests. One thing worth knowing before planning step 2:
_publish_exhausted (spawn_supervisor.py:375-392) already exists alongside
_park, so there are two exhaustion outputs with one unreachable caller between
them - decide which is authoritative before wiring, rather than feeding both.
Partial work is welcome and normal here. Title the PR "Partial implementation of
this issue" and say what remains.
Problem
Four operator surfaces report parked sessions. None of them can ever report one.
SpawnSupervisor.spawn()(src/bernstein/core/agents/spawn_supervisor.py:276)is the only path to
_consume_respawn(:348),_park(:361-369) andSupervisorState.PARKED. GreppingSpawnSupervisorandspawn_supervisoracross
src/finds the module itself, docstring cross-references, and threeread-only CLI call sites:
src/bernstein/cli/commands/status_cmd.py:428-430and
src/bernstein/cli/commands/agents_cmd.py:579-581and:601-603, whichcall
parked_sessions()andresume(). Nothing in the orchestrator or thespawner ever calls
.spawn().So the respawn budget cannot be consumed in production, and
bernstein agents resume <id>can only resume a session parked by a unit test.The aggregator has the same shape independently.
load_parked_sessions()reads.sdd/runtime/spawn_supervisor/parked.json(
src/bernstein/core/orchestration/supervisor_aggregator.py:168-179). Greppingparked.jsonacrosssrc/andtests/returns only that reader. Itsfallback branch keys on
.sdd/runtime/failures/*.jsonentries withkind == "respawn_exhausted"(supervisor_aggregator.py:193); greppingrespawn_exhaustedacrosssrc/andtests/finds only that reader and onetest name. The function structurally returns the empty set.
Everything built on it inherits that:
bernstein status,bernstein supervisor,bernstein fleetand the TUI status bar(
src/bernstein/tui/status_bar.py:465) report "nothing parked" unconditionallyand always, on every run, whatever happened.
This is the worst shape a recovery path can have. It is not absent, so nobody
notices it is missing; it is not broken in a way that errors; it answers, and
the answer is always the reassuring one.
Proposal
Two things, in order.
Wire the writer. The spawner's failure path calls
SpawnSupervisor.spawn()so respawn attempts are budgeted and exhaustion parks the session, and
_parkpersists to the path the aggregator already reads. The read surfaces do not
change; they start being fed.
Then make the silence impossible to repeat. A surface that reports the
absence of something must be able to distinguish "none occurred" from "the
producer never ran".
load_parked_sessions()returns a result that carrieswhether the store has ever been written in this run, and the surfaces render
0 parkedandparked state unavailabledifferently.The second half is the part that generalises. An empty recovery surface should
be a claim the run can support, not a default.
Why this shape
Every other guarantee in this project is checkable rather than assumed, and this
is a surface that has been quietly asserting a fact for its whole life with
nothing behind it. Making the unavailable case distinct from the empty case is
what turns the status bar from a decoration into evidence, and it is cheap: the
store either has a write for this run or it does not.
Scope
SpawnSupervisor.spawn()from the spawner's failure and respawn path sothe budget is consumed and exhaustion parks.
_parkpersists to.sdd/runtime/spawn_supervisor/parked.json, the pathsupervisor_aggregator.load_parked_sessionsalready reads.load_parked_sessions()distinguishes empty from unavailable.bernstein status,bernstein supervisor,bernstein fleetand the TUIstatus bar render the two cases differently.
bernstein agents resume <id>resumes a session parked by a real run.Done means
bernstein statusand in the TUI status barbernstein agents resume <id>resumes that sessionunavailable rather than reporting zero
entry is on disk at the path the aggregator reads
respawn_exhaustedfallback branch is either fed by a real writer orremoved, not left as a third unreachable path
Start here
Picking this up cold? Land this first and stop.
Step 1 - make the empty answer honest, before wiring anything. Change
load_parked_sessions()insrc/bernstein/core/orchestration/supervisor_aggregator.py:168-179to return aresult that distinguishes three cases: the store file is absent (unavailable),
present and empty (zero parked), present with entries (the list). Render the
unavailable case distinctly in
src/bernstein/cli/commands/status_cmd.py:428-430.Do not touch the spawner yet.
Proves it: a test named for the property that absent and empty are different
answers. Assert the unavailable result with no file on disk; write an empty
store and assert the zero-parked result; write one entry and assert it is
returned. Then assert the status output text differs between the first two -
that assertion is the whole point of the step, because it is the one that would
have caught this.
Not in step 1: calling
SpawnSupervisor.spawn()from the spawner. That isthe larger half and it changes live failure handling, so it wants its own PR
with its own failure-path tests. One thing worth knowing before planning step 2:
_publish_exhausted(spawn_supervisor.py:375-392) already exists alongside_park, so there are two exhaustion outputs with one unreachable caller betweenthem - decide which is authoritative before wiring, rather than feeding both.
Partial work is welcome and normal here. Title the PR "Partial implementation of
this issue" and say what remains.