flower
/
All briefs
complete feedback flower
from feedback #70 · Parked-but-alive daemon is indistinguishable from a...

Feedback #70: Parked-but-alive daemon is indistinguishable from a crashed one on the roster

canonical · plan

Spec

markdown

hand-off · dispatch

Dispatch

Auto-dispatch

when it reaches planned

Design-loop

design pass before build

This brief is complete — dispatch is closed.

#86 done fresh flower · flower/160-roster-parked-state
agent: claude
You are being dispatched from flower Brief #160: Feedback #70: Parked-but-alive daemon is indistinguishable from a crashed one on the roster

Recall pointer:
- Use recall_brief with id 160 for the full folder if you need provenance.

Target:
- project: flower (/Users/mikeferrara/Documents/code/flower)
- branch: flower/160-roster-parked-state
- worktree: not specified
- kind: fresh

Current brief spec:
(no spec yet)

This is a direct request, not a fully-specced plan. If it's clear, resolve it. If you hit a blocking ambiguity, call brief_ask (or brief_append) with your questions and flip the brief to `refining` before proceeding — don't guess.

Recent/key trace events:
[1] participant_joined operator:mike: (no body)
[2] note_added operator:mike: Feedback #70
Authority: OPERATOR APPROVAL REQUIRED
Funnel: A - operator approved brief
Gate: this brief must not be dispatchable until the operator approves it.
Kind: idea
Source: flower-orchestrator (daemon 17)

Summary:
Parked-but-alive daemon is indistinguishable from a crashed one on the roster

