quick-agent 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,7 @@ from quick_agent.orchestrator import Orchestrator
27
27
  from quick_agent.quick_agent import QuickAgent
28
28
  from quick_agent.quick_agent import build_model
29
29
  from quick_agent.quick_agent import resolve_schema
30
+ from quick_agent.prompting import make_user_prompt
30
31
 
31
32
 
32
33
  class DummyProvider:
@@ -77,7 +78,8 @@ class FakeAgent:
77
78
  def __init__(
78
79
  self,
79
80
  model: Any,
80
- instructions: str,
81
+ instructions: str | None,
82
+ system_prompt: str | list[str],
81
83
  toolsets: list[Any],
82
84
  output_type: Any,
83
85
  model_settings: Any | None = None,
@@ -85,6 +87,7 @@ class FakeAgent:
85
87
  FakeAgent.last_init = {
86
88
  "model": model,
87
89
  "instructions": instructions,
90
+ "system_prompt": system_prompt,
88
91
  "toolsets": toolsets,
89
92
  "output_type": output_type,
90
93
  "model_settings": model_settings,
@@ -188,7 +191,12 @@ def _make_loaded_with_chain(
188
191
  output=output or OutputSpec(file="out/result.json"),
189
192
  handoff=handoff or HandoffSpec(),
190
193
  )
191
- return LoadedAgentFile(spec=spec, body="system", step_prompts={"step:one": "do thing"})
194
+ return LoadedAgentFile.from_parts(
195
+ spec=spec,
196
+ instructions="system",
197
+ system_prompt="",
198
+ step_prompts={"step:one": "do thing"},
199
+ )
192
200
 
193
201
 
194
202
  def _permissions(tmp_path: Path | None = None) -> DirectoryPermissions:
@@ -237,7 +245,7 @@ def test_resolve_schema_valid_missing_and_invalid() -> None:
237
245
  chain=[ChainStepSpec(id="s1", kind="text", prompt_section="step:one")],
238
246
  schemas={"Good": "schemas.orch:GoodSchema", "Bad": "schemas.orch:NotSchema"},
239
247
  )
240
- loaded = LoadedAgentFile(spec=spec, body="", step_prompts={})
248
+ loaded = LoadedAgentFile.from_parts(spec=spec, instructions="", system_prompt="", step_prompts={})
241
249
 
242
250
  try:
243
251
  assert resolve_schema(loaded, "Good") is GoodSchema
@@ -408,21 +416,6 @@ def test_build_structured_model_settings_openai_injects_schema() -> None:
408
416
  assert json_schema_obj["strict"] is True
409
417
 
410
418
 
411
- def test_build_user_prompt_raises_for_missing_section() -> None:
412
- step = ChainStepSpec(id="s1", kind="text", prompt_section="step:missing")
413
- loaded = LoadedAgentFile(spec=_make_loaded_with_chain([step]).spec, body="body", step_prompts={})
414
- run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
415
-
416
- qa = object.__new__(QuickAgent)
417
- qa.loaded = loaded
418
- qa.run_input = run_input
419
- qa.state = {"agent_id": "agent-1", "steps": {}, "final_output": None}
420
- with pytest.raises(KeyError):
421
- qa._build_user_prompt(
422
- step=step,
423
- )
424
-
425
-
426
419
  def test_build_user_prompt_uses_prompting(monkeypatch: pytest.MonkeyPatch) -> None:
427
420
  step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
428
421
  loaded = _make_loaded_with_chain([step])
@@ -434,19 +427,40 @@ def test_build_user_prompt_uses_prompting(monkeypatch: pytest.MonkeyPatch) -> No
434
427
  qa.loaded = loaded
435
428
  qa.run_input = run_input
436
429
  qa.state = {"agent_id": "agent-1", "steps": {}, "final_output": None}
437
- result = qa._build_user_prompt(
438
- step=step,
439
- )
430
+ result = qa._build_user_prompt()
440
431
 
441
432
  assert result == "prompt"
442
433
  assert recorder.calls == [
443
434
  (
444
- (loaded.step_prompts["step:one"], run_input, {"agent_id": "agent-1", "steps": {}, "final_output": None}),
435
+ (run_input, {"agent_id": "agent-1", "steps": {}, "final_output": None}),
445
436
  {},
446
437
  )
447
438
  ]
448
439
 
449
440
 
441
+ @pytest.mark.anyio
442
+ async def test_run_text_step_raises_for_missing_section(monkeypatch: pytest.MonkeyPatch) -> None:
443
+ monkeypatch.setattr(qa_module, "Agent", FakeAgent)
444
+
445
+ step = ChainStepSpec(id="s1", kind="text", prompt_section="step:missing")
446
+ loaded = _make_loaded_with_chain([step])
447
+ run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
448
+
449
+ qa = object.__new__(QuickAgent)
450
+ qa.loaded = loaded
451
+ qa.model = cast(OpenAIChatModel, object())
452
+ qa.model_settings_json = None
453
+ qa.toolset = RecordingToolset()
454
+ qa.tool_ids = []
455
+ qa.run_input = run_input
456
+ qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
457
+
458
+ with pytest.raises(KeyError):
459
+ await qa._run_text_step(
460
+ step=step,
461
+ )
462
+
463
+
450
464
  @pytest.mark.anyio
451
465
  async def test_run_step_text_returns_output(monkeypatch: pytest.MonkeyPatch) -> None:
452
466
  monkeypatch.setattr(qa_module, "Agent", FakeAgent)
@@ -461,6 +475,7 @@ async def test_run_step_text_returns_output(monkeypatch: pytest.MonkeyPatch) ->
461
475
  qa.model = cast(OpenAIChatModel, object())
462
476
  qa.model_settings_json = None
463
477
  qa.toolset = RecordingToolset()
478
+ qa.tool_ids = []
464
479
  qa.run_input = run_input
465
480
  qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
466
481
  output, final = await qa._run_step(
@@ -470,10 +485,37 @@ async def test_run_step_text_returns_output(monkeypatch: pytest.MonkeyPatch) ->
470
485
  assert output == "hello"
471
486
  assert final == "hello"
472
487
  assert FakeAgent.last_init is not None
473
- assert FakeAgent.last_init["instructions"] == "system"
488
+ assert FakeAgent.last_init["instructions"] == "systemdo thing"
489
+ assert FakeAgent.last_init["system_prompt"] == []
474
490
  assert FakeAgent.last_init["output_type"] is str
475
491
 
476
492
 
493
+ @pytest.mark.anyio
494
+ async def test_run_text_step_omits_tools_when_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
495
+ monkeypatch.setattr(qa_module, "Agent", FakeAgent)
496
+ FakeAgent.next_output = "hello"
497
+
498
+ step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
499
+ loaded = _make_loaded_with_chain([step])
500
+ run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
501
+
502
+ qa = object.__new__(QuickAgent)
503
+ qa.loaded = loaded
504
+ qa.model = cast(OpenAIChatModel, object())
505
+ qa.model_settings_json = None
506
+ qa.toolset = RecordingToolset()
507
+ qa.run_input = run_input
508
+ qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
509
+ qa.tool_ids = []
510
+
511
+ await qa._run_text_step(
512
+ step=step,
513
+ )
514
+
515
+ assert FakeAgent.last_init is not None
516
+ assert FakeAgent.last_init["toolsets"] == []
517
+
518
+
477
519
  @pytest.mark.anyio
478
520
  async def test_run_step_structured_parses_json_with_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
479
521
  monkeypatch.setattr(qa_module, "Agent", FakeAgent)
@@ -490,7 +532,12 @@ async def test_run_step_structured_parses_json_with_fallback(monkeypatch: pytest
490
532
  chain=[step],
491
533
  schemas={"Example": "schemas.struct:ExampleSchema"},
492
534
  )
493
- loaded = LoadedAgentFile(spec=spec, body="system", step_prompts={"step:one": "do thing"})
535
+ loaded = LoadedAgentFile.from_parts(
536
+ spec=spec,
537
+ instructions="system",
538
+ system_prompt="",
539
+ step_prompts={"step:one": "do thing"},
540
+ )
494
541
  run_input = RunInput(source_path="in.json", kind="json", text="{}", data={})
495
542
 
496
543
  try:
@@ -499,6 +546,7 @@ async def test_run_step_structured_parses_json_with_fallback(monkeypatch: pytest
499
546
  qa.model = cast(OpenAIChatModel, object())
500
547
  qa.model_settings_json = {"extra_body": {"format": "json"}}
501
548
  qa.toolset = RecordingToolset()
549
+ qa.tool_ids = []
502
550
  qa.run_input = run_input
503
551
  qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
504
552
  output, final = await qa._run_step(
@@ -510,6 +558,8 @@ async def test_run_step_structured_parses_json_with_fallback(monkeypatch: pytest
510
558
  assert output == {"x": 7}
511
559
  assert isinstance(final, ExampleSchema)
512
560
  assert final.x == 7
561
+ assert FakeAgent.last_init is not None
562
+ assert FakeAgent.last_init["output_type"] is ExampleSchema
513
563
 
514
564
 
515
565
  @pytest.mark.anyio
@@ -544,6 +594,7 @@ async def test_run_text_step_uses_build_user_prompt(monkeypatch: pytest.MonkeyPa
544
594
  qa.loaded = loaded
545
595
  qa.model = cast(OpenAIChatModel, object())
546
596
  qa.toolset = RecordingToolset()
597
+ qa.tool_ids = []
547
598
  qa.run_input = run_input
548
599
  monkeypatch.setattr(qa, "_build_user_prompt", SyncCallRecorder(return_value="prompt"))
549
600
 
@@ -556,6 +607,129 @@ async def test_run_text_step_uses_build_user_prompt(monkeypatch: pytest.MonkeyPa
556
607
  assert FakeAgent.last_prompt == "prompt"
557
608
 
558
609
 
610
+ @pytest.mark.anyio
611
+ async def test_run_text_step_no_instructions_or_system_prompt(monkeypatch: pytest.MonkeyPatch) -> None:
612
+ monkeypatch.setattr(qa_module, "Agent", FakeAgent)
613
+ FakeAgent.next_output = "ok"
614
+
615
+ step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
616
+ spec = AgentSpec(
617
+ name="test",
618
+ model=ModelSpec(base_url="http://x", model_name="m"),
619
+ chain=[step],
620
+ output=OutputSpec(file=None),
621
+ )
622
+ loaded = LoadedAgentFile.from_parts(
623
+ spec=spec,
624
+ instructions="",
625
+ system_prompt="",
626
+ step_prompts={"step:one": "do thing"},
627
+ )
628
+ run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
629
+
630
+ qa = object.__new__(QuickAgent)
631
+ qa.loaded = loaded
632
+ qa.model = cast(OpenAIChatModel, object())
633
+ qa.model_settings_json = None
634
+ qa.toolset = RecordingToolset()
635
+ qa.tool_ids = []
636
+ qa.run_input = run_input
637
+ qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
638
+
639
+ output, final = await qa._run_text_step(
640
+ step=step,
641
+ )
642
+
643
+ assert output == "ok"
644
+ assert final == "ok"
645
+ assert FakeAgent.last_init is not None
646
+ assert FakeAgent.last_init["instructions"] == "do thing"
647
+ assert FakeAgent.last_init["system_prompt"] == []
648
+ assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
649
+
650
+
651
+ @pytest.mark.anyio
652
+ async def test_run_text_step_system_prompt_only(monkeypatch: pytest.MonkeyPatch) -> None:
653
+ monkeypatch.setattr(qa_module, "Agent", FakeAgent)
654
+ FakeAgent.next_output = "ok"
655
+
656
+ step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
657
+ spec = AgentSpec(
658
+ name="test",
659
+ model=ModelSpec(base_url="http://x", model_name="m"),
660
+ chain=[step],
661
+ output=OutputSpec(file=None),
662
+ )
663
+ loaded = LoadedAgentFile.from_parts(
664
+ spec=spec,
665
+ instructions="",
666
+ system_prompt="You are concise.",
667
+ step_prompts={"step:one": "do thing"},
668
+ )
669
+ run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
670
+
671
+ qa = object.__new__(QuickAgent)
672
+ qa.loaded = loaded
673
+ qa.model = cast(OpenAIChatModel, object())
674
+ qa.model_settings_json = None
675
+ qa.toolset = RecordingToolset()
676
+ qa.tool_ids = []
677
+ qa.run_input = run_input
678
+ qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
679
+
680
+ output, final = await qa._run_text_step(
681
+ step=step,
682
+ )
683
+
684
+ assert output == "ok"
685
+ assert final == "ok"
686
+ assert FakeAgent.last_init is not None
687
+ assert FakeAgent.last_init["instructions"] == "do thing"
688
+ assert FakeAgent.last_init["system_prompt"] == "You are concise."
689
+ assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
690
+
691
+
692
+ @pytest.mark.anyio
693
+ async def test_run_text_step_instructions_only(monkeypatch: pytest.MonkeyPatch) -> None:
694
+ monkeypatch.setattr(qa_module, "Agent", FakeAgent)
695
+ FakeAgent.next_output = "ok"
696
+
697
+ step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
698
+ spec = AgentSpec(
699
+ name="test",
700
+ model=ModelSpec(base_url="http://x", model_name="m"),
701
+ chain=[step],
702
+ output=OutputSpec(file=None),
703
+ )
704
+ loaded = LoadedAgentFile.from_parts(
705
+ spec=spec,
706
+ instructions="Use the tool.",
707
+ system_prompt="",
708
+ step_prompts={"step:one": "do thing"},
709
+ )
710
+ run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
711
+
712
+ qa = object.__new__(QuickAgent)
713
+ qa.loaded = loaded
714
+ qa.model = cast(OpenAIChatModel, object())
715
+ qa.model_settings_json = None
716
+ qa.toolset = RecordingToolset()
717
+ qa.tool_ids = []
718
+ qa.run_input = run_input
719
+ qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
720
+
721
+ output, final = await qa._run_text_step(
722
+ step=step,
723
+ )
724
+
725
+ assert output == "ok"
726
+ assert final == "ok"
727
+ assert FakeAgent.last_init is not None
728
+ assert FakeAgent.last_init["instructions"] == "Use the tool.do thing"
729
+ assert FakeAgent.last_init["system_prompt"] == []
730
+ assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
731
+
732
+
559
733
  @pytest.mark.anyio
560
734
  async def test_run_structured_step_missing_schema_raises() -> None:
561
735
  step = ChainStepSpec(id="s1", kind="structured", prompt_section="step:one", output_schema=None)
@@ -590,7 +764,12 @@ async def test_run_structured_step_parses_json(monkeypatch: pytest.MonkeyPatch)
590
764
  chain=[step],
591
765
  schemas={"Example": "schemas.struct2:ExampleSchema"},
592
766
  )
593
- loaded = LoadedAgentFile(spec=spec, body="system", step_prompts={"step:one": "do thing"})
767
+ loaded = LoadedAgentFile.from_parts(
768
+ spec=spec,
769
+ instructions="system",
770
+ system_prompt="",
771
+ step_prompts={"step:one": "do thing"},
772
+ )
594
773
  run_input = RunInput(source_path="in.json", kind="json", text="{}", data={})
595
774
 
596
775
  try:
@@ -599,6 +778,7 @@ async def test_run_structured_step_parses_json(monkeypatch: pytest.MonkeyPatch)
599
778
  qa.model = cast(OpenAIChatModel, object())
600
779
  qa.model_settings_json = None
601
780
  qa.toolset = RecordingToolset()
781
+ qa.tool_ids = []
602
782
  qa.run_input = run_input
603
783
  qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
604
784
  output, final = await qa._run_structured_step(
@@ -627,7 +807,12 @@ async def test_run_structured_step_adds_json_schema_for_openai(monkeypatch: pyte
627
807
  chain=[step],
628
808
  schemas={"Example": "schemas.struct3:ExampleSchema"},
629
809
  )
630
- loaded = LoadedAgentFile(spec=spec, body="system", step_prompts={"step:one": "do thing"})
810
+ loaded = LoadedAgentFile.from_parts(
811
+ spec=spec,
812
+ instructions="system",
813
+ system_prompt="",
814
+ step_prompts={"step:one": "do thing"},
815
+ )
631
816
  run_input = RunInput(source_path="in.json", kind="json", text="{}", data={})
632
817
 
633
818
  try:
@@ -636,6 +821,7 @@ async def test_run_structured_step_adds_json_schema_for_openai(monkeypatch: pyte
636
821
  qa.model = cast(OpenAIChatModel, DummyOpenAIModel("https://api.openai.com/v1"))
637
822
  qa.model_settings_json = None
638
823
  qa.toolset = RecordingToolset()
824
+ qa.tool_ids = []
639
825
  qa.run_input = run_input
640
826
  qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
641
827
  await qa._run_structured_step(
@@ -675,6 +861,80 @@ async def test_run_chain_updates_state_and_returns_last() -> None:
675
861
  assert qa.calls == ["s1", "s2"]
676
862
 
677
863
 
864
+ @pytest.mark.anyio
865
+ async def test_run_chain_single_shot_system_prompt_only(monkeypatch: pytest.MonkeyPatch) -> None:
866
+ monkeypatch.setattr(qa_module, "Agent", FakeAgent)
867
+ FakeAgent.next_output = "hello"
868
+
869
+ spec = AgentSpec(
870
+ name="test",
871
+ model=ModelSpec(base_url="http://x", model_name="m"),
872
+ chain=[],
873
+ output=OutputSpec(file=None),
874
+ )
875
+ loaded = LoadedAgentFile.from_parts(
876
+ spec=spec,
877
+ instructions="",
878
+ system_prompt="You are concise.",
879
+ step_prompts={},
880
+ )
881
+ run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
882
+
883
+ qa = object.__new__(QuickAgent)
884
+ qa.loaded = loaded
885
+ qa.model = cast(OpenAIChatModel, object())
886
+ qa.model_settings_json = None
887
+ qa.toolset = RecordingToolset()
888
+ qa.tool_ids = []
889
+ qa.run_input = run_input
890
+ qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
891
+
892
+ output = await qa._run_chain()
893
+
894
+ assert output == "hello"
895
+ assert FakeAgent.last_init is not None
896
+ assert FakeAgent.last_init["instructions"] is None
897
+ assert FakeAgent.last_init["system_prompt"] == "You are concise."
898
+ assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
899
+
900
+
901
+ @pytest.mark.anyio
902
+ async def test_run_chain_single_shot_instructions_only(monkeypatch: pytest.MonkeyPatch) -> None:
903
+ monkeypatch.setattr(qa_module, "Agent", FakeAgent)
904
+ FakeAgent.next_output = "hello"
905
+
906
+ spec = AgentSpec(
907
+ name="test",
908
+ model=ModelSpec(base_url="http://x", model_name="m"),
909
+ chain=[],
910
+ output=OutputSpec(file=None),
911
+ )
912
+ loaded = LoadedAgentFile.from_parts(
913
+ spec=spec,
914
+ instructions="Use the tool.",
915
+ system_prompt="",
916
+ step_prompts={},
917
+ )
918
+ run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
919
+
920
+ qa = object.__new__(QuickAgent)
921
+ qa.loaded = loaded
922
+ qa.model = cast(OpenAIChatModel, object())
923
+ qa.model_settings_json = None
924
+ qa.toolset = RecordingToolset()
925
+ qa.tool_ids = []
926
+ qa.run_input = run_input
927
+ qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
928
+
929
+ output = await qa._run_chain()
930
+
931
+ assert output == "hello"
932
+ assert FakeAgent.last_init is not None
933
+ assert FakeAgent.last_init["instructions"] == "Use the tool."
934
+ assert FakeAgent.last_init["system_prompt"] == []
935
+ assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
936
+
937
+
678
938
  def test_write_final_output_serializes_model(tmp_path: Path) -> None:
679
939
  safe_root = tmp_path / "safe"
680
940
  out_path = safe_root / "out.json"
@@ -768,7 +1028,12 @@ async def test_run_agent_wires_dependencies(monkeypatch: pytest.MonkeyPatch, tmp
768
1028
  tools=["tool.a", "agent.call", "tool.a"],
769
1029
  output=OutputSpec(file=str(tmp_path / "out.json")),
770
1030
  )
771
- loaded = LoadedAgentFile(spec=spec, body="system", step_prompts={"step:one": "do thing"})
1031
+ loaded = LoadedAgentFile.from_parts(
1032
+ spec=spec,
1033
+ instructions="system",
1034
+ system_prompt="",
1035
+ step_prompts={"step:one": "do thing"},
1036
+ )
772
1037
 
773
1038
  run_input = RunInput(source_path=str(tmp_path / "input.json"), kind="json", text="{}", data={})
774
1039
  toolset = RecordingToolset()
@@ -819,6 +1084,8 @@ async def test_run_agent_wires_dependencies(monkeypatch: pytest.MonkeyPatch, tmp
819
1084
  assert load_args[1].root == _permissions(tmp_path).root
820
1085
  assert build_model_recorder.calls == [((loaded.spec.model,), {})]
821
1086
 
1087
+ assert build_settings_recorder.calls == [((loaded.spec.model,), {})]
1088
+
822
1089
  assert build_toolset_recorder.calls
823
1090
  args, kwargs = build_toolset_recorder.calls[0]
824
1091
  assert kwargs == {}
@@ -829,7 +1096,6 @@ async def test_run_agent_wires_dependencies(monkeypatch: pytest.MonkeyPatch, tmp
829
1096
  ]
830
1097
  assert isinstance(args[1], DirectoryPermissions)
831
1098
 
832
- assert build_settings_recorder.calls == [((loaded.spec.model,), {})]
833
1099
  maybe_args, maybe_kwargs = maybe_inject_recorder.calls[0]
834
1100
  assert maybe_kwargs == {}
835
1101
  assert maybe_args[0] == [
@@ -851,6 +1117,65 @@ async def test_run_agent_wires_dependencies(monkeypatch: pytest.MonkeyPatch, tmp
851
1117
  assert handoff_recorder.calls == [{"args": ("final",), "kwargs": {}}]
852
1118
 
853
1119
 
1120
+ @pytest.mark.anyio
1121
+ async def test_run_skips_write_when_output_file_missing(
1122
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
1123
+ ) -> None:
1124
+ step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
1125
+ spec = AgentSpec(
1126
+ name="test",
1127
+ model=ModelSpec(base_url="http://x", model_name="m"),
1128
+ chain=[step],
1129
+ output=OutputSpec(file=None),
1130
+ )
1131
+ loaded = LoadedAgentFile.from_parts(
1132
+ spec=spec,
1133
+ instructions="system",
1134
+ system_prompt="",
1135
+ step_prompts={"step:one": "do thing"},
1136
+ )
1137
+
1138
+ run_input = RunInput(source_path=str(tmp_path / "input.json"), kind="json", text="{}", data={})
1139
+ toolset = RecordingToolset()
1140
+ model = object()
1141
+
1142
+ load_input_recorder = SyncCallRecorder(return_value=run_input)
1143
+ build_model_recorder = SyncCallRecorder(return_value=model)
1144
+ build_toolset_recorder = SyncCallRecorder(return_value=toolset)
1145
+ build_settings_recorder = SyncCallRecorder(return_value=None)
1146
+ maybe_inject_recorder = SyncCallRecorder(return_value=None)
1147
+ run_chain_recorder = AsyncCallRecorder(return_value="final")
1148
+ write_output_recorder = SyncCallRecorder(return_value=tmp_path / "out.json")
1149
+ handoff_recorder = AsyncCallRecorder(return_value=None)
1150
+
1151
+ monkeypatch.setattr(input_adaptors_module, "load_input", load_input_recorder)
1152
+ monkeypatch.setattr(qa_module, "build_model", build_model_recorder)
1153
+ monkeypatch.setattr(QuickAgent, "_build_model_settings", build_settings_recorder)
1154
+ monkeypatch.setattr(QuickAgent, "_run_chain", run_chain_recorder)
1155
+ monkeypatch.setattr(QuickAgent, "_write_final_output", write_output_recorder)
1156
+ monkeypatch.setattr(QuickAgent, "_handle_handoff", handoff_recorder)
1157
+
1158
+ tools = AgentTools([tmp_path])
1159
+ monkeypatch.setattr(tools, "build_toolset", build_toolset_recorder)
1160
+ monkeypatch.setattr(tools, "maybe_inject_agent_call", maybe_inject_recorder)
1161
+ fake_registry = FakeRegistry(loaded)
1162
+
1163
+ agent = QuickAgent(
1164
+ registry=fake_registry,
1165
+ tools=tools,
1166
+ directory_permissions=_permissions(tmp_path),
1167
+ agent_id="agent-1",
1168
+ input_data=tmp_path / "input.json",
1169
+ extra_tools=None,
1170
+ )
1171
+
1172
+ result = await agent.run()
1173
+
1174
+ assert result == "final"
1175
+ assert write_output_recorder.calls == []
1176
+ assert handoff_recorder.calls == [{"args": ("final",), "kwargs": {}}]
1177
+
1178
+
854
1179
  @pytest.mark.anyio
855
1180
  @pytest.mark.parametrize(
856
1181
  ("nested_output", "expected_write_output"),