From 86a423fe6045e5b2bb0859623546ffdbdec39fe5 Mon Sep 17 00:00:00 2001 From: Lovebug MCP Date: Fri, 5 Jun 2026 21:38:13 +0000 Subject: [PATCH] =?UTF-8?q?mcp:=20project-plan=20=E2=80=94=20petal-dispatc?= =?UTF-8?q?h=20Phase=203=20brief?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/Dev/petal-dispatch-phase3-brief.md | 109 +++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 Sources/Dev/petal-dispatch-phase3-brief.md diff --git a/Sources/Dev/petal-dispatch-phase3-brief.md b/Sources/Dev/petal-dispatch-phase3-brief.md new file mode 100644 index 0000000..b5fa99e --- /dev/null +++ b/Sources/Dev/petal-dispatch-phase3-brief.md @@ -0,0 +1,109 @@ +--- +created: '2026-06-05' +path: Sources/Dev +project: petal-dispatch-phase3-brief +tags: +- petal-dispatch +- lovebug +- phase-3 +- web-client +- fastapi +- sse +- trellis +- postgres +- p4 +- execution +type: project-plan +--- + +# petal-dispatch — Phase 3 — Web Client — Worker Brief + +**Project:** petal-dispatch (p4) · **Thread:** 588 · **Repo:** `petal-power/petal-dispatch` (Gitea) +**Plan ref:** p4 Phase 3 · Satisfies FR-001, FR-003, FR-005, FR-006, FR-007, FR-012 +**Structure:** Three sequential gated spawns (3a → 3b → 3c). Each lands a handoff to thread 588 before the next spawns. + +## Goal + +A functional web client for petal-dispatch — the first real client, deliberately before Android (p4 D-1), to prove **store → API → client → render, both directions**, on the web stack before committing to Kotlin. The load-bearing half is the **write loop** (client → orchestrator → echo back through the store), because that is the path Android copies; a read-only client would not prove what Phase 3 exists to prove. + +**Phase 3 success bar:** Travis sends a message and gets a conversational response from the orchestrator, round-trip through the store (FR-005), rendered. **Orchestrator only — no worker spawn, no worker-completion dependency.** Worker-task surfaces and stall detection (SC-7) are out of Phase 3; no workers exist this phase. + +The existing `static/index.html` (Phase 2 poll-based cards) is **reference-only, not a base.** Mine it for markup/render logic; ship fresh. Standard worker discipline (zero-check, a-review per handoff) applies per env instructions. + +## Locked Decisions + +**Store boundary (p4 D-22) — governs the whole phase.** Two stores by kind of truth. Journal = execution truth (events, handoffs). Trellis = context truth (threads, working picture). 3b is journal-only and survives Trellis-down. 3c is the only Trellis-backed surface. petal-dispatch's Phase 3 *code* reads Trellis; it does not write it (writes are synthesis, FR-013, Stage C — out of scope this phase). + +**Transport (p4 D-6, with shipped-WS drift).** The Phase 3 web client targets SSE over the harness `/events/stream`. The app's shipped WebSocket (`/api/tasks/events`) is left in place untouched; Android repoints to REST+SSE in Phase 4. The existing v0 `/ws` chat endpoint is a separate question handled in 3a recon (see below). + +**Auth.** `OPEN_MODE=1` for v1 — no auth, tailnet-only (D-14). No login wall. New routes follow the same `_check_auth` + `OPEN_MODE` pattern as Phase 1/2: when `OPEN_MODE=1`, auth no-ops; otherwise the existing identity-header check applies. + +**Trellis read mechanism (3c).** petal-dispatch gets its **own dedicated DB role** on `petalbrain.trellis` — separate from `dispatch_journal`, which has no business reading Trellis. The role is provisioned **read/write** (forward-looking: synthesis at Stage C will need write to land context keyed by `thread_id`), but **Phase 3 code uses read (SELECT) only** — FR-012/D-22 forbid petal-dispatch writing Trellis in v1. "Trellis unavailable" for the graceful-degradation test therefore means "the DB query failed" — the same failure surface as every other petal-dispatch query, wrapped in try/except. Rejected: calling trellis-mcp over its HTTP transport (adds a hard runtime dependency — trellis-mcp down would take 3c with it). + +**Rendering.** Render-on-final, keyed by the reconcile key (PD-issued UUID, D-10/D-12) — the same id-keyed path optimistic render layers onto later (Phase 5); do not foreclose it. + +**Tap-to-switch is render-only this phase.** No orchestrator-side "active thread" repointing is wired; switching focus changes the highlighted tile, nothing more. + +## Open Items + +- **3a recon, input/transport (the `/ws` fork).** `main.py` already runs a WebSocket at `/ws` → `handle_ws(websocket, proc, bus)`, where `proc` is the Dispatch claude singleton — i.e. the orchestrator-only target Chat wants, already wired to the right backend. Recon must surface, not infer: does Chat (a) use the existing `/ws`, (b) add an HTTP POST + SSE path and coexist with / retire `/ws`? This brushes the p4 D-6 "SSE not WebSocket" line — reusing `/ws` is faster but deepens the WS drift p4 wanted gone; building POST+SSE is p4-faithful but more work. **This is a Travis call, not a worker call** — recon checkpoints the tradeoff before 3b's Chat transport is decided. +- **3a recon, input-path state.** Does a working client → REST → orchestrator-stdin → echo-via-`USER_MESSAGE` round-trip exist in v1, or was it only proven in the prototype? If unbuilt, 3a absorbs the FR-005 backend write path — surface as a scope finding at the checkpoint, do not silently expand. +- **3a recon, reconcile-key population rule.** Which event tiers/types carry a non-NULL `message_id` (reconcile key, D-12)? Chat-message rendering keys on it. (Fact lives in the Phase 0 schema + writer.) +- **3a recon, harness SSE wire contract.** Envelope shape, field names, event types on `/events/stream`, `?from_seq=N` backfill semantics. +- **Synthesis-richness seam (3c).** v1 renders native Trellis threads. When synthesis (Stage C) later writes a distilled working-context picture to Trellis (keyed by `thread_id`, migration-003 seam), the tiles should get richer with no client change. Build the render path to accommodate; do not build synthesis. + +## Phases + +### 3a — Backend: event bus + recon + +**Recon first, then wire. Checkpoint findings before building if anything changes scope.** Lovebug indicates most of 3a is already in place — recon likely confirms the input path exists and returns the contract, leaving 3a light. Verify, don't assume. + +**Drift paid down here:** Phase 1 shipped the extractor trigger in-process (fire-and-forget asyncio from `TaskManager`), not via LISTEN/NOTIFY as p4 D-6 specifies. 3a gets task/handoff events onto the bus so the SSE endpoint carries them. Without this, a client wired to `/events/stream` receives no task events. + +Recon deliverables (answer before wiring): +- [ ] Input/transport `/ws` fork (see Open Items) — surfaced + checkpointed for Travis's transport call. +- [ ] Input → stdin path state (exists / built-in-3a / scope flag). +- [ ] Reconcile-key population rule across event types. +- [ ] Harness SSE wire contract (envelope, field names, event types, `?from_seq` backfill). + +Wiring: +- [ ] Publish task lifecycle + handoff events onto LISTEN/NOTIFY → SSE so `/events/stream` carries real task events. + +Handoff outputs (3b depends on these): +- [ ] Documented SSE wire contract · reconcile-key rule · input-path + `/ws` transport finding. + +**3a done =** recon answered · task/handoff events on `/events/stream` with `?from_seq` backfill proven · input→stdin path confirmed-or-built · SSE contract + reconcile rule + transport decision in handoff. + +### 3b — Web client (fresh) + +Fresh client, `static/` reference-only. Served from `/static`, same Traefik route. `OPEN_MODE=1` — no login wall. + +Two surfaces, both journal-backed — zero Trellis dependency (this is why 3b survives Trellis-down): +- [ ] **Chat** *(success-bar surface)* — source: journal round-trip. Send input → orchestrator conversational response → echo via `USER_MESSAGE` through the store (FR-005), rendered. **Orchestrator only, no worker spawn.** Input-during-run queued client-side with visible pending state (FR-007). This is what "done" is measured on. Transport per 3a's decided fork. +- [ ] **Activity** — source: journal event feed via SSE. Chronological event stream. + +Rendering: +- [ ] Render-on-final, keyed by reconcile key (D-10/D-12) — same path optimistic render layers onto later (Phase 5). +- [ ] Consumes 3a's SSE contract over `/events/stream`. Survives reconnect (re-subscribe, re-render from store). +- [ ] Layout: logical, function over form. Design iteration happens later with Lovebug — iterate-shaped surface (p4), not a spec gate. No home-tab/default-tab lock. + +**3b done =** message sent → orchestrator conversational response → round-trip through store (FR-005), rendered, orchestrator only no worker · Activity renders from SSE, survives reconnect · layout logical. + +### 3c — Trellis threads/context surface + +Completes FR-012 (the backend read surface p4 assigned to Phase 1, which under-delivered) and renders it. The only Trellis-backed surface in Phase 3. + +- [ ] **Backend:** read-only `/api/trellis/...` over Trellis thread/context state, via the dedicated DB role on `petalbrain.trellis` (provisioned read/write, code uses SELECT only — see Locked Decisions). petal-dispatch reads Trellis; does not write it this phase. +- [ ] **Frontend:** thread/context tiles rendering native Trellis state — thread list, live thread highlighted, recent events, tap-to-switch-focus (render-only, SC-3 context switch ≤1 action). Lovebug's mockups are the starting point. Iterate-shaped. +- [ ] **Synthesis seam (accommodate, do not build):** render path built so Stage-C synthesized context drops in without a rewrite. +- [ ] **Graceful degradation (required test):** Trellis DB query fails → tiles show unavailable state; journal-backed surfaces (Chat, Activity) unaffected (p4 edge case). + +**3c done =** tiles render native Trellis threads (list, live thread, recent events, tap-to-switch render-only) · degrade gracefully when Trellis-role query fails · journal surfaces unaffected by Trellis outage. + +## Notes + +**Drift touched by Phase 3** (existing 7 items in checkpoint 1088 untouched): in-process-trigger → bus, paid down in 3a; FR-012 backend, completed in 3c (Phase-1 execution under-delivery, not Phase-3 scope creep); the repo-rename-not-done and shipped-WS items remain documented divergences, not corrected here. + +**Why 3b is journal-only.** Splitting Chat+Activity (journal) from the Trellis surface (3c) makes the journal/Trellis boundary map one-to-one onto the 3b/3c split. 3b cannot break when Trellis is down because it never touches Trellis — the graceful-degradation edge case becomes near-free. + +**Gated structure.** 3a recon → checkpoint (Travis decides Chat transport on the `/ws` fork) → 3a wires → 3b builds against the decided contract → 3c. Sequential spawns, handoff to 588 between each. \ No newline at end of file