Files
galaxy/plans/long-running-command-side-agent.md
T

13 KiB

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>.
    • 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 <id> 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.
  • Add deterministic unit coverage for duplicate monitor registration and cleanup.

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.
  • If a monitor turn finishes with a still-running command but no polling tool call, issue one bounded continuation nudge; reset that guard after a real snapshot/action result.
  • 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. Run count_forever() { i=0; while :; do i=$((i+1)); printf 'tick=%d\\n' "$i"; sleep 1; done; }; count_forever and ask the monitor to stop it at tick 50; verify it continues polling after each snapshot and interrupts the original process at the requested point.
  8. Trigger a provider budget limit on the thinking model: coding model is tried once.
  9. Trigger limits on both profile models: clear rate-limit message is shown and no crash occurs.