Skip to content

Add in-session agent and model picker with runtime switching#390

Merged
subsy merged 4 commits into
mainfrom
t3code/c30b6361
May 13, 2026
Merged

Add in-session agent and model picker with runtime switching#390
subsy merged 4 commits into
mainfrom
t3code/c30b6361

Conversation

@subsy
Copy link
Copy Markdown
Owner

@subsy subsy commented May 12, 2026

Summary

  • Adds AgentModelPicker overlay component (press a) for switching the active agent and model without restarting the session
  • Implements ExecutionEngine.switchToUserAgent() — validates agent availability and model compatibility before mutating engine state
  • Adds listModels() to the AgentPlugin interface; implemented for Claude (sonnet/opus/haiku), Gemini (2.5-pro/2.5-flash), and Kiro (four model aliases); defaults to empty array for open-ended agents
  • Adds model field to StoredConfig schema so the model override can be persisted via the picker's "save as default" toggle
  • Rebinds a → open agent/model picker (local tab only); A → add new remote instance (previously a)
  • Upgrades propagateSettingsToEngine to async so agent/model switches are validated before config is persisted; errors surface to the caller rather than silently failing
  • Introduces 'user-selected' as a new ActiveAgentReason, with matching log formatting and iteration summary text

Testing

  • Unit tests added for switchToUserAgent covering: successful switch with state mutation and event emission, rejection on invalid model without state mutation, rejection on detect failure without state mutation
  • Unit tests added for AgentModelPicker helpers: agent config resolution, model listing, model validation, and model normalization
  • Unit tests added for ClaudeAgentPlugin.listModels, GeminiAgentPlugin.listModels, and KiroAgentPlugin.listModels
  • Existing engine.test.ts updated to include listModels stub on mock agents
  • UI not interactively tested in this review — manual verification of the picker overlay, keyboard navigation (Tab, j/k, Enter, Escape), and "save as default" toggle recommended before merge

Summary by CodeRabbit

  • New Features

    • In-view Agent/Model picker in the TUI (open with a) to switch agent and model mid-run; supports free-text models and validation.
    • Option to save selected agent/model as the project default.
    • New top-level Model setting in TUI settings to set a default model.
    • Plugins can enumerate available models for selection.
    • Saving settings can now apply an immediate agent/model switch to the running engine.
  • Documentation

    • Updated keyboard shortcuts: a opens agent/model picker, A adds remote instances.
    • Docs added for switching agents/models during a run and config examples for model.

Review Change Stack

- New `AgentModelPicker` TUI overlay (press `a`) to select agent and model
- `ExecutionEngine.switchToUserAgent` validates and applies changes without restart
- `listModels()` added to agent plugin interface; Claude, Gemini, and Kiro implement it
- `model` field persisted to StoredConfig so selection survives restarts
- Rebinds `A` (shift) for "add remote"; `a` now opens the picker
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ralph-tui Ready Ready Preview, Comment May 13, 2026 9:20am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 68dc7cf9-dae1-4c3a-b786-e8d42f9bcd85

📥 Commits

Reviewing files that changed from the base of the PR and between 7d123a1 and 697e8a9.

📒 Files selected for processing (11)
  • src/chat/engine.test.ts
  • src/commands/run.tsx
  • src/config/index.ts
  • src/config/schema.ts
  • src/config/types.ts
  • src/engine/index.ts
  • src/engine/types.ts
  • src/tui/components/RunApp.tsx
  • website/content/docs/cli/run.mdx
  • website/content/docs/configuration/config-file.mdx
  • website/content/docs/configuration/options.mdx
✅ Files skipped from review due to trivial changes (4)
  • src/chat/engine.test.ts
  • website/content/docs/configuration/config-file.mdx
  • website/content/docs/configuration/options.mdx
  • website/content/docs/cli/run.mdx
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/engine/types.ts
  • src/config/index.ts
  • src/config/types.ts
  • src/commands/run.tsx
  • src/engine/index.ts
  • src/tui/components/RunApp.tsx

📝 Walkthrough

Walkthrough

Adds an in-run Agent/Model picker (with optional save-as-default), a plugin model-listing API, an ExecutionEngine API to switch to a user-selected agent/model (with deferral), TUI wiring and keyboard changes, config schema/setting for top-level model, tests, and documentation updates.

Changes

User-Selected Agent Switching with Runtime Persistence

