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

Epic-lead Slice D — Epic-branch merge + baton coexistence (#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.

#117 done fresh flower · flower/234-epic-lead-slice-d
agent: claude
You are being dispatched from flower Brief #234: Epic-lead Slice D — Epic-branch merge + baton coexistence (#226)

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

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

Current brief spec:
# Slice D — Epic-branch merge + baton coexistence

Parent epic: #226. Design: `docs/design/226-epic-lead-orchestration.md` (§Baton & merge, §Worktree). **Depends on Slice C (#233).** Target ≤~300 lines. Honors decided fork **#55**.

## Deliverable
1. **Child → epic-branch merge discipline** — the lead merges `flower/epic-<id>-<child>` → `flower/epic-<id>` inside its epic worktree, guarded by an **epic-branch-scoped Solo lock** `mcp__solo__lock_acquire('flower-epic-<id>-merge')` — a distinct namespace from the MAIN baton (`Setting daemon_reset.active_orchestrator_daemon_id`). These never reach master, so they need no baton. (Mostly charter/convention text + a small guard/helper; the lead is the sole writer of its epic branch.)
2. **The single MAIN merge** — when the epic branch is whole (all children merged + epic-branch suite green), the lead enqueues `epic_return{action:merge}`; MAIN, under the baton, does the one `git merge --no-edit flower/epic-<id>` → master from the MAIN checkout per the existing merge protocol (clean tree → merge → keep all FoundationTest tables → migrate → full suite → verify on real MySQL). Then MAIN `epic_delegate{action:merged}` back.
3. **Baton invariant preserved** — assert/verify the lead never triggers a master merge and never holds the baton; the baton's authority over master merges is untouched. The invariant is convention-enforced (`assertCanDrainOrMerge` is dead code) — the change is charter prose + confirming no code path lets a lead merge to master.
4. **Child dispatch scoping** — the lead uses **direct/lead-mediated dispatch** (out of the project `auto_max_concurrent` accounting, which is per-project + keyed on `recommended->auto_dispatch->signal_kind`) with its own `config('flower.epic_lead.max_concurrent', 3)` fan-out cap. Reviews via `brief_request_review`/`brief_review` (`ReviewService` auto-bounce). Respects `brief_depend` gating on children.

## Tests
- Baton assertions unaffected by a live lead.
- `epic_return{merge}` routes to MAIN's merge path; the epic-lock namespace is disjoint from the baton.
- Lead child dispatch does not increment the project auto-dispatch concurrency count.

## Notes
- No new git-merge *service* — MAIN merges by hand per charter, as today; this slice defines the epic-branch target + the handoff, not a merge automation.

Recent/key trace events:
[1] participant_joined flower-226-worker: (no body)
[2] note_added flower-226-worker: # Slice D — Epic-branch merge + baton coexistence

Design: `docs/design/226-epic-lead-orchestration.md` (§Baton & merge, §Worktree). Depends on Slice C. Target ≤~300 lines. Honors decided fork **#55**.

## Deliverable
1. **Child → epic-branch merge discipline** — the lead merges `flower/epic-<id>-<child>` → `flower/epic-<id>` inside its epic worktree, guarded by an **epic-branch-scoped Solo lock** `mcp__solo__lock_acquire('flower-epic-<id>-merge')` — a distinct namespace from the MAIN baton (`Setting daemon_reset.active_orchestrator_daemon_id`). These never reach master, so they need no baton. (Mostly charter/convention text + a small guard/helper; the lead is the sole writer of its epic branch.)
2. **The single MAIN merge** — when the epic branch is whole (all children merged + epic-branch suite green), the lead enqueues `epic_return{action:merge}`; MAIN, under the baton, does the one `git merge --no-edit flower/epic-<id>` → master from the MAIN checkout per the existing merge protocol (clean tree → merge → keep all FoundationTest tables → migrate → full suite → verify on real MySQL). Then MAIN `epic_delegate{action:merged}` back.
3. **Baton invariant preserved** — assert/verify the lead never triggers a master merge and never holds the baton; the baton's authority over master merges is untouched. The invariant is convention-enforced (`assertCanDrainOrMerge` is dead code) — the change is charter prose + confirming no code path lets a lead merge to master.
4. **Child dispatch scoping** — the lead uses **direct/lead-mediated dispatch** (out of the project `auto_max_concurrent` accounting, which is per-project + keyed on `recommended->auto_dispatch->signal_kind`) with its own `config('flower.epic_lead.max_concurrent', 3)` fan-out cap. Reviews via `brief_request_review`/`brief_review` (`ReviewService` auto-bounce). Respects `brief_depend` gating on children.

## Tests
- Baton assertions unaffected by a live lead.
- `epic_return{merge}` routes to MAIN's merge path; the epic-lock namespace is disjoint from the baton.
- Lead child dispatch does not increment the project auto-dispatch concurrency count.

## Notes
- No new git-merge *service* — MAIN merges by hand per charter, as today; this slice defines the epic-branch target + the handoff, not a merge automation.
[3] parent_set flower-226-worker: Grouped under epic #226.
[4] dependency_added flower-226-worker: Now depends on #233 (Epic-lead Slice C — Coordination signals epic_delegate / epic_return (#226)).
[5] plan_proposed flower-226-worker: # Slice D — Epic-branch merge + baton coexistence

Parent epic: #226. Design: `docs/design/226-epic-lead-orchestration.md` (§Baton & merge, §Worktree). **Depends on Slice C (#233).** Target ≤~300 lines. Honors decided fork **#55**.

## Deliverable
1. **Child → epic-branch merge discipline** — the lead merges `flower/epic-<id>-<child>` → `flower/epic-<id>` inside its epic worktree, guarded by an **epic-branch-scoped Solo lock** `mcp__solo__lock_acquire('flower-epic-<id>-merge')` — a distinct namespace from the MAIN baton (`Setting daemon_reset.active_orchestrator_daemon_id`). These never reach master, so they need no baton. (Mostly charter/convention text + a small guard/helper; the lead is the sole writer of its epic branch.)
2. **The single MAIN merge** — when the epic branch is whole (all children merged + epic-branch suite green), the lead enqueues `epic_return{action:merge}`; MAIN, under the baton, does the one `git merge --no-edit flower/epic-<id>` → master from the MAIN checkout per the existing merge protocol (clean tree → merge → keep all FoundationTest tables → migrate → full suite → verify on real MySQL). Then MAIN `epic_delegate{action:merged}` back.
3. **Baton invariant preserved** — assert/verify the lead never triggers a master merge and never holds the baton; the baton's authority over master merges is untouched. The invariant is convention-enforced (`assertCanDrainOrMerge` is dead code) — the change is charter prose + confirming no code path lets a lead merge to master.
4. **Child dispatch scoping** — the lead uses **direct/lead-mediated dispatch** (out of the project `auto_max_concurrent` accounting, which is per-project + keyed on `recommended->auto_dispatch->signal_kind`) with its own `config('flower.epic_lead.max_concurrent', 3)` fan-out cap. Reviews via `brief_request_review`/`brief_review` (`ReviewService` auto-bounce). Respects `brief_depend` gating on children.

## Tests
- Baton assertions unaffected by a live lead.
- `epic_return{merge}` routes to MAIN's merge path; the epic-lock namespace is disjoint from the baton.
- Lead child dispatch does not increment the project auto-dispatch concurrency count.

## Notes
- No new git-merge *service* — MAIN merges by hand per charter, as today; this slice defines the epic-branch target + the handoff, not a merge automation.
[6] participant_joined flower-233-worker: (no body)
[7] unblocked flower-233-worker: Unblocked — #233 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: #234` 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 21h ago
    agent · system:commit-trailer
  2. participant joined 21h ago
    system · system:commit-trailer
  3. merged 22h ago

    Merged flower/234-epic-lead-slice-d (Slice D — EpicBranch merge-lock + DaemonResetService::canMergeToMaster + EpicLeadPolicy::maxConcurrent baton coexistence) to master by flower-orchestrator (daemon 40). Combined suite green 1191/1189 pass/2 skip. Unblocks Slice F (#236).

    agent · flower-orchestrator
  4. status change 22h ago
    agent · flower-234-worker
  5. dispatched 22h ago

    Dispatch request #117 marked done.

    agent · flower-234-worker
  6. note added 22h ago

    Slice D landed on branch flower/234-epic-lead-slice-d (commit fa1eb40, trailer Brief: #234). Full suite green (1188 passed / 2 pre-existing skips), Pint clean. NOT merged — MAIN owns the merge. Context on scope: Slice B's lead charter (DaemonCharterDefaults::leadBody) already carries D's *runtime prose* (child→epic merge under lock_acquire('flower-epic-<id>-merge'), the epic_return{merge} handoff, "never hold the baton / never merge to master", direct dispatch under flower.epic_lead.max_concurrent). So D's incremental value is the load-bearing CODE + tests that make those invariants real and give Slice F clean APIs — not re-writing prose. What landed: 1. app/Support/EpicBranch.php (NEW) — single source of truth for epic-branch + merge-lock naming: branch(id)→flower/epic-<id>, childBranch(id, child)→flower/epic-<id>-<git-safe-token>, mergeLock(id)→flower-epic-<id>-merge, plus epicBriefId()/isEpicBranch() reverse-parse. The merge-lock namespace is DELIBERATELY DISJOINT from the MAIN baton (Setting daemon_reset.active_orchestrator_daemon_id). SpawnEpicLead now derives the default epic branch from EpicBranch::branch() instead of an inline "flower/epic-{$id}" literal (kills the drift). 2. DaemonResetService::canMergeToMaster() (NEW predicate, fork #55) — master-merge authority is ONLY the baton-holding orchestrator; always false for a lead (setActiveOrchestrator already rejects any non-orchestrator role, and assertCanDrainOrMerge rejects a lead). Pure read, never mutates the baton — a clean primitive F can lean on. 3. EpicLeadPolicy::maxConcurrent() — the lead's own bounded child fan-out cap (flower.epic_lead.max_concurrent, default 3), independent of the project-wide flower.dispatch.auto_max_concurrent accounting (which counts only dispatch_requests marked recommended->auto_dispatch->signal_kind). Tests (13 new, all green): - EpicBranchTest (unit): naming + child-token git-safety + epicBriefId round-trip + rejects non-epic branches; **the merge-lock namespace is disjoint from the baton key** (the brief's "epic-lock namespace disjoint from baton" test); positive-id guard. - EpicBatonCoexistenceTest (feature): a lead can never be assigned the baton; canMergeToMaster is only the baton-holding orchestrator (false for a lead even with a live orchestrator holding it); a live lead does not disturb the orchestrator baton (baton assertions unaffected by a live lead). - EpicLeadDispatchScopingTest (feature): lead-mediated (direct) dispatch does NOT increment activeAutoDispatchCount; the lead fan-out cap is independent of the auto cap + floors at 1. Per the brief note: no new git-merge *service* — MAIN merges by hand per the existing merge protocol; this slice defines the epic-branch target, the merge-lock namespace, and the baton-coexistence invariant, not merge automation. Handoff to Slice F (#236, last slice): EpicBranch (branch/childBranch/mergeLock/epicBriefId) is the naming API for the retire/cleanup + worktree teardown; DaemonResetService::canMergeToMaster() + the existing CloseDaemon(winddown_ready) path are the invariants F's cooperative-retire builds on; EpicLeadPolicy::maxConcurrent() + the epic-keyed epic_return{action:retire} from C are the levers. childrenRollup() still returns only {total, complete, by_status} — F should add the explicit `terminal` count (complete+cancelled+abandoned) per the design.

    agent · flower-234-worker
  7. participant joined 22h ago
    system · flower-234-worker
  8. dispatched 22h ago

    Dispatch request #117 queued for flower.

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

    Unblocked — #233 reached complete.

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

    # Slice D — Epic-branch merge + baton coexistence Parent epic: #226. Design: `docs/design/226-epic-lead-orchestration.md` (§Baton & merge, §Worktree). **Depends on Slice C (#233).** Target ≤~300 lines. Honors decided fork **#55**. ## Deliverable 1. **Child → epic-branch merge discipline** — the lead merges `flower/epic-<id>-<child>` → `flower/epic-<id>` inside its epic worktree, guarded by an **epic-branch-scoped Solo lock** `mcp__solo__lock_acquire('flower-epic-<id>-merge')` — a distinct namespace from the MAIN baton (`Setting daemon_reset.active_orchestrator_daemon_id`). These never reach master, so they need no baton. (Mostly charter/convention text + a small guard/helper; the lead is the sole writer of its epic branch.) 2. **The single MAIN merge** — when the epic branch is whole (all children merged + epic-branch suite green), the lead enqueues `epic_return{action:merge}`; MAIN, under the baton, does the one `git merge --no-edit flower/epic-<id>` → master from the MAIN checkout per the existing merge protocol (clean tree → merge → keep all FoundationTest tables → migrate → full suite → verify on real MySQL). Then MAIN `epic_delegate{action:merged}` back. 3. **Baton invariant preserved** — assert/verify the lead never triggers a master merge and never holds the baton; the baton's authority over master merges is untouched. The invariant is convention-enforced (`assertCanDrainOrMerge` is dead code) — the change is charter prose + confirming no code path lets a lead merge to master. 4. **Child dispatch scoping** — the lead uses **direct/lead-mediated dispatch** (out of the project `auto_max_concurrent` accounting, which is per-project + keyed on `recommended->auto_dispatch->signal_kind`) with its own `config('flower.epic_lead.max_concurrent', 3)` fan-out cap. Reviews via `brief_request_review`/`brief_review` (`ReviewService` auto-bounce). Respects `brief_depend` gating on children. ## Tests - Baton assertions unaffected by a live lead. - `epic_return{merge}` routes to MAIN's merge path; the epic-lock namespace is disjoint from the baton. - Lead child dispatch does not increment the project auto-dispatch concurrency count. ## Notes - No new git-merge *service* — MAIN merges by hand per charter, as today; this slice defines the epic-branch target + the handoff, not a merge automation.

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

    Now depends on #233 (Epic-lead Slice C — Coordination signals epic_delegate / epic_return (#226)).

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

    Grouped under epic #226.

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

    # Slice D — Epic-branch merge + baton coexistence Design: `docs/design/226-epic-lead-orchestration.md` (§Baton & merge, §Worktree). Depends on Slice C. Target ≤~300 lines. Honors decided fork **#55**. ## Deliverable 1. **Child → epic-branch merge discipline** — the lead merges `flower/epic-<id>-<child>` → `flower/epic-<id>` inside its epic worktree, guarded by an **epic-branch-scoped Solo lock** `mcp__solo__lock_acquire('flower-epic-<id>-merge')` — a distinct namespace from the MAIN baton (`Setting daemon_reset.active_orchestrator_daemon_id`). These never reach master, so they need no baton. (Mostly charter/convention text + a small guard/helper; the lead is the sole writer of its epic branch.) 2. **The single MAIN merge** — when the epic branch is whole (all children merged + epic-branch suite green), the lead enqueues `epic_return{action:merge}`; MAIN, under the baton, does the one `git merge --no-edit flower/epic-<id>` → master from the MAIN checkout per the existing merge protocol (clean tree → merge → keep all FoundationTest tables → migrate → full suite → verify on real MySQL). Then MAIN `epic_delegate{action:merged}` back. 3. **Baton invariant preserved** — assert/verify the lead never triggers a master merge and never holds the baton; the baton's authority over master merges is untouched. The invariant is convention-enforced (`assertCanDrainOrMerge` is dead code) — the change is charter prose + confirming no code path lets a lead merge to master. 4. **Child dispatch scoping** — the lead uses **direct/lead-mediated dispatch** (out of the project `auto_max_concurrent` accounting, which is per-project + keyed on `recommended->auto_dispatch->signal_kind`) with its own `config('flower.epic_lead.max_concurrent', 3)` fan-out cap. Reviews via `brief_request_review`/`brief_review` (`ReviewService` auto-bounce). Respects `brief_depend` gating on children. ## Tests - Baton assertions unaffected by a live lead. - `epic_return{merge}` routes to MAIN's merge path; the epic-lock namespace is disjoint from the baton. - Lead child dispatch does not increment the project auto-dispatch concurrency count. ## Notes - No new git-merge *service* — MAIN merges by hand per charter, as today; this slice defines the epic-branch target + the handoff, not a merge automation.

    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-233-worker participant · active
  • flower-orchestrator participant · active
  • flower-234-worker participant · active
  • system:commit-trailer participant · active

trace · graph

Links

  • Commit #4047 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.