flower
/
All briefs
complete draft note flower

Wire auto-dispatch-on-planned via the orchestrator drain loop (the missing consumer)

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.

#18 done fresh flower · flower/98-auto-dispatch-wiring
agent: claude
You are being dispatched from flower Brief #98: Wire auto-dispatch-on-planned via the orchestrator drain loop (the missing consumer)

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

Target:
- project: flower (/Users/mikeferrara/Documents/code/flower)
- branch: flower/98-auto-dispatch-wiring
- 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 flower-orchestrator: (no body)
[2] note_added flower-orchestrator: Operator design (2026-07-03): the `auto_dispatch_on_planned` toggle has no consumer (verified — nothing in app/Jobs/Console/Services reads it; UI admits "no agent acts on it yet"). Rather than build new job/queue infra, make the ORCHESTRATOR'S existing heartbeat/drain loop the consumer.

## The insight
`recall_dispatch_queue(dispatchable_only=true)` already returns exactly the right candidate set: status=planned, unblocked, NO dispatch_request in flight. The auto-dispatch consumer is just: on each orchestrator heartbeat, take that set, keep the ones with `auto_dispatch_on_planned=true`, and dispatch each (spawn/assign a worker + brief_dispatch), subject to a concurrency cap.

## Work
1. **Surface the flag in the dispatch queue:** add `auto_dispatch_on_planned` to each brief in `recall_dispatch_queue` output (and the `dispatchable` list) so the orchestrator can filter without N extra recall_brief calls.
2. **Add the auto-dispatch step to the orchestrator drain conventions** (`AgentConventions::daemonLines()` orchestrator-drain line + the heartbeat-timer body): "each heartbeat, after draining signals, dispatch any dispatchable briefs with auto_dispatch_on_planned=true, up to the concurrency cap." Keep the actual spawn/dispatch as orchestrator behavior (it needs judgment + a worker), not a headless job.
3. **Guardrails:** (a) idempotency — the dispatchable filter already excludes briefs with a request in flight, so no double-dispatch; (b) concurrency cap — a config (e.g. `flower.dispatch.auto_max_concurrent`, default small like 2-3) so a batch of toggled briefs doesn't spawn an unbounded fleet; (c) respect blockers/needs_operator_approval (already excluded by dispatchable).
4. **Re-enable the toggle:** once wired, the toggle in the brief detail view is live — coordinate with #96 (which was going to disable it) so it stays ENABLED with an accurate hint ("auto-dispatches when it reaches planned").

## Constraints
- Isolated worktree + sqlite tests; `php artisan test` green; `./vendor/bin/pint`. Add tests for the dispatch-queue flag surfacing + the concurrency-cap gate. Commit trailer `Brief: #<this-id>`.
[3] 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: #98` 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 2d ago
    agent · system:commit-trailer
  2. participant joined 2d ago
    system · system:commit-trailer
  3. note added 2d ago

    MERGED to master on MAIN by orchestrator daemon 10 (proc 996). Worker 999 (flower-98-worker, flower-backend) built commit 224c60a; merged --no-ff as 30bd0dd (merge-base 449a2e8, clean). config:clear run (config/flower.php changed). No migration, no job code → no horizon:terminate. Affected tests green on MAIN (Dispatch|AgentConventions filter: 51 pass, 368 assertions); worker ran full suite 636 pass/1 skip. Shipped: recall_dispatch_queue surfaces auto_dispatch_on_planned (requests + dispatchable, incl. the DispatchService::queue eager-load fix so it's not null on in-flight); config flower.dispatch.auto_max_concurrent (default 2); AgentConventions orchestrator-drain line. #96 is now UNBLOCKED — and per the plan, #96 keeps the auto-dispatch toggle ENABLED (wired now) with an "auto-dispatches when it reaches planned" hint. NOTE for the orchestrator loop: going forward, each heartbeat drain should also auto-dispatch dispatchable briefs with auto_dispatch_on_planned=true up to the cap (none flagged yet).

    agent · flower-orchestrator
  4. status change 2d ago
    agent · flower-98-worker
  5. dispatched 2d ago

    Dispatch request #18 marked done.

    agent · flower-98-worker
  6. note added 2d ago

    Implemented on branch flower/98-auto-dispatch-wiring (off master 449a2e8), NOT merged. Commit 224c60a8d7feed0306c8f0d1b9e9d2283c5f9e76 (`Brief: #98`). Scope delivered (surface flag + cap + conventions only — NO headless auto-spawn job, per the operator design): 1. **recall_dispatch_queue surfaces `auto_dispatch_on_planned`** on BOTH lists: - `dispatchable[]` — Brief has all columns loaded, so `(bool) $brief->auto_dispatch_on_planned` in RecallDispatchQueueTool::dispatchablePayload(). - `requests[].brief` — added the field to requestPayload(); also added `needs_operator_approval,auto_dispatch_on_planned` to DispatchService::queue()'s `brief:...` eager-load select so they aren't silently null on the restricted column set. 2. **config `flower.dispatch.auto_max_concurrent`** (new top-level `dispatch` key in config/flower.php; env `FLOWER_DISPATCH_AUTO_MAX_CONCURRENT`, default 2). 3. **AgentConventions::daemonLines()** — one new orchestrator-drain line (right after the recall_signals drain line, matching the daemonLines voice): "Each heartbeat, after draining signals, auto-dispatch: call recall_dispatch_queue(dispatchable_only=true), keep the briefs with auto_dispatch_on_planned=true, and dispatch each … up to flower.dispatch.auto_max_concurrent per heartbeat. … dispatch stays orchestrator behavior, not a headless job." Idempotency note included (dispatchable set already excludes in-flight/blocked/needs-approval). Tests (sqlite, this worktree): +4 — dispatchable payload carries the flag (true/false), in-flight request brief carries it, config default = 2, daemon conventions line mentions auto-dispatch/recall_dispatch_queue(dispatchable_only=true)/auto_dispatch_on_planned=true/flower.dispatch.auto_max_concurrent. `php artisan test` green (636 passed, 1 pre-existing skip, 4467 assertions); pint clean on all changed files. NOT done (correctly out of this brief): the #96 UI toggle re-enable (coordinate separately). No migration (column already exists). No Horizon reload needed (no job/pipeline code changed) — but the flower MCP server must reload for the new recall_dispatch_queue field to appear, and daemon packets pick up the new conventions line on next render.

    agent · flower-98-worker
  7. participant joined 2d ago
    system · flower-98-worker
  8. dispatched 2d ago

    Dispatch request #18 queued for flower.

    agent · flower-orchestrator
  9. status change 2d ago
    agent · flower-orchestrator
  10. status change 2d ago
    agent · flower-orchestrator
  11. note added 2d ago

    Operator design (2026-07-03): the `auto_dispatch_on_planned` toggle has no consumer (verified — nothing in app/Jobs/Console/Services reads it; UI admits "no agent acts on it yet"). Rather than build new job/queue infra, make the ORCHESTRATOR'S existing heartbeat/drain loop the consumer. ## The insight `recall_dispatch_queue(dispatchable_only=true)` already returns exactly the right candidate set: status=planned, unblocked, NO dispatch_request in flight. The auto-dispatch consumer is just: on each orchestrator heartbeat, take that set, keep the ones with `auto_dispatch_on_planned=true`, and dispatch each (spawn/assign a worker + brief_dispatch), subject to a concurrency cap. ## Work 1. **Surface the flag in the dispatch queue:** add `auto_dispatch_on_planned` to each brief in `recall_dispatch_queue` output (and the `dispatchable` list) so the orchestrator can filter without N extra recall_brief calls. 2. **Add the auto-dispatch step to the orchestrator drain conventions** (`AgentConventions::daemonLines()` orchestrator-drain line + the heartbeat-timer body): "each heartbeat, after draining signals, dispatch any dispatchable briefs with auto_dispatch_on_planned=true, up to the concurrency cap." Keep the actual spawn/dispatch as orchestrator behavior (it needs judgment + a worker), not a headless job. 3. **Guardrails:** (a) idempotency — the dispatchable filter already excludes briefs with a request in flight, so no double-dispatch; (b) concurrency cap — a config (e.g. `flower.dispatch.auto_max_concurrent`, default small like 2-3) so a batch of toggled briefs doesn't spawn an unbounded fleet; (c) respect blockers/needs_operator_approval (already excluded by dispatchable). 4. **Re-enable the toggle:** once wired, the toggle in the brief detail view is live — coordinate with #96 (which was going to disable it) so it stays ENABLED with an accurate hint ("auto-dispatches when it reaches planned"). ## Constraints - Isolated worktree + sqlite tests; `php artisan test` green; `./vendor/bin/pint`. Add tests for the dispatch-queue flag surfacing + the concurrency-cap gate. Commit trailer `Brief: #<this-id>`.

    agent · flower-orchestrator
  12. participant joined 2d ago
    system · flower-orchestrator

epic · dependencies

Relationships

epic parent

depends on

No dependencies — dispatchable once planned.

agents · waves

Participants

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

trace · graph

Links

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