Skip to content

fix: agent model selection persistence, display, and provider routing - #3

Merged
justinchuby merged 4 commits into
mainfrom
claude
Jun 11, 2026
Merged

fix: agent model selection persistence, display, and provider routing#3
justinchuby merged 4 commits into
mainfrom
claude

Conversation

@justinchuby

Copy link
Copy Markdown
Member

Summary

Fixes several issues with agent model selection not persisting correctly and not routing to the correct provider on restart.

Problems fixed

  1. Model selection lost on restartrestartAgent passed model: undefined, silently re-routing agents to the adapter's default provider.
  2. Runtime not persisted — When auto-resolved, the runtime was never saved to the DB, so subsequent spawns couldn't route correctly.
  3. Config path mismatch — The web API wrote model config to a different path than what agent spawn read from (due to process.cwd() vs project subpath inconsistency).
  4. Live session model APIconfigOptions alone silently dropped selections on runtimes that don't expose a 'model' config option; now prefers unstable_setSessionModel when available.
  5. UI didn't group models by runtime — Made it impossible to tell which provider a model belongs to, and didn't pass runtime info on selection.
  6. Schema gapmodel column was written via raw SQL but missing from the Drizzle schema, so select() never returned it.

Changes

  • AcpAdapter: Prefer unstable_setSessionModel API, fall back to configOptions
  • AgentManager: Fix ModelConfig path resolution, use resolvedRuntime for Claude detection, persist runtime, pass model/runtime on restart, resolve owning runtime on model change
  • HttpServer / gateway: Align config path resolution (fd.status().config.cwd ?? fd.project.subpath('.'))
  • Schema: Add model, contextWindowTokens, contextWindowLimit columns
  • SqliteStore: Add updateAgentRuntimeName, remove (row as any) cast
  • Orchestrator: Pass cwd and autoResolve: true to auto-spawned agents
  • Web UI: Group models by runtime in dropdown, pass runtime on selection
  • Shared types: Add model? to Agent interface
  • Tests: Regression tests for restart persistence, model setting, runtime routing, and no-cwd spawn

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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 and SqliteStore, 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.

Comment thread packages/web/src/components/AgentDetailPanel.tsx Outdated
Comment thread packages/server/src/agents/AgentManager.ts
justinchuby and others added 3 commits June 11, 2026 13:04
- 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>
@justinchuby
justinchuby merged commit 93f3659 into main Jun 11, 2026
1 of 3 checks passed
@justinchuby
justinchuby deleted the claude branch June 11, 2026 21: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.

2 participants