876 lines
55 KiB
Markdown
876 lines
55 KiB
Markdown
# 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 `<WorkspaceView>`, 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).
|
||
|
||
**Measured, 2026-08-07 — the durability claim above is no longer an argument.** Six tests run live
|
||
against the running system, deliberately looking for the break. Method and raw evidence:
|
||
`COMMS/handoff-durability-2026-08-07.md`.
|
||
|
||
| what was done to it mid-handoff | turn completed | narration durable |
|
||
|---|---|---|
|
||
| nothing (control) | ✅ | ✅ |
|
||
| `pm2 restart officer` | ✅ | ✅ |
|
||
| **`officer` stopped for 25 s** | ✅ | ✅ **6 events written while the server was down** |
|
||
| target's `claude` process SIGKILLed | ❌ lost | ⚠️ partial, with an explicit error row |
|
||
| next handoff to that same agent | ✅ | ✅ same Claude session resumed |
|
||
| handoff POSTed while `officer` is down | ❌ lost | ❌ no queue, no retry |
|
||
|
||
Three things follow, and they change how the outstanding work should be read:
|
||
|
||
- **The "no browser open" requirement is satisfied, and so is the harder one.** Not one of these runs had
|
||
a page open, and the middle row is the proof that officer is genuinely off the delivery path: the agent
|
||
sidecar committed the model's own words to Postgres during a 25-second server outage.
|
||
- **A restart costs a *turn*, not an *agent*.** After being killed mid-turn, the receiver resumed the
|
||
identical Claude session on the next handoff and volunteered which work had been lost. Continuity —
|
||
`sessionKey` minted once, write-through map on disk — does the job it was built for. That makes the
|
||
outstanding claude **stage 5** a smaller problem than its position on the list suggests.
|
||
- **The remaining hole is on the *sending* side.** A handoff POSTed while officer is down is refused and
|
||
dropped, and nothing in the introduction text tells the agent to retry — so the sender can believe it
|
||
handed off when it did not. That, not the receiving side, is where store-and-forward would earn its
|
||
keep.
|
||
|
||
One topology fact found while setting this up, worth stating here because it is the practical limit on
|
||
working unattended: **`officer-agent` is `sidecar/claude/user-instance.ts`, and every `claude` process on
|
||
the machine is its direct child.** `pm2 restart officer-agent` therefore kills every agent on every
|
||
dashboard at once, mid-turn. `CLAUDE.md` reassures that restarting *officer* is safe — it is, and that is
|
||
verified above — but is silent on this one.
|
||
|
||
### 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/<email>/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: <claudeSessionId>`, 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
|
||
|
||
*(the re-rank. Written 2026-08-07 after the MVP was built and proven running, so it is ranked against
|
||
what the mechanism turned out to need, not against what it was predicted to need. `workspace-panel-todo.md`
|
||
is ordered by defect severity; this section says which of those defects the **objective** actually cares
|
||
about. Where the two disagree, this section wins for prioritisation and the todo keeps the severity note.)*
|
||
|
||
### 6.1 The headline: most of the panel defect list is not on this path
|
||
|
||
That is the finding, and it follows directly from Q1 and §1.16. **The mechanism is server-side; the
|
||
panels are a window onto it.** An agent's identity, its session, its transcript and its inbox all live in
|
||
Postgres and on the sidecar's disk. So a panel can remount, re-render, lose its React state, be dragged
|
||
across the dashboard, or not be rendered at all — and the work continues. Whole sections of the todo that
|
||
rank high on severity rank near-zero here.
|
||
|
||
The corollary, and it is the useful half: the defects that *do* matter are almost all the same defect
|
||
wearing four hats — **a write that silently does not persist.** Panel identity is the one piece of
|
||
coordination state that lives in the layout jsonb rather than in a table of its own, so every silent
|
||
persistence failure in this list is now a path by which a panel forgets which agent it is.
|
||
|
||
### 6.2 Tier A — on the critical path
|
||
|
||
**A1. Stop swallowing persist failures.** (§1, third item — `state/src/useDashboardState.ts:46`,
|
||
`.catch(() => {})`.) *The single highest-value item in the whole list against this objective.* The
|
||
panel's agent name is written through this path. A swallowed 500 leaves the optimistic cache correct, so
|
||
the panel shows its name, answers to its name, and **forgets it on the next reload** — the failure is
|
||
invisible for exactly as long as the human is not looking, which is the entire window this project
|
||
exists to serve. §2 requires *loud failure*; this is the loudest silence in the codebase.
|
||
|
||
**A2. The PATCH dispatcher's missing `else`.** (§2, first item.) The server half of A1. `ws-layout-*` is
|
||
matched today so panel `config` does persist — verified, the demo dashboard round-tripped with
|
||
`config: {agentName: …}` intact — but a chain of `if (…) continue` with no fallback means the *next* key
|
||
family added for coordination is a silent no-op that returns 200. Add the 400.
|
||
|
||
**A3. Validate the layout on read, and fix the `'[]'` default.** (§4, items 2 and 3.) Panel `config` is
|
||
new unvalidated jsonb inside an already-unvalidated tree. `normalizeLayout` calling `.children.map` on a
|
||
stored `[]` throws, and with no error boundary (A4) that is a white screen where the dashboard should be.
|
||
|
||
**A4. Add an error boundary around `WorkspaceRenderer`.** (§4, item 1 — the repo has none, anywhere.)
|
||
Ranked here for one specific reason: this is the **last mile of the whole objective.** Work proceeds
|
||
overnight; the owner opens the dashboard at 07:00 to see it. A malformed row turning that into a white
|
||
screen loses the result at the only moment it is consumed. The work itself is safe — it is in
|
||
`chat_session_events` — which is exactly why the recovery path must offer "restore default layout"
|
||
rather than requiring SQL.
|
||
|
||
**A5. The resize-debounce items.** (§5.5, first two.) Both are lost-update bugs against the layout, and
|
||
the layout is now where a panel's identity lives. The 500 ms timer closing over a pre-drag tree does not
|
||
merely resurrect a deleted panel — it **re-writes an older `config`**, so a panel that was just named
|
||
reverts to anonymous. Concurrent same-key writes clobbering has the same effect from a window resize.
|
||
The prescribed fix (an updater-form `setValue` + cleanup) fixes identity loss as a consequence.
|
||
|
||
**A6. Test `layout-utils.ts`.** (§9.) Promoted from "cheapest high-value target" to critical path, and
|
||
the reason is mine to own: `e588524` put agent identity inside those pure functions —
|
||
`setContents`, `swapPanels`, `movePanel`, `setPanelConfig`, `collectPanelConfigs`, `newPanelFrom` — and
|
||
shipped them with **no tests**. A regression in `swapPanels` now silently swaps two agents' identities
|
||
between panels, which is worse than any layout glitch it could previously cause. 214 lines of pure
|
||
functions over a serialisable tree; there is no excuse.
|
||
|
||
**A7. Two windows must not disagree about the roster.** (§5.5, "the cache is never invalidated" —
|
||
`staleTime: Infinity`, no `invalidateQueries` anywhere, and every PATCH already returns a fresh state
|
||
blob the client throws away.) Q1 makes the dashboard *a window onto server-side work*. Two windows onto
|
||
the same work that permanently diverge, and neither told, is a direct contradiction of that. Cheap:
|
||
consume the response that is already being computed.
|
||
|
||
### 6.3 Tier B — needed for the surface, not for the mechanism
|
||
|
||
**B1. `dashboardId` is a bag whose format is parsed by its consumers.** (§5.9, third item.) Promote —
|
||
and note a hazard this document's own MVP introduced. The agent address book is keyed on `dashboardId`,
|
||
which is literally `workspace.key` (`WorkspaceView.tsx:163`) — so the demo dashboard's agents are stored
|
||
under `ws-layout-agent-mvp`. **Renaming a screen key, or changing that derivation, silently orphans
|
||
every agent row on that dashboard.** The rows survive, the sessions survive, and the panels can no longer
|
||
find them. The prescribed fix — give the context `{kind, id}` instead of the raw key — is now load-bearing
|
||
rather than tidy. Not Tier A only because it is stable today and the failure requires someone to change
|
||
the derivation.
|
||
|
||
**B2. Panel lifecycle — but the ranking inverts.** (§5.1.) Against the terminal-orphan objective this was
|
||
"the highest-value change here." Against *this* objective the priority is the opposite one: **closing a
|
||
chat panel must never destroy the agent.** §5 Q2 records the hazard precisely — the explicit `disconnect`
|
||
path calls `clearClaudeSession`, which destroys the `sessionKey → claudeSessionId` pointer and orphans the
|
||
transcript, whereas idle reaping deliberately does not. So what is wanted from `onClose` here is a
|
||
*guarantee that nothing rides that path*, not an eager cleanup hook. Build the hook for the terminal by
|
||
all means; do not let a chat panel be wired into it without deciding that question first. A panel is a
|
||
pointer, and closing a window should not delete what it points at.
|
||
|
||
**B3. `normalizeLayout` as framework, not convention.** (§5.4.) Matters for one consequence: a panel
|
||
whose appType is allow-listed but no longer in the registry renders, on a `locked` screen, as an
|
||
unrecoverable empty box. A chat panel in that state is a *visible* agent the human cannot reach — though
|
||
note its peers still can, because the mechanism does not go through the browser. Real, but a display
|
||
failure over a live agent rather than a lost one.
|
||
|
||
**B4. The mobile collapse decision.** (§6 of the todo, first item.) Genuinely undecided against this
|
||
objective, and worth putting to the owner rather than guessing: *"I want to be able to sleep at night"*
|
||
raises the obvious question of whether the 03:00 check-in happens on a phone. If yes, a user-created
|
||
dashboard rendering only its left column forever is a Tier A problem wearing a mobile hat. If the answer
|
||
is "I check on the laptop, and mobile web is being retired for the native app" — which is what
|
||
`CLAUDE.md` currently says — it stays Tier C. **One question, one answer, large ranking swing.**
|
||
|
||
### 6.4 Tier C — orthogonal to this objective, keep them ranked by severity
|
||
|
||
Not dismissals. These are real defects and several are severe; they simply do not touch agents
|
||
coordinating with each other, and should be scheduled on their own merits rather than pulled forward by
|
||
this project.
|
||
|
||
- **§1's Running Shells items and §5.1's pty follow-ups.** The terminal orphan leak is its own worthwhile
|
||
problem. It is not this one.
|
||
- **§3, multi-user correctness.** Ranks on its own timer (a second member creating a dashboard), which is
|
||
unrelated to this path.
|
||
- **§5.2, the remount table.** *The largest downgrade in this re-rank.* A remount used to threaten
|
||
whatever the panel was holding; a panel now holds nothing. A chat panel that remounts re-runs
|
||
`resume-cursor` from its stored cursor and replays the durable log — it costs latency, and §2 declares
|
||
latency free. Fix these for the interaction quality they are actually about; do not fix them for this.
|
||
- **§5.3, drag-to-move.** *The second-largest downgrade, and it was on the critical path when the north
|
||
star was written* (§1.5, P3: "dragging a panel would silently sever its session binding"). Two things
|
||
disarmed it. Q3 made the **name** the address and the panel id merely where it currently lives; and
|
||
`e588524` made `swapPanels`/`movePanel` carry `{appType, config}` as one unit, so the name travels with
|
||
the panel. `useAgentPanel` resolves by name and re-anchors the row's `panelId` afterwards. The
|
||
finish-or-delete decision is still open and still worth taking — it is ~180 lines of dead code — but it
|
||
is no longer gating anything.
|
||
- **§5.6 registry, §5.7 effect hygiene, §5.8 navigation, §5.10 channel hygiene, §8 dead code.** All
|
||
orthogonal. Two notes in passing: §5.10's observation that `preview:refresh` and `chat:active-session`
|
||
are written by `ChatPanelWrapper` and read by nobody survives the MVP rewrite unchanged — I kept both
|
||
writes rather than widen the diff. And §5.8 is not a prerequisite here; the mechanism never goes
|
||
through a channel, because it never goes through the browser at all.
|
||
|
||
### 6.5 What the re-rank did *not* find, and that is the result
|
||
|
||
No defect in `workspace-panel-todo.md` blocked building the MVP. It was built, and it ran unattended, on
|
||
the framework as it stands. The framework needed exactly one addition — per-panel config that survives a
|
||
move — and that is now `e588524`.
|
||
|
||
So the honest summary is: **this list is not the bottleneck, and it should stop being treated as the
|
||
prerequisite queue for the objective.** Tier A is seven items, five of which are the same "a write
|
||
silently did not land" defect, and all seven are small. Everything else in the list should be scheduled
|
||
for its own reasons, on its own timetable, by severity — which is what the file was already ordered by.
|