add D-77: drive Claude via stream-json; clide-owned unified session model
test / unit + widget + golden + a11y (push) Failing after 29s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 27s
test / unit + widget + golden + a11y (push) Failing after 29s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 27s
Accepted (phased). Pivot the Claude pane from the interactive tmux TUI to the stream-json control protocol: structured events instead of transcript tailing, permissions + AskUserQuestion handled natively via canUseTool, persistence via --resume. Claude's tmux agent-team mode is headless- incompatible, so teams become clide-orchestrated — N managed sessions coordinated by a clide-hosted MCP broker, with team-awareness injected via --append-system-prompt/--agents. Captures the unified-session-model upside: teammate / secondary tab / forked branch / inline subagent collapse into one primitive (a managed session rendered as a pane), with the sidebar as the cockpit. Amends D-41 (persistence) and evolves D-75 (rendering source). Phase 1 single-agent first; phase 2 the unified model. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2220,3 +2220,4 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by,
|
|||||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-157', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 20:52:47', '2026-05-23 20:52:47', '2026-05-23 20:52:47', NULL, 'b2b31ab5fe70fb5027773775b0945bd5', 1) ON CONFLICT(hash) DO NOTHING;
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-157', 'status', 'backlog', 'in_progress', NULL, '2026-05-23 20:52:47', '2026-05-23 20:52:47', '2026-05-23 20:52:47', NULL, 'b2b31ab5fe70fb5027773775b0945bd5', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-157', 'status', 'in_progress', 'done', NULL, '2026-05-23 20:57:07', '2026-05-23 20:57:07', '2026-05-23 20:57:07', NULL, 'ce114090979d31512a9aa09f1c9311a1', 1) ON CONFLICT(hash) DO NOTHING;
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-157', 'status', 'in_progress', 'done', NULL, '2026-05-23 20:57:07', '2026-05-23 20:57:07', '2026-05-23 20:57:07', NULL, 'ce114090979d31512a9aa09f1c9311a1', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-159', 'status', 'backlog', 'done', NULL, '2026-05-24 08:54:30', '2026-05-24 08:54:30', '2026-05-24 08:54:30', NULL, '478956f24d051f603aab2c7ceec328b2', 1) ON CONFLICT(hash) DO NOTHING;
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-159', 'status', 'backlog', 'done', NULL, '2026-05-24 08:54:30', '2026-05-24 08:54:30', '2026-05-24 08:54:30', NULL, '478956f24d051f603aab2c7ceec328b2', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-161', 'status', 'backlog', 'done', NULL, '2026-05-24 09:45:07', '2026-05-24 09:45:07', '2026-05-24 09:45:07', NULL, 'd7b99c3dff59fc5a70a7866567fbfe0f', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
|||||||
@@ -2682,3 +2682,12 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority,
|
|||||||
- Plays well with the existing Enter-submit / Shift+Enter-newline and the slash typeahead (Up/Down already drive the typeahead when it''s open — history nav only applies when the typeahead is closed).
|
- Plays well with the existing Enter-submit / Shift+Enter-newline and the slash typeahead (Up/Down already drive the typeahead when it''s open — history nav only applies when the typeahead is closed).
|
||||||
|
|
||||||
Implementation: a small history controller + caret-line detection in ClaudeComposer (EditableText); the Up/Down handling lives alongside the existing _onKey/Shortcuts. Acceptance: send a few prompts, Up walks back through them, Down returns through them to the restored draft; multiline prompts navigate line-by-line before crossing into history; widget tests for the bound conditions (first/last line) and draft stash/restore.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-24 09:42:44', '2026-05-24 09:42:44', NULL, 'c2d0a0a7c96dba20b920a2cc9d20d2e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
Implementation: a small history controller + caret-line detection in ClaudeComposer (EditableText); the Up/Down handling lives alongside the existing _onKey/Shortcuts. Acceptance: send a few prompts, Up walks back through them, Down returns through them to the restored draft; multiline prompts navigate line-by-line before crossing into history; widget tests for the bound conditions (first/last line) and draft stash/restore.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-24 09:42:44', '2026-05-24 09:42:44', NULL, 'c2d0a0a7c96dba20b920a2cc9d20d2e0', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
|
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-161', 'bug', 'T-132', 'Typed input reaches no live Claude after respawn — pane renders transcript only', 'Observed via make run (2026-05-24). After using /clear and /resume (which appear to work), regular typed messages produce no response and never appear in the conversation stream.
|
||||||
|
|
||||||
|
Diagnosis (live process inspection): the clide app (PID 77023) had ZERO child processes; ''tmux -L clide list-sessions'' reported ''no server running''. clide spawns pane processes (tmux client / claude) via posix_spawn as its own children, so no children + no -L clide server = NO live Claude backing the pane. The two stray ''claude'' procs were the user''s other sessions (an agent + a game-coding session), not clide''s. The conversation still renders because TranscriptReader tails the transcript FILE — so /clear and /resume only re-point the reader at a different file; there is no live session behind them.
|
||||||
|
|
||||||
|
Leading hypothesis: T-156''s _respawnWithSession (/clear, /resume) kills the tmux session (killing the last session also kills the -L clide server) and the subsequent _spawn does not leave a durable session — but clide may still have _usingTmux=true and routes input via tmux.sendMessage to a now-dead session, where set-buffer/paste-buffer/send-keys silently no-op (tmuxRunner ignores non-zero exit). Net: keystrokes vanish.
|
||||||
|
|
||||||
|
To localise: does a BRAND-NEW Claude tab (fresh spawn, no respawn) accept input and respond? If yes, the respawn path is the culprit; if no, the spawn path itself regressed. Also check the make-run console for pane.spawn errors / ''session exited''.
|
||||||
|
|
||||||
|
Acceptance: after /clear and /resume, a live tmux session (or no-tmux PTY) exists and typed input reaches Claude and streams a response; clide''s _usingTmux/_paneId/_sessionName stay consistent with the actual spawned process; regression test or a documented manual verification. Relates to T-156, T-147 (self-heal), D-41.', 'done', 'high', NULL, NULL, NULL, '2026-05-24 09:24:02', '2026-05-24 09:45:07', NULL, 'e7e053905caff3775e6aeb5eab4c0039', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ You might also want, project-permitting:
|
|||||||
- [D-74: IPC command schema is co-registered with the handler, validated at dispatch](decisions/architecture.md#d-74-ipc-command-schema-is-co-registered-with-the-handler-validated-at-dispatch) — _architecture_
|
- [D-74: IPC command schema is co-registered with the handler, validated at dispatch](decisions/architecture.md#d-74-ipc-command-schema-is-co-registered-with-the-handler-validated-at-dispatch) — _architecture_
|
||||||
- [D-75: Claude rendered natively from transcripts; terminal retained as general tool only](decisions/architecture.md#d-75-claude-rendered-natively-from-transcripts-terminal-retained-as-general-tool-only) — _architecture_
|
- [D-75: Claude rendered natively from transcripts; terminal retained as general tool only](decisions/architecture.md#d-75-claude-rendered-natively-from-transcripts-terminal-retained-as-general-tool-only) — _architecture_
|
||||||
- [D-76: ClaudeConfig — Claude's config surface is clide's app settings (builtin-owned, watched, probe-cached per version)](decisions/architecture.md#d-76-claudeconfig--claudes-config-surface-is-clides-app-settings-builtin-owned-watched-probe-cached-per-version) — _architecture_
|
- [D-76: ClaudeConfig — Claude's config surface is clide's app settings (builtin-owned, watched, probe-cached per version)](decisions/architecture.md#d-76-claudeconfig--claudes-config-surface-is-clides-app-settings-builtin-owned-watched-probe-cached-per-version) — _architecture_
|
||||||
|
- [D-77: Drive Claude via the stream-json control protocol; teams become a clide-owned coordination layer](decisions/architecture.md#d-77-drive-claude-via-the-stream-json-control-protocol-teams-become-a-clide-owned-coordination-layer) — _architecture_
|
||||||
|
|
||||||
## Open questions
|
## Open questions
|
||||||
|
|
||||||
|
|||||||
@@ -316,3 +316,19 @@ Core, rendering, IPC, kernel, panel manager.
|
|||||||
- **Raised by:** 2026-05-23 — slash-command typeahead work surfaced that clide has no command/skill enumeration today; user directed a top-level builtin-owned config object reused across surfaces, probe-cached per claude version.
|
- **Raised by:** 2026-05-23 — slash-command typeahead work surfaced that clide has no command/skill enumeration today; user directed a top-level builtin-owned config object reused across surfaces, probe-cached per claude version.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### D-77: Drive Claude via the stream-json control protocol; teams become a clide-owned coordination layer
|
||||||
|
- **Date:** 2026-05-24
|
||||||
|
- **Status:** accepted — phased (direction confirmed 2026-05-24; phase-1 single-agent first, phase-2 team-coordination scope refined per its own tickets)
|
||||||
|
- **Decision:** Drive the Claude pane through Claude Code's **stream-json control protocol** (`claude --input-format stream-json --output-format stream-json --verbose`, with `--permission-prompt-tool` / SDK-style `canUseTool` handling) instead of running an interactive TUI inside `tmux -L clide`. Conversation content comes from the structured event stream (assistant / tool_use / tool_result / result), not from tailing the transcript JSONL. Session continuity is via `--resume <session-id>` (persistence through the transcript files, not tmux). **Consequence:** Claude Code's built-in experimental **agent-team mode is tmux/interactive-only and not usable headless**, so clide stops *observing* a Claude-run tmux team and instead **orchestrates its own team** — N independent stream-json Claude processes that clide spawns, renders natively, and coordinates through a **clide-hosted MCP server** that supplies the messaging/task tooling Claude's tmux mode provided for free.
|
||||||
|
- **Rationale:** (a) **Closes the interactive-prompt gap** that the TUI model can't: permission requests and **AskUserQuestion** arrive as structured `canUseTool` callbacks (`toolName == 'AskUserQuestion'`, input carries the questions); clide renders a native prompt and returns the answer — exactly the native-rendering control clide wants, with no `capture-pane` text-scraping. (b) **Structured events** replace fragile transcript-file tailing and the "is this the active session?" guessing. (c) **`--resume` persistence** is documented to be identical to the TUI and retires the `--session-id`-already-in-use / session-fork class of bugs (T-156, T-161) and the tmux-session lifecycle entirely. (d) Owning team orchestration is more in line with "own the stack" than wrapping an undocumented, drift-prone tmux feature.
|
||||||
|
- **Team-awareness + messaging (the load-bearing design question):** Claude has no documented way for an independent session to *be* a teammate — the team config/`SendMessage`/task-list are undocumented and coupled to the tmux runtime. So clide manufactures team membership:
|
||||||
|
- **Awareness** — inject roster + role into each agent via `--append-system-prompt` (and/or `--agents <json>`): "you are `<name>`, a teammate on `<team>`; members are …; use the team tools to message them." The agent doesn't *know* it's solo; it's told it's a member and given tools that behave like membership.
|
||||||
|
- **Messaging/tasks** — clide hosts a small **MCP server** (attached to every agent via `--mcp-config`) exposing `send_message(to, text)`, `broadcast(text)`, `list_teammates()`, `inbox()` / a shared `claim_task`/`task_status`. clide is the **broker**: a message from agent A's tool call is delivered into agent B's next turn (as a user/tool message on B's stream-json stdin). The built-in `SendMessage` + auto-delivery + task list do **not** work outside tmux mode, so clide reimplements them — which means clide fully controls routing, ordering, and what the UI shows.
|
||||||
|
- **Orchestration** — there is no SDK/CLI "multi-agent controller" primitive; clide spawns and multiplexes the N processes itself. The existing team UI (T-140 tiles, T-141/T-157 sidebar) is re-pointed from "observe tmux team" to "render clide-managed agents"; the T-139 observer becomes an orchestrator.
|
||||||
|
- **What it unlocks (unified session model):** With clide owning spawn + I/O + the broker, four things that are distinct today collapse into one primitive — a **clide-managed Claude session rendered as a pane**: a teammate, a secondary tab, a forked branch, and an inline subagent become the same thing. Consequences: (a) **the sidebar becomes the cockpit** — the task list and inter-agent messages are local data clide owns, so the sidebar doesn't just display them, it lets the user act (reassign a task, inject/redirect a message, mute/spawn/show/hide an agent); (b) **panel-vs-inline dissolves** — every session is always live, and showing it as a pane is just a visibility toggle on the roster; (c) **fork-into-a-pane** — "branch this conversation" is simply spawning a managed session seeded from another's context (`--resume` + `--fork-session`) through the same plumbing, surfaced as a slash action or a sidebar button. This reframes phase 2 from "reimplement Claude's tmux teams" to "build the unified model the tmux teams only approximated," and is a primary argument *for* the pivot.
|
||||||
|
- **Cost / risk:** Large. It reworks the Claude input/render path (stream-json multiplexer + `canUseTool` prompt UI), replaces D-41's tmux persistence with `--resume`, and rebuilds teams as a clide-owned coordination layer + MCP broker — building messaging/task-sync that the tmux mode gave for free. Undocumented surfaces (team-awareness mechanism, `SendMessage` schema, AskUserQuestion-over-stream-json specifics) mean clide **reimplements rather than wraps**, accepting drift risk isolated behind the orchestrator + MCP server. The interactive terminal builtin remains for general shell use (unchanged). **Mitigation / phasing:** ship the **single-agent pivot first** (stream-json events + `canUseTool` permissions + AskUserQuestion UI + `--resume`), which alone fixes the prompt gap and the session-lifecycle bugs; treat **clide-owned teams** as a separate follow-on epic, decided on its own once the single-agent path is proven.
|
||||||
|
- **Cross-reference:** amends [D-41](#) (tmux-for-persistence → `--resume`; tmux retained only for the general terminal, not Claude), evolves [D-75](#d-75-claude-rendered-natively-from-transcripts-terminal-retained-as-general-tool-only) (native rendering kept, but sourced from the stream-json event stream rather than the transcript file), and supersedes the alternative "keep the tmux TUI + a `capture-pane`/`send-keys` prompt bridge" (rejected as fragile text-scraping that still can't give structured permissions). Relates to [D-1](#d-1-cli-first-not-mcp) (clide now also *hosts* an MCP server for inter-agent tooling), [D-76](#d-76-claudeconfig--claudes-config-surface-is-clides-app-settings-builtin-owned-watched-probe-cached-per-version).
|
||||||
|
- **Raised by:** 2026-05-24 — user testing found the UI cannot handle AskUserQuestion / permission prompts (they live in the TUI, never hit the transcript). Spike (docs + SDK) confirmed stream-json surfaces prompts via `canUseTool` but agent teams are tmux-only; user chose the stream-json direction, accepting that teams must become clide-orchestrated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user