Phase 3 reference solution
An example scenario for an edge LOOP-1…5 never exercises: malformed JSON in the model’s tool-call arguments, which the docstring contract says must fall back to an empty dict rather than crash. Append to features/agent_loop.feature:
Scenario: LOOP-6 malformed tool arguments fall back to an empty dict
Given the scripted model calls tool "travel_time" with malformed arguments, then yields
When the agent loop runs
Then the loop ran 2 rounds
And tool "travel_time" got empty arguments
And the two new steps in features/steps/agent_loop_steps.py (the existing _call helper always emits valid JSON, so build the malformed call directly):
@given('the scripted model calls tool "{name}" with malformed arguments, then yields')
def step_call_malformed(context, name):
call = SimpleNamespace(
id="call-1",
type="function",
function=SimpleNamespace(name=name, arguments="{not json"),
)
context.script = [
_turn("<think>bad args</think>", [call]),
_turn("<think>done</think>recovered"),
]
@then('tool "{name}" got empty arguments')
def step_empty_args(context, name):
assert (name, {}) in context.calls, context.calls
Verified: all 6 scenarios pass against the phase-2 reference loop.