diff --git a/src/agent-engine.ts b/src/agent-engine.ts index c3a6ff83..dcaf8960 100644 --- a/src/agent-engine.ts +++ b/src/agent-engine.ts @@ -95,7 +95,9 @@ import { } from "./harness-session.js"; import { resolveLaunchModelFlag, + resolveSpawnEffort, resolveSpawnModelPolicy, + type CodexEffort, type SpawnModelPolicy, } from "./model-policy.js"; import { @@ -151,6 +153,7 @@ type ProcessLiveness = "alive" | "gone" | "unknown"; export interface SpawnAgentParams { repo: string; model?: string; + effort?: string; cli: CliType; prompt: string; boot_prompt_timeout_ms?: number; @@ -754,7 +757,12 @@ export function buildLaunchCommand( // registry-prefix registrations launch correctly. Honored for the launcher // CLIs (claude/codex/cursor/gemini); ignored for kiro (raw cd+exec). launcherName?: string, - opts?: { cwd?: string; envPrefix?: string; allowModelOverride?: boolean }, + opts?: { + cwd?: string; + envPrefix?: string; + allowModelOverride?: boolean; + effort?: CodexEffort; + }, ): string { const safeRepo = sanitizeRepoName(repo); const modelFlag = resolveLaunchModelFlag(cli, model, { @@ -764,18 +772,20 @@ export function buildLaunchCommand( const launcherModelArgs = formattedModelFlag ? ` -m ${formattedModelFlag}` : ""; + const claudeModelArgs = modelFlag === "sonnet" ? " -S" : launcherModelArgs; const rawModelArgs = formattedModelFlag ? ` --model ${formattedModelFlag}` : ""; const launcherWorktreeArg = opts?.cwd ? ` -w ${shellQuote(opts.cwd)}` : ""; + const launcherEffortArg = opts?.effort ? ` -E ${opts.effort}` : ""; const rawCdPrefix = opts?.cwd ? `cd ${shellQuote(opts.cwd)} && ` : ""; const envPrefix = opts?.envPrefix ? `${opts.envPrefix} ` : ""; switch (cli) { case "claude": // repoGolem launcher handles env vars via ralph-registry - return `${envPrefix}${launcherName ?? `${safeRepo}Claude`} -s${launcherModelArgs}${launcherWorktreeArg}`; + return `${envPrefix}${launcherName ?? `${safeRepo}Claude`} -s${claudeModelArgs}${launcherWorktreeArg}`; case "codex": - return `${envPrefix}${launcherName ?? `${safeRepo}Codex`} -s${launcherModelArgs}${launcherWorktreeArg}`; + return `${envPrefix}${launcherName ?? `${safeRepo}Codex`} -s${launcherModelArgs}${launcherEffortArg}${launcherWorktreeArg}`; case "gemini": // repoGolem launcher (e.g. golemsGemini -s) wires antigravity + MCP. return `${envPrefix}${launcherName ?? `${safeRepo}Gemini`} -s${launcherModelArgs}${launcherWorktreeArg}`; @@ -4673,6 +4683,7 @@ export class AgentEngine { */ async spawnAgent(params: SpawnAgentParams): Promise { const modelPolicy = resolveSpawnModelPolicy(params.cli, params.model); + const effort = resolveSpawnEffort(params.cli, params.effort); const spawnParams: SpawnAgentParams = { ...params, model: modelPolicy.effective_model, @@ -4891,6 +4902,7 @@ export class AgentEngine { cwd: spawnParams.cwd, envPrefix: spawnParams.mcp_env, allowModelOverride: modelPolicy.override_allowed, + effort: effort ?? undefined, }, ); try { diff --git a/src/model-policy.ts b/src/model-policy.ts index 6db25f5a..e37f9a4d 100644 --- a/src/model-policy.ts +++ b/src/model-policy.ts @@ -1,6 +1,13 @@ import type { CliType } from "./agent-types.js"; export const MODEL_OVERRIDE_ENV = "REPOGOLEM_ALLOW_MODEL"; +export const CODEX_EFFORT_VALUES = [ + "medium", + "high", + "xhigh", + "ultra", +] as const; +export type CodexEffort = (typeof CODEX_EFFORT_VALUES)[number]; export interface CliModelPolicyContract { defaultModel: string; @@ -129,6 +136,47 @@ function ownModelAlias(cli: CliType, normalized: string): string | null { return typeof alias === "string" && alias ? alias : null; } +function modelAliasUsesUngatedLauncherPath( + cli: CliType, + alias: string, +): boolean { + if (cli === "claude") return alias === "sonnet"; + return cli === "gemini" || cli === "kiro"; +} + +function acceptedModelNames( + cli: CliType, + allowModelOverride: boolean, +): string[] { + const contract = MODEL_POLICY_CONTRACT.cli[cli]; + const aliases = Object.keys(contract.modelAliases).filter( + (alias) => + allowModelOverride || modelAliasUsesUngatedLauncherPath(cli, alias), + ); + return [...new Set([contract.defaultModel, ...aliases])]; +} + +export function resolveSpawnEffort( + cli: CliType, + effort?: string, +): CodexEffort | null { + const requested = effort?.trim(); + if (!requested) return null; + + if (!(CODEX_EFFORT_VALUES as readonly string[]).includes(requested)) { + throw new Error( + `Invalid Codex effort "${requested}". Accepted values: ${CODEX_EFFORT_VALUES.join(", ")}. No agent was spawned.`, + ); + } + if (cli !== "codex") { + throw new Error( + `Codex effort "${requested}" cannot be used with cli "${cli}". Set cli to "codex" or omit effort. No agent was spawned.`, + ); + } + + return requested as CodexEffort; +} + export function resolveModelAlias(cli: CliType, model: string): string { const trimmed = model.trim(); const normalized = normalizeModelKey(trimmed); @@ -154,6 +202,13 @@ export function resolveLaunchModelFlag( } const alias = ownModelAlias(cli, normalizeModelKey(requested)); + if ( + cli === "claude" && + alias !== "sonnet" && + !opts?.allowModelOverride + ) { + return null; + } return alias ?? null; } @@ -170,6 +225,22 @@ export function resolveSpawnModelPolicy( const requestedOrDefault = requestedWasOmitted ? defaultModel : requestedModel; const resolvedRequested = resolveModelAlias(cli, requestedOrDefault); + // Cursor deliberately accepts arbitrary model strings behind its escape + // hatch. Every launcher-backed CLI, however, has a finite alias table. Do + // not let the older Codex coercion branch turn an unknown alias into an + // apparently successful default-model spawn. + if ( + !requestedWasOmitted && + cli !== "cursor" && + !modelMatchesDefault(cli, requestedModel) && + ownModelAlias(cli, normalizeModelKey(requestedModel)) === null + ) { + const acceptedModels = acceptedModelNames(cli, overrideAllowed); + throw new Error( + `Unsupported model "${requestedModel}" for cli "${cli}": without a valid alias, the launcher would actually run "${defaultModel}". Accepted models: ${acceptedModels.join(", ")}. No agent was spawned.`, + ); + } + if ( !requestedWasOmitted && !contract.allowModelOverrideByDefault && @@ -195,14 +266,27 @@ export function resolveSpawnModelPolicy( }; } + const launcherModel = requestedWasOmitted + ? null + : resolveLaunchModelFlag(cli, resolvedRequested, { + allowModelOverride: overrideAllowed, + }); + if ( + !requestedWasOmitted && + !modelMatchesDefault(cli, resolvedRequested) && + launcherModel === null + ) { + const acceptedModels = acceptedModelNames(cli, overrideAllowed); + throw new Error( + `Unsupported model "${requestedModel}" for cli "${cli}": without a valid alias, the launcher would actually run "${defaultModel}". Accepted models: ${acceptedModels.join(", ")}. No agent was spawned.`, + ); + } + return { cli, requested_model: requestedModel, effective_model: resolvedRequested, - launcher_model: - requestedWasOmitted || modelMatchesDefault(cli, resolvedRequested) - ? null - : resolvedRequested, + launcher_model: launcherModel, coerced: false, warnings: [], override_env: MODEL_OVERRIDE_ENV, diff --git a/src/server.ts b/src/server.ts index 77d73110..37fe6e28 100644 --- a/src/server.ts +++ b/src/server.ts @@ -23,6 +23,11 @@ import { assertMutationAllowed, parseReservedModeKey } from "./mode-policy.js"; import { extractPrefix, replaceTaskSuffix } from "./naming.js"; import { createStaleBuildWarner, RUNNING_VERSION } from "./version.js"; import { buildSpawnToolReturn, shapeSpawnResponse } from "./spawn-response.js"; +import { + CODEX_EFFORT_VALUES, + resolveSpawnEffort, + resolveSpawnModelPolicy, +} from "./model-policy.js"; import { StateManager } from "./state-manager.js"; import { createDefaultCloseForensicsRunner } from "./close-forensics.js"; import { @@ -9159,6 +9164,12 @@ export function createServer(opts?: CreateServerOptions): McpServer { .describe( "OPTIONAL — leave UNSET so the launcher pins the top-tier model. Only set this if you have a specific reason NOT to use the top model (e.g. a deliberately cheaper 'sonnet' pass, or a non-claude engine variant like 'codex'). Never pass 'opus' for claude — the top Claude model is already the default.", ), + effort: z + .enum(CODEX_EFFORT_VALUES) + .optional() + .describe( + "Optional Codex reasoning effort passed to the repoGolem launcher. Accepted values: medium, high, xhigh, ultra. The launcher defaults to xhigh when omitted; max is not accepted by the current launcher.", + ), cli: z .enum(["claude", "codex", "gemini", "kiro", "cursor"]) .describe("CLI tool to launch"), @@ -9264,6 +9275,8 @@ export function createServer(opts?: CreateServerOptions): McpServer { selectedRoleField, ); const effectiveRole = normalizedRole.role; + resolveSpawnModelPolicy(args.cli, args.model); + resolveSpawnEffort(args.cli, args.effort); const bootPromptPath = getBootPromptPath(args.boot_prompt_path); assertBootPromptMode(args.prompt, bootPromptPath); assertSpawnPromptInputAllowed({ @@ -9353,6 +9366,7 @@ export function createServer(opts?: CreateServerOptions): McpServer { result = await engine.spawnAgent({ repo: args.repo, model: args.model, + effort: args.effort, cli: args.cli, prompt: spawnPrompt, boot_prompt_pending: @@ -9713,6 +9727,7 @@ export function createServer(opts?: CreateServerOptions): McpServer { let result: Awaited> | undefined; let mutationWorkspace: string | undefined; try { + resolveSpawnModelPolicy(args.cli, args.model); assertBootPromptMode(args.prompt, null); assertSpawnPromptInputAllowed({ tool: "new_worktree_split", @@ -10032,6 +10047,9 @@ export function createServer(opts?: CreateServerOptions): McpServer { compatibilityWarning: normalizedRole.warning, }; }); + for (const agent of normalizedAgents) { + resolveSpawnModelPolicy(agent.cli, agent.model); + } const compatibilityWarnings = normalizedAgents.flatMap((agent) => agent.compatibilityWarning ? [agent.compatibilityWarning] : [], ); diff --git a/tests/agent-engine.test.ts b/tests/agent-engine.test.ts index 5e8bcc87..02d48cc1 100644 --- a/tests/agent-engine.test.ts +++ b/tests/agent-engine.test.ts @@ -664,7 +664,7 @@ describe("AgentEngine", () => { ).mock.calls[0]; expect(surface).toBe("surface:new"); expect(opts).toEqual({ workspace: "ws:1" }); - expect(launchCmd).toBe("brainlayerClaude -s -m sonnet"); + expect(launchCmd).toBe("brainlayerClaude -s -S"); }); it("launches with the launcher name resolved by preflight", async () => { @@ -4237,6 +4237,7 @@ describe("AgentEngine", () => { it.each([ [ + "codex", "codex", "019d9aa5-93c0-7a52-9c47-9be1f7625f3e", `gpt-5.4 @@ -4245,25 +4246,28 @@ To continue this session, run codex resume 019d9aa5-93c0-7a52-9c47-9be1f7625f3e` ], [ "claude", + "sonnet", "5b9f4f35-2942-4c8b-b1af-d89d4e36c95d", `Claude Code Session ID: 5b9f4f35-2942-4c8b-b1af-d89d4e36c95d`, ], [ "cursor", + "auto", "9e26fe1a-2374-4b15-b9b2-646ac7a8c2ef", `Cursor Agent chatId: 9e26fe1a-2374-4b15-b9b2-646ac7a8c2ef`, ], [ "gemini", + "pro", "8c2f7f0c-00ee-4c6e-856d-cc7ae91f5274", `Gemini CLI Resumable session: 8c2f7f0c-00ee-4c6e-856d-cc7ae91f5274`, ], ] as const)( "captures %s session ids from the boot banner within the first sweep", - async (cli, sessionId, banner) => { + async (cli, model, sessionId, banner) => { liveSurfaces = [makeSpawnSurface()]; (mockClient.readScreen as ReturnType).mockResolvedValue({ surface: "surface:new", @@ -4275,7 +4279,7 @@ Resumable session: 8c2f7f0c-00ee-4c6e-856d-cc7ae91f5274`, engine.startSweep(1000); const result = await engine.spawnAgent({ repo: "brainlayer", - model: "sonnet", + model, cli, prompt: "Fix gap F", }); @@ -8788,7 +8792,6 @@ To continue this session, run codex resume ${sessionId}`, await expect( defaultEngine.spawnAgent({ repo: `missinglauncher${suffix}`, - model: "test", cli, prompt: "", }), @@ -8815,7 +8818,6 @@ To continue this session, run codex resume ${sessionId}`, await expect( defaultEngine.spawnAgent({ repo: "missinglauncher", - model: "test", cli: "claude", prompt: "", cwd: "/tmp/cmux-worktree", @@ -10580,9 +10582,17 @@ describe("buildLaunchCommand", () => { ); }); + it("passes an explicit Codex effort to the repoGolem launcher", () => { + expect( + buildLaunchCommand("codex", "brainlayer", undefined, undefined, { + effort: "medium", + }), + ).toBe("brainlayerCodex -s -E medium"); + }); + it("adds safe model flags for recognized launcher model aliases", () => { expect(buildLaunchCommand("claude", "brainlayer", "sonnet")).toBe( - "brainlayerClaude -s -m sonnet", + "brainlayerClaude -s -S", ); expect( buildLaunchCommand("codex", "brainlayer", "gpt-5.3-codex-spark"), @@ -10683,7 +10693,7 @@ describe("buildLaunchCommand", () => { buildLaunchCommand("claude", "golems", "sonnet", undefined, { cwd: "/p/wt", }), - ).toBe("golemsClaude -s -m sonnet -w '/p/wt'"); + ).toBe("golemsClaude -s -S -w '/p/wt'"); expect( buildLaunchCommand("kiro", "golems", undefined, undefined, { cwd: "/p/wt", @@ -10714,7 +10724,7 @@ describe("buildLaunchCommand", () => { "sonnet", "agenthtmlhostClaude", ), - ).toBe("agenthtmlhostClaude -s -m sonnet"); + ).toBe("agenthtmlhostClaude -s -S"); }); it("honors an explicitly resolved launcher name for gemini", () => { diff --git a/tests/agent-hierarchy.test.ts b/tests/agent-hierarchy.test.ts index 04cdadf3..3e611391 100644 --- a/tests/agent-hierarchy.test.ts +++ b/tests/agent-hierarchy.test.ts @@ -129,7 +129,7 @@ describe("Agent Hierarchy", () => { // Spawn a child (second newSplit call will also return surface:new — ok for test) const child = await engine.spawnAgent({ repo: "voicelayer", - model: "haiku", + model: "sonnet", cli: "claude", prompt: "Sub task", parent_agent_id: root.agent_id, diff --git a/tests/audit-fixes.test.ts b/tests/audit-fixes.test.ts index 43fd594e..44aed861 100644 --- a/tests/audit-fixes.test.ts +++ b/tests/audit-fixes.test.ts @@ -352,7 +352,7 @@ describe("spawn_agent MCP schema includes parent_agent_id and max_cost_per_agent const childResult = await spawn.handler( { repo: "test", - model: "haiku", + model: "sonnet", cli: "claude", workspace: "workspace:1", parent_agent_id: parentId, @@ -372,7 +372,6 @@ describe("spawn_agent MCP schema includes parent_agent_id and max_cost_per_agent const result = await spawn.handler( { repo: "test", - model: "opus", cli: "claude", workspace: "workspace:1", max_cost_per_agent: 5.0, diff --git a/tests/model-policy.test.ts b/tests/model-policy.test.ts index e9bc38c7..3496c993 100644 --- a/tests/model-policy.test.ts +++ b/tests/model-policy.test.ts @@ -97,4 +97,40 @@ describe("model policy contract", () => { "gemini-2.5-pro", ); }); + + it("advertises only Claude models reachable without the override gate", () => { + let message = ""; + try { + resolveSpawnModelPolicy("claude", "fable-5", {}); + } catch (error) { + message = error instanceof Error ? error.message : String(error); + } + expect(message).toContain( + "Accepted models: claude-opus-5[1m], sonnet.", + ); + expect(message).not.toMatch(/haiku|, opus/); + expect(() => resolveSpawnModelPolicy("claude", "haiku", {})).toThrow( + /Unsupported model "haiku"/, + ); + + expect( + resolveSpawnModelPolicy("claude", "haiku", { + [MODEL_OVERRIDE_ENV]: "1", + }).launcher_model, + ).toBe("haiku"); + }); + + it("rejects an unknown Codex model before the legacy override coercion", () => { + expect(() => + resolveSpawnModelPolicy("codex", "gpt-5.6-sol", {}), + ).toThrow( + /Unsupported model "gpt-5\.6-sol".*would actually run "codex".*Accepted models: codex.*No agent was spawned/, + ); + + expect(() => + resolveSpawnModelPolicy("codex", "gpt-5.6-sol", { + [MODEL_OVERRIDE_ENV]: "1", + }), + ).toThrow(/Unsupported model "gpt-5\.6-sol"/); + }); }); diff --git a/tests/server-agent-tools.test.ts b/tests/server-agent-tools.test.ts index 4bd7b86e..5835f182 100644 --- a/tests/server-agent-tools.test.ts +++ b/tests/server-agent-tools.test.ts @@ -546,7 +546,7 @@ describe("lean spawn tool responses", () => { const spawn = (server as any)._registeredTools["spawn_agent"]; const result = await spawn.handler( - { repo: "cmuxlayer", model: "fable-5", cli: "claude" }, + { repo: "cmuxlayer", model: "sonnet", cli: "claude" }, {} as any, ); const parsed = @@ -560,7 +560,7 @@ describe("lean spawn tool responses", () => { agent_id: parsed.agent_id, tab_name: "cmuxlayerClaude [surface:new]", session_name: null, - model: "fable-5", + model: "sonnet", permission_mode: "skip-permissions", cwd: join(homedir(), "Gits", "cmuxlayer"), repo: "cmuxlayer", @@ -593,7 +593,7 @@ describe("lean spawn tool responses", () => { const spawn = (server as any)._registeredTools["spawn_agent"]; await spawn.handler( - { repo: "cmuxlayer", model: "fable-5", cli: "claude" }, + { repo: "cmuxlayer", model: "sonnet", cli: "claude" }, {} as any, ); @@ -666,6 +666,172 @@ describe("lean spawn tool responses", () => { }); expect(result.structuredContent.warnings).not.toHaveLength(0); }); + + it("spawn_agent passes an explicit Codex effort to the launcher", async () => { + const mockExec = makeLifecycleExec(); + const server = createLifecycleServer(mockExec); + const spawn = (server as any)._registeredTools["spawn_agent"]; + const args = spawn.inputSchema.parse({ + repo: "cmuxlayer", + cli: "codex", + effort: "medium", + }); + + const result = await spawn.handler(args, {} as any); + + expect(result.structuredContent.ok).toBe(true); + expect(mockExec).toHaveBeenCalledWith( + "cmux", + expect.arrayContaining(["send", "cmuxlayerCodex -s -E medium"]), + ); + }); + + it("spawn_agent schema rejects max effort before invoking the handler", () => { + const mockExec = makeLifecycleExec(); + const server = createLifecycleServer(mockExec); + const spawn = (server as any)._registeredTools["spawn_agent"]; + + const parsed = spawn.inputSchema.safeParse({ + repo: "cmuxlayer", + cli: "codex", + effort: "max", + }); + + expect(parsed.success).toBe(false); + expect(parsed.error.issues).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: "invalid_enum_value", + received: "max", + options: ["medium", "high", "xhigh", "ultra"], + }), + ]), + ); + expect( + mockExec.mock.calls.some(([, callArgs]) => callArgs.includes("new-split")), + ).toBe(false); + }); + + it("spawn_agent rejects Codex effort for another CLI before creating a surface", async () => { + const mockExec = makeLifecycleExec(); + const server = createLifecycleServer(mockExec); + const spawn = (server as any)._registeredTools["spawn_agent"]; + const args = spawn.inputSchema.parse({ + repo: "cmuxlayer", + cli: "claude", + effort: "medium", + }); + + const result = await spawn.handler(args, {} as any); + + expect(result.structuredContent).toMatchObject({ ok: false }); + expect(result.structuredContent.error).toContain( + 'Codex effort "medium" cannot be used with cli "claude"', + ); + expect( + mockExec.mock.calls.some(([, callArgs]) => callArgs.includes("new-split")), + ).toBe(false); + }); + + it("spawn_agent rejects an unsupported model before creating a surface", async () => { + const mockExec = makeLifecycleExec(); + const server = createLifecycleServer(mockExec); + const spawn = (server as any)._registeredTools["spawn_agent"]; + + const result = await spawn.handler( + { repo: "cmuxlayer", cli: "claude", model: "fable-5" }, + {} as any, + ); + + expect(result.structuredContent).toMatchObject({ ok: false }); + expect(result.structuredContent.error).toContain( + 'Unsupported model "fable-5" for cli "claude"', + ); + expect(result.structuredContent.error).toContain( + 'would actually run "claude-opus-5[1m]"', + ); + expect(result.structuredContent.error).toContain( + "Accepted models: claude-opus-5[1m], sonnet", + ); + expect( + mockExec.mock.calls.some(([, callArgs]) => callArgs.includes("new-split")), + ).toBe(false); + }); + + it("new_worktree_split rejects an unsupported model before preparing a worktree", async () => { + const gitsDir = join(TEST_DIR, "Gits"); + mkdirSync(join(gitsDir, "cmuxlayer"), { recursive: true }); + const mockExec = makeLifecycleExec(); + const worktreeExec = vi.fn().mockResolvedValue({ stdout: "", stderr: "" }); + const server = createTrackedServer({ + exec: mockExec, + stateDir: TEST_DIR, + disableSpawnPreflight: true, + sessionIdentityResolver: () => null, + worktreeHomeDir: gitsDir, + worktreeExec, + }); + const spawn = (server as any)._registeredTools["new_worktree_split"]; + + const result = await spawn.handler( + { + repo: "cmuxlayer", + cli: "claude", + model: "fable-5", + worktree: { name: "must-not-exist" }, + }, + {} as any, + ); + + expect(result.structuredContent).toMatchObject({ ok: false }); + expect(result.structuredContent.error).toContain( + 'Unsupported model "fable-5" for cli "claude"', + ); + expect(worktreeExec).not.toHaveBeenCalled(); + expect( + mockExec.mock.calls.some(([, callArgs]) => callArgs.includes("new-split")), + ).toBe(false); + }); + + it("spawn_in_workspace validates every model before creating the workspace", async () => { + const mockExec = makeLifecycleExec(); + const server = createLifecycleServer(mockExec); + const spawn = (server as any)._registeredTools["spawn_in_workspace"]; + + const result = await spawn.handler( + { + workspace_title: "Must not exist", + agents: [ + { + repo: "cmuxlayer", + cli: "codex", + model: "codex", + role: "worker", + }, + { + repo: "cmuxlayer", + cli: "claude", + model: "fable-5", + role: "worker", + }, + ], + }, + {} as any, + ); + + expect(result.structuredContent).toMatchObject({ ok: false }); + expect(result.structuredContent.error).toContain( + 'Unsupported model "fable-5" for cli "claude"', + ); + expect( + mockExec.mock.calls.some(([, callArgs]) => + callArgs.includes("create-workspace"), + ), + ).toBe(false); + expect( + mockExec.mock.calls.some(([, callArgs]) => callArgs.includes("new-split")), + ).toBe(false); + }); }); function moveOnlyAgentStateDir(prefix: string) { @@ -9370,7 +9536,7 @@ codex> const myAgents = (server as any)._registeredTools["my_agents"]; await spawn.handler( - { repo: "voicelayer", model: "opus", cli: "claude" }, + { repo: "voicelayer", cli: "claude" }, {} as any, ); await spawn.handler( @@ -9517,7 +9683,6 @@ codex> const parentResult = await spawn.handler( { repo: "orchestrator", - model: "opus", cli: "claude", }, {} as any, @@ -9638,7 +9803,7 @@ codex> const myAgents = (server as any)._registeredTools["my_agents"]; await spawn.handler( - { repo: "golems", model: "opus", cli: "claude", prompt: "audit" }, + { repo: "golems", cli: "claude", prompt: "audit" }, {} as any, ); @@ -9770,7 +9935,7 @@ codex> const engine = (server as any)._registeredTools["interact"]._engine; const spawnResult = await spawn.handler( - { repo: "voicelayer", model: "opus", cli: "claude", prompt: "fix tts" }, + { repo: "voicelayer", cli: "claude", prompt: "fix tts" }, {} as any, ); const agentId = spawnResult.structuredContent.agent_id; @@ -10096,7 +10261,7 @@ describe("auto-focus discipline (focus target before split, restore after render { repo: "cmuxlayer", cli: "codex", - model: "gpt-5.6-sol", + model: "codex", workspace: "workspace:2", worktree: false, }, @@ -10121,7 +10286,7 @@ describe("auto-focus discipline (focus target before split, restore after render { repo: "cmuxlayer", cli: "codex", - model: "gpt-5.6-sol", + model: "codex", role: "worker", }, ], @@ -10146,7 +10311,7 @@ describe("auto-focus discipline (focus target before split, restore after render { repo: "cmuxlayer", cli: "codex", - model: "gpt-5.6-sol", + model: "codex", role: "worker", }, ], @@ -10259,7 +10424,7 @@ describe("auto-focus discipline (focus target before split, restore after render { repo: "cmuxlayer", cli: "codex", - model: "gpt-5.6-sol", + model: "codex", workspace: "workspace:2", worktree: false, }, @@ -10288,7 +10453,7 @@ describe("auto-focus discipline (focus target before split, restore after render { repo: "cmuxlayer", cli: "codex", - model: "gpt-5.6-sol", + model: "codex", role: "worker", }, ],