Detail:
A standing daemon that deliberately parks — stops arming its heartbeat loop while awaiting operator go-ahead (per its charter's loop-arming gate) — stops checking in and then reads "dead/stale" on recall_roster, identical to a crash. Live example: refine daemon 15 (proc 1040) self-parked ~14:55 after draining its queue (only #36/#144 left, both blocked on operator answers), stayed alive and quiet, but showed dead on the roster for ~6h; the operator thought it was lost and asked to respawn it (which would have duplicated it). Proposal: a distinct roster state (e.g. "parked"/"awaiting-operator") set when a daemon intentionally holds, so parked != dead and MIA/replacement logic + the operator don't misread it. The daemon noting its park in a scratchpad works as a convention, but the roster UI still misleads.
[3] link_added operator:mike: (no body)
[4] note_added operator:mike: Operator approved this feedback-born brief for dispatch.
[5] status_change operator:mike: (no body)

Recommended linked context:
{
    "todos": [],
    "scratchpads": []
}

Execution notes:
- Treat the brief as the source of truth.
- Keep work scoped to this dispatch request.
- Use brief_append / brief_update_status when reporting material progress; as your final dispatched-worker step, call brief_dispatch_complete with dispatch_request_id (or brief_id) and actor_ref.
- Codex workers should verify mutating Flower tools with tool_search query `brief_append brief_dispatch_complete flower_feedback` (limit 20) when tool availability is in doubt; report raw SEE/LOAD vs NOT visible instead of silently using local fallbacks.
- Add a git commit trailer `Brief: #160` to every commit for this brief so flower can exact-link commits back to the brief.

provenance · append-only

Trace

live
or paste a screenshot uploading…
  1. link added 1d ago
    agent · system:commit-trailer
  2. link added 1d ago
    agent · system:commit-trailer
  3. participant joined 1d ago
    system · system:commit-trailer
  4. merged 1d ago

    Merged to master by flower-orchestrator (daemon 29). Worker commit c2d35d5 (branch flower/160-roster-parked-state). First-class parked daemon state: DaemonStatus enum + parked_at migration (ran on MAIN MySQL, 47ms) + daemon_park/daemon_unpark MCP tools + never-decays liveness (markStale skips parked) + auto-unpark on next check-in + EnsureDaemons treats parked as present (SATISFIED_LIVENESS += parked, kills the dup-spawn that nearly duplicated refine daemon 15) + HealthService + /roster UI badge/filter/counts. Adversarial review PASS: parked is ALIVE so it stays VISIBLE in the lean default (isRetiredOrDead only hides retired/dead — #137/#192 shape untouched); migration is sqlite-portable (nullable, ->after() no-op). Full suite green on MAIN: 971 tests / 969 passed / 2 skipped / 0 failed. NOTE: the new daemon_park/unpark tools + parked recall_roster shape go live in MCP sessions after the flower MCP server next reloads.

    agent · flower-orchestrator
  5. status change 1d ago
    agent · flower-160-roster-parked-worker
  6. dispatched 1d ago

    Dispatch request #86 marked done.

    agent · flower-160-roster-parked-worker
  7. note added 1d ago

    Built the distinct "parked" roster state (Feedback #70). Branch flower/160-roster-parked-state off master; commit c2d35d5 (trailer Brief: #160). WHAT CHANGED - New DaemonStatus::Parked + nullable parked_at column (migration 2026_07_04_090000) + model cast. - DaemonRosterService: livenessFor + livenessPayload short-circuit Parked → liveness 'parked' (is_parked=true, is_alive=true, is_dead=false); markStale now SKIPS parked (never decays a deliberate hold to stale/dead no matter how long quiet, mirroring how it skips Expected); daemonPayload exposes parked_at; new park(id,actorRef,note?) + unpark(id,actorRef) methods; check-in auto-un-parks (status→Live, parked_at cleared, appends 'unparked' audit). - New MCP tools daemon_park / daemon_unpark (actor_ref required), registered in FlowerServer. (Covered by the coarse mcp__flower allowlist — no per-tool allowlist change.) - Lean-default preserved: parked is ALIVE so it stays VISIBLE in recall_roster's lean payload; only retired/dead still hide (#137/#192 shape untouched). - Anti-duplication: EnsureDaemons.SATISFIED_LIVENESS now includes 'parked' so "Water the garden" treats a parked daemon as present and never respawns a duplicate — the exact failure mode from #70 (refine daemon 15). Roster UI allExpectedRunning satisfied set also gains 'parked'. - /roster UI: parked liveness badge (var(--done) periwinkle) + filter ribbon chip + counts via LIVENESS_META/FILTERABLE_LIVENESS (propagates to roster detail + project detail views, which reuse LIVENESS_META). HealthService daemon-liveness summary gains a 'parked' count. TESTS (all green; full suite 962 passed / 2 pre-existing skips; pint clean) - daemon_park requires actor_ref, marks parked, records note in audit, and parked stays visible in lean roster (count=1, hidden_retired=0). - parked daemon forced 500m quiet survives markStale (stays Parked, liveness 'parked') and stays in the lean payload — distinct from a dead daemon that would hide. - check-in un-parks (status Live, parked_at null, 'unparked' audit). - daemon_unpark tool wakes by re-deriving status. - EnsureDaemons: a parked orchestrator counts as present → only the missing role is spawned, parked one NOT respawned. Design notes / open choices for orchestrator review: - park is a deliberate, sticky declaration cleared only by check-in or unpark (not by time). Trade-off: if a genuinely-parked daemon later dies, it stays 'parked' until an operator removes it — accepted, since the whole point is trusting a deliberate hold over activity decay. - Kept UI to display + filter (no operator "park" button); the daemon self-parks via daemon_park, and check-in is the natural wake. unpark tool covers the explicit operator/orchestrator wake. - Parked daemons are intentionally EXCLUDED from dispatch/promotion targeting (those select status=Live), which is correct — a parked daemon shouldn't be handed new work. Awaiting review + merge by flower-orchestrator on MAIN (Horizon reload not needed — no job/pipeline code changed).

    agent · flower-160-roster-parked-worker
  8. participant joined 1d ago
    system · flower-160-roster-parked-worker
  9. dispatched 1d ago

    Dispatch request #86 queued for flower.

    agent · flower-orchestrator
  10. status change 1d ago
    agent · flower-orchestrator
  11. participant joined 1d ago
    system · flower-orchestrator
  12. status change 1d ago
    agent · operator:mike
  13. note added 1d ago

    Operator approved this feedback-born brief for dispatch.

    operator · operator:mike
  14. link added 1d ago
    agent · operator:mike
  15. note added 1d ago

    Feedback #70 Authority: OPERATOR APPROVAL REQUIRED Funnel: A - operator approved brief Gate: this brief must not be dispatchable until the operator approves it. Kind: idea Source: flower-orchestrator (daemon 17) Summary: Parked-but-alive daemon is indistinguishable from a crashed one on the roster Detail: A standing daemon that deliberately parks — stops arming its heartbeat loop while awaiting operator go-ahead (per its charter's loop-arming gate) — stops checking in and then reads "dead/stale" on recall_roster, identical to a crash. Live example: refine daemon 15 (proc 1040) self-parked ~14:55 after draining its queue (only #36/#144 left, both blocked on operator answers), stayed alive and quiet, but showed dead on the roster for ~6h; the operator thought it was lost and asked to respawn it (which would have duplicated it). Proposal: a distinct roster state (e.g. "parked"/"awaiting-operator") set when a daemon intentionally holds, so parked != dead and MIA/replacement logic + the operator don't misread it. The daemon noting its park in a scratchpad works as a convention, but the roster UI still misleads.

    agent · operator:mike
  16. participant joined 1d ago
    system · operator:mike

epic · dependencies

Relationships

epic parent

depends on

No dependencies — dispatchable once planned.

agents · waves

Participants

  • operator:mike participant · active
  • flower-orchestrator participant · active
  • flower-160-roster-parked-worker participant · active
  • system:commit-trailer participant · active

trace · graph

Links

  • Commit #3970 execution
  • Commit #3971 execution
  • Feedback #70 seed

scope

Projects

  • flower · primary

dogfood · read-only

Agent’s-eye view

The literal recall_brief payload an agent gets — same service path as the MCP tool.