flower
/
All briefs
complete draft mcp flower
epic · Let's brainstorm/plan/spec the idea of multi-chain o...

Epic-lead Slice C — Coordination signals epic_delegate / epic_return (#226)

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.

#114 done fresh flower · flower/233-epic-lead-slice-c
agent: claude
You are being dispatched from flower Brief #233: Epic-lead Slice C — Coordination signals epic_delegate / epic_return (#226)

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

Target:
- project: flower (/Users/mikeferrara/Documents/code/flower)
- branch: flower/233-epic-lead-slice-c
- worktree: not specified
- kind: fresh

Current brief spec:
# Slice C — Coordination signals: epic_delegate / epic_return

Parent epic: #226. Design: `docs/design/226-epic-lead-orchestration.md` (§Coordination protocol). **Depends on Slice A (#231); parallel to B.** Target ≤~300 lines.

## Deliverable
1. **Two new kinds** on `CoordinationQueue` (:18-28): `KIND_EPIC_DELEGATE = 'epic_delegate'` (MAIN→lead), `KIND_EPIC_RETURN = 'epic_return'` (lead→MAIN). `kind` is free-form `string(64)` — **no migration, no enum, no allowlist**.
2. **Enqueue helpers — the load-bearing reset-robust rule:** `enqueueEpicDelegate(project, payload, requestedBy)` / `enqueueEpicReturn(project, payload, requestedBy)` enqueue **project-scoped with `target_daemon_id = null`**, keyed on `payload.epic_brief_id`. Rationale (critical): `excludePendingHeldForReset()` (:438) hides pending signals whose target daemon has `reset_pending=true`, and **`reset_pending` is never cleared on close/retire** — so a daemon-targeted signal is hidden forever after that lead retires. Null-target takes the `orWhereNull('target_daemon_id')` branch (:442) → always visible → genuinely reset-robust. **Keep both OUT of `resetLifecycleKinds()`** (:451) — null-targeting already keeps them visible.
3. **Lead drains `epic_delegate`** — each heartbeat, fetch project pending signals, filter client-side to `kind=epic_delegate AND payload.epic_brief_id=<mine>`, claim → act → complete. No code role/baton gate on `signal_claim` (`assertCanDrainOrMerge` is convention only, never called in `app/`), so this works as-is + a charter carve-out (Slice B).
4. **MAIN drains `epic_return`** — new per-kind clause in `AgentConventions::daemonLines()` (:59): `action:merge` → run the single epic→master merge (Slice D); `action:retire` → close the lead (Slice F). Plus the baton carve-out note.
5. **Liveness** — the lead **re-enqueues `epic_return` idempotently each heartbeat until it observes the state change** (merge landed / lead closed); dedupe collapses repeats. No reconciler needed.
6. **Optional** — thin MCP wrappers `epic_delegate`/`epic_return` (register in `FlowerServer`); a `kind`+`epic_brief_id` server-side filter on `recall_signals` to lean the lead's poll.

## Tests
- Enqueue/dedupe by epic (same epic → one pending row).
- **A pending signal survives the target lead's retire (never-cleared `reset_pending`) and a successor lead drains it** — the reset-robustness guarantee.
- MAIN drains `epic_return`; re-enqueue is idempotent.

## Notes
- This slice resolves the two protocol blockers found in the design review — do not regress to daemon-targeted enqueue.

Recent/key trace events:
[1] participant_joined flower-226-worker: (no body)
[2] note_added flower-226-worker: # Slice C — Coordination signals: epic_delegate / epic_return

Design: `docs/design/226-epic-lead-orchestration.md` (§Coordination protocol). Depends on Slice A; parallel to B. Target ≤~300 lines.

## Deliverable
1. **Two new kinds** on `CoordinationQueue` (:18-28): `KIND_EPIC_DELEGATE = 'epic_delegate'` (MAIN→lead), `KIND_EPIC_RETURN = 'epic_return'` (lead→MAIN). `kind` is free-form `string(64)` — **no migration, no enum, no allowlist**.
2. **Enqueue helpers — the load-bearing reset-robust rule:** `enqueueEpicDelegate(project, payload, requestedBy)` / `enqueueEpicReturn(project, payload, requestedBy)` enqueue **project-scoped with `target_daemon_id = null`**, keyed on `payload.epic_brief_id`. Rationale (critical): `excludePendingHeldForReset()` (:438) hides pending signals whose target daemon has `reset_pending=true`, and **`reset_pending` is never cleared on close/retire** — so a daemon-targeted signal is hidden forever after that lead retires. Null-target takes the `orWhereNull('target_daemon_id')` branch (:442) → always visible → genuinely reset-robust. **Keep both OUT of `resetLifecycleKinds()`** (:451) — null-targeting already keeps them visible.
3. **Lead drains `epic_delegate`** — each heartbeat, fetch project pending signals, filter client-side to `kind=epic_delegate AND payload.epic_brief_id=<mine>`, claim → act → complete. No code role/baton gate on `signal_claim` (`assertCanDrainOrMerge` is convention only, never called in `app/`), so this works as-is + a charter carve-out (Slice B).
4. **MAIN drains `epic_return`** — new per-kind clause in `AgentConventions::daemonLines()` (:59): `action:merge` → run the single epic→master merge (Slice D); `action:retire` → close the lead (Slice F). Plus the baton carve-out note.
5. **Liveness** — the lead **re-enqueues `epic_return` idempotently each heartbeat until it observes the state change** (merge landed / lead closed); dedupe collapses repeats. No reconciler needed.
6. **Optional** — thin MCP wrappers `epic_delegate`/`epic_return` (register in `FlowerServer`); a `kind`+`epic_brief_id` server-side filter on `recall_signals` to lean the lead's poll.

## Tests
- Enqueue/dedupe by epic (same epic → one pending row).
- **A pending signal survives the target lead's retire (never-cleared `reset_pending`) and a successor lead drains it** — the reset-robustness guarantee.
- MAIN drains `epic_return`; re-enqueue is idempotent.

## Notes
- This slice is where the two protocol blockers found in the design review are resolved — do not regress to daemon-targeted enqueue.
[3] parent_set flower-226-worker: Grouped under epic #226.
[4] dependency_added flower-226-worker: Now depends on #231 (Epic-lead Slice A — Lead identity & roster scaffolding (#226)).
[5] plan_proposed flower-226-worker: # Slice C — Coordination signals: epic_delegate / epic_return

Parent epic: #226. Design: `docs/design/226-epic-lead-orchestration.md` (§Coordination protocol). **Depends on Slice A (#231); parallel to B.** Target ≤~300 lines.

## Deliverable
1. **Two new kinds** on `CoordinationQueue` (:18-28): `KIND_EPIC_DELEGATE = 'epic_delegate'` (MAIN→lead), `KIND_EPIC_RETURN = 'epic_return'` (lead→MAIN). `kind` is free-form `string(64)` — **no migration, no enum, no allowlist**.
2. **Enqueue helpers — the load-bearing reset-robust rule:** `enqueueEpicDelegate(project, payload, requestedBy)` / `enqueueEpicReturn(project, payload, requestedBy)` enqueue **project-scoped with `target_daemon_id = null`**, keyed on `payload.epic_brief_id`. Rationale (critical): `excludePendingHeldForReset()` (:438) hides pending signals whose target daemon has `reset_pending=true`, and **`reset_pending` is never cleared on close/retire** — so a daemon-targeted signal is hidden forever after that lead retires. Null-target takes the `orWhereNull('target_daemon_id')` branch (:442) → always visible → genuinely reset-robust. **Keep both OUT of `resetLifecycleKinds()`** (:451) — null-targeting already keeps them visible.
3. **Lead drains `epic_delegate`** — each heartbeat, fetch project pending signals, filter client-side to `kind=epic_delegate AND payload.epic_brief_id=<mine>`, claim → act → complete. No code role/baton gate on `signal_claim` (`assertCanDrainOrMerge` is convention only, never called in `app/`), so this works as-is + a charter carve-out (Slice B).
4. **MAIN drains `epic_return`** — new per-kind clause in `AgentConventions::daemonLines()` (:59): `action:merge` → run the single epic→master merge (Slice D); `action:retire` → close the lead (Slice F). Plus the baton carve-out note.
5. **Liveness** — the lead **re-enqueues `epic_return` idempotently each heartbeat until it observes the state change** (merge landed / lead closed); dedupe collapses repeats. No reconciler needed.
6. **Optional** — thin MCP wrappers `epic_delegate`/`epic_return` (register in `FlowerServer`); a `kind`+`epic_brief_id` server-side filter on `recall_signals` to lean the lead's poll.

## Tests
- Enqueue/dedupe by epic (same epic → one pending row).
- **A pending signal survives the target lead's retire (never-cleared `reset_pending`) and a successor lead drains it** — the reset-robustness guarantee.
- MAIN drains `epic_return`; re-enqueue is idempotent.

## Notes
- This slice resolves the two protocol blockers found in the design review — do not regress to daemon-targeted enqueue.
[6] participant_joined flower-231-worker: (no body)
[7] unblocked flower-231-worker: Unblocked — #231 reached complete.
[8] participant_joined flower-orchestrator: (no body)
[9] status_change flower-orchestrator: (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: #233` 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 22h ago
    agent · system:commit-trailer
  2. participant joined 22h ago
    system · system:commit-trailer
  3. merged 22h ago

    Merged flower/233-epic-lead-slice-c (Slice C — epic_delegate/epic_return coordination signals + CoordinationQueue + Epic*Tools) to master by flower-orchestrator (daemon 40). Suite green 1180. Unblocks Slice D (#234).

    agent · flower-orchestrator
  4. status change 23h ago
    agent · flower-233-worker
  5. dispatched 23h ago

    Dispatch request #114 marked done.

    agent · flower-233-worker
  6. note added 23h ago

    Slice C landed on branch flower/233-epic-lead-slice-c (commit a4fd2bd, trailer Brief: #233). Full suite green (1152 passed / 2 pre-existing skips), Pint clean. NOT merged — MAIN orchestrator owns the epic→master merge. What landed: 1. CoordinationQueue: KIND_EPIC_DELEGATE (MAIN→lead) / KIND_EPIC_RETURN (lead→MAIN) consts — free-form string(64), no migration/enum/allowlist. enqueueEpicDelegate / enqueueEpicReturn helpers (shared private enqueueEpicSignal) enqueue project-scoped with target_daemon_id=null, keyed on payload.epic_brief_id (validated positive). Kept OUT of resetLifecycleKinds() with a doc note — null-targeting already keeps them visible past a never-cleared reset_pending (the reset-robust rule from the design review; did NOT regress to daemon-targeted enqueue). 2. AgentConventions::daemonLines(): MAIN epic_return drain clause (action=merge → single epic-branch→master merge under the baton [Slice D]; action=retire → close the quiesced lead via CloseDaemon [Slice F]) + baton carve-out (lead's epic-branch merges + its own epic_delegate mailbox drain permitted though it never holds the baton) + idempotent re-enqueue-until-observed liveness note. 3. Thin MCP wrappers epic_delegate / epic_return (shared abstract EpicSignalTool base; accept epic_brief_id/project/action/actor_ref/note/payload), registered in FlowerServer. These are the ENQUEUE path (no generic enqueue-signal tool existed); draining reuses generic recall_signals / signal_claim / signal_complete. 4. recall_signals gains optional kind + epic_brief_id filters (PHP-side over the collection, portable) so the lead's epic-keyed poll is lean. Tests (10 new, all green): enqueue/dedupe per epic (same epic+payload → one row; different action/epic → distinct); a null-targeted signal survives the target lead's retire while a daemon-targeted one is hidden, and a successor lead drains it (the reset-robustness guarantee); MAIN drains epic_return with idempotent re-enqueue; MCP wrappers null-targeted + unknown-project error; recall_signals epic filter narrows to one mailbox; requires positive epic_brief_id; AgentConventions epic drain/carve-out prose. Handoff to Slice D (#234): epic_return{action:merge} is the trigger to wire to MAIN's single epic→master merge; epic_delegate{action:merged} is MAIN→lead post-merge. The typed wrappers + recall_signals(kind, epic_brief_id) filter are the entry points D/F extend. Charter prose (lead-side epic_delegate drain loop + re-enqueue loop) belongs to Slice B's lead charter — Slice C provides the infra + the MAIN-facing half.

    agent · flower-233-worker
  7. participant joined 23h ago
    system · flower-233-worker
  8. dispatched 23h ago

    Dispatch request #114 queued for flower.

    agent · flower-orchestrator
  9. status change 23h ago
    agent · flower-orchestrator
  10. status change 23h ago
    agent · flower-orchestrator
  11. participant joined 23h ago
    system · flower-orchestrator
  12. unblocked 23h ago

    Unblocked — #231 reached complete.

    system · flower-231-worker
  13. participant joined 23h ago
    system · flower-231-worker
  14. plan proposed 1d ago

    # Slice C — Coordination signals: epic_delegate / epic_return Parent epic: #226. Design: `docs/design/226-epic-lead-orchestration.md` (§Coordination protocol). **Depends on Slice A (#231); parallel to B.** Target ≤~300 lines. ## Deliverable 1. **Two new kinds** on `CoordinationQueue` (:18-28): `KIND_EPIC_DELEGATE = 'epic_delegate'` (MAIN→lead), `KIND_EPIC_RETURN = 'epic_return'` (lead→MAIN). `kind` is free-form `string(64)` — **no migration, no enum, no allowlist**. 2. **Enqueue helpers — the load-bearing reset-robust rule:** `enqueueEpicDelegate(project, payload, requestedBy)` / `enqueueEpicReturn(project, payload, requestedBy)` enqueue **project-scoped with `target_daemon_id = null`**, keyed on `payload.epic_brief_id`. Rationale (critical): `excludePendingHeldForReset()` (:438) hides pending signals whose target daemon has `reset_pending=true`, and **`reset_pending` is never cleared on close/retire** — so a daemon-targeted signal is hidden forever after that lead retires. Null-target takes the `orWhereNull('target_daemon_id')` branch (:442) → always visible → genuinely reset-robust. **Keep both OUT of `resetLifecycleKinds()`** (:451) — null-targeting already keeps them visible. 3. **Lead drains `epic_delegate`** — each heartbeat, fetch project pending signals, filter client-side to `kind=epic_delegate AND payload.epic_brief_id=<mine>`, claim → act → complete. No code role/baton gate on `signal_claim` (`assertCanDrainOrMerge` is convention only, never called in `app/`), so this works as-is + a charter carve-out (Slice B). 4. **MAIN drains `epic_return`** — new per-kind clause in `AgentConventions::daemonLines()` (:59): `action:merge` → run the single epic→master merge (Slice D); `action:retire` → close the lead (Slice F). Plus the baton carve-out note. 5. **Liveness** — the lead **re-enqueues `epic_return` idempotently each heartbeat until it observes the state change** (merge landed / lead closed); dedupe collapses repeats. No reconciler needed. 6. **Optional** — thin MCP wrappers `epic_delegate`/`epic_return` (register in `FlowerServer`); a `kind`+`epic_brief_id` server-side filter on `recall_signals` to lean the lead's poll. ## Tests - Enqueue/dedupe by epic (same epic → one pending row). - **A pending signal survives the target lead's retire (never-cleared `reset_pending`) and a successor lead drains it** — the reset-robustness guarantee. - MAIN drains `epic_return`; re-enqueue is idempotent. ## Notes - This slice resolves the two protocol blockers found in the design review — do not regress to daemon-targeted enqueue.

    agent · flower-226-worker
  15. dependency added 1d ago

    Now depends on #231 (Epic-lead Slice A — Lead identity & roster scaffolding (#226)).

    agent · flower-226-worker
  16. parent set 1d ago

    Grouped under epic #226.

    agent · flower-226-worker
  17. note added 1d ago

    # Slice C — Coordination signals: epic_delegate / epic_return Design: `docs/design/226-epic-lead-orchestration.md` (§Coordination protocol). Depends on Slice A; parallel to B. Target ≤~300 lines. ## Deliverable 1. **Two new kinds** on `CoordinationQueue` (:18-28): `KIND_EPIC_DELEGATE = 'epic_delegate'` (MAIN→lead), `KIND_EPIC_RETURN = 'epic_return'` (lead→MAIN). `kind` is free-form `string(64)` — **no migration, no enum, no allowlist**. 2. **Enqueue helpers — the load-bearing reset-robust rule:** `enqueueEpicDelegate(project, payload, requestedBy)` / `enqueueEpicReturn(project, payload, requestedBy)` enqueue **project-scoped with `target_daemon_id = null`**, keyed on `payload.epic_brief_id`. Rationale (critical): `excludePendingHeldForReset()` (:438) hides pending signals whose target daemon has `reset_pending=true`, and **`reset_pending` is never cleared on close/retire** — so a daemon-targeted signal is hidden forever after that lead retires. Null-target takes the `orWhereNull('target_daemon_id')` branch (:442) → always visible → genuinely reset-robust. **Keep both OUT of `resetLifecycleKinds()`** (:451) — null-targeting already keeps them visible. 3. **Lead drains `epic_delegate`** — each heartbeat, fetch project pending signals, filter client-side to `kind=epic_delegate AND payload.epic_brief_id=<mine>`, claim → act → complete. No code role/baton gate on `signal_claim` (`assertCanDrainOrMerge` is convention only, never called in `app/`), so this works as-is + a charter carve-out (Slice B). 4. **MAIN drains `epic_return`** — new per-kind clause in `AgentConventions::daemonLines()` (:59): `action:merge` → run the single epic→master merge (Slice D); `action:retire` → close the lead (Slice F). Plus the baton carve-out note. 5. **Liveness** — the lead **re-enqueues `epic_return` idempotently each heartbeat until it observes the state change** (merge landed / lead closed); dedupe collapses repeats. No reconciler needed. 6. **Optional** — thin MCP wrappers `epic_delegate`/`epic_return` (register in `FlowerServer`); a `kind`+`epic_brief_id` server-side filter on `recall_signals` to lean the lead's poll. ## Tests - Enqueue/dedupe by epic (same epic → one pending row). - **A pending signal survives the target lead's retire (never-cleared `reset_pending`) and a successor lead drains it** — the reset-robustness guarantee. - MAIN drains `epic_return`; re-enqueue is idempotent. ## Notes - This slice is where the two protocol blockers found in the design review are resolved — do not regress to daemon-targeted enqueue.

    agent · flower-226-worker
  18. participant joined 1d ago
    system · flower-226-worker

epic · dependencies

Relationships

depends on

agents · waves

Participants

  • flower-226-worker participant · active
  • flower-231-worker participant · active
  • flower-orchestrator participant · active
  • flower-233-worker participant · active
  • system:commit-trailer participant · active

trace · graph

Links

  • Commit #4030 execution

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.