1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
# FE handoff — todo task list surface
Courier this to `../dispatch-web` (cross-repo contract change; `lsp references` does
not span repos — ORCHESTRATOR §7). All changes are ADDITIVE — nothing existing breaks.
## What shipped (backend)
A per-conversation **task list** the AI model maintains via a `todo_write` tool. The
list is exposed to the frontend as a per-conversation **surface** (read-only). The
model creates/updates the list during a turn; the surface updates live so the FE can
render the current state.
- **`todo_write` tool** — the model passes the FULL list each call (replaces the
existing list). Returns the list as JSON. The tool description guides the model on
when to use it (3+ step tasks, planning, etc.).
- **State** — in-memory, per-conversation. No persistence (the list lives for the
process lifetime of the conversation).
- **No new wire types, no version bumps.** The todo surface uses the existing
`custom` surface field kind (`ui-contract` unchanged). The `TodoItem` type is
defined by the `todo` extension and carried in the surface payload — it is NOT
in `@dispatch/wire` or `@dispatch/transport-contract`.
## The surface
The `todo` extension contributes a per-conversation surface:
- **Surface id:** `"todo"`
- **Scope:** `"conversation"` (subscribe with the `conversationId`)
- **Region:** `"side"`
- **Title:** `"Tasks"`
- **One `custom` field**, `rendererId: "todo"`, `payload: TodoPayload`
```ts
interface TodoPayload {
todos: readonly TodoItem[];
}
interface TodoItem {
content: string;
status: "pending" | "in_progress" | "completed" | "cancelled";
}
```
- **Read-only** — no `invoke` actions. The model mutates the list via the
`todo_write` tool; the FE only renders.
- **Updates** on every `todo_write` call (subscriber-notify → full new spec with the
updated `todos` array).
- **Empty list** — an idle conversation (no todo list created yet, or the model
cleared it with an empty array) renders `todos: []`. Hide the panel when empty.
## What the FE needs to do
1. **Subscribe** to the `todo` surface per conversation (same pattern as
`message-queue` and `cache-warming` — `scope: "conversation"`, pass
`conversationId` on subscribe).
2. **Custom renderer** for `rendererId: "todo"` — render the `payload.todos` array
as a task list. Suggested UI:
- Each item shows `content` with a status indicator:
- `pending` — empty circle / checkbox
- `in_progress` — spinner / filled circle (highlight)
- `completed` — checkmark (strikethrough or dim the content)
- `cancelled` — X / dash (dim/strikethrough)
- Order is significant — items are in the order the model provided them (array
index = identity).
- Only one item should be `in_progress` at a time (the tool description enforces
this via guidance, not validation — but the model should comply).
3. **Live updates** — the surface pushes a new spec on every `todo_write` call. No
polling needed. Just re-render from the new `payload.todos`.
4. **Empty state** — when `todos` is `[]`, hide the panel (the model hasn't created
a list yet, or cleared it).
## No other integration points
- No new WS ops (no `chat.queue` equivalent — the model is the only writer).
- No new HTTP endpoints (the list is tool-driven, not API-driven).
- No new `AgentEvent` types (the list is not on the chat stream).
- No version bumps in `@dispatch/wire` or `@dispatch/transport-contract`.
## Notes
- **In-memory only** — the todo list does NOT persist across server restarts. If
the server restarts, the list is cleared. The model recreates it on the next
`todo_write` call. This mirrors the message-queue behavior.
- **Per-conversation** — each conversation has its own list. Switching conversations
means subscribing to a different `conversationId` and rendering that conversation's
list.
- **Model-driven** — the FE has no control over the list (read-only surface). The
model creates, updates, and clears items. The FE just displays the current state.
|