diff --git a/docs/python-repl.md b/docs/python-repl.md index e5b940739..2fac77bbb 100644 --- a/docs/python-repl.md +++ b/docs/python-repl.md @@ -146,7 +146,7 @@ The backend settings `eval.py` / `eval.js` default to `true`; `eval.rb` / `eval. The tool's session-scoped schema lists only enabled runtimes. If Python preflight fails while another runtime is enabled, `eval` remains available for that runtime and a `py` call reports a Python-backend availability error with enabled alternatives. -Python prelude helpers include `agent(prompt, *, agent="task", model=None, label=None, schema=None, schema_mode=None, isolated=None, apply=None, merge=None, handle=False)`. It synchronously calls the host bridge and returns final text, or parsed data when `schema` is supplied. `schema_mode` selects permissive or strict structured-output handling; the isolation/apply/merge flags control task worktree behavior. With `handle=True`, it returns a DAG node dict (`{"text", "output", "handle", "id", "agent"}`) whose handle is the recoverable `agent://` URI; parsed output is also stored under `"data"` when available. +Python prelude helpers include `agent(prompt, *, agent="task", label=None, schema=None, schema_mode=None, isolated=None, apply=None, merge=None, handle=False)`. It synchronously calls the host bridge and returns final text, or parsed data when `schema` is supplied. `schema_mode` selects permissive or strict structured-output handling; the isolation/apply/merge flags control task worktree behavior. With `handle=True`, it returns a DAG node dict (`{"text", "output", "handle", "id", "agent"}`) whose handle is the recoverable `agent://` URI; parsed output is also stored under `"data"` when available. ## Execution flow and cancellation/timeout diff --git a/docs/tools/eval.md b/docs/tools/eval.md index 26347a1e5..5c4760a1f 100644 --- a/docs/tools/eval.md +++ b/docs/tools/eval.md @@ -149,9 +149,9 @@ A stateless, tool-free one-shot model call: Runs one subagent through `runStructuredSubagent(...)`: -- JS supports the preferred `await agent(prompt, { agent?, model?, label?, schema?, schemaMode?, isolated?, apply?, merge?, handle? })`; legacy positional slots are still implemented. +- JS supports the preferred `await agent(prompt, { agent?, label?, schema?, schemaMode?, isolated?, apply?, merge?, handle? })`; legacy positional slots are still implemented. - Python/Ruby/Julia use keyword arguments (`schema_mode` outside JS). -- `agent` defaults from the current spawn policy. `model` may pin a selector/fallback chain. `schema` overrides agent/session schemas; `schemaMode`/`schema_mode` chooses `permissive` or `strict`. +- `agent` defaults from the current spawn policy; the selected agent's frontmatter model and settings always apply (there is no per-call model override — `model` is not accepted). `schema` overrides agent/session schemas; `schemaMode`/`schema_mode` chooses `permissive` or `strict`. - `isolated` requests isolation. `apply` controls whether captured changes are integrated; `merge=false` selects patch mode while the normal setting controls branch mode. - `handle=true` returns `{ text, output, handle, id, agent }`, optional parsed `data`, and isolation metadata instead of only output/data. - Eval subagents are one-shot (`keepAlive=false`), are unregistered/disposed after completion, and **do not share the caller's eval executor** (`shareEvalSession=false`). Their code mutations therefore do not appear in the caller's retained VM/kernel. diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 84a743e43..172c8a7ff 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Removed the per-call `model` override from the eval `agent()` helper (all runtimes), completing the earlier task-tool removal (`9f8aa87dbf`). Subagents always use their selected agent's frontmatter model and settings; a legacy `model` argument is silently ignored, so an explicit `model: "default"` can no longer route children onto the parent session model ([#6438](https://github.com/can1357/oh-my-pi/issues/6438)). + ## [17.2.7] - 2026-08-03 ### Changed diff --git a/packages/coding-agent/src/eval/agent-bridge.ts b/packages/coding-agent/src/eval/agent-bridge.ts index 07483da39..296e7726b 100644 --- a/packages/coding-agent/src/eval/agent-bridge.ts +++ b/packages/coding-agent/src/eval/agent-bridge.ts @@ -23,7 +23,6 @@ export const EVAL_AGENT_BRIDGE_NAME = "__agent__"; const agentArgsSchema = type({ prompt: "string>0", "agent?": "string>0", - "model?": "string>0|string>0[]", "label?": "string", "schema?": "unknown", "schemaMode?": "'permissive' | 'strict'", @@ -31,12 +30,12 @@ const agentArgsSchema = type({ "apply?": "boolean", "merge?": "boolean", "handle?": "boolean", + "+": "delete", }); interface EvalAgentArgs { prompt: string; agent?: string; - model?: string | string[]; label?: string; schema?: unknown; schemaMode?: StructuredSubagentSchemaMode; @@ -148,7 +147,6 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption invocationKind: "eval", assignment: parsed.prompt, ...(parsed.agent !== undefined ? { agent: parsed.agent } : {}), - ...(parsed.model !== undefined ? { model: parsed.model } : {}), ...(Object.hasOwn(parsed, "schema") ? { outputSchema: parsed.schema } : {}), ...(parsed.schemaMode !== undefined ? { schemaMode: parsed.schemaMode } : {}), ...(parsed.label !== undefined ? { identity: { label: parsed.label } } : {}), diff --git a/packages/coding-agent/src/eval/jl/prelude.jl b/packages/coding-agent/src/eval/jl/prelude.jl index 13dcfd9c7..c90481222 100644 --- a/packages/coding-agent/src/eval/jl/prelude.jl +++ b/packages/coding-agent/src/eval/jl/prelude.jl @@ -519,14 +519,11 @@ function completion(prompt::String; model="default", system=nothing, schema=noth return schema === nothing ? text : Main.json_parse(string(text)) end -function agent(prompt::String; agent="task", model=nothing, label=nothing, schema=nothing, schema_mode=nothing, isolated=nothing, apply=nothing, merge=nothing, handle=false, kwargs...) +function agent(prompt::String; agent="task", label=nothing, schema=nothing, schema_mode=nothing, isolated=nothing, apply=nothing, merge=nothing, handle=false, kwargs...) args_dict = Dict{String, Any}("prompt" => prompt) if agent !== nothing args_dict["agent"] = agent end - if model !== nothing - args_dict["model"] = model - end if label !== nothing args_dict["label"] = label end diff --git a/packages/coding-agent/src/eval/js/shared/prelude.txt b/packages/coding-agent/src/eval/js/shared/prelude.txt index c29df1c9c..9acdf51a5 100644 --- a/packages/coding-agent/src/eval/js/shared/prelude.txt +++ b/packages/coding-agent/src/eval/js/shared/prelude.txt @@ -104,8 +104,8 @@ if (!globalThis.__omp_js_prelude_loaded__) { "agent", opts, rest, - ["agent", "model", "label", "schema", "isolated", "apply", "merge", "schemaMode"], - "{ agent, model, label, schema, isolated, apply, merge, schemaMode, handle }", + ["agent", "label", "schema", "isolated", "apply", "merge", "schemaMode"], + "{ agent, label, schema, isolated, apply, merge, schemaMode, handle }", ); const { handle, ...callArgs } = o; const res = await globalThis.__omp_call_tool__("__agent__", { prompt, ...callArgs, handle: Boolean(handle) }); diff --git a/packages/coding-agent/src/eval/py/prelude.py b/packages/coding-agent/src/eval/py/prelude.py index 8d6c8fd51..491eac219 100644 --- a/packages/coding-agent/src/eval/py/prelude.py +++ b/packages/coding-agent/src/eval/py/prelude.py @@ -488,7 +488,6 @@ if "__omp_prelude_loaded__" not in globals(): prompt, *, agent="task", - model=None, label=None, schema=None, schema_mode=None, @@ -506,8 +505,6 @@ if "__omp_prelude_loaded__" not in globals(): args = {"prompt": prompt} if agent is not None: args["agent"] = agent - if model is not None: - args["model"] = model if label is not None: args["label"] = label if schema is not None: diff --git a/packages/coding-agent/src/eval/rb/prelude.rb b/packages/coding-agent/src/eval/rb/prelude.rb index ddb8991ae..c6ae9c321 100644 --- a/packages/coding-agent/src/eval/rb/prelude.rb +++ b/packages/coding-agent/src/eval/rb/prelude.rb @@ -392,10 +392,9 @@ unless defined?($__omp_prelude_loaded) && $__omp_prelude_loaded schema.nil? ? text : JSON.parse(text) end - def agent(prompt, agent: "task", model: nil, label: nil, schema: nil, schema_mode: nil, isolated: nil, apply: nil, merge: nil, handle: false) + def agent(prompt, agent: "task", label: nil, schema: nil, schema_mode: nil, isolated: nil, apply: nil, merge: nil, handle: false) args = { "prompt" => prompt } args["agent"] = agent unless agent.nil? - args["model"] = model unless model.nil? args["label"] = label unless label.nil? args["schema"] = schema unless schema.nil? args["schemaMode"] = schema_mode unless schema_mode.nil? diff --git a/packages/coding-agent/test/eval/agent-bridge-policy.test.ts b/packages/coding-agent/test/eval/agent-bridge-policy.test.ts index fb290ced8..95036176c 100644 --- a/packages/coding-agent/test/eval/agent-bridge-policy.test.ts +++ b/packages/coding-agent/test/eval/agent-bridge-policy.test.ts @@ -290,10 +290,7 @@ describe("runEvalAgent", () => { }), }); - await runEvalAgent( - { prompt: " hello ", label: "My Agent", model: "p/override", schema }, - { session, signal: abortController.signal }, - ); + await runEvalAgent({ prompt: " hello ", label: "My Agent", schema }, { session, signal: abortController.signal }); await runEvalAgent({ prompt: "plain" }, { session }); const firstOptions = runSpy.mock.calls[0]?.[0]; @@ -306,10 +303,26 @@ describe("runEvalAgent", () => { expect(firstOptions.outputSchemaOverridesAgent).toBe(true); expect(firstOptions.assignment).toBe("hello"); expect(firstOptions.description).toBe("My Agent"); - expect(firstOptions.modelOverride).toEqual(["p/override"]); + // No per-call override: the agent's own frontmatter model applies. + expect(firstOptions.modelOverride).toEqual(["p/current"]); expect(secondOptions.outputSchema).toBeUndefined(); expect(secondOptions.outputSchemaOverridesAgent).toBeUndefined(); }); + + it("drops a per-call model argument on agent() (removed, issue #6438)", async () => { + mockAgents(); + const runSpy = vi.spyOn(taskExecutor, "runSubprocess").mockImplementation(async options => singleResult(options)); + + // The schema strips unknown keys; a legacy `model` argument is silently + // discarded so resolution is identical to omitting it — the agent's own + // frontmatter model applies (issue #6438). + await runEvalAgent({ prompt: "work", model: "default" }, { session: makeSession() }); + await runEvalAgent({ prompt: "work" }, { session: makeSession() }); + + const withModel = runSpy.mock.calls[0]?.[0]; + const withoutModel = runSpy.mock.calls[1]?.[0]; + expect(withModel?.modelOverride).toEqual(withoutModel?.modelOverride); + }); it("returns host-parsed data for caller, agent, and inherited schemas", async () => { const agentSchema = { type: "object" }; const sessionSchema = { type: "object" }; diff --git a/packages/coding-agent/test/eval/prelude-agent.test.ts b/packages/coding-agent/test/eval/prelude-agent.test.ts index 4aa4019aa..e74aa34f5 100644 --- a/packages/coding-agent/test/eval/prelude-agent.test.ts +++ b/packages/coding-agent/test/eval/prelude-agent.test.ts @@ -66,12 +66,11 @@ describe("eval js agent() handle", () => { ) => Promise; const schema = { type: "object", properties: { ok: { type: "boolean" } } }; - await positionalAgent("scout", "reviewer", "p/model", "Legacy", schema, true, false, true, "strict"); + await positionalAgent("scout", "reviewer", "Legacy", schema, true, false, true, "strict"); expect(seenArgs).toEqual({ prompt: "scout", agent: "reviewer", - model: "p/model", label: "Legacy", schema, isolated: true,