Context
Agent-daemon currently runs claude CLI in TUI mode inside tmux sessions, exposed via WebSocket for terminal clients (kanbanned TUI and browser). To support non-terminal clients (Telegram), we need a second mode where the claude process runs with --output-format stream-json --input-format stream-json, exposing a prompt-level API.
Terminology
- Session = the tmux session, tied to a worktree. Lives until explicitly killed (issue work is done).
DELETE /sessions/<id> kills the session.
- Mode = how the claude process runs inside that tmux session:
terminal (TUI) or structured (stream-json). Switching mode restarts the claude process, not the tmux session.
- Resume =
--resume {session_id} carries conversation history across claude process restarts within the same tmux session. Same cwd, same worktree, same session files — just different process flags.
Requirements
Dual-mode claude process
- The claude process inside a tmux session can run in either terminal mode (TUI, existing) or structured mode (stream-json)
- Mode is part of session state, visible in
GET /sessions response
- Switching mode: kill the current claude process, respawn with
--resume {session_id} and the appropriate flags, inside the same tmux session
- The tmux session and worktree are unaffected by mode switches
- Mutual exclusion: only one claude process per session at a time
Prompt API (structured mode only)
POST /sessions/<id>/prompt — send a text prompt, stream back JSON response
- Response streaming via SSE or WebSocket (TBD)
- Permission callbacks forwarded to the caller (for Telegram approval buttons)
Mode switching
POST /sessions/<id>/mode — switch between terminal and structured
- Uses
--resume to carry conversation history across mode switches
- Same tmux session, same cwd — resume should work naturally since session files stay in place
CLI flags for structured mode
Based on how claude-agent-sdk works internally:
claude --output-format stream-json \
--input-format stream-json \
--permission-prompt-tool stdio \
--resume {session_id}
The daemon reads/writes JSON on the process stdin/stdout instead of piping terminal I/O.
Session lifecycle (unchanged)
POST /sessions — creates tmux session + worktree + launches claude (TUI by default)
POST /sessions/<id>/mode — restarts claude process with different flags (NEW)
DELETE /sessions/<id> — kills tmux session entirely, optionally cleans up worktree (existing, unchanged)
Non-goals
- Replacing terminal mode — terminal clients (kanbanned TUI, browser) continue using the existing WebSocket terminal API
- Python SDK dependency — the daemon speaks the JSON protocol directly
Context
Agent-daemon currently runs claude CLI in TUI mode inside tmux sessions, exposed via WebSocket for terminal clients (kanbanned TUI and browser). To support non-terminal clients (Telegram), we need a second mode where the claude process runs with
--output-format stream-json --input-format stream-json, exposing a prompt-level API.Terminology
DELETE /sessions/<id>kills the session.terminal(TUI) orstructured(stream-json). Switching mode restarts the claude process, not the tmux session.--resume {session_id}carries conversation history across claude process restarts within the same tmux session. Same cwd, same worktree, same session files — just different process flags.Requirements
Dual-mode claude process
GET /sessionsresponse--resume {session_id}and the appropriate flags, inside the same tmux sessionPrompt API (structured mode only)
POST /sessions/<id>/prompt— send a text prompt, stream back JSON responseMode switching
POST /sessions/<id>/mode— switch betweenterminalandstructured--resumeto carry conversation history across mode switchesCLI flags for structured mode
Based on how claude-agent-sdk works internally:
The daemon reads/writes JSON on the process stdin/stdout instead of piping terminal I/O.
Session lifecycle (unchanged)
POST /sessions— creates tmux session + worktree + launches claude (TUI by default)POST /sessions/<id>/mode— restarts claude process with different flags (NEW)DELETE /sessions/<id>— kills tmux session entirely, optionally cleans up worktree (existing, unchanged)Non-goals