Layer / File(s) Summary
Config schema and model field
src/config/schema.ts, src/config/types.ts, src/config/index.ts, website/content/docs/configuration/*
Adds optional top-level model to StoredConfig schema/types, updates merge/build logic and docs/examples to expose the top-level model shorthand.
Plugin model listing API and implementations
src/plugins/agents/types.ts, src/plugins/agents/base.ts, src/plugins/agents/builtin/*
Adds listModels(): string[] to AgentPlugin and BaseAgentPlugin; implements overrides in Claude, Gemini (with GEMINI_MODELS), and Kiro; adds tests covering model listing.
AgentModelPicker component and tests
src/tui/components/AgentModelPicker.tsx, src/tui/components/AgentModelPicker.test.ts
New overlay component and exported helpers for resolving agent configs, listing/validating/normalizing models, keyboard navigation, windowed lists, and async apply with "save as default"; tests validate helper behavior and normalization.
Engine user-agent switching core
src/engine/types.ts, src/engine/index.ts
Adds ExecutionEngine.switchToUserAgent(agentConfig, model), deferral/queuing across iterations, updates to switchAgent recording and rate-limit handling, and extends ActiveAgentReason with user-selected.
Engine switch tests
src/engine/switch-to-user-agent.test.ts
Bun test suite validating success, clearing model override, queued-swap behavior, invalid-model rejection, and detect-failure rejection without state mutation.
RunApp picker integration and keyboard
src/tui/components/RunApp.tsx
Wires picker into RunApp, adds showAgentModelPicker state and handleAgentModelConfirm to save defaults or call engine switch, updates keyboard handling so a opens local picker and A is add-remote.
Settings view, theme, and propagation
src/tui/components/SettingsView.tsx, src/tui/theme.ts, src/commands/run.tsx
Adds editable model setting (no restart), updates keyboard shortcuts, and refactors propagateSettingsToEngine to async to detect/apply agent/model changes via engine API before saving config.
Logging, docs, and test mocking
src/logs/persistence.ts, src/logs/types.ts, src/chat/engine.test.ts, README.md, website/content/docs/*
Updates agent-switch logging/metadata to include user-selected, adjusts JSDoc, adds listModels() mock in chat tests, and updates README/site docs and quick-start to document the picker and new model setting.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an in-session agent and model picker with runtime switching capability, which is the core feature implemented across the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 12, 2026

Codecov Report

❌ Patch coverage is 79.28994% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.37%. Comparing base (4c5ab18) to head (697e8a9).

Files with missing lines Patch % Lines
src/engine/index.ts 84.11% 17 Missing ⚠️
src/commands/run.tsx 57.89% 16 Missing ⚠️
src/plugins/agents/base.ts 50.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #390      +/-   ##
==========================================
+ Coverage   48.25%   48.37%   +0.12%     
==========================================
  Files         117      117              
  Lines       38723    38879     +156     
==========================================
+ Hits        18685    18807     +122     
- Misses      20038    20072      +34     
Files with missing lines Coverage Δ
src/config/index.ts 60.38% <100.00%> (+0.05%) ⬆️
src/config/schema.ts 95.55% <100.00%> (+0.03%) ⬆️
src/config/types.ts 100.00% <ø> (ø)
src/engine/types.ts 100.00% <ø> (ø)
src/logs/persistence.ts 55.10% <100.00%> (+0.20%) ⬆️
src/logs/types.ts 100.00% <ø> (ø)
src/plugins/agents/builtin/claude.ts 54.98% <100.00%> (+0.15%) ⬆️
src/plugins/agents/builtin/gemini.ts 56.94% <100.00%> (+0.35%) ⬆️
src/plugins/agents/builtin/kiro.ts 80.95% <100.00%> (+0.18%) ⬆️
src/tui/theme.ts 89.34% <100.00%> (+0.11%) ⬆️
... and 3 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/commands/run.tsx (1)

269-295: ⚡ Quick win

Consider adding clarifying comments for complex logic.

The code on lines 269-273 and 284-287 contains complex conditional fallback chains that would benefit from explanatory comments to clarify:

  • The priority order for resolving agent names (agent → defaultAgent → default from agents array → first agent)
  • Why the shouldSwitchAgent logic switches when previousConfig === undefined or when configs differ

The getDefaultAgentConfig(newConfig, {}) call on line 290 is correct; an empty RuntimeOptions object is valid here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/run.tsx` around lines 269 - 295, Add concise explanatory
comments near getConfiguredAgentName and normalizeModel that state the
resolution priority (agent → defaultAgent → agents.find(agent.default).name →
agents[0].name) and that normalizeModel trims and treats empty strings as
undefined; also add a brief comment above the shouldSwitchAgent computation
explaining the two cases that trigger an agent switch (initial run when
previousConfig is undefined and either a configured agent or model exists, or
when previous vs new agent name or normalized model differ). Keep comments short
(1-2 lines each) and mention getDefaultAgentConfig and engine.switchToUserAgent
nearby to clarify that when shouldSwitchAgent is true we fetch the default agent
config and call engine.switchToUserAgent with nextModel.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/engine/index.ts`:
- Around line 1911-1913: The branch that builds completion summaries never sees
"user-selected" switches because the code only pushes switchEntry into
this.currentIterationAgentSwitches when reason !== 'user-selected'; update the
logic so that switchEntry is also added for 'user-selected' cases (either remove
the reason check or add an explicit push for reason === 'user-selected') so that
buildCompletionSummary(...) can see and process user-selected switches; refer to
currentIterationAgentSwitches, switchEntry, reason, and buildCompletionSummary
to locate and fix the code path.

---

Nitpick comments:
In `@src/commands/run.tsx`:
- Around line 269-295: Add concise explanatory comments near
getConfiguredAgentName and normalizeModel that state the resolution priority
(agent → defaultAgent → agents.find(agent.default).name → agents[0].name) and
that normalizeModel trims and treats empty strings as undefined; also add a
brief comment above the shouldSwitchAgent computation explaining the two cases
that trigger an agent switch (initial run when previousConfig is undefined and
either a configured agent or model exists, or when previous vs new agent name or
normalized model differ). Keep comments short (1-2 lines each) and mention
getDefaultAgentConfig and engine.switchToUserAgent nearby to clarify that when
shouldSwitchAgent is true we fetch the default agent config and call
engine.switchToUserAgent with nextModel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 983d39b1-963a-4b09-9c3e-1bd0d703106f

📥 Commits

Reviewing files that changed from the base of the PR and between 4c5ab18 and 970a7bc.

📒 Files selected for processing (28)
  • README.md
  • src/chat/engine.test.ts
  • src/commands/run.tsx
  • src/config/index.ts
  • src/config/schema.ts
  • src/config/types.ts
  • src/engine/index.ts
  • src/engine/switch-to-user-agent.test.ts
  • src/engine/types.ts
  • src/logs/persistence.ts
  • src/logs/types.ts
  • src/plugins/agents/base.ts
  • src/plugins/agents/builtin/claude.test.ts
  • src/plugins/agents/builtin/claude.ts
  • src/plugins/agents/builtin/gemini.test.ts
  • src/plugins/agents/builtin/gemini.ts
  • src/plugins/agents/builtin/kiro.test.ts
  • src/plugins/agents/builtin/kiro.ts
  • src/plugins/agents/types.ts
  • src/tui/components/AgentModelPicker.test.ts
  • src/tui/components/AgentModelPicker.tsx
  • src/tui/components/RunApp.tsx
  • src/tui/components/SettingsView.tsx
  • src/tui/theme.ts
  • website/content/docs/cli/run.mdx
  • website/content/docs/configuration/config-file.mdx
  • website/content/docs/configuration/options.mdx
  • website/content/docs/getting-started/quick-start.mdx

Comment thread src/engine/index.ts Outdated
Comment thread src/engine/index.ts Outdated
- Remove guard that excluded 'user-selected' reason from switch history
- Add inline comments clarifying agent resolution priority and model normalization logic
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/engine/index.ts (2)

2107-2151: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block agent/model switches while an iteration is still executing.

Lines 2137-2151 mutate this.agent, config.agent, config.model, and rate-limit state immediately. If currentExecution is still the previous handle, the rest of that iteration is attributed to the wrong agent/model.

Suggested fix
 async switchToUserAgent(
   agentConfig: AgentPluginConfig,
   model: string | undefined
 ): Promise<void> {
+  if (this.currentExecution) {
+    throw new Error('Cannot switch agent/model while an iteration is running');
+  }
+
   const normalizedModel = model?.trim() ? model.trim() : undefined;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/engine/index.ts` around lines 2107 - 2151, switchToUserAgent is mutating
this.agent, this.config.agent, this.config.model, this.primaryAgentInstance,
this.rateLimitConfig, this.rateLimitedAgents and this.state.currentModel
immediately which can misattribute an ongoing iteration; detect if an iteration
is in progress by checking the existing execution handle (e.g.,
this.currentExecution or similar) and do not apply the swap immediately—either
queue the requested agent/model switch (store the new agentConfig,
normalizedModel and rateLimit settings in a pendingSwap object) and return, or
await completion of the currentExecution before applying the mutations;
implement applying the pending swap at the end of the iteration (or when
currentExecution resolves/rejects) and ensure switchAgent(agentConfig.plugin,
'user-selected', previousAgent) is invoked only after the swap is applied.

2151-2151: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The user-selected completion summary is still unreachable in the intended flow.

switchToUserAgent() is explicitly for subsequent iterations, but the entry pushed at Line 2151 is cleared by the currentIterationAgentSwitches = [] reset at Line 937 before the next iteration starts. That leaves Lines 2277-2279 dead for normal picker usage.

Also applies to: 2277-2279

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/engine/index.ts` at line 2151, The pushed 'user-selected' switch is being
cleared by the reset of currentIterationAgentSwitches, making the user-selected
completion unreachable; update the logic so that instead of pushing into
currentIterationAgentSwitches at the time of calling
this.switchAgent(agentConfig.plugin, 'user-selected', previousAgent) you either
(A) move that push into the persistent nextIterationAgentSwitches (or equivalent
longer-lived buffer) so it survives the reset, or (B) call the existing
switchToUserAgent(...) helper (or extend it) so the user-selected entry is
scheduled after the reset; adjust code around switchAgent, switchToUserAgent,
and currentIterationAgentSwitches to ensure the 'user-selected' entry persists
into the next iteration rather than being cleared.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/engine/index.ts`:
- Around line 2123-2133: The code incorrectly calls
newInstance.validateModel('') when normalizedModel is undefined, which forces
plugins to reject an empty-string override; instead, in the else/clear-override
path either skip validation or call newInstance.validateModel(undefined) so the
initialize() default-model path remains valid—update the block that references
normalizedModel and newInstance.validateModel to avoid passing '' (use undefined
or omit the call) and throw only if validateModel actually returns an error for
a real override.

---

Duplicate comments:
In `@src/engine/index.ts`:
- Around line 2107-2151: switchToUserAgent is mutating this.agent,
this.config.agent, this.config.model, this.primaryAgentInstance,
this.rateLimitConfig, this.rateLimitedAgents and this.state.currentModel
immediately which can misattribute an ongoing iteration; detect if an iteration
is in progress by checking the existing execution handle (e.g.,
this.currentExecution or similar) and do not apply the swap immediately—either
queue the requested agent/model switch (store the new agentConfig,
normalizedModel and rateLimit settings in a pendingSwap object) and return, or
await completion of the currentExecution before applying the mutations;
implement applying the pending swap at the end of the iteration (or when
currentExecution resolves/rejects) and ensure switchAgent(agentConfig.plugin,
'user-selected', previousAgent) is invoked only after the swap is applied.
- Line 2151: The pushed 'user-selected' switch is being cleared by the reset of
currentIterationAgentSwitches, making the user-selected completion unreachable;
update the logic so that instead of pushing into currentIterationAgentSwitches
at the time of calling this.switchAgent(agentConfig.plugin, 'user-selected',
previousAgent) you either (A) move that push into the persistent
nextIterationAgentSwitches (or equivalent longer-lived buffer) so it survives
the reset, or (B) call the existing switchToUserAgent(...) helper (or extend it)
so the user-selected entry is scheduled after the reset; adjust code around
switchAgent, switchToUserAgent, and currentIterationAgentSwitches to ensure the
'user-selected' entry persists into the next iteration rather than being
cleared.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: caa09c8a-3fc0-42ee-b6db-038ed6e41108

📥 Commits

Reviewing files that changed from the base of the PR and between 970a7bc and e8fe0cd.

📒 Files selected for processing (2)
  • src/commands/run.tsx
  • src/engine/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/run.tsx

Comment thread src/engine/index.ts
subsy and others added 2 commits May 13, 2026 09:49
- Queue switchToUserAgent calls in pendingUserAgentSwap when an execution is running
- Apply pending swap after execution finishes via applyPendingUserAgentSwap
- Carry user-selected switch entries into the next iteration's tracking buffer
- Drop empty-model validation when clearing model override (undefined → no call)
@subsy subsy merged commit b58a99c into main May 13, 2026
10 checks passed
@subsy subsy deleted the t3code/c30b6361 branch May 13, 2026 09:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant