فهرست منبع

fix: align model selection with current settings remotes

Dudu-0223 1 ماه پیش
والد
کامیت
e49e7202c1

+ 3 - 0
packages/client/ui-settings-plugins/package.json

@@ -32,6 +32,7 @@
   "dsh": {
     "client": {
       "inject": [
+        "@deepseek-ai/dsh-client-connection",
         "@deepseek-ai/dsh-client-locale",
         "@deepseek-ai/dsh-client-ui-settings",
         "@deepseek-ai/dsh-api-remotes"
@@ -47,6 +48,7 @@
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
+    "@deepseek-ai/dsh-client-connection": "workspace:^",
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-ui-settings": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
@@ -55,6 +57,7 @@
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
+    "@deepseek-ai/dsh-client-connection": "workspace:^",
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-store": "workspace:^",
     "@deepseek-ai/dsh-client-test-runtime": "workspace:^",

+ 5 - 1
packages/client/ui-settings-plugins/src/client/subagent-model-selection-card-controller.ts

@@ -280,7 +280,11 @@ export class SubagentModelSelectionCardController {
     this.publish()
     await this.scope.mutate([
       { op: 'set', path: ['enabled'], value: desiredEnabled },
-      { op: 'set', path: ['allowedModels'], value: desired },
+      {
+        op: 'set',
+        path: ['allowedModels'],
+        value: desired.map(route => ({ provider: route.provider, model: route.model })),
+      },
     ], this.draftRevision)
     if (generation !== this.saveGeneration) return
     const landed = this.currentEnabled() === desiredEnabled && sameRoutes(this.currentRoutes(), desired)

+ 8 - 7
packages/client/ui-settings/tests/settings-scope.client.spec.ts

@@ -188,7 +188,7 @@ describe('SettingsScopeController', () => {
 
     const write = scope.mutate(ops)
     ops[0] = { op: 'unset', path: ['enabled'] }
-    ;(ops[1] as { value: Array<{ model: string }> }).value[0]!.model = 'changed'
+    ;(ops[1] as unknown as { value: Array<{ model: string }> }).value[0]!.model = 'changed'
     await write
 
     expect(mutate).toHaveBeenCalledWith(
@@ -202,7 +202,7 @@ describe('SettingsScopeController', () => {
   })
 
   it('preserves an editor-owned revision fence behind earlier queued writes', async () => {
-    const first = deferred<RpcResponse<SettingsNamespaceView>>()
+    const first = deferred<Answer<SettingsNamespaceView>>()
     const describeCall = vi.fn()
       .mockResolvedValueOnce(described({ preference: 'system' }, 7))
       .mockResolvedValueOnce(described({ preference: 'dark' }, 8))
@@ -217,11 +217,12 @@ describe('SettingsScopeController', () => {
     first.resolve(ok(view({ preference: 'dark' }, 8)))
     await Promise.all([earlier, fenced])
 
-    expect(mutate).toHaveBeenNthCalledWith(2, {
-      ns: 'ui-test',
-      ops: [{ op: 'set', path: ['preference'], value: 'light' }],
-      expectedRevision: 7,
-    })
+    expect(mutate).toHaveBeenNthCalledWith(
+      2,
+      'ui-test',
+      [{ op: 'set', path: ['preference'], value: 'light' }],
+      7,
+    )
     expect(scope.getSnapshot()).toMatchObject({ value: { preference: 'dark' }, revision: 8 })
   })
 

+ 22 - 7
packages/subagent/tool-subagent/tests/harness.ts

@@ -2,7 +2,7 @@ import { Context } from '@deepseek-ai/cordis'
 import LlmRuntime, { ToolCallId } from '@deepseek-ai/dsh-llm'
 import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
 import ToolRuntime from '@deepseek-ai/dsh-tools'
-import type { Agent } from '@deepseek-ai/dsh-agent'
+import type { Agent, AgentOptions } from '@deepseek-ai/dsh-agent'
 import AgentLoop from '@deepseek-ai/dsh-agent-loop'
 import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
 import SubagentRuntime from '@deepseek-ai/dsh-subagent'
@@ -22,14 +22,18 @@ export function fakeAgent(id = 'parent-1'): Agent {
 
 /** Mount the real tool and service stack around one scripted subagent provider. */
 const setupAgents = new WeakMap<Context, Agent>()
+const setupProviders = new WeakMap<Context, Awaited<ReturnType<typeof mock.mountScriptedProvider>>>()
 let setupAgentCounter = 0
 
 /** Test-only opt-in translated to the real Host setting and Session path. */
-type SetupConfig = tool.Config & { withModelSelection?: boolean }
+type SetupConfig = tool.Config & {
+  withModelSelection?: boolean
+  parentAgentOptions?: AgentOptions
+}
 
 const TEST_ALLOWED_MODELS = [
-  'allowed-model', 'configured-model', 'current-model', 'fast-model', 'other-model',
-  'parent-model', 'unlisted-model',
+  'allowed-model', 'child-model', 'configured-model', 'current-model', 'fast-model',
+  'other-model', 'parent-model', 'selected-model', 'unlisted-model',
 ].flatMap(model => [
   { provider: 'alpha', model },
   { provider: 'current-provider', model },
@@ -38,7 +42,7 @@ const TEST_ALLOWED_MODELS = [
 
 export async function setup(toolConfig: SetupConfig, mockConfig: Partial<mock.Config> = {}): Promise<Context> {
   const ctx = new Context()
-  const { withModelSelection, ...config } = toolConfig
+  const { withModelSelection, parentAgentOptions, ...config } = toolConfig
   if (withModelSelection === true) {
     await ctx.plugin(SubagentModelSelectionConfig, {
       enabled: true,
@@ -47,9 +51,11 @@ export async function setup(toolConfig: SetupConfig, mockConfig: Partial<mock.Co
     await mountAgentLoopTestDependencies(ctx)
     await ctx.plugin(AgentLoop, { agents: [] })
     await ctx.plugin(SubagentRuntime)
-    await mock.mountScriptedProvider(ctx, { name: 'mock', ...mockConfig })
+    const provider = await mock.mountScriptedProvider(ctx, { name: 'mock', ...mockConfig })
+    setupProviders.set(ctx, provider)
     const handle = await ctx.agents.create({
       sessionId: SessionId(`model-selection-setup-${++setupAgentCounter}`),
+      ...parentAgentOptions !== undefined ? { agentOptions: parentAgentOptions } : {},
       setup: async (agentCtx) => {
         await agentCtx.plugin(tool, { ...config, modelSelectionSettings: true })
       },
@@ -61,11 +67,20 @@ export async function setup(toolConfig: SetupConfig, mockConfig: Partial<mock.Co
   await ctx.plugin(SystemPrompt)
   await ctx.plugin(ToolRuntime)
   await ctx.plugin(SubagentRuntime)
-  await mock.mountScriptedProvider(ctx, { name: 'mock', ...mockConfig })
+  const provider = await mock.mountScriptedProvider(ctx, { name: 'mock', ...mockConfig })
+  setupProviders.set(ctx, provider)
   await ctx.plugin(tool, config)
   return ctx
 }
 
+/** Dispose the scripted provider mounted by {@link setup}. */
+export async function disposeSetupProvider(ctx: Context): Promise<void> {
+  const provider = setupProviders.get(ctx)
+  if (provider === undefined) throw new Error('context has no setup provider')
+  setupProviders.delete(ctx)
+  await provider.dispose()
+}
+
 /** Return the real Agent created for a settings-controlled setup. */
 export function modelSelectionSetupAgent(ctx: Context): Agent {
   const agent = setupAgents.get(ctx)

+ 7 - 0
packages/subagent/tool-subagent/tests/list-models.spec.ts

@@ -238,4 +238,11 @@ describe('list_subagent_models', () => {
     expect(text(result)).toContain('available providers: alpha')
     expect(text(result)).not.toContain('secret')
   })
+
+  it('reports no available provider when the authorized registry intersection is empty', async () => {
+    const ctx = await setupListTool([{ provider: 'missing', model: 'fast' }])
+    const result = await call(ctx, { provider: 'missing' })
+    expect(result.isError).toBe(true)
+    expect(text(result)).toContain('available providers: (none)')
+  })
 })

+ 2 - 2
packages/subagent/tool-subagent/tests/model-selection-settings.spec.ts

@@ -2,7 +2,7 @@
 
 import { describe, expect, it, vi } from 'vitest'
 import { Context } from '@deepseek-ai/cordis'
-import { CallId } from '@deepseek-ai/dsh-llm'
+import { ToolCallId } from '@deepseek-ai/dsh-llm'
 import { Session, SessionId } from '@deepseek-ai/dsh-session'
 import type { SessionEvent } from '@deepseek-ai/dsh-session'
 import { bindScopeParent, createScope, scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope'
@@ -171,7 +171,7 @@ describe('SubagentModelSelectionConfig', () => {
 
     const result = await ctx.tools.execute({
       signal: new AbortController().signal,
-      callId: CallId('disallowed-session-route'),
+      callId: ToolCallId('disallowed-session-route'),
       name: 'subagent',
       arguments: {
         description: 'forced route',

+ 11 - 1
packages/subagent/tool-subagent/tests/model-selection.spec.ts

@@ -10,7 +10,11 @@ import { Session, SessionId } from '@deepseek-ai/dsh-session'
 import { MockAdapter } from '../../../core/agent-loop/tests/mock-adapter.ts'
 import * as mock from './scripted-provider.ts'
 import * as tool from '../src/index.ts'
-import { assertAllowedModelRoutes, assertAllowedModelSelection } from '../src/model-selection.ts'
+import {
+  assertAllowedModelRoutes,
+  assertAllowedModelSelection,
+  preflightChildLlmRoute,
+} from '../src/model-selection.ts'
 import { callSubagent, modelSelectionSetupAgent, setup, text } from './harness.ts'
 
 const REASONING = {
@@ -284,6 +288,12 @@ describe('dsh-tool-subagent model selection', () => {
     expect(text(result)).toContain('without an effective provider and model')
   })
 
+  it('rejects preflight without an effective provider and model', async () => {
+    const ctx = await setup({ provider: 'mock' })
+    await expect(preflightChildLlmRoute(ctx.llm, {}, undefined, AbortSignal.abort()))
+      .rejects.toThrow('without an effective provider and model')
+  })
+
   it.each([
     { provider: 'alpha' },
     { model: 'fast-model' },

+ 40 - 76
packages/subagent/tool-subagent/tests/tool-subagent.spec.ts

@@ -4,7 +4,7 @@ import { tmpdir } from 'node:os'
 import path from 'node:path'
 import { Context } from '@deepseek-ai/cordis'
 import Loader from '@deepseek-ai/cordis-plugin-loader'
-import LlmRuntime, { ToolCallId, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
+import { ToolCallId, ReasoningEffortId } from '@deepseek-ai/dsh-llm'
 import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
 import ToolRuntime, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
 import { assembleContextFor, type Agent } from '@deepseek-ai/dsh-agent'
@@ -21,7 +21,15 @@ import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-a
 import * as mock from './scripted-provider.ts'
 import * as tool from '../src/index.ts'
 import { Session, SessionId } from '@deepseek-ai/dsh-session'
-import { callSubagent, fakeAgent, setup, testToolSignal, text } from './harness.ts'
+import {
+  callSubagent,
+  disposeSetupProvider,
+  fakeAgent,
+  modelSelectionSetupAgent,
+  setup,
+  testToolSignal,
+  text,
+} from './harness.ts'
 
 /**
  * Drives the REAL plugin body: mounts `dsh-tool-subagent` on a real
@@ -221,36 +229,19 @@ describe('dsh-tool-subagent', () => {
   })
 
   it('merges model overrides over provider-owned route defaults before preflight', async () => {
-    let seen: { agentOptions?: { provider?: string; model?: string; reasoningEffort?: string; maxTokens?: number } } | undefined
-    const ctx = new Context()
-    await ctx.plugin(LlmRuntime)
-    await ctx.plugin(SystemPrompt)
-    await ctx.plugin(ToolRuntime)
-    await ctx.plugin(SubagentRuntime)
-    ctx.subagents.registerProvider({
-      name: 'capture',
-      capabilities: { agentOptions: true, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
-      inheritsParentContext: false,
+    let seen: SubagentStartRequest | undefined
+    const ctx = await setup({
+      provider: 'mock',
+      withModelSelection: true,
+      agentOptions: { reasoningEffort: ReasoningEffortId('high'), maxTokens: 321 },
+      maxDepth: 'provider-managed',
+    }, {
       agentRouteDefaults: { provider: 'alpha', model: 'child-model' },
-      start: async (request) => {
-        seen = request
-        return {
-          id: SessionId('capture-child'),
-          localAgent: undefined,
-          result: Promise.resolve({ output: [{ type: 'text', text: 'ok' }], stopReason: 'completed' as const }),
-          dispose: async () => {},
-        }
-      },
+      onStart: (request) => { seen = request },
     })
     ctx.llm.registerAdapter(['alpha'], new MockAdapter([], {
       efforts: [{ id: ReasoningEffortId('high'), name: 'High' }],
     }))
-    await ctx.plugin(tool, {
-      provider: 'capture',
-      enableModelSelection: true,
-      agentOptions: { reasoningEffort: ReasoningEffortId('high'), maxTokens: 321 },
-      maxDepth: 'provider-managed',
-    })
 
     await callSubagent(ctx, {
       description: 'd',
@@ -258,7 +249,7 @@ describe('dsh-tool-subagent', () => {
       provider: 'alpha',
       model: 'child-model',
     })
-    expect(ctx.tools.schemas().find(schema => schema.name === 'subagent')?.description)
+    expect(ctx.tools.schemas(modelSelectionSetupAgent(ctx)).find(schema => schema.name === 'subagent')?.description)
       .toContain('this provider\'s route defaults')
     expect(seen?.agentOptions).toEqual({
       provider: 'alpha',
@@ -270,40 +261,21 @@ describe('dsh-tool-subagent', () => {
 
   it('does not inherit parent effort for a provider-owned route default', async () => {
     let seen: SubagentStartRequest | undefined
-    const ctx = new Context()
-    await ctx.plugin(LlmRuntime)
-    await ctx.plugin(SystemPrompt)
-    await ctx.plugin(ToolRuntime)
-    await ctx.plugin(SubagentRuntime)
-    ctx.subagents.registerProvider({
-      name: 'provider-defaults',
-      capabilities: { agentOptions: true, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
-      inheritsParentContext: false,
-      agentRouteDefaults: { provider: 'alpha', model: 'child-model' },
-      start: async (request) => {
-        seen = request
-        return {
-          id: SessionId('provider-default-child'),
-          localAgent: undefined,
-          result: Promise.resolve({ output: [{ type: 'text', text: 'ok' }], stopReason: 'completed' as const }),
-          dispose: async () => {},
-        }
-      },
-    })
-    ctx.llm.registerAdapter(['alpha'], new MockAdapter([]))
-    await ctx.plugin(tool, {
-      provider: 'provider-defaults',
-      enableModelSelection: true,
-      maxDepth: 'provider-managed',
-    })
-    const parent = {
-      ...fakeAgent('same-route-parent'),
-      options: {
+    const ctx = await setup({
+      provider: 'mock',
+      withModelSelection: true,
+      parentAgentOptions: {
         provider: 'alpha',
         model: 'child-model',
         reasoningEffort: ReasoningEffortId('high'),
       },
-    } as Agent
+      maxDepth: 'provider-managed',
+    }, {
+      agentRouteDefaults: { provider: 'alpha', model: 'child-model' },
+      onStart: (request) => { seen = request },
+    })
+    ctx.llm.registerAdapter(['alpha'], new MockAdapter([]))
+    const parent = modelSelectionSetupAgent(ctx)
 
     const result = await callSubagent(ctx, {
       description: 'd',
@@ -312,6 +284,7 @@ describe('dsh-tool-subagent', () => {
       model: 'child-model',
     }, { agent: parent })
 
+    if (result.isError) throw new Error(text(result))
     expect(result.isError).toBe(false)
     expect(seen?.agentOptions).toEqual({ provider: 'alpha', model: 'child-model' })
   })
@@ -994,24 +967,15 @@ describe('dsh-tool-subagent background mode', () => {
   })
 
   it('rejects startup when the provider changes during asynchronous route preflight', async () => {
-    const ctx = new Context()
-    await ctx.plugin(LlmRuntime)
-    await ctx.plugin(SystemPrompt)
-    await ctx.plugin(ToolRuntime)
-    await ctx.plugin(SubagentRuntime)
-    const oldStart = vi.fn(async (): Promise<never> => { throw new Error('old provider must not start') })
+    const oldStart = vi.fn()
     const replacementStart = vi.fn(async (): Promise<never> => { throw new Error('replacement provider must not start') })
-    const disposeOld = ctx.subagents.registerProvider({
-      name: 'swapped',
-      capabilities: { agentOptions: true, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
-      inheritsParentContext: false,
-      agentRouteDefaults: { provider: 'alpha', model: 'selected-model' },
-      start: oldStart,
-    })
-    await ctx.plugin(tool, {
-      provider: 'swapped',
-      enableModelSelection: true,
+    const ctx = await setup({
+      provider: 'mock',
+      withModelSelection: true,
       maxDepth: 'provider-managed',
+    }, {
+      agentRouteDefaults: { provider: 'alpha', model: 'selected-model' },
+      onStart: oldStart,
     })
     const adapter = new MockAdapter([])
     let releasePreflight!: () => void
@@ -1029,9 +993,9 @@ describe('dsh-tool-subagent background mode', () => {
       model: 'selected-model',
     })
     await vi.waitFor(() => { expect(resolveModel).toHaveBeenCalledOnce() })
-    disposeOld()
+    await disposeSetupProvider(ctx)
     ctx.subagents.registerProvider({
-      name: 'swapped',
+      name: 'mock',
       capabilities: { agentOptions: true, outputSchema: false, depthLimit: false, toolFilter: false, persona: false },
       inheritsParentContext: false,
       agentRouteDefaults: { provider: 'beta', model: 'replacement-model' },

+ 3 - 0
pnpm-lock.yaml

@@ -3127,6 +3127,9 @@ importers:
       '@deepseek-ai/dsh-api-remotes':
         specifier: workspace:^
         version: link:../../api/remotes
+      '@deepseek-ai/dsh-client-connection':
+        specifier: workspace:^
+        version: link:../connection
       '@deepseek-ai/dsh-client-locale':
         specifier: workspace:^
         version: link:../locale

+ 7 - 0
snapshots/acp/cancel-tool-calls/stdout.expected.jsonl

@@ -0,0 +1,7 @@
+{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"mcpCapabilities":{"http":true},"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false},"sessionCapabilities":{"close":{},"list":{},"resume":{}}},"authMethods":[]}}
+{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}","configOptions":[{"id":"model","name":"Model","category":"model","type":"select","currentValue":"[\"deepseek-official\",\"deepseek-v4-flash\"]","options":[{"group":"deepseek-official","name":"DeepSeek","options":[{"value":"[\"deepseek-official\",\"deepseek-v4-flash\"]","name":"deepseek-v4-flash"},{"value":"[\"deepseek-official\",\"deepseek-v4-pro\"]","name":"deepseek-v4-pro"}]}]}]}}
+{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call","toolCallId":"call_wait","title":"bash","kind":"other","status":"in_progress","rawInput":{"command":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","description":"Wait until cancellation"}}}}
+{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_wait","status":"failed","content":[{"type":"content","content":{"type":"text","text":"Error: tool call aborted"}}]}}}
+{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call","toolCallId":"call_skipped","title":"bash","kind":"other","status":"in_progress","rawInput":{"command":"printf skipped > skipped.txt","description":"Write skipped marker"}}}}
+{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_skipped","status":"failed","content":[{"type":"content","content":{"type":"text","text":"Error: tool call aborted before dispatch"}}]}}}
+{"jsonrpc":"2.0","id":3,"result":{"stopReason":"cancelled"}}

+ 1 - 30
snapshots/session/ralph-loop/tool-schemas.1.expected.json

@@ -260,23 +260,6 @@
         }
       }
     },
-    {
-      "name": "list_subagent_models",
-      "description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "provider": {
-            "type": "string",
-            "description": "Registered LLM provider id. Omit to list providers."
-          },
-          "model": {
-            "type": "string",
-            "description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
-          }
-        }
-      }
-    },
     {
       "name": "ralph",
       "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
@@ -504,7 +487,7 @@
     },
     {
       "name": "subagent",
-      "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
+      "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
       "parameters": {
         "type": "object",
         "properties": {
@@ -516,18 +499,6 @@
             "type": "string",
             "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
           },
-          "provider": {
-            "type": "string",
-            "description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
-          },
-          "model": {
-            "type": "string",
-            "description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
-          },
-          "reasoning_effort": {
-            "type": "string",
-            "description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
-          },
           "run_in_background": {
             "type": "boolean",
             "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."

+ 1 - 30
snapshots/session/ralph-loop/tool-schemas.2.expected.json

@@ -260,23 +260,6 @@
         }
       }
     },
-    {
-      "name": "list_subagent_models",
-      "description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
-      "parameters": {
-        "type": "object",
-        "properties": {
-          "provider": {
-            "type": "string",
-            "description": "Registered LLM provider id. Omit to list providers."
-          },
-          "model": {
-            "type": "string",
-            "description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
-          }
-        }
-      }
-    },
     {
       "name": "ralph",
       "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
@@ -504,7 +487,7 @@
     },
     {
       "name": "subagent",
-      "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
+      "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
       "parameters": {
         "type": "object",
         "properties": {
@@ -516,18 +499,6 @@
             "type": "string",
             "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
           },
-          "provider": {
-            "type": "string",
-            "description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
-          },
-          "model": {
-            "type": "string",
-            "description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
-          },
-          "reasoning_effort": {
-            "type": "string",
-            "description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
-          },
           "run_in_background": {
             "type": "boolean",
             "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."

+ 0 - 12
snapshots/session/subagent-configured-effort-rejection/snapshot.yml

@@ -1,12 +0,0 @@
-version: 1
-scenario: subagent-configured-effort-rejection
-profile: headless
-composition: subagent-configured-effort
-recording: authored
-header:
-  class: subagent-configured-effort
-  pin: true
-  systemPromptSource: text-turn
-  toolSchemasSource: text-turn
-replay:
-  override: true