mirror of
https://github.com/usestrix/strix.git
synced 2026-08-28 05:25:00 +00:00
fix(core): settle a non-interactive agent's status before its exception unwinds
An exception escaping a non-interactive cycle re-raised before the status handling, so a dying child stayed 'running' and its parent waited out the timeout on a completion report the child could no longer send. Set the terminal status and wake the parent on the way out too.
This commit is contained in:
parent
4a455b1e62
commit
3bcf3778f0
2 changed files with 40 additions and 3 deletions
|
|
@ -780,17 +780,21 @@ async def _run_cycle( # noqa: PLR0912, PLR0915
|
||||||
await coordinator.set_status(agent_id, "failed", error=str(exc))
|
await coordinator.set_status(agent_id, "failed", error=str(exc))
|
||||||
await notify_parent_on_terminal(coordinator, agent_id, "failed")
|
await notify_parent_on_terminal(coordinator, agent_id, "failed")
|
||||||
return None
|
return None
|
||||||
if not interactive:
|
|
||||||
raise
|
|
||||||
if isinstance(exc, MaxTurnsExceeded):
|
if isinstance(exc, MaxTurnsExceeded):
|
||||||
status: Status = "stopped"
|
status: Status = "stopped"
|
||||||
elif isinstance(exc, UserError | AgentsException | APIError):
|
elif isinstance(exc, UserError | AgentsException | APIError):
|
||||||
status = "failed"
|
status = "failed"
|
||||||
else:
|
else:
|
||||||
status = "crashed"
|
status = "crashed"
|
||||||
logger.exception("agent run failed for %s; parking as %s", agent_id, status)
|
logger.exception("agent run failed for %s; marking %s", agent_id, status)
|
||||||
|
# Settle the status and wake the parent before the exception unwinds a
|
||||||
|
# non-interactive agent's task: a child that dies still owes its parent a
|
||||||
|
# report, and the parent would otherwise wait out its timeout on a message
|
||||||
|
# the dead child can no longer send.
|
||||||
await coordinator.set_status(agent_id, status, error=str(exc) or type(exc).__name__)
|
await coordinator.set_status(agent_id, status, error=str(exc) or type(exc).__name__)
|
||||||
await notify_parent_on_terminal(coordinator, agent_id, status)
|
await notify_parent_on_terminal(coordinator, agent_id, status)
|
||||||
|
if not interactive:
|
||||||
|
raise
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return cast("RunResultBase | None", stream)
|
return cast("RunResultBase | None", stream)
|
||||||
|
|
|
||||||
|
|
@ -855,6 +855,39 @@ async def test_structured_provider_refusal_fails_noninteractive_child(
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_crashing_noninteractive_child_settles_and_wakes_its_parent(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
# The exception ends the child's task, so its status and the parent's wake-up
|
||||||
|
# have to be settled on the way out or the parent waits on a dead child.
|
||||||
|
def _boom(*_args: Any, **_kwargs: Any) -> Any:
|
||||||
|
raise RuntimeError("sandbox died mid-turn")
|
||||||
|
|
||||||
|
monkeypatch.setattr("strix.core.execution.Runner.run_streamed", _boom)
|
||||||
|
coordinator = AgentCoordinator()
|
||||||
|
await coordinator.register("root", "strix", parent_id=None)
|
||||||
|
await coordinator.register("child", "recon", parent_id="root")
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="sandbox died mid-turn"):
|
||||||
|
await execution._run_cycle(
|
||||||
|
MagicMock(),
|
||||||
|
coordinator,
|
||||||
|
"child",
|
||||||
|
input_data="task",
|
||||||
|
run_config=MagicMock(),
|
||||||
|
context={"parent_id": "root"},
|
||||||
|
max_turns=5,
|
||||||
|
session=None,
|
||||||
|
interactive=False,
|
||||||
|
event_sink=None,
|
||||||
|
hooks=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert coordinator.statuses["child"] == "crashed"
|
||||||
|
assert coordinator.pending_counts.get("root", 0) > 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_run_agent_loop_seeds_identity_before_first_cycle(
|
async def test_run_agent_loop_seeds_identity_before_first_cycle(
|
||||||
tmp_path: Any, monkeypatch: pytest.MonkeyPatch
|
tmp_path: Any, monkeypatch: pytest.MonkeyPatch
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue