flower
/
All briefs
complete draft deep_review flower

Auto-dispatch reconciler: backfill signals for planned + auto_dispatch_on_planned briefs stranded by enqueue-on-transition (no live orchestrator / flagged-after-planned)

canonical · plan

Spec

markdown

deep review · proposals

Proposed briefs

hand-off · dispatch

Dispatch

Auto-dispatch

when it reaches planned

Design-loop

design pass before build

This brief is complete — dispatch is closed.

#80 done fresh flower · flower/155-199-auto-dispatch-autonomy
agent: claude
You are being dispatched from flower Brief #199: Auto-dispatch reconciler: backfill signals for planned + auto_dispatch_on_planned briefs stranded by enqueue-on-transition (no live orchestrator / flagged-after-planned)

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

Target:
- project: flower (/Users/mikeferrara/Documents/code/flower)
- branch: flower/155-199-auto-dispatch-autonomy
- worktree: not specified
- kind: fresh

Current brief spec:
## Auto-dispatch reconciler (operator: AUTO-SPAWN TO CAP, 2026-07-04)
Full detail + code cites in the brief note. Summary: auto_dispatch signals are enqueued ONLY on the planned-transition (`BriefService.php:481` → `AutoDispatchService::enqueueIfEligible`) and only if a live orchestrator exists at that instant — with no reconciler — so briefs flagged-after-planned or planned-during-an-orchestrator-gap strand with NO signal forever (~16 observed 2026-07-04; 0 signals in queue).

## Build
A bounded, idempotent reconciler: a `flower:reconcile-auto-dispatch` command + scheduler entry (~2–5 min on the fast queue) AND an orchestrator check-in/boot hook, that finds briefs which are planned + auto_dispatch_on_planned=true + unblocked + NOT needs_operator_approval + have NO pending/claimed auto_dispatch signal AND NO active auto dispatch_request, and enqueues the missing signal via `CoordinationQueue` / `AutoDispatchService::enqueueIfEligible`. Idempotent (never double-enqueue); bounded per run; log what it enqueued.

## Pairs with #155
#155 makes the orchestrator auto-drain + auto-spawn to cap. Together = flagged+planned briefs flow to running agents hands-off. Same worker/branch as #155 is fine (files barely overlap: this = AutoDispatchService/command/scheduler; #155 = charter defaults/conventions).

## Acceptance
- A stranded flagged-planned brief gets a signal on the next reconcile (test).
- Re-running does NOT duplicate signals/requests (idempotency test).
- Orchestrator boot/check-in triggers a sweep.
- `php artisan test` green + `./vendor/bin/pint`. `Brief: #199` trailer. Worktree-pinned; never edit MAIN.

Recent/key trace events:
[1] participant_joined flower-orchestrator: (no body)
[2] note_added flower-orchestrator: ## Problem (confirmed in code, 2026-07-04)
`auto_dispatch_on_planned=true` briefs are meant to auto-dispatch (orchestrator-mediated, up to `flower.dispatch.auto_max_concurrent`=2). But the auto_dispatch coordination signal is enqueued from EXACTLY ONE place — `AutoDispatchService::enqueueIfEligible()`, called only from `BriefService.php:481` WHEN A BRIEF TRANSITIONS INTO planned — and it silently `return null`s if there is no orchestrator with `status=Live` + `solo_process_id` AT THAT INSTANT (`AutoDispatchService.php:36-39`, `liveOrchestrator()`). There is NO reconciler / scheduled backfill (grep of `app/Console` + `routes/console.php` confirms — only ingest-state/projects/actor_ref reconcilers exist).

Consequence: any brief that (a) was flagged `auto_dispatch_on_planned=true` AFTER it had already reached planned (bulk-flag), or (b) reached planned during an orchestrator gap / reset / boot window, gets NO signal, EVER — it strands as planned forever. Observed 2026-07-04: ~16 planned + auto_dispatch_on_planned=true briefs (#112 #128 #137 #140 #155 #158 #160 #167 #178 #179 #191 #192 #194 #197 …) with ZERO auto_dispatch signals in the coordination queue. (Not the cap — 0 active; not approval — all needs_operator_approval=false.)

## Change — a reconciler that backfills stranded signals
Add a bounded, idempotent reconciler that finds briefs that are: status=planned AND auto_dispatch_on_planned=true AND NOT needs_operator_approval AND dispatchable (unblocked) AND have NO pending/claimed auto_dispatch signal AND NO active (in-flight) auto dispatch_request — and enqueues the missing auto_dispatch signal (reuse `AutoDispatchService::enqueueIfEligible` / `CoordinationQueue`). Run it:
1. On a SCHEDULE (e.g. every ~2–5 min on the fast queue; a `flower:reconcile-auto-dispatch` command + scheduler entry), and
2. On orchestrator check-in / boot (so a freshly-spawned orchestrator immediately backfills the backlog instead of only reacting to NEW planned-transitions).
Optionally also re-enqueue when `auto_dispatch_on_planned` flips true on an already-planned brief (same transition gap).

## Guards / non-goals
- IDEMPOTENT: never enqueue if a pending/claimed signal OR an active dispatch_request already exists for that brief. The existing drain enforces `auto_max_concurrent`, so the reconciler only needs to ensure a signal EXISTS, not to dispatch.
- Does NOT change the concurrency cap, approval gate, or the orchestrator-mediated dispatch model. This fixes ONLY the enqueue-reliability gap.
- Bounded per run (cap how many it enqueues per sweep; log what it enqueued).

## Acceptance
- A planned + auto_dispatch_on_planned=true + unblocked + no-approval brief with no signal/request gets a signal enqueued on the next reconcile (test asserts this).
- Re-running the reconciler does NOT duplicate signals/requests (idempotency test).
- Orchestrator check-in/boot triggers a backfill sweep.
- `php artisan test` green + `./vendor/bin/pint`. `Brief: #<this>` trailer. Worktree-pinned; never edit MAIN.

## Provenance
Found by flower-orchestrator-2 on 2026-07-04 answering the operator's question "why aren't auto_dispatch_on_planned + planned briefs auto-dispatching?" Behavioral sibling: #155 (remove the daemon operator-go-ahead gate so a live orchestrator actually drains + spawns). Auto-dispatch signal design: #126. Together, #155 + this brief are the full fix for "flagged-planned briefs never flow."
[3] status_change flower-orchestrator: (no body)
[4] plan_proposed flower-orchestrator: ## Auto-dispatch reconciler (operator: AUTO-SPAWN TO CAP, 2026-07-04)
Full detail + code cites in the brief note. Summary: auto_dispatch signals are enqueued ONLY on the planned-transition (`BriefService.php:481` → `AutoDispatchService::enqueueIfEligible`) and only if a live orchestrator exists at that instant — with no reconciler — so briefs flagged-after-planned or planned-during-an-orchestrator-gap strand with NO signal forever (~16 observed 2026-07-04; 0 signals in queue).

## Build
A bounded, idempotent reconciler: a `flower:reconcile-auto-dispatch` command + scheduler entry (~2–5 min on the fast queue) AND an orchestrator check-in/boot hook, that finds briefs which are planned + auto_dispatch_on_planned=true + unblocked + NOT needs_operator_approval + have NO pending/claimed auto_dispatch signal AND NO active auto dispatch_request, and enqueues the missing signal via `CoordinationQueue` / `AutoDispatchService::enqueueIfEligible`. Idempotent (never double-enqueue); bounded per run; log what it enqueued.

## Pairs with #155
#155 makes the orchestrator auto-drain + auto-spawn to cap. Together = flagged+planned briefs flow to running agents hands-off. Same worker/branch as #155 is fine (files barely overlap: this = AutoDispatchService/command/scheduler; #155 = charter defaults/conventions).

## Acceptance
- A stranded flagged-planned brief gets a signal on the next reconcile (test).
- Re-running does NOT duplicate signals/requests (idempotency test).
- Orchestrator boot/check-in triggers a sweep.
- `php artisan test` green + `./vendor/bin/pint`. `Brief: #199` trailer. Worktree-pinned; never edit MAIN.

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: #199` 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. participant joined 1d ago
    system · system:commit-trailer
  3. merged 1d ago

    Merged to master (commit fd81f21, grouped with #155). AutoDispatchReconciler + `flower:reconcile-auto-dispatch` command (5m schedule via schedule:work + orchestrator check-in sweep in DaemonCheckin) are live. Review PASS: reconciler confirmed correct + idempotent incl. the claimed-signal dedup gap (CoordinationQueue dedups only pending; reconciler excludes pending OR claimed at the query level). Stranded flagged-planned briefs (the ~14 that never got a signal) will now backfill — orchestrator auto-drains + auto-spawns to cap.

    agent · flower-orchestrator
  4. status change 1d ago
    agent · flower-155-199-worker
  5. dispatched 1d ago

    Dispatch request #80 marked done.

    agent · flower-155-199-worker
  6. note added 1d ago

    Built on branch flower/155-199-auto-dispatch-autonomy (worktree foundation / Solo 54), grouped with #155. Commit fd81f21 (Brief: #199). AutoDispatchReconciler (app/Services/Briefs/AutoDispatchReconciler.php): bounded + idempotent. Finds planned + auto_dispatch_on_planned + not-approval briefs, EXCLUDES brief ids that already have a pending/claimed auto_dispatch signal at the query level (so the CLAIMED-signal case the CoordinationQueue payload-dedup misses can't double-enqueue), limits per run (default 25), and enqueues the missing signal via AutoDispatchService::enqueueIfEligible (which re-checks flag/planned/approval/dispatchable — the latter already excludes briefs with an active dispatch_request — and requires a live orchestrator to target). Wiring: flower:reconcile-auto-dispatch command (app/Console/Commands/ReconcileAutoDispatch.php, logs what it enqueued) + scheduler entry (routes/console.php, everyFiveMinutes + withoutOverlapping) + orchestrator boot/check-in sweep (DaemonCheckin runs the reconciler for role=orchestrator, best-effort so a reconcile failure never fails the heartbeat). Tests (all green): AutoDispatchReconcilerTest (stranded backfill; idempotent across runs; claimed-signal no-double-enqueue; no-op without a live orchestrator; blocked/approval/already-dispatched/unflagged skips; limit bounding), ReconcileAutoDispatchCommandTest (backfill/idempotent/limit), DaemonCheckinCommandTest (orchestrator boot-sweep + non-orchestrator does-not-sweep). Full suite: MEILISEARCH_KEY=LARAVEL-HERD ~/bin/php artisan test → 935 passed / 1 skipped / 0 failed. pint clean. Not merged — awaiting Phase-2 review.

    agent · flower-155-199-worker
  7. participant joined 1d ago
    system · flower-155-199-worker
  8. dispatched 1d ago

    Dispatch request #80 queued for flower.

    agent · flower-orchestrator
  9. status change 1d ago
    agent · flower-orchestrator
  10. plan proposed 1d ago

    ## Auto-dispatch reconciler (operator: AUTO-SPAWN TO CAP, 2026-07-04) Full detail + code cites in the brief note. Summary: auto_dispatch signals are enqueued ONLY on the planned-transition (`BriefService.php:481` → `AutoDispatchService::enqueueIfEligible`) and only if a live orchestrator exists at that instant — with no reconciler — so briefs flagged-after-planned or planned-during-an-orchestrator-gap strand with NO signal forever (~16 observed 2026-07-04; 0 signals in queue). ## Build A bounded, idempotent reconciler: a `flower:reconcile-auto-dispatch` command + scheduler entry (~2–5 min on the fast queue) AND an orchestrator check-in/boot hook, that finds briefs which are planned + auto_dispatch_on_planned=true + unblocked + NOT needs_operator_approval + have NO pending/claimed auto_dispatch signal AND NO active auto dispatch_request, and enqueues the missing signal via `CoordinationQueue` / `AutoDispatchService::enqueueIfEligible`. Idempotent (never double-enqueue); bounded per run; log what it enqueued. ## Pairs with #155 #155 makes the orchestrator auto-drain + auto-spawn to cap. Together = flagged+planned briefs flow to running agents hands-off. Same worker/branch as #155 is fine (files barely overlap: this = AutoDispatchService/command/scheduler; #155 = charter defaults/conventions). ## Acceptance - A stranded flagged-planned brief gets a signal on the next reconcile (test). - Re-running does NOT duplicate signals/requests (idempotency test). - Orchestrator boot/check-in triggers a sweep. - `php artisan test` green + `./vendor/bin/pint`. `Brief: #199` trailer. Worktree-pinned; never edit MAIN.

    agent · flower-orchestrator
  11. status change 1d ago
    agent · flower-orchestrator
  12. note added 1d ago

    ## Problem (confirmed in code, 2026-07-04) `auto_dispatch_on_planned=true` briefs are meant to auto-dispatch (orchestrator-mediated, up to `flower.dispatch.auto_max_concurrent`=2). But the auto_dispatch coordination signal is enqueued from EXACTLY ONE place — `AutoDispatchService::enqueueIfEligible()`, called only from `BriefService.php:481` WHEN A BRIEF TRANSITIONS INTO planned — and it silently `return null`s if there is no orchestrator with `status=Live` + `solo_process_id` AT THAT INSTANT (`AutoDispatchService.php:36-39`, `liveOrchestrator()`). There is NO reconciler / scheduled backfill (grep of `app/Console` + `routes/console.php` confirms — only ingest-state/projects/actor_ref reconcilers exist). Consequence: any brief that (a) was flagged `auto_dispatch_on_planned=true` AFTER it had already reached planned (bulk-flag), or (b) reached planned during an orchestrator gap / reset / boot window, gets NO signal, EVER — it strands as planned forever. Observed 2026-07-04: ~16 planned + auto_dispatch_on_planned=true briefs (#112 #128 #137 #140 #155 #158 #160 #167 #178 #179 #191 #192 #194 #197 …) with ZERO auto_dispatch signals in the coordination queue. (Not the cap — 0 active; not approval — all needs_operator_approval=false.) ## Change — a reconciler that backfills stranded signals Add a bounded, idempotent reconciler that finds briefs that are: status=planned AND auto_dispatch_on_planned=true AND NOT needs_operator_approval AND dispatchable (unblocked) AND have NO pending/claimed auto_dispatch signal AND NO active (in-flight) auto dispatch_request — and enqueues the missing auto_dispatch signal (reuse `AutoDispatchService::enqueueIfEligible` / `CoordinationQueue`). Run it: 1. On a SCHEDULE (e.g. every ~2–5 min on the fast queue; a `flower:reconcile-auto-dispatch` command + scheduler entry), and 2. On orchestrator check-in / boot (so a freshly-spawned orchestrator immediately backfills the backlog instead of only reacting to NEW planned-transitions). Optionally also re-enqueue when `auto_dispatch_on_planned` flips true on an already-planned brief (same transition gap). ## Guards / non-goals - IDEMPOTENT: never enqueue if a pending/claimed signal OR an active dispatch_request already exists for that brief. The existing drain enforces `auto_max_concurrent`, so the reconciler only needs to ensure a signal EXISTS, not to dispatch. - Does NOT change the concurrency cap, approval gate, or the orchestrator-mediated dispatch model. This fixes ONLY the enqueue-reliability gap. - Bounded per run (cap how many it enqueues per sweep; log what it enqueued). ## Acceptance - A planned + auto_dispatch_on_planned=true + unblocked + no-approval brief with no signal/request gets a signal enqueued on the next reconcile (test asserts this). - Re-running the reconciler does NOT duplicate signals/requests (idempotency test). - Orchestrator check-in/boot triggers a backfill sweep. - `php artisan test` green + `./vendor/bin/pint`. `Brief: #<this>` trailer. Worktree-pinned; never edit MAIN. ## Provenance Found by flower-orchestrator-2 on 2026-07-04 answering the operator's question "why aren't auto_dispatch_on_planned + planned briefs auto-dispatching?" Behavioral sibling: #155 (remove the daemon operator-go-ahead gate so a live orchestrator actually drains + spawns). Auto-dispatch signal design: #126. Together, #155 + this brief are the full fix for "flagged-planned briefs never flow."

    agent · flower-orchestrator
  13. participant joined 1d ago
    system · flower-orchestrator

epic · dependencies

Relationships

epic parent

depends on

No dependencies — dispatchable once planned.

agents · waves

Participants

  • flower-orchestrator participant · active
  • flower-155-199-worker participant · active
  • system:commit-trailer participant · active

trace · graph

Links

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