Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
17 KiB
status, system, created, updated, owners
| status | system | created | updated | owners | |
|---|---|---|---|---|---|
| draft | cli | 2026-05-16 | 2026-09-09 |
|
CLI-Mode Task Parity (Kanban) Requirements
Overview
Anthropic has announced that agent-SDK / claude -p usage will draw from a paid API budget, while the interactive claude CLI continues to draw from the user's Pro/Max subscription quota. Today kandev drives Claude almost exclusively through the ACP bridge (@agentclientprotocol/claude-agent-acp), which is an SDK-mode integration. After the change, every kandev task run by a subscription user would burn API dollars they do not have.
Requirements
REQ-CLI-CLI-MODE-PARITY-001: CLI-Mode Task Parity (Kanban)
Intent: Anthropic has announced that agent-SDK / claude -p usage will draw from a paid API budget, while the interactive claude CLI continues to draw from the user's Pro/Max subscription quota. Today kandev drives Claude almost exclusively through the ACP bridge (@agentclientprotocol/claude-agent-acp), which is an SDK-mode integration. After the change, every kandev task run by a subscription user would burn API dollars they do not have.
Acceptance criteria
- AC-CLI-CLI-MODE-PARITY-001.1: GIVEN a Pi profile with
cli_passthrough: trueand apiexecutable on the Kandev processPATHthat passespi --version - AC-CLI-CLI-MODE-PARITY-001.2: WHEN Kandev starts its passthrough PTY
- AC-CLI-CLI-MODE-PARITY-001.3: THEN the command starts with
pi - AC-CLI-CLI-MODE-PARITY-001.4: AND Kandev does not launch
pi-acpornpxas the passthrough process - AC-CLI-CLI-MODE-PARITY-001.5: GIVEN a Pi profile with
cli_passthrough: false - AC-CLI-CLI-MODE-PARITY-001.6: WHEN Kandev starts structured chat or one-shot inference
- AC-CLI-CLI-MODE-PARITY-001.7: THEN the command uses
npx --yes --prefer-offline pi-acp@<effective-version> - AC-CLI-CLI-MODE-PARITY-001.8: GIVEN Pi is unavailable on the Kandev host
Migrated source detail
Why
Anthropic has announced that agent-SDK / claude -p usage will draw from a paid API budget, while the interactive claude CLI continues to draw from the user's Pro/Max subscription quota. Today kandev drives Claude almost exclusively through the ACP bridge (@agentclientprotocol/claude-agent-acp), which is an SDK-mode integration. After the change, every kandev task run by a subscription user would burn API dollars they do not have.
Kandev already supports a per-profile CLI Passthrough mode that launches the agent CLI under a PTY (apps/backend/internal/agentctl/server/process/interactive_runner.go). Users who enable it keep their subscription billing — but the experience is bare:
- The task-create dialog disables the prompt textarea when a CLI agent is selected (legacy assumption: "you'll type your prompt directly into the terminal"). So the user can't even attach a description to a CLI-mode task.
- The task description that kandev would have sent through ACP is not delivered to the CLI. The user has to retype it.
- The chat compose box on a CLI-mode session does nothing — there is no path from kandev's UI into the agent's stdin for follow-up messages.
This spec brings CLI-passthrough mode up to feature parity with ACP for the kanban task-execution surface. Office (autonomous) mode is explicitly deferred.
What
ACP and passthrough commands remain distinct
A built-in agent that uses a separate ACP adapter and interactive CLI keeps those execution surfaces separate. Structured chat and one-shot inference use the ACP adapter. CLI passthrough launches the interactive CLI directly under the PTY; managed ACP runtime resolution does not replace that passthrough command.
For Pi, structured chat and inference use
npx --yes --prefer-offline pi-acp@<effective-version>, while CLI passthrough
launches the globally installed pi executable. The effective version comes
from the operator selection or the reviewed Kandev default, 0.0.33. Kandev's
Pi install action runs
npm install -g --ignore-scripts @earendil-works/pi-coding-agent, and agent
discovery treats a pi executable on the Kandev process PATH that passes its
non-interactive --version check as the installation signal.
Prompt allowed at task creation in CLI mode
The task-create dialog's prompt textarea is enabled when a CLI/passthrough-capable agent is selected. Users can write the prompt the same way they would for ACP. The control flow that today gates this off when cli_passthrough is true is removed.
Task-prompt injection (idle-based, agent-agnostic)
When a passthrough session starts for a task that has a description:
- Kandev launches the PTY as today.
- After the existing idle detector in
InteractiveRunnerfires for the first time (i.e. the CLI has stopped emitting output and is presumed to be at its input prompt), kandev writes the task description plus a configurable submit sequence to the PTY's stdin when the agent has noPromptFlag. - Agents that use
PromptFlag(headless one-shot mode) are unaffected — their prompt is already on the CLI before launch.PassthroughConfig.AutoInjectPromptremains in discovered metadata for compatibility, but the presence ofPromptFlagis the runtime delivery boundary.
No per-agent pattern matchers. The existing idle window is the only readiness signal. If an agent's CLI is unusual enough that an idle window misfires (writes a banner, then waits 5 seconds, then prompts), we make the idle window per-agent-configurable in PassthroughConfig (already exists as IdleTimeout). No new detection machinery.
For the Claude case: claude_acp.go retains AutoInjectPrompt: true as compatibility metadata and uses a 150 ms SubmitDelay. The prompt body uses bracketed-paste framing, then the submit carriage return is sent as a separate delayed write. This keeps the submit byte out of the Ink paste burst. Custom terminal agents can set DisableBracketedPaste: true when they do not accept bracketed-paste delimiters; Kandev then uses bounded, paced unframed writes. The pacing is an empirical fallback because PTY writes do not provide a receipt from the TUI. Other passthrough-capable agents without a PromptFlag use the same idle-based stdin route.
Follow-up prompts via PTY stdin
The orchestrator's PromptTask handler branches on IsPassthroughSession(sessionID) and writes the prompt text + SubmitSequence to the agent's PTY stdin instead of sending over ACP. This is the same path used by auto-injection and is reachable today from any caller that hits agent.prompt for a passthrough session.
PassthroughToolbar provides the UI surface for this route. Its chat control opens PassthroughComposerPanel, which wraps the shared ChatInputContainer so passthrough follow-ups use the same rich input, context chips, attachment control, and plan-mode toggle as ACP task chat. Submissions go through message.add for the current passthrough session and are written to PTY stdin by the backend.
Kandev MCP tools are wired for CLI mode
When a passthrough agent supports an MCP config flag, kandev generates a per-session MCP config file that points at the task's local agentctl MCP endpoint and launches the CLI with that config. The generated config exposes the same kandev tool server used by ACP sessions, scoped by the already-known KANDEV_TASK_ID / KANDEV_SESSION_ID context.
For the Claude case, the passthrough command includes --mcp-config <generated-file> with an mcpServers.kandev HTTP entry for the local agentctl /mcp endpoint.
Stop sends Ctrl-C
The orchestrator's CancelAgent handler branches on IsPassthroughSession(sessionID) and writes \x03 to the PTY's stdin instead of sending an ACP cancel. DB reconciliation still runs so the UI unsticks regardless of the write outcome.
Users can still press Ctrl-C directly inside the xterm terminal. A dedicated toolbar button that calls the same cancel route remains a follow-up.
Scenarios
Pi uses its interactive CLI in passthrough mode
- GIVEN a Pi profile with
cli_passthrough: trueand apiexecutable on the Kandev processPATHthat passespi --version - WHEN Kandev starts its passthrough PTY
- THEN the command starts with
pi - AND Kandev does not launch
pi-acpornpxas the passthrough process
Pi keeps its ACP adapter for structured execution
- GIVEN a Pi profile with
cli_passthrough: false - WHEN Kandev starts structured chat or one-shot inference
- THEN the command uses
npx --yes --prefer-offline pi-acp@<effective-version>
Pi installation provisions the passthrough executable
- GIVEN Pi is unavailable on the Kandev host
- WHEN the operator runs the Pi install action and rescans agents
- THEN Kandev runs
npm install -g --ignore-scripts @earendil-works/pi-coding-agent - AND discovery finds the installed
piexecutable after its version check
CLI-mode task can have a prompt
- GIVEN a Claude profile with
cli_passthrough: true - WHEN the user opens the task-create dialog and types in the prompt field
- THEN the prompt textarea accepts input (today it is disabled / hidden)
Prompt injection on fresh start
- GIVEN a CLI-mode task whose agent has no
PromptFlagand a non-empty description - WHEN the task starts
- THEN the PTY launches, the idle detector fires once after the CLI settles, and the task description is written to stdin followed by
SubmitSequence - AND the description appears in the terminal output
Resume does NOT re-inject
- GIVEN a CLI-mode task whose PTY exited (backend restart, crash) and is resumed via
--resume - WHEN the user reopens the terminal and the session resumes
- THEN no auto-injection occurs (the conversation is already in the agent's history)
Fresh-start fallback DOES inject
- GIVEN a CLI-mode task whose resume launch fast-failed and the fresh-start fallback ran (existing
attemptResumeFallbackpath) - THEN auto-injection runs against the new fresh session — because that fallback is functionally a new conversation
Follow-up prompt route is in place (UI surface follow-up)
- GIVEN a CLI-mode task running
- WHEN any caller invokes
PromptTaskfor the session (today: future kandev compose surface, integration tests) - THEN the text +
SubmitSequenceis written to the PTY - AND no ACP prompt is sent
Kandev MCP config is present for Claude passthrough
- GIVEN a Claude profile with
cli_passthrough: true - WHEN kandev starts or fresh-starts its passthrough command
- THEN the argv includes
--mcp-config <generated-file> - AND the generated file contains
mcpServers.kandevpointing at the local agentctl/mcpendpoint
Stop / cancel route is in place (UI surface follow-up)
- GIVEN a CLI-mode task running
- WHEN any caller invokes
CancelAgentfor the session - THEN
\x03is written to the PTY - AND DB reconciliation still completes so the session unsticks
Passthrough agent without PromptFlag
- GIVEN a passthrough-capable agent with no
PromptFlag, regardless of its compatibility metadata - WHEN a task starts
- THEN the task description is written to stdin after the first idle window
Kandev toolbar above the passthrough terminal
Where it appears
PassthroughToolbar (apps/web/components/task/passthrough-toolbar.tsx) wraps PassthroughTerminal with the same kandev controls that ACP sessions get from ChatStatusBar + ChatInputArea. It is mounted:
- Full-page task view:
dockview-shared.tsx(DockviewSharedContent) renders it for passthrough sessions in place of the chat input area; the dockview desktop layout intask-center-panel.tsxuses the same shared content. - Kanban Preview tab:
preview-session-tabs.tsxrendersPassthroughToolbarfor passthrough sessions in the side-peek surface, so the preview and full-page task views share the same terminal, status row, and composer behavior.
What it surfaces
The status row at the bottom of PassthroughToolbar contains, left to right:
PRStatusChip— PR open/merged/draft indicator for the task.PRMergedBanner— banner shown when the PR is merged.passthrough-proceed-next-stepbutton — moves the task to the next workflow step. Only shown whennextStepNameis non-null and the agent is not busy (sessionStateis notRUNNINGorSTARTING).ChatToggleButton— togglesPassthroughComposerPanelopen and closed. Showsvariant="default"when the composer is open or when there are pending review comments; shows a numeric chip (data-testid="passthrough-pending-count") when collapsed with pending comments.
Default focus contract
The terminal (PassthroughTerminal) renders above the status row and fills the remaining height. The compose box is collapsed by default so xterm retains keyboard focus for raw PTY interaction. The user opts in to the kandev composer explicitly via the Chat button.
Passthrough sessions have a dedicated configurable focus shortcut, FOCUS_PASSTHROUGH_INPUT (Focus CLI Chat Input, default Cmd/Ctrl+Shift+Y). It opens the composer and focuses the rich input when the user is not already typing in another input. The global ACP chat focus shortcut remains separate so pressing / while the terminal has focus continues to go to the PTY.
Composer assistance
PassthroughComposerPanel delegates authoring to ChatInputContainer with passthrough-specific command behavior:
- Slash command suggestions are disabled (
hasAgentCommands={false}) because passthrough sessions do not receive ACP-reported slash-command capabilities. A literal/typed in the composer remains text, and a/typed while the terminal has focus goes to the PTY. @file, prompt, plan, and task mentions use the shared rich-input lookup. Selected items render as the same context chips used by ACP chat.- The top chip bar shows selected context items. The bottom toolbar provides plan mode, attachment upload, and context-item add controls.
- Uploaded attachments are sent through
message.add; for passthrough sessions the backend materializes them under.kandev/attachments/<session>/...and appends their file paths to the PTY prompt. - File and prompt context items are expanded into
<kandev-system>context blocks before sending. Real filesystem-backed files are also sent ascontext_filesmetadata.
How the composer formats and sends pending review comments
When PassthroughComposerPanel submits:
usePendingDiffCommentsByFile(sessionId)provides comments grouped by file path.- Non-empty pending diff comments are formatted via
formatReviewCommentsAsMarkdownor the sharedbuildSubmitMessagepath, then included with the typed text. - Selected context files, prompt mentions, and task mentions are appended as system context blocks. Real context files are included in
context_files; uploaded attachments are included inattachments. - The combined payload is sent via
client.request("message.add", { task_id, session_id, content, attachments?, context_files? }, timeout). markCommentsSent(ids)is called only on WS success — a failed send leaves both the composer open (typed text preserved for retry) and the comments pending.- A
PendingCommentsBanner(data-testid="passthrough-pending-comments-banner") renders inside the open composer whenpendingCount > 0.
How Stop maps to Ctrl-C
Pressing Ctrl-C inside the passthrough terminal writes \x03 to PTY stdin. A future Stop affordance in PassthroughTerminal can call client.request("agent.cancel", { session_id }, 10_000) over WS; that route reaches Service.CancelAgent -> agentManager.CancelAgentBySessionID in apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go, which branches on execution.PassthroughProcessID != "" and writes \x03 to PTY stdin. DB reconciliation completes regardless of the write outcome so the UI unsticks.
Not included
TodoIndicator— ACP-only (requires message stream); not rendered inPassthroughToolbar.- Per-message model/mode picker — ACP-only feature; not applicable to PTY sessions.
Out of scope
- Office / autonomous-agent CLI mode — explicitly deferred. Office launches stay on ACP for now.
- Parsing PTY output into
task_messages(transcriber). The terminal panel shows live output; the chat transcript only shows user-sent text in CLI mode. - Billing-type / subscription-quota badge or DTO surface. That belongs to
subscription-usage.md. - Adding passthrough support to agents that don't have it (
Supported: false). - Migrating away from the current ACP bridge.
- Headless
-pmode for Claude (drains API credit; we deliberately do not use it).
Follow-ups
- Stop button overlay in
PassthroughTerminal. Small affordance that calls the existing cancel route. Today users press Ctrl-C inside xterm directly. - Agent-specific prompt-injection tuning — passthrough agents without a
PromptFlaguse the idle-based stdin route; adding agent-specific submit behavior requires verifying its submit sequence and idle behavior.
Open questions
- Submit sequence for Claude TUI:
"\r"is the expected default. If the TUI swallows the first\ras a focus-acquisition event we may need a tiny pre-write delay or"\r\n". Resolve empirically during dogfooding; the field is configurable per agent. - Idle window for Claude TUI: today's default (3s) should be enough — the TUI prints its banner and then sits at the prompt. Tune the per-agent
IdleTimeoutonly if real sessions misfire.