Inherit model overrides in parallel tool workers
Alessandro committed
Aug 25, 2026 at 20:19 UTC
cf29923cedcfa73a826cb580b4c45979b237c2c3
3 files changed
+45
helpers/parallel_tools.py
+4
@@ -477,6 +477,10 @@ async def _run_direct_tool_job(parent_context_id: str, job: ParallelJob) -> str:
477
worker_context.set_data(PARALLEL_WORKER_PARENT_CONTEXT_KEY, parent_context_id)
478
worker_context.set_data(PARALLEL_WORKER_JOB_KEY, job.id)
479
worker_context.set_data(PARALLEL_WORKER_KIND_KEY, job.kind)
480
+ worker_context.set_data(
481
+ "chat_model_override",
482
+ parent_context.get_data("chat_model_override"),
483
+ )
484
job.worker_context_id = worker_context.id
485
_copy_project(parent_context, worker_context)
486
helpers/parallel_tools.py.dox.md
+1
@@ -32,6 +32,7 @@
32
- Subordinate child chats are tagged with job metadata, remain outside the scheduler task list, and may use normal child-chat tools including `parallel`.
33
- Nested parallel jobs started by a parallel subordinate are registered as child `DeferredTask` instances so stopping the ancestor also stops its descendants.
34
- Direct tool jobs run in isolated background contexts and are blocked from recursively invoking `parallel`.
35
+- Direct tool jobs inherit the parent's active per-chat model override.
36
- Direct tool background context cleanup removes both the in-memory context and any transient chat folder left on disk.
37
- Parent-visible child log items are created for each wrapped call so the WebUI can inspect concurrent children separately while the wrapper result remains model-history-only.
38
- Child tool logs mirror normal tool-call visible args; job ids remain available through wrapper results and prompt extras rather than visible process-step args.
tests/test_parallel_tool.py
+40
@@ -347,6 +347,46 @@ async def test_parallel_remove_context_deletes_persisted_worker_chat(monkeypatch
347
assert removed == ["missing-worker"]
348
349
350
+@pytest.mark.asyncio
351
+async def test_direct_parallel_worker_inherits_chat_model_override(monkeypatch) -> None:
352
+ from agent import AgentConfig, AgentContext
353
+
354
+ parent_id = "ctx-parallel-model-override"
355
+ AgentContext.remove(parent_id)
356
+ parent = AgentContext(
357
+ AgentConfig(mcp_servers="", profile="agent0"),
358
+ id=parent_id,
359
+ set_current=False,
360
+ )
361
+ override = {"preset_name": "Text only"}
362
+ parent.set_data("chat_model_override", override)
363
+ observed = {}
364
+
365
+ async def fake_execute_tool_call(agent, *_args, **_kwargs):
366
+ observed["override"] = agent.context.get_data("chat_model_override")
367
+ return "done"
368
+
369
+ async def remove_context(context_id):
370
+ AgentContext.remove(context_id)
371
+
372
+ monkeypatch.setattr(parallel_tools, "execute_tool_call", fake_execute_tool_call)
373
+ monkeypatch.setattr(parallel_tools, "_remove_context", remove_context)
374
+ job = parallel_tools.ParallelJob(
375
+ id="vision-load-override",
376
+ parent_context_id=parent_id,
377
+ index=0,
378
+ tool_name="vision_load",
379
+ tool_args={"paths": ["/tmp/example.png"]},
380
+ kind="tool",
381
+ )
382
+
383
+ try:
384
+ assert await parallel_tools._run_direct_tool_job(parent_id, job) == "done"
385
+ assert observed["override"] == override
386
+ finally:
387
+ AgentContext.remove(parent_id)
388
+
389
+
390
@pytest.mark.asyncio
391
async def test_parallel_recursion_guard_allows_subordinate_children_but_blocks_tool_workers() -> None:
392
from extensions.python.tool_execute_before._20_block_parallel_recursion import (