diff --git a/plans/long-running-command-side-agent.md b/plans/long-running-command-side-agent.md new file mode 100644 index 00000000..2d34f4db --- /dev/null +++ b/plans/long-running-command-side-agent.md @@ -0,0 +1,267 @@ +# Long-Running Command Side-Agent Plan + +## Goal + +When an Agent Mode shell command runs longer than three seconds, automatically open a side-agent chat that monitors the process, reports meaningful status, and feeds its observations back into the main agent conversation. + +The side agent should use the configured profile's lower-cost **coding/typing model** where appropriate. It must not silently rotate across arbitrary models. + +## Current architecture + +Relevant existing paths: + +- `app/src/ai/blocklist/action_model/execute/shell_command.rs` + - `ShellCommandExecutor` emits `ShellCommandExecutorEvent::ExecuteCommand` when an agent command starts. + - `action_result_future` polls/waits for command completion and snapshots. +- `app/src/terminal/view.rs` + - `handle_shell_command_executor_event` executes the command. + - It already schedules a delayed check using `LONG_RUNNING_COMMAND_DURATION_MS` and detects `block.is_active_and_long_running()`. + - It currently locks the input in Agent Mode after the command becomes long-running. +- `app/src/ai/blocklist/agent_view/controller.rs` + - `AgentViewController` enters fullscreen or inline Agent View. + - `AgentViewEntryOrigin::LongRunningCommand` already exists. +- `app/src/ai/blocklist/context_model.rs` + - Owns conversation selection and pending query state. +- `app/src/ai/blocklist/controller.rs` + - `BlocklistAIController::send_request_input` sends requests for an existing conversation. + - `RequestInput` carries both `model_id` and `coding_model_id`. +- `app/src/ai/blocklist/controller/response_stream.rs` + - Resolves provider configuration from a model ID. + - Retry/failure behavior is centralized here. +- `app/src/ai/agent/mod.rs` + - `AIAgentInput::UserQuery` supports `running_command: Option`. + - `RunningCommand` carries command text, block ID, terminal snapshot, cursor, and alt-screen state. +- `app/src/ai/agent/api/convert_to.rs` + - Converts local `AIAgentInput` into request API input. +- `app/src/ai/agent/api/convert_conversation.rs` + - Converts shell command snapshots/results back into agent context. + +## Product behavior + +### Trigger + +- Start a three-second timer when an Agent Mode command begins executing. +- If the command completes, is cancelled, or the block is no longer active before the timer fires, do nothing. +- If it is still active and long-running at three seconds, start monitoring automatically. +- Do not start a second monitor for the same command/block. + +### Side-agent conversation + +- Create a new local Agent View conversation associated with the same terminal surface. +- Enter the side conversation in the existing fullscreen/subagent chat UI, using `AgentViewEntryOrigin::LongRunningCommand`. +- The side agent must not steal or destroy the main conversation state. +- The UI should clearly indicate that the conversation is monitoring a running command. +- The side agent should use the active profile's `coding_model_id` as its model, because this is the configured typing/lower-cost role. + +### Initial monitoring prompt + +Generate a structured prompt containing: + +- The exact command text. +- Working directory, when available. +- Start time and elapsed time. +- Current output snapshot. +- Exit status: unavailable while running. +- Explicit instructions to: + - identify normal progress, + - describe observable success signals, + - describe observable failure signals, + - flag suspicious stalls or lack of progress, + - flag dangerous behavior or unexpected writes, + - avoid declaring success without evidence, + - avoid taking actions unless explicitly authorized. + +Example guidance: + +> Monitor the running command below. Do not assume it succeeded merely because output stopped. Look for concrete completion or success indicators, explicit errors, non-zero exit status, repeated retries, blocked input, lock waits, and no-progress intervals. For database operations, flag a statement that appears stuck while updating a small number of rows, but distinguish a legitimate long-running query from a deadlock or lock wait when possible. Report observations and uncertainty; do not interrupt or modify the process. + +### Ongoing updates + +- Feed fresh command snapshots/output to the side conversation while it remains active. +- Prefer existing `RunningCommand`/long-running snapshot representations over inventing a second terminal-output protocol. +- Throttle updates to avoid flooding the model and UI. Initial proposal: no more than one update every 2 seconds, and only when output or command state changes. +- Include elapsed time on every update or enough metadata for the side agent to calculate it. +- On completion, send a final update containing: + - exit code, + - final output, + - completion timestamp, + - whether the command was cancelled or preempted. + +### Main-agent context integration + +Every side-agent observation must become available to the main agent. + +Preferred design: + +1. Store a compact monitor transcript on the command's main conversation/action state. +2. When the side agent emits a meaningful observation, append a system/tool-style context item to the main conversation. +3. On command completion, append a final monitor summary to the main conversation before the main agent receives the command result. +4. Preserve the monitor transcript across retries/session restoration where the command conversation is persisted. +5. Avoid duplicating every token from the side-agent stream; persist completed observations and periodic summaries instead. + +The main agent should receive explicit provenance, e.g.: + +> Side-agent monitor update for command `` at `00:07`: no completion signal observed; output unchanged for 5 seconds; process may be waiting on a database lock. This is an observation, not a confirmed failure. + +### Cleanup + +- Stop monitor updates when the command finishes, is cancelled, or the terminal view closes. +- Close or mark the side conversation complete without losing its transcript. +- If the user manually opens another conversation, keep the side monitor associated with the original command rather than replacing the main conversation. +- If monitoring fails, report the monitor failure to the main agent but never terminate the user command because the monitor failed. + +## Model fallback behavior + +The profile has two explicit model roles: + +- `RequestInput.model_id`: thinking/base model. +- `RequestInput.coding_model_id`: typing/coding model. + +For ordinary requests: + +1. Try the selected thinking model. +2. If the provider reports a rate-limit or budget-exhausted error before any client/tool action has occurred, retry once with the profile's configured coding model. +3. Do not retry if both model IDs are equal. +4. Do not rotate through arbitrary models or provider entries. +5. Do not switch models after tools/actions have already executed. +6. If both roles fail, return a clear user-facing rate-limit/budget message. + +For the side agent specifically: + +- Start directly with `coding_model_id`. +- If it rate-limits, do not silently switch to the thinking model unless a future product decision explicitly allows that. +- Surface that monitoring is unavailable while leaving the main command running. + +## Proposed implementation phases + +### Phase 1: Model fallback state + +Files: + +- `app/src/ai/blocklist/controller/response_stream.rs` +- `app/src/ai/blocklist/controller/response_stream_tests.rs` +- `app/src/ai/agent/api/impl.rs` +- `app/src/server/server_api.rs` + +Tasks: + +- Add a structured provider-budget/rate-limit classification instead of matching only formatted strings at the final UI boundary. +- Add `coding_model_id` to the retry/fallback state available to `ResponseStream`. +- Add a one-time `retry_with_coding_model` path. +- Re-resolve provider configuration using the coding model ID. +- Ensure the retry does not occur after client actions or when IDs match. +- Add tests for: + - thinking model succeeds, + - thinking model rate-limits and coding model succeeds, + - both models rate-limit, + - same model IDs do not retry, + - non-rate-limit errors retain existing behavior, + - post-action rate limits do not silently switch models. + +### Phase 2: Command monitor state + +Files: + +- `app/src/terminal/view.rs` +- `app/src/ai/blocklist/action_model/execute/shell_command.rs` +- likely a new monitor model/module under `app/src/ai/blocklist/` + +Tasks: + +- Add a terminal-view-scoped monitor registry keyed by command/block ID. +- Replace the current one-off delayed input-lock check with a monitor-start event or extend the existing event with a long-running transition. +- Capture command metadata and snapshots without holding `TerminalModel` locks across async work. +- Add cancellation/completion cleanup paths. +- Add focused unit tests for timer race cases and duplicate monitor prevention. + +### Phase 3: Side-agent conversation creation + +Files: + +- `app/src/ai/blocklist/agent_view/controller.rs` +- `app/src/ai/blocklist/agent_view/conversation_selection.rs` +- `app/src/ai/blocklist/context_model.rs` +- `app/src/ai/blocklist/controller.rs` +- `app/src/terminal/view.rs` + +Tasks: + +- Add an explicit API for creating a monitoring conversation without treating it as a user navigation action. +- Use `AgentViewEntryOrigin::LongRunningCommand`. +- Ensure the main active conversation ID is not overwritten unintentionally. +- Add a monitor-specific conversation association record. +- Select the coding model for monitor requests. + +### Phase 4: Monitoring prompt and updates + +Files: + +- `app/src/ai/agent/mod.rs` +- `app/src/ai/agent/api/convert_to.rs` +- `app/src/ai/blocklist/controller.rs` +- new monitor prompt/context helpers + +Tasks: + +- Build the initial structured monitoring prompt. +- Reuse `RunningCommand` and shell snapshot conversion where possible. +- Add throttled snapshot updates. +- Add explicit completion/cancellation summaries. +- Redact secrets using the existing input redaction path before sending snapshots to the side agent. + +### Phase 5: Main-agent context bridge + +Files: + +- `app/src/ai/blocklist/controller.rs` +- `app/src/ai/blocklist/agent_view/` monitor conversation code +- `app/src/ai/agent/conversation.rs` +- persistence models/snapshot code as needed + +Tasks: + +- Define a structured side-agent observation type. +- Append meaningful observations to the main conversation context. +- Persist completed summaries and association metadata. +- Avoid token-by-token duplication. +- Add tests proving the main request receives monitor observations before and after command completion. + +### Phase 6: UI polish and failure handling + +Tasks: + +- Add a visible “Monitoring command…” indicator in the side-agent header or conversation metadata. +- Show monitor unavailable/rate-limited state without stopping the command. +- Ensure user takeover and command cancellation are represented in both conversations. +- Add telemetry only for high-level lifecycle events, not command output or secrets. + +## Safety and correctness requirements + +- Never terminate or modify the user's process because the side agent failed. +- Never send unredacted command output or secrets to the side agent. +- Never acquire nested `TerminalModel` locks across async boundaries. +- Do not let side-agent tool calls execute arbitrary commands by default. +- Do not claim success based solely on inactivity. +- Preserve the main agent's existing action ordering and tool-result pairing invariants. +- Keep monitoring optional at the implementation boundary so it can be disabled if the UI or provider is unavailable, while the three-second trigger remains automatic when the feature is enabled. + +## Validation plan + +Targeted checks: + +- `cargo test -p galaxy --lib response_stream` +- `cargo test -p galaxy --lib shell_command` +- `cargo test -p galaxy --lib agent_view` +- `cargo test -p galaxy --lib blocklist` +- `cargo check -p galaxy --lib` + +Manual verification: + +1. Run `sleep 1`: no side agent opens. +2. Run `sleep 5`: side agent opens automatically at approximately three seconds. +3. Run a command with periodic output: side agent reports progress. +4. Run a command with no output but successful completion: side agent does not claim failure prematurely and receives the final exit code. +5. Run a command that exits non-zero: side agent identifies failure and main agent receives the summary. +6. Run a simulated database lock/wait: side agent flags suspicious lack of progress with uncertainty. +7. Trigger a provider budget limit on the thinking model: coding model is tried once. +8. Trigger limits on both profile models: clear rate-limit message is shown and no crash occurs.