diff --git a/CLAUDE.md b/CLAUDE.md index 13e5dd3f..3a2e8fa1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -280,6 +280,10 @@ link-focusable). Half the app still does this; none of the new code should. - `docs/navigation-audit.md` — **authoritative** on routing/navigation: the opaque-click anti-pattern, a severity-ranked findings table, the channel-selection map and the four-phase plan +- `docs/agent-coordination.md` — **the north star** for the workspace/panel work: agents on one + dashboard coordinating with each other instead of through the human, the handoff protocol, and what + is deliberately *not* being built. Read it before ranking, deferring or starting any panel item — + it is what `docs/workspace-panel-todo.md` is ranked against. - `docs/workspace-panels.md` — how the Workspace/Panel framework works: the layout tree, how a panel is mounted and what it knows, the URL-vs-channel split for panel-to-panel communication, and the persistence key families. Read before building a panel app. Its defect list is diff --git a/docs/agent-coordination.md b/docs/agent-coordination.md new file mode 100644 index 00000000..d97207a0 --- /dev/null +++ b/docs/agent-coordination.md @@ -0,0 +1,711 @@ +# Agent coordination — the north star + +**What this is:** the objective that the Workspace/Panel work serves — several agents working +concurrently on one body of work, **coordinating with each other rather than through the human**, with +a human authoring the workflow at the top. Written live during the conversation of 2026-08-07, from the +owner's own words; where a section records a decision, that decision is his, not a proposal. + +**Read this before ranking, deferring or starting any workspace/panel item.** It is the thing every +other workspace/panel document is ranked *against*: + +- `docs/workspace-panels.md` — how the framework works today (descriptive, no opinions) +- `docs/workspace-panel-todo.md` — the work queue, currently ordered by defect severity +- `COMMS/workspace-panel-framework-analysis-2026-08-07.md` — the reasoning behind that queue + +Once this document is complete, the todo is to be **re-ranked against the objective** rather than +against severity, and both of the above updated to reflect it. That re-rank is deferred by the owner +until this conversation is finished — it is not to be started early. + +--- + +## 1. The objective + +### 1.1 The surface that matters — two routes + +Everything that follows is about **`/chat`** and **`/dashboards`**. Verified against +`src/apps/officer-web/App.tsx`: + +| route | element | line | +|---|---|---| +| `/chat`, `/chat/new`, `/chat/new/g/*`, `/chat/g/*`, `/chat/:sessionId` | `Dashboard.SessionListPage` | 42–46 | +| `/dashboards` | `Dashboard.DashboardsScreen` | 82 | +| `/dashboards/:id` | `Dashboard.DashboardScreen` | 83 | + +One structural fact worth stating early, because it shapes everything: **`/dashboards/:id` is the only +place in the app where a workspace is not `locked`.** Every other screen ships a layout as the product +and forbids the user from splitting, closing, or re-apping panels. Dashboards is where the user composes +their own. So of the two routes named, one is the agent and the other is the only user-authored surface +in the entire platform. + +### 1.2 The agent is the centre of gravity + +Stated by the owner up front: **the most important things always come back to the Claude chats.** The +dashboards story is being explained first only because it is the easier of the two to describe — not +because it is the more important. Whatever is built for dashboards should be understood as eventually +serving the agent. + +There are two agent backends, and they are not equal right now: + +- **Claude** — what is actually used. Runs as the `officer-agent` PM2 peer behind + `officer-anthropic-proxy`. +- **OpenCode** — the `officer-opencode` sidecar. **Development stopped a while ago and is not resuming + now**; the owner may pick it back up later. + +The rule that follows: OpenCode is **dormant, not dead**. Do not spend effort on it, do not build for +it, and do not treat it as a requirement — but equally, do not design something that makes Claude the +only agent that could ever exist. Where the cost of keeping the seam is near zero, keep it. Where +keeping it would distort the design, favour Claude and note the assumption here. + +### 1.3 The dashboards scenario — the live example + +The owner's chosen illustration is **what he is doing at this moment**: running *two Claude agents in +parallel, in two different chat windows, both working on the platform.* + +**Stated as fact by the owner** (not inferred): + +- Two agents, two chat windows, same platform, at the same time. +- This is precisely why the standing "never restart the server yourself" rule exists: a + `pm2 restart officer` is a **shared, destructive-ish event** across every agent working on the + platform, so it must be *timed* by the owner rather than triggered by whichever agent happens to + finish first. + +**Observed by me during this same session**, as corroborating detail — the frictions this arrangement +actually produces: + +1. **The owner is the scheduler.** Each agent independently reaches a point where it needs a restart and + asks. Nothing in the system knows another agent exists, so the owner is the only thing that can + serialise it. (He also had to tell me, separately, to stop *repeating* the request once made.) +2. **The owner is the message bus.** Neither agent can see the other's work, so anything one needs to + know about the other has to be relayed by hand. +3. **Shared tree, shared `master`.** Two agents, one working copy. This produced the session's sharpest + instruction — *"The problem is committing each other's work. Like, that can't happen, man."* — and + the mitigation is purely behavioural: each agent must be told, separately, to stage explicit paths + and never `git add -A`. Nothing enforces it. +4. **Uncertain ownership of a failure.** I hit a real typecheck error (`CodeBlock.tsx:138`) and could not + tell whether it was mine, and had to ask. The answer was that it belonged to the other agent. + +**Unconfirmed inference — to be confirmed or corrected by the owner before it is treated as the +objective:** that the dashboards half of the holy grail is a surface where these parallel agent sessions +are *visible together and manageable together* — one screen, multiple live agents as panels, with the +state they contend over (restarts, the git tree, who is touching what) legible — so the human stops +being both the scheduler and the message bus between them. + +### 1.4 The proof of concept — the owner's words, made precise + +> A new dashboard with two vertical panels. Each panel is a chat session that I can navigate to whatever +> path I want, to continue or start a new session from a specific path. Each chat panel gets attributed +> some kind of persistent ID related to that dashboard. + +And the behaviour that PoC is *for*: + +> I can let you both work, and at the end of your turn you ask the other agent "can I restart?", wait for +> his output, restart yourself. And the same from the other side — the other agent, when he finishes his +> turn, is able to communicate with yourself to ask "can I restart?", restart, and let you know that I +> did my restart and I'm going to keep going with something else. + +> If we get this to work, the sky is the limit. + +Decomposed into the five capabilities it actually requires: + +| # | capability | exists today? | +|---|---|---| +| P1 | Two chat panels in one dashboard, each an **independent** session | **No** | +| P2 | Each panel pointed at **its own path** (cwd) | **No** | +| P3 | A **persistent id** per chat panel, scoped to the dashboard, that survives reload | Partly | +| P4 | Agent-to-agent **messaging** between two live sessions | **No** | +| P5 | An agent **surviving the restart** it triggers | **Yes** | + +### 1.5 Verified state of the code against those five + +Read from source on 2026-08-07, not assumed: + +**P1 — the blocker.** `ChatPanelWrapper` (`apps/Chat/ChatPanelWrapper.tsx:45`) is declared +`() => {…}` — **it takes no props at all, not even `panelId`.** Everything it uses comes from +`useWorkspace()`: `dashboardId`, `cwd`, `root`, `promptPrefix` — all of which are *per screen*. Two +chat panels dropped into one dashboard today are therefore **byte-for-byte identical**: same cwd, same +context, same session-resolution path. There is no per-panel anything. It also calls +`useChat(undefined, undefined, …)`, so no session id is passed in — a panel cannot be told which session +to continue. + +**P2 — does not exist.** `cwd` is a prop on ``, set once per screen, delivered ambiently +through `WorkspaceContext`. `scoped = cwd !== '~'`. Every panel on a screen necessarily shares one cwd. + +**P3 — the good news, with one sharp edge.** Panel ids (`layout-utils.ts:4`, +`` uid = () => `p-${Date.now()}-${++counter}` ``) are generated once and **persisted inside the layout +`jsonb`**, so a panel id *is* already stable across reloads. That makes panel id a viable durable key — +which is the single most load-bearing fact for this PoC. The edge: `movePanel` mints a **new** id +(`layout-utils.ts:192`, `:207`) rather than carrying the old one, so dragging a panel would silently +sever its session binding. That is gap **G2** in the analysis, and it is now on the critical path. + +Also already half-built, and worth knowing: for a *user* dashboard the wrapper already derives +`{ context: 'dashboard', contextId: dashboardId }` (`ChatPanelWrapper.tsx:49-55`) — a notion of +dashboard-scoped chat context exists. It is keyed to the **dashboard**, not the panel, which is exactly +one level too coarse for this. + +**P4 — does not exist**, in any form. The nearest primitive, `usePanelChannel`, is browser-local and +dies on reload; it cannot carry a message between two agent processes. This is new machinery, and +almost certainly belongs on the sidecar side rather than in the frontend. + +**P5 — already true, and it is the reason the rest is worth building.** Per `CLAUDE.md`, verified +2026-07-30: `pm2 restart officer` no longer kills an agent session. The agent is its own PM2 peer +(`officer-agent`), and output produced while `officer` is down survives because the sidecar writes to +`chat_session_events` itself. A restart costs a socket reconnect, not the session. **The single hardest +prerequisite of the owner's scenario is therefore already met.** + +### 1.6 Owner's corrections — walked back through §1.5, from the end + +**On P5 (restart survival) — my summary was right but incomplete, and the gap is the whole reason the +PoC has the shape it has.** + +A lot of work landed today and over the last few days: **a session now survives a server restart with no +refresh and no user action.** One case remains broken, and the owner has **decided not to solve it**: + +> *unless the agent is currently outputting — the restart of the server interrupts that output.* + +This reframes the PoC entirely. **The by-turn handshake is not merely coordination; it is a deliberate +route around the one failure mode that is not going to be fixed.** Restarts are made safe by +guaranteeing they only ever happen when no agent is mid-output — negotiated, rather than engineered +away. Design consequences: + +- The protocol is **turn-boundary only, by construction.** "I have finished my output, you may restart" + is the entire safety property. A design that lets a restart fire at an arbitrary moment defeats the + purpose even if it is otherwise more elegant. +- Therefore **an agent must be able to declare turn-end** and another must be able to observe it. That, + not the messaging, is the load-bearing signal. +- The full owner-described cycle: A finishes → A tells B "I'm done, you may restart" → B restarts → B + tells A "restarted, continuing" → A resumes. Both directions, symmetric. +- **Do not** spend effort making mid-output restarts survivable. Explicitly out of scope, by decision. + +**The second thing it buys, for free — and the owner has been missing it for a long time:** + +> a lot of the time when an agent finishes a tool call or whatever, and if I'm not at the computer to +> communicate with them, they don't continue, because they are expecting an input from the human — but +> that input or that trigger can come from the other agent. + +So the same primitive is also a **liveness mechanism**: an agent stalled at a turn boundary waiting on a +human can be unblocked by its peer. Today the owner's physical presence is a hard dependency for +progress; this removes it. Recorded properly in §3, because it is not the objective — but it is a strong +signal the direction is right, since it falls out of the PoC at no extra cost. + +### 1.7 The actual objective — the software factory + +**The restart problem is not the goal, and is barely even a problem.** It exists only because the owner +is currently using the platform to fix the live platform, for velocity. It is a *dogfooding artifact*. +It has been chosen as the proof of concept because it is small, real, and falsifiable — not because it +is the target. + +The target: + +> I can turn my platform into a **software factory** with agents working together with each other. One +> for the backend, one for the front end, one for the mobile apps. Doing code reviews, making sure all +> the conventions and all the rules are being correctly applied, before merging — code reviews, running +> tests. There's no limit to this vision. + +So the north star is: **several specialised agents, working concurrently on one codebase, coordinating +with each other rather than through the human, with quality gates between them and the mainline.** + +The dashboards surface is how a human *watches and steers* that factory. The chat panels are the +workers. The restart handshake is the first, smallest instance of the general primitive: agents +negotiating a shared resource without a human in the middle. + +### 1.8 Partnership, and the standing instruction to push back + +Stated by the owner, and binding from here on: + +> I also want you to call me out and say "dude, you're being crazy, this isn't possible, we need to find +> something simpler". Please don't feed my ego and call me out whenever you feel necessary. I am from +> this moment on considering you a partner. + +This is a **standing instruction, not a courtesy**. It outranks agreeableness. Where a plan is +over-reaching, where a proof of concept is being over-read, or where an easier path exists, say so +plainly and early — the cost of a late correction here is much higher than the cost of an unwelcome one. + +### 1.9 There is always a human orchestrator — and the flow already works manually + +**Answering the "you may over-read the PoC" challenge.** The owner's response materially changes the +risk profile, and it is recorded here because it is the strongest single argument in the whole +conversation. + +**Constraint, binding:** *there will always be a human orchestrator* — the owner, or whoever later runs +the platform. **The goal is explicitly not agents ping-ponging inputs and outputs with no structure.** +Any design that removes the human from the top of the loop is wrong, not ambitious. + +**And the workflow is not hypothetical — it is executed manually today**, with three screens and two +computers. The owner's worked example, verbatim in substance: + +1. Hand the git repo to the **platform agent**, plus "there is a Soulseek server on localhost:X, here is + its API and web-UI documentation." Ask for a sidecar, a route to enter the connection string and API + key, and as much of the web UI as can be built — **"don't expect me to be here to answer questions, + just present me a version 0.1 of the whole thing."** The sidecar process itself is by now + well-documented, and the documentation keeps being updated with new learnings. +2. The owner **shifts focus entirely** to other work — mobile monorepo, platform architecture — for one + to two hours, without having to hold Soulseek in his head. +3. The platform agent reports: *"Soulseek is up, give it a try, here is how to test it."* +4. The owner restarts, enters credentials, confirms it works, and the agent pushes. +5. The owner pulls on the MacBook and tells the **mobile agent** — which already knows the mobile + infrastructure — "create me a Soulseek app based on everything the platform has today." It works. + +**So the pattern is proven by human execution.** What is being automated is not "can agents collaborate" +— it is the *bridging role*, which the owner currently performs and describes as: *stressful, a lot to +keep in my head*, though enjoyable and exciting. + +### 1.10 The midnight scenario — the shape of the target + +> It's midnight, I create a dashboard with **four chat panels that are aware of each other**: you are the +> front end, you are the platform backend, you are the mobile app, and you are the **code reviewer** — +> able to coordinate, check one against the other, and say to the front end "change this", to the backend +> "this is not according to spec", to mobile "maybe change this" — and in the end be **responsible for +> the joining of everything, which is currently the work that I'm doing.** + +The owner's own framing: *a holy grail by its nature doesn't exist — but I really think we can get +there.* + +Structural requirements this adds, beyond the two-panel PoC: + +- **N panels, not two.** The handshake generalises to a roster. +- **Panels have roles/identities** ("you are the front end"), not just sessions. +- **Panels are aware of each other** — an agent must be able to enumerate its peers. +- **Panels span repositories** — platform and `monorepo-mobile` are different repos with different + remotes. +- **The fourth role is different in kind from the first three.** Roles 1–3 are *do the work*, and are + already proven by the manual flow. Role 4 is *hold the whole picture and judge* — the role the owner + performs today with human judgement. See §5 for why this is flagged as the research risk rather than + an engineering task. + +### 1.11 Do not design for the examples — the owner's counterpoints + +Recorded because every one of these is a correction of *my* over-constraining, and the same mistake will +be easy to repeat later. + +- **The Soulseek flow is one example, not the specification.** Other workflows will exist; some need only + two agents. *"This coordination is the point I want to ultimately reach."* +- **Roles are malleable.** Not every run involves four agents, and not with those roles. Fixing + "front end / backend / mobile / reviewer" into the design would be inventing a constraint the owner + does not have. +- **There is no paradigm.** *"It's whatever we want it to be."* +- **The owner's current needs are not the end state.** He has a day job unrelated to mobile that would + benefit from the same coordination. Designing narrowly around platform+mobile development is a trap. +- **Cross-machine is NOT the hard problem, and I was wrong to raise it as a fork.** The owner has already + solved it at small scale: a second Claude on the MacBook with a 15-minute timer pulling the latest + platform changes and replicating them for the mobile apps. Git hooks or cron do the same. + *"That's the least painful point of all this."* + +**The painful point, in the owner's words:** *panel communication inside a single web page, or a single +workspace, on our platform Web UI.* That is the problem to solve. Everything else is downstream. + +This yields a natural two-tier split, which the design should respect rather than unify: + +| tier | mechanism | status | +|---|---|---| +| Agents in **one workspace** | direct, in-page, turn-boundary messaging | **the hard part — this is the work** | +| Agents across **machines** | the git repo itself, polled on a timer / hook / cron | already solved, cheap, not our problem | + +### 1.12 The minimal protocol — and why the PoC is not a toy + +The owner's own reduction of what agents need to say to each other: + +> I finished this, you can check the latest changes and start building your part, and tell me when you're +> done so I can continue with my part. + +Compare it to the restart handshake: + +> I finished my output, you can restart the server, and tell me when you're done so I can continue. + +**These are the same protocol with a different payload.** Both are: *declare turn-end → hand off → +await the peer's completion → resume.* The restart PoC is therefore not a toy standing in for the real +thing; it is the real protocol, exercised on the smallest possible payload. + +The design consequence: **build the primitive general and keep the roles as configuration.** A named, +addressable agent that can send and receive at turn boundaries is the whole mechanism. "You are the +reviewer" is then a prompt, not a feature. That is how "there is no paradigm" becomes buildable instead +of unbuildable. + +### 1.13 The crux — the human authors the workflow; agents only execute and hand off + +**This is the single most important constraint in the document.** It is what separates this from the +multi-agent designs that fail, and it should be defended against "improvement" later. + +The owner explicitly **rejects emergent self-organisation**: + +> I don't expect to come and start a new dashboard, open four chat panels and do a small prompt that says +> "implement Soulseek" and each one knows what to do and what to communicate. **No.** + +What he expects instead: + +> I'm going to tell one: you're the front end developer. You're the back end developer — you're going to +> start by reading the docs from Soulseek, you're going to write a specification and create the API. When +> you're done, pass that work to the front end developer. And **the front end developer is the front end +> developer because I tell him that he is**, and he is expecting work from Agent 2. And so on. + +> Every dashboard will hopefully be something **not long lived**, but it will always require an **initial +> human setup**: setting the rules, creating the proper workflow, letting each agent know what they're +> about and what they should expect and when they should start doing their own thing. + +**What this means for what we build — the scope collapses dramatically:** + +- **No role registry. No orchestration engine. No planner. No task allocator.** None of these are wanted, + and building any of them would be inventing a paradigm the owner explicitly does not have. +- The **workflow graph lives in the prompts**, authored by the human at dashboard setup. +- The system's entire job is: give each agent a **stable, addressable identity**, and **deliver messages + between them at turn boundaries**. That is it. +- The failure mode this avoids is the one that kills most multi-agent systems: agents deciding *what* to + do and *who* should do it. Here, the human decides both, up front, once. + +**One consequence worth stating** (observation, not a decision taken): if roles are prompts, then +*addressing* must still resolve. "Pass that work to the front end developer" needs a destination. The +consistent answer is that the **human names each panel at setup** and tells each agent the names of its +peers — so addressing is a string the human chose, and the system merely routes it. A system-maintained +roster of roles would re-import the paradigm through the back door. + +**Also note:** *not long lived* lowers the persistence bar for a dashboard's workflow configuration — +but **not** for panel identity, which must still survive a reload for the whole PoC to work (§1.5, P3). + +### 1.14 The charter — and what is explicitly *not* mine + +**Owner's ruling on the shared-working-tree challenge (my push-back #2). Accepted, not to be +re-litigated.** + +- It has been working in practice: three agents at a time on the platform, and *the way the platform was + modularised means they don't step on each other's toes ~90% of the time.* Nothing is or will be + perfect. +- Worktrees, branches, everything-on-master: **not the focus.** The owner has ~20 years professional + experience, has never used a git worktree, and is willing to adopt one when it becomes necessary. +- Explicit division of labour, verbatim: *"that's my problem as a software engineer, as an architect, to + solve."* + +So git isolation is **owner-owned, deliberately deferred, and not a work item here.** It is recorded so +it is not lost, not so it gets picked up. Raising it once was welcomed; raising it again is noise. + +**Important clarification from the owner — this is not a narrowing of the push-back instruction (§1.8):** + +> Don't take what I said as a restriction on you to push back on things that you think might come up that +> are maybe not directly related with your particular mission. I just want you to understand that I *do* +> know what I'm doing — I've been through all those things my whole career. Those stones in my shoe are +> mine to bear, not yours. + +The working rule that follows: **raise anything, once, plainly — including things outside the charter.** +Then the owner rules on it, and the ruling is respected without re-litigation. Ownership of a problem is +settled by the ruling; the right to raise the next one is not affected. + +**My charter, in the owner's words:** + +> Imagine, architect and build a proper **communication system between panels running on the same page**. +> That's it. + +And the expectations around it: + +- **Learn to walk first.** The owner does not expect that the night after this works he creates a + dashboard with four windows and builds a project. The *practice* of using it will be perfected over + time, separately from the mechanism. +- **This mission will take some time to reach an initial state.** It is not a quick change. + +**The problems the owner explicitly wants thought about and documented** — these are the real design +work, and they come *after* the basics are proven: + +1. Does the system survive a **server restart**? +2. Does it survive a **page refresh**? +3. **Should state be stored in the database?** — i.e. what part of the coordination is durable, and what + is legitimately in-memory and disposable. + +### 1.15 Recon: what the chat hardening already gives us (2026-08-07) + +Survey of the chat/agent commits since 2026-08-01, read from diffs and source. **The foundation is much +further along than assumed.** Load-bearing findings: + +**The injection channel already exists.** `sidecar.spawnClaudeStreaming({sessionKey, prompt, …})` called +on an *existing* `sessionKey` does **not** spawn anything — it pushes a user message onto the live input +queue (`claude-manager.ts:380-391` → `pushTurn` → `input.push`). **No browser involved.** Existing +callers: `websocket.ts:354`, `agent-runner.ts:187`, `pipeline-executor.ts:238`. This is how one agent +delivers a message to another. + +**The turn-boundary signal already exists.** `result` is the explicit terminal event +(`chat/types.ts:143-153`, emitted `stream-parser.ts:144`), and `onTurnComplete(hadToolCalls)` is already +a public option on `useChat` (`useChat.ts:28`, fired at `:286-314`). Terminal set is +**`result` | `error` | `stopped` | `cut-off`**. Note: the *session outlives the turn* — `task:started` / +`task:notification` arrive **after** `result`, so "turn ended" ≠ "agent idle". + +**Durable, cursor-addressed log:** `chat_session_events` — global monotonic `bigserial` cursor, +per-session index, `prevSeq` continuity chain, at-least-once replay from a client-held cursor. +**Caveat: 7-day retention** (`api/chat/retention.ts`) — a replay buffer, not an archive. + +**Peer-restart notification:** `onClaudeSidecarStarted` (`sidecar-registry.ts:69-91`) — keyed off the +agent *registering*, not disconnecting. **Liveness oracle:** `claude:is-generating`, answered only by +the process that owns the session, **failing toward alive**. + +**Principles this codebase has already paid for — adopt, don't re-derive:** + +1. *Never route the durability guarantee over the link expected to break.* The agent writes to + `chat_session_events` itself, then notifies; officer relays. Write durable, then notify. +2. *Infer liveness from the birth of the new process, not the death of the socket* — socket death fires + on the innocent case (`pm2 restart officer`). +3. *An availability check must fail toward the less-alarming answer.* +4. *Classify by recoverability, not severity* — `cut-off` (seam + Retry) is a different object from + `error` (red bubble). +5. *An id that never crosses the process boundary is not an address* (`47d03de`). +6. *Disambiguate at the only site holding the extra bit*, and set the flag **before** the await that can + race it. + +**⚠ Flagged for the chat owner — NOT mine to fix (§1.14 rule).** `sessionKey` (officer's uuid, the key +for `chat_session_events`) and `claudeSessionId` (the transcript uuid, the URL permalink) reportedly only +converge once a conversation has been resumed at least once — so an event appended by `sessionKey` may +not be found by a session reopened under the transcript uuid. Reported from +`docs/chat-ui-walkthrough.md:589-593`, not independently tested. **This sits directly on the addressing +path for agent-to-agent messaging.** To be written up in `COMMS/` and handed off. + +### 1.16 The real optimisation target: unattended continuity, not parallelism + +**Correcting a wrong assumption of mine.** The owner does *not* want three or four agents running flat +out at once: + +> I don't expect to have three or four agents running at the same time like crazy. **What I want is to be +> able to sleep at night.** + +The goal is **progress without a human present**, not throughput. Consequences for the design, and they +are large: + +- **Latency is nearly free.** Seconds or minutes between handoffs is fine. Do not optimise for speed. +- **Concurrency is not the point.** Sequential handoff — A works, hands to B, B works, hands back — is a + perfectly good and probably preferred shape. Do not build for contention that will not occur. +- **Durability is everything.** A handoff that fires at 03:00 must survive whatever the environment does + between then and morning. + +**And the consequence that dominates the architecture — flagged for the owner to confirm (§5):** if the +owner is *asleep*, **the dashboard page is closed.** A handoff must therefore work with **no browser +open**. That rules out every browser-resident mechanism — `usePanelChannel`, React state, anything in the +document — not on elegance grounds but because the document will not exist when the message is sent. + +This does not contradict the owner's framing of the problem as *"panel communication inside a single web +page"*; it refines it. **The panels are the view; the mechanism must live server-side.** The page is how +a human watches and steers a conversation that continues without it — which is also precisely what +§1.15 shows the chat system was rebuilt to support (durable event log, cursor replay, agent-as-writer, +session outliving the socket). + +### 1.17 The restart payload is temporary — the protocol is not + +> The restart thing is giving me pain right now. Pretty soon that won't be a problem, because I won't +> have the necessity of editing the platform in real time from the platform as I'm doing today. + +Further confirmation that the PoC is **scaffolding**: the *payload* is disposable, the *protocol* is the +deliverable. Reinforces §1.12 — build message passing, not a restart-negotiation feature. If the restart +case disappeared tomorrow, nothing built should need to be deleted. + +### 1.18 Ruling on push-back #3 (agents reviewing agents) + +Same ruling as §1.14: **not our problem, not related to the mission.** The owner's framing — *that is +assuming the owner is dumb, which is important sometimes, but not for this mission.* + +Correct, and worth stating why so the boundary is understood rather than merely obeyed: **the quality of +an agent's review is a *usage* concern, downstream of the mechanism.** Whether the reviewer is any good +is a property of the prompt the human wrote, not of the transport. The mechanism is the postal service; +it is not accountable for what is in the envelopes. + +--- + +## 2. What it implies structurally + +Derived from §1. These are the constraints the design must satisfy. + +**Build exactly this:** + +1. **A stable, addressable identity per chat panel**, persisted, surviving reload. Panel id is the + natural key (§1.5, P3) — subject to the `movePanel` hazard. +2. **Message passing between named sessions at turn boundaries.** Messages carry *content* (a handoff of + work), not just signals (§1.12, and the owner's escalation: work handoff is *the whole crux*). +3. **Loud failure** when a message is dropped, a peer does not exist, or a handoff never lands (§1.8 as + refined; already a value in this codebase — `9eb8fa1`). + +**Do not build:** + +- No role registry, orchestration engine, planner, or task allocator (§1.13). +- No restart-negotiation feature — restart is a *payload* (§1.17). +- No git, repo, branch, build, or test awareness. **No knowledge of software at all** (§1.11 + the + domain-agnosticism constraint): the mechanism must be as ignorant of the work as a postal service is + of what is in the envelope. +- No guardrails against the operator (§1.8 clarification). +- No worktree/branch isolation — owner-owned (§1.14). + +**Hard constraints:** + +- **Turn-boundary only.** The safety property comes from negotiation, not robustness (§1.6). +- **Must work with no browser open.** The owner's goal is to sleep; the page will be closed. The + mechanism is server-side; panels are the view (§1.16). *Pending owner confirmation — see §5.* +- **N-way from day one.** Parallelism is not the current target but must not be foreclosed — no "the + other agent" singular anywhere, no single-writer ordering assumptions (§1.16 correction). +- **Durability over latency.** Seconds or minutes between handoffs is fine; a lost 03:00 handoff is not. +- **Human authors the workflow.** Always a human orchestrator at the top (§1.9, §1.13). + +## 3. What it unlocks — the nice-to-haves + +Not the objective. Recorded because each falls out at little or no extra cost, which is evidence the +primitive is the right one. + +- **Unblocking a stalled agent** (§1.6). An agent waiting on a human at a turn boundary can be woken by + a peer. Removes the owner's physical presence as a hard dependency for progress. +- **Unattended overnight progress** — the owner's stated wish to sleep (§1.16). +- **Automatic handoff of cross-domain findings** — e.g. this document's own §1.15 chat defect, which the + owner must currently carry by hand to the chat agent (§1.14). The rule and the mission are the same + shape. +- **The software factory** (§1.7) — several specialised agents with quality gates, as *usage* built on + the primitive rather than as features of it. +- **Non-software domains entirely**, and other users with unrelated goals. +- **True parallelism**, later — *"the literal definition of heaven on earth."* + +## 4. Constraints and rules laid down + +*(ground rules stated by the owner for this body of work, verbatim in substance)* + +- Nothing is started — including trivial fixes — until the picture is complete and played back to the + owner, and the owner has confirmed it is correct. +- This document is kept live *during* the conversation, not written up afterwards. +- Re-ranking the existing todo waits until the conversation is finished, and is then reflected both here + and in the documents that already exist. + +### 4.1 Explicitly de-scoped — not wrong, just not now + +Stated by the owner before the objective itself, and it is a *priority* judgement, not a correctness +one. These are acknowledged as poor architecture and are nonetheless **not to be worked on**: + +- **Everything downstream of the file browser at `/files`** — the ephemeral-panel machinery + (`ephemeral` prop, `useFileViewerPanels`, the search-param-driven viewer/player/side-chat that opens + beside the browser without entering your saved layout). The owner's words: *horrible architecture*, + and *everything is working as much as I need it*. +- The query-string-driven sub-panel approach generally. + +The rule that follows: **do not open these as work items, and do not let a fix wander into them.** If +one of them is genuinely blocking the objective, that is a finding to raise with the owner — not a +licence to start. Some of them will likely improve *inadvertently*, as a side effect of work done for +the objective, and that is the expected and acceptable way for them to get better. + +This section is a live list. Anything else the owner de-scopes gets added here rather than being +silently dropped from the todo — a de-scoped item stays visible, it just stops being ranked. + +## 5. Open questions + +Not to be guessed at. Ranked by how much they would change the design. + +**Q1 — RESOLVED by the owner, 2026-08-07. Server-side mechanism; the page is a view.** + +> Ultimately I don't care if the web page is open or closed. I care that if I come back and open a +> specific dashboard that was created for a specific mission — one Claude on one side and another on the +> other — whatever is happening server side, I load that page and see the continuation or the result of +> it. Everything is managed by the Claude architecture: session, saving, etc. + +So the dashboard is a **window onto server-side work already in progress**, never the place the work +lives. This is settled and is a hard constraint (§2). + +**Q2 — RESOLVED by reading the code, 2026-08-07. Let sessions be reaped; resume on delivery. No +heartbeat.** + +The chain, verified: + +- Idle GC is real: `IDLE_TIMEOUT_MS = 30 * 60 * 1000` (`claude-manager.ts:39`); `armIdle` (`:225-236`) + calls `killClaudeSession` after 30 idle minutes, sparing only a session that is generating or has + pending tasks. +- **But `killClaudeSession` (`:411-427`) does not clear the resume pointer.** It aborts the query, closes + the input queue and drops the in-memory entry — and deliberately does *not* call + `clearClaudeSession`. That is a separate function (`clearSession`, `:430`) on the explicit-disconnect + path. +- The `sessionKey → claudeSessionId` map is **write-through to disk** (`state.ts:86-102`, at + `DATA_PATH//sidecar/claude-state.json`), specifically so it survives a crash or SIGKILL — + commit `f4be4fd`. +- On a fresh spawn, `createSession` reads it back: `resumeId = getClaudeSession(sessionKey) ?? + params.resumeSessionId` (`:258`), passed as `resume` to the query (`:295`). + +**Therefore `spawnClaudeStreaming` on a reaped `sessionKey` transparently re-creates the session with +`resume: `, context intact from the on-disk transcript.** This is the good answer, and +it is better than a heartbeat on every axis: + +- **Zero cost between handoffs.** A dormant panel holds no process, no memory, no query. Ten dashboards + left open overnight cost nothing. +- **Survives everything** — idle GC, `pm2 restart officer`, an agent-sidecar restart, a crash, a SIGKILL. + A heartbeat survives none of those; it would only paper over the idle timer while remaining fragile to + the cases that actually happen. +- **Costs only latency**, which §2 already declares free. + +Design consequence: **a panel is a pointer to a transcript, not a held resource.** The smallest possible +durable object. Delivery is "resume that transcript and push a turn." + +⚠ **The one hazard to respect:** the explicit `disconnect` path *does* call `clearClaudeSession`, which +destroys the resume pointer and orphans the transcript. Coordination must never ride that path, and +whatever closes a panel must not trigger it. + +**Q3 — RESOLVED by the owner, 2026-08-07: *"Yes, we can do that. I name them all."*** The human assigns +each panel a name at setup; the system routes a string the human chose and knows nothing about its +meaning. **The name is the address; the panel id is merely where it currently lives** — which also +disarms the `movePanel` hazard (§1.5, P3), since dragging changes position, not identity. + +**Q6 — RESOLVED, and downgraded from blocker to report-only.** The owner's answer to *how a panel +acquires its Claude session id*: + +> We can wait for the first conversation with a certain agent to start and get the first output, so we +> get the session id from Claude and add it to our session key. Or basically we **fire up each session +> with an initial introduction prompt** — so when I'm creating the dashboard I'm saying "this is front +> end, this is back end", and that introduction can be our first message to Claude, sent automatically on +> dashboard creation or session creation. + +**Adopt the second.** It is strictly better, because it collapses two problems into one act: the role +prompt the human must write anyway *is* the message that brings the session into existence. Consequences: + +- The **address book is fully populated at dashboard-creation time** — no lazy state, no "panel exists + but has no session yet" hole, no first-handoff race. +- Every panel has a real, resumable transcript from the moment it is created. +- It is not a feature: it is the human's setup prose, delivered. + +**And this removes the `sessionKey` / `claudeSessionId` divergence from our critical path.** Our address +book is `panel name → our sessionKey`; the sidecar privately owns `sessionKey → claudeSessionId` +(`state.ts`, write-through) and resolves it on resume (§5 Q2). **We never address by `claudeSessionId`.** +The reported divergence therefore affects the chat UI's permalink path, not coordination. + +Still to be written up for the chat owner per §1.14 — it is a real reported defect and reporting it is +the rule — but it **does not block this work.** + +**Q4 — RESOLVED by the owner, 2026-08-07. The payload is opaque text.** + +> What the receiving agent will see will be a resulting `prompt.md` kind of thing, which the previous +> agent will be instructed to write at the end of its work, having in mind to whom that prompt is going +> to be delivered. **For proof of concept it could just be a dot character.** + +So: the sending agent *composes* the message; the system carries it and does not parse it. Same rule as +roles — semantics in the prose, mechanism dumb. **PoC success criterion collapses to: did a turn land in +the other panel.** A single `.` is a sufficient payload to prove the mechanism. + +**Q5 — RESOLVED. Coordination gets its own record; it does not ride `chat_session_events`.** See §5.1. +The 7-day retention question is therefore moot for coordination state. + +**Q6 — `sessionKey` vs `claudeSessionId` divergence** (§1.15). Reported, not independently tested. Sits +on the addressing path. **Chat-owned — to be written up in `COMMS/` and handed off, not fixed here.** + +### 5.1 The ledger — wanted, and explicitly **version 2** + +Owner, 2026-08-07: + +> It would be nice to have the whole flow — and I'm not talking about all the outputs, just **who did +> what, who handed off what to whom**. Recorded as a session in the database, **completely independent +> from what Claude records itself.** Every dashboard would be a session id, with a clear recording of who +> did what, who passed what prompt to whom. Just the basics — I don't need the whole output, Claude is +> already recording that. So the proper structure would be a collection of Claude session ids and the +> sequence that worked from start to finish, what were the prompts passed from one to another. +> **But this is something for version 2.** + +Shape: one row per *dashboard run*, holding the roster of Claude session ids and an ordered list of +handoffs (from, to, prompt, timestamp). Deliberately **not** an output log — Claude's own transcripts and +`chat_session_events` already hold the content, and duplicating them is the mistake to avoid. + +**Do not build this in v1.** But do not preclude it either: v1 must emit enough that the ledger is purely +*additive* later. + +### 5.2 The distinction that keeps v1 small: address book vs ledger + +These are two different things and conflating them would inflate v1 into v2: + +| | what it is | when | +|---|---|---| +| **Address book** | the durable mapping *panel → session*, so a message can be delivered at all | **v1 — required.** Without it there is no delivery. | +| **Ledger** | the durable *history* of who handed what to whom | **v2 — deferred** (§5.1). | + +v1 needs the address book and nothing more. Provenance recorded in v1 should be the minimum that makes a +handoff *visible and its failure loud* (§2), not a history feature. + +## 6. How the found defects map onto the path + +*(deferred — this is the re-rank, and it is explicitly not to be started yet)* diff --git a/docs/workspace-panel-todo.md b/docs/workspace-panel-todo.md index 2f4906d9..e9a491e0 100644 --- a/docs/workspace-panel-todo.md +++ b/docs/workspace-panel-todo.md @@ -7,6 +7,10 @@ Move anything fully settled to §7. **How the framework actually works is documented separately, in `workspace-panels.md`** — read that first if you are new to it. This file is only the defect list and the work queue. +**Why any of it matters is in `agent-coordination.md`** — the north star. This list is currently ordered +by defect severity; it is to be **re-ranked against that objective**, and until that happens an item's +position here says nothing about its importance to the goal. + Findings and full reasoning: `COMMS/workspace-panel-framework-analysis-2026-08-07.md`. Every `file:line` below was opened; DB claims were run against live `officer_dev`. Paths are relative to `platform/src/workspaces/officerdev/src/` unless they start with `servers/`, `databases/` or `sidecars/`. @@ -61,7 +65,7 @@ Both are two-line fixes, and without them you cannot tell whether any later fix (`databases/officer_db/src/queries/dashboards.ts:70`) → 23502. - [ ] **`HostTerminalWrapper` never strips the prefix, so it mints phantom dashboards.** *(found - 2026-08-08, latent — `dashboards` is still 0 rows)* + 2026-08-07, latent — `dashboards` is still 0 rows)* `apps/Terminal/HostTerminalWrapper.tsx:12` is `` `ws-host-terminals-${dashboardId}` `` with **no regex**, while its sibling `TerminalWrapper.tsx:13-14` correctly matches `^ws-layout-(.+)$` first. So the key becomes `ws-host-terminals-ws-layout-` or `ws-host-terminals-screens/terminal`, the @@ -327,7 +331,7 @@ All the same bug: an app guessing "am I being closed?" from an unmount, or payin (`frontend.tsx:15-21`) with no per-route scoping, and `reset` is called nowhere. Authority: `docs/navigation-audit.md`. -### 5.9 The context has grown an app-config section — *(found 2026-08-08)* +### 5.9 The context has grown an app-config section — *(found 2026-08-07)* `WorkspaceContext` is 18 fields, of which the framework itself reads none of the first six. Apps never touch the framework half, so the abstraction holds in one direction; the leak is entirely outbound. @@ -354,7 +358,7 @@ touch the framework half, so the abstraction holds in one direction; the leak is inside a `DashboardPreview` fall through to the `createContext` defaults. Whatever survives the three items above should be constructed in one place, not twice by hand. -### 5.10 Channel hygiene — *(found 2026-08-08)* +### 5.10 Channel hygiene — *(found 2026-08-07)* Cheap to fix, and prerequisites for the 5.8 migration rather than alternatives to it. diff --git a/docs/workspace-panels.md b/docs/workspace-panels.md index 923869cd..bb0382d7 100644 --- a/docs/workspace-panels.md +++ b/docs/workspace-panels.md @@ -4,6 +4,10 @@ Orientation for anyone building a panel app or a dashboard route. This describes is**, not as it should be; the defect list and the work queue live in `workspace-panel-todo.md`, and the full reasoning behind them in `COMMS/workspace-panel-framework-analysis-2026-08-07.md`. +Where the framework is *going* — and why anything here is worth changing at all — is +**`agent-coordination.md`**: agents on one dashboard coordinating with each other instead of through +the human. Read that first if you are deciding what to build; read this one if you are deciding how. + Code: `src/workspaces/officerdev/src/components/Workspace/` — 12 files, ~1,700 lines, no tests. ---