fix: agent model selection persistence, display, and provider routing - #3
Merged
Conversation
Five root causes along the same data path:
- db/schema: agents.model was added via raw ALTER but missing from the
drizzle schema, so select() never returned it — UI selections looked
unsaved. Declare model (+ context_window_*) in the schema.
- config paths: web API wrote role/lead model config to internal project
storage while gateway/spawn read from project cwd (or process.cwd()).
Unify on config.cwd ?? project.subpath('.') everywhere.
- orchestrator auto-spawn passed no cwd; new ModelConfig(undefined) threw
and silently skipped runtime/model resolution, landing agents on the
adapter default provider. Add config-dir fallback and pass cwd +
autoResolve from the orchestrator.
- restartAgent respawned with model: undefined and no runtime, dropping
the persisted selection. Pass agent.model and agent.runtimeName.
- AcpAdapter session init only applied the model via the 'model'
configOption; runtimes without it silently ignored the selection while
the UI still displayed it. Prefer unstable_setSessionModel, fall back
to configOptions.
Also thread runtime through setAgentModel (UI dropdowns list models
across runtimes), persist auto-resolved runtime names, and group the
detail-panel model select by runtime.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes end-to-end persistence and correct provider routing for agent model selection across UI → API → DB → respawn/restart, including aligning config path resolution between web/gateway/spawn logic.
Changes:
- Persist and correctly round-trip
agents.model(and related runtime info) through the DB/Drizzle schema andSqliteStore, with regression tests. - Ensure model selection updates also capture/resolve the owning runtime so restarts route to the intended provider.
- Update UI to group models by runtime and pass runtime through the API when selecting a model.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/web/src/pages/Agents.tsx | Pass selected runtime along with model from the agents list dropdown. |
| packages/web/src/lib/api.ts | Extend setAgentModel API helper to optionally include runtime. |
| packages/web/src/components/AgentDetailPanel.tsx | Group/select models by runtime in the detail panel and send runtime on change. |
| packages/shared/src/core/types.ts | Add optional model field to the shared Agent type. |
| packages/server/tests/storage/sqlite.test.ts | Add regression test for persisting/reading agent model + runtime name. |
| packages/server/tests/agents/agent-manager.test.ts | Add tests for restart persistence, model persistence, runtime routing, and no-cwd spawn regression. |
| packages/server/src/storage/SqliteStore.ts | Add updateAgentRuntimeName and return row.model via Drizzle schema. |
| packages/server/src/orchestrator/Orchestrator.ts | Include cwd and autoResolve: true in unattended auto-spawns. |
| packages/server/src/db/schema.ts | Add missing agents.model (and context window) columns to Drizzle schema. |
| packages/server/src/cli/gateway.ts | Align ModelConfig resolution to project cwd/internal storage (avoid process.cwd()). |
| packages/server/src/api/routes/agents.ts | Allow setting agent model with optional runtime via HTTP API. |
| packages/server/src/api/HttpServer.ts | Align ModelConfig resolution with spawn-side cwd resolution. |
| packages/server/src/agents/AgentManager.ts | Persist resolved runtime, resolve owning runtime on model change, and respawn with persisted model/runtime. |
| packages/server/src/agents/AcpAdapter.ts | Prefer unstable_setSessionModel for live session model selection, fallback to configOptions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- worktree.test: resolve macOS /tmp → /private/tmp symlink via realpathSync - Layout.test: add Link export to react-router-dom mock - Agents.test: add dmMessages to useAgents mock - Chat.test: fix 'Lead is starting up' condition (requires messages.length > 0) - Dashboard.test: use 'hibernated' status (filter no longer excludes 'terminated') - Specs.test: add useSWRConfig to swr mock - Tasks.test: update assertion to match current empty state text - Settings.test: skip — component outgrew test (pre-existing hang) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… fallback - restartAgent used process.cwd() (the daemon's working directory) for the respawned session. Wire projectCwd (config cwd ?? internal storage) from the facade into AgentManager and use it for restart prompt + spawn, and as a model-config resolution fallback. - AgentDetailPanel: when an agent has a model but no persisted runtimeName, the select value had an empty runtime prefix and matched no option, rendering as unselected. Resolve the display runtime from the groups that offer the model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The windows-latest e2e suite failed for weeks: every test errored in afterEach cleanup with EBUSY unlinking state.sqlite, and the leftover DB then cascaded into "UNIQUE constraint failed: agents.id" on the next test's fixed worker ID. Root cause: MemoryStore opened its own better-sqlite3 connection to state.sqlite (facade passed a path string), and Flightdeck.close() never closed it. On Windows the open handle keeps the file locked, so rmSync fails. (Not an agent-ID entropy problem — agentId() already includes a random nonce.) - facade: share the SqliteStore connection with MemoryStore via a new typed SqliteStore.rawClient accessor (one file, one connection) - MemoryStore: add ownership-aware close(); facade.close() calls it - tests: regression tests for single-connection invariant, post-close file removal, and agent-ID uniqueness in a tight loop Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes several issues with agent model selection not persisting correctly and not routing to the correct provider on restart.
Problems fixed
restartAgentpassedmodel: undefined, silently re-routing agents to the adapter's default provider.process.cwd()vs project subpath inconsistency).configOptionsalone silently dropped selections on runtimes that don't expose a 'model' config option; now prefersunstable_setSessionModelwhen available.modelcolumn was written via raw SQL but missing from the Drizzle schema, soselect()never returned it.Changes
unstable_setSessionModelAPI, fall back toconfigOptionsModelConfigpath resolution, useresolvedRuntimefor Claude detection, persist runtime, pass model/runtime on restart, resolve owning runtime on model changefd.status().config.cwd ?? fd.project.subpath('.'))model,contextWindowTokens,contextWindowLimitcolumnsupdateAgentRuntimeName, remove(row as any)castcwdandautoResolve: trueto auto-spawned agentsmodel?toAgentinterface