flower
/
All briefs
complete draft mcp flower

Orchestrator coordination queue (DB substrate) — wire poke (66b) + fix feedback→orchestrator routing (#49)

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.

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. status change 2d ago
    agent · flower-orchestrator
  4. status change 2d ago
    agent · flower-orchestrator
  5. plan proposed 2d ago

    # Orchestrator coordination queue (DB substrate) ## Why solo-cli 0.9.3 lacks `kv`, `timers`, `send-input`, so the app can't inject/wake a Solo process or share a queue. Two features are broken/blocked identically: poke (66b/#71 — wake an idle daemon) and feedback→orchestrator routing (#49 — Solo-KV `routed_pending` + `timerSet` wake). ONE DB-backed intent queue, drained by the orchestrator (an MCP-connected agent) which performs the actual injection via its OWN MCP calls, fixes both. Also the substrate for #76 self-reset. ## Data model Migration + model `DaemonSignal` (table `daemon_signals`): `id`, `kind` (string: `poke` | `route_feedback`, extensible), `target_daemon_id` (nullable FK daemon_agents), `project_id` (nullable FK), `payload` (json), `status` (pending|claimed|done|failed|cancelled), `requested_by` (actor_ref), `claimed_by` (nullable), `result` (nullable json), `error` (nullable), `created_at`, `claimed_at` (nullable), `completed_at` (nullable). Indexes: (status, project_id), (target_daemon_id). ## Service — `CoordinationQueue` - `enqueue(kind, ?target, ?project, payload, requestedBy): DaemonSignal` — DEDUPE: don't stack an identical pending signal (e.g. a second pending `poke` for the same target is a no-op / returns the existing). - `pending(?scope): Collection` — pending signals for a scope (project + platform/null). - `claim(signal, actorRef): bool` — atomic pending→claimed (guard double-claim). - `complete(signal, result)` / `fail(signal, error)`. ## Consumers 1. **poke (delivers 66b/#71):** wire the /roster poke button (Livewire action in Roster/Index.php + the daemon-row partial) → `enqueue('poke', $daemon, $daemon->project, [...], actorRef)`. Flash "poke queued — orchestrator will nudge <daemon> on its next cycle". Add a `daemon_poke` MCP tool for parity. 2. **feedback routing (fixes #49):** in FeedbackPromotionService, REPLACE the Solo-KV `routed_pending` read/write (the `kvGet`/`kvSet` / `appendRoutedPending` / `routedPendingEntry` path) AND the `timerSet` wake with `enqueue('route_feedback', target=orchestrator daemon if resolvable, project, payload={feedback_id, todo_id, scratchpad_id, funnel}, requestedBy)`. Keep the todo/scratchpad mirroring. Remove the dead `timerSet` call. `SoloClient::timerSet`/`kvGet`/`kvSet` — leave the methods but stop calling them from live paths (comment: unsupported by solo-cli 0.9.3; superseded by the DB queue). Result: `flower:feedback-promote --to=orchestrator` works with NO solo-cli kv/timers. ## MCP surface (register in the flower server) - `recall_signals` (read): pending/claimed signals for the caller's scope; include kind, target daemon id + its solo_process_id, payload — enough to act. - `signal_claim(signal_id, actor_ref)`, `signal_complete(signal_id, actor_ref, result?)`, `signal_fail(signal_id, actor_ref, error)`. - `daemon_poke(daemon id/ref, actor_ref)` — enqueue a poke (parity with the button). ## Orchestrator drain (AgentConventions daemon block) On each heartbeat the ORCHESTRATOR calls `recall_signals` for its scope and, per pending signal, claims + executes: - `poke` → `mcp__solo__timer_set(project_id, delivery_process_id = target.solo_process_id, delay_ms=0, body="Operator poke via /roster — resume your loop: do a check-in and continue your work.")` (the app can't inject; the orchestrator does, via MCP). - `route_feedback` → pick up the routed feedback work (todo/scratchpad already exist; act on it). Then `signal_complete`. Latency = ≤ one orchestrator heartbeat (v1 — document it; a faster drain is a later optimization). ## Guardrails (HARD) - Work ONLY in flower-wt2 (branch flower/coordination-queue). sqlite tests only; write the migration but do NOT run it against a live DB; do NOT touch MAIN; do NOT run live solo-cli / spawn / real MCP injection (the drain is CONVENTION + fake-tested; the worker never fires a real poke). `./vendor/bin/pint`; keep `php artisan test` green. Commit trailer `Brief: #84`. - Commit to `flower/coordination-queue` — do NOT merge or push. Report files changed + test summary + which SoloClient methods you retired vs kept + anything deferred. ## Tests (sqlite) - Queue lifecycle: enqueue (+ dedupe pending), pending(scope), claim (no double-claim), complete/fail. - poke button + `daemon_poke` tool enqueue a `poke` for the right daemon. - FeedbackPromotionService routes via the queue (a `route_feedback` signal is enqueued) and NO LONGER calls Solo-KV/timerSet — assert via a fake SoloClient whose kvGet/kvSet/timerSet throw if called. - recall_signals returns pending for scope; claim/complete transitions; signal_fail path. - AgentConventions daemon block contains the drain protocol. ## Done = DaemonSignal table + CoordinationQueue + poke wired (66b) + feedback routing rewired (fixes #49) + MCP tools + drain convention, tests green, committed. MAIN migration + the live drain are the orchestrator's.

    agent · flower-orchestrator
  6. note added 2d ago

    The keystone substrate. The app cannot inject/wake Solo processes or read a shared queue because solo-cli 0.9.3 lacks kv + timers + send-input. So TWO features are broken/blocked the same way: poke (66b/#71 — wake an idle daemon) and feedback→orchestrator routing (#49 — `routed_pending` in Solo KV + `timerSet` wake). Fix both with ONE flower-owned DB-backed intent queue that the orchestrator (an MCP-connected agent) drains on its loop and executes via its own MCP calls (mcp__solo__timer_set / send_input). Deliver: (1) a `daemon_signals` table + CoordinationQueue service (enqueue/pending/claim/complete/fail); (2) wire the /roster poke button → enqueue('poke', daemon) [delivers 66b/#71]; (3) rewire FeedbackPromotionService off Solo-KV `routed_pending` + `timerSet` onto the queue [fixes #49 / feedback_promote --to=orchestrator]; (4) MCP surface: recall_signals (orchestrator lists pending for its scope) + signal_claim/signal_complete + a daemon_poke enqueue tool; (5) AgentConventions: the orchestrator drains recall_signals each heartbeat and executes (poke → mcp__solo__timer_set to the target's solo_process_id; route_feedback → pick up the routed work), then signal_complete. Wake latency = ≤ one orchestrator heartbeat (v1, documented). Also becomes the substrate for #76 self-reset. Full spec to follow.

    agent · flower-orchestrator
  7. 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
  • system:commit-trailer participant · active

trace · graph

Links

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