revert(core): drop the agent revival machinery; the engine already resumes

A user message already resumes a failed agent's lifecycle from where it stopped.
`send` lifts that agent's human-wake gate, `wait_for_message` returns, the loop
resets the nudge and idle-resume budgets - "real input is real progress" - and
`mark_running` retires the error before the next turn. Any agent can be
addressed: `_send_message` takes an arbitrary agent id, so a failed child is
reachable by selecting it, exactly as a parked one is.

So there was nothing here to fix. The premise behind these commits - that only
the root is reachable, stranding every other agent that dies - was wrong, and
each revision built a larger mechanism on it: a scan-wide revival, then an
attempt budget to bound the runaway that revival introduced.

What remains is the TUI, which is where the reported symptom always was: an
error that had already resolved stayed on the screen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
0xallam 2026-08-28 21:30:56 +03:00
parent ad97ccd58c
commit b1174ed95a
2 changed files with 0 additions and 173 deletions

View file

@ -22,15 +22,6 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
_REVIVED_NOTICE = {
"from": "system",
"type": "revived",
"content": (
"[Recovery] Your last turn failed and the user has since sent a message. "
"Pick your current task back up from where it stopped."
),
}
Status = Literal["running", "waiting", "completed", "stopped", "crashed", "failed", "budget_paused"]
# Why an agent parked. The user can message any agent, so this - not the agent's
@ -129,36 +120,6 @@ class AgentCoordinator:
},
)
async def revive_failed_agents(self, *, exclude: str | None = None) -> list[str]:
"""Lift the human-wake gate on every agent that died, on the user's word.
A failed agent parks until a human tells it to go again. The user can only
realistically reach the root, so that word never arrives for anything below
it and a child that dies stays dead for the rest of the scan - however it
died. A message from the user is that word for all of them: it says a person
is present and has changed something. Nothing else revives an agent, so
there is no runaway here to bound - the person deciding whether to try again
is the limit.
"""
revived: list[str] = []
async with self._lock:
for agent_id, status in self.statuses.items():
if agent_id == exclude or status not in {"failed", "crashed"}:
continue
self.statuses[agent_id] = "waiting"
self.errors.pop(agent_id, None)
runtime = self.runtimes.setdefault(agent_id, AgentRuntime())
runtime.user_wake_required = False
# An empty mailbox parks the agent straight back where it was, so the
# note is what actually resumes it as well as what explains the gap.
runtime.mailbox.append(dict(_REVIVED_NOTICE))
self.pending_counts[agent_id] = self.pending_counts.get(agent_id, 0) + 1
runtime.wake.set()
revived.append(agent_id)
if revived:
logger.info("user message revived %d failed agent(s)", len(revived))
return revived
async def reset_budget_stops(
self,
*,
@ -321,10 +282,6 @@ class AgentCoordinator:
from_user = message.get("from") == "user"
if from_user and self._budget_paused:
await self.resume_from_budget_pause(exclude=target_agent_id)
if from_user:
# The gate below only unsticks the agent addressed. Everything else that
# died is waiting on the same human, who has just spoken.
await self.revive_failed_agents(exclude=target_agent_id)
async with self._lock:
if target_agent_id not in self.statuses:
logger.debug("agent.send dropped unknown target=%s", target_agent_id)

View file

@ -1,130 +0,0 @@
"""A user's message revives every agent that died, not just the one addressed.
A failed agent parks until a human tells it to go again, and the user can only
realistically reach the root. Drawn from a run where a provider outage failed
eighteen agents in thirteen seconds and only the root ever came back.
"""
from __future__ import annotations
import pytest
from strix.core.agents import AgentCoordinator
async def _fleet(children: int = 3) -> AgentCoordinator:
coordinator = AgentCoordinator()
await coordinator.register("root", "strix", parent_id=None)
for index in range(children):
await coordinator.register(f"child{index}", f"hunter {index}", parent_id="root")
return coordinator
def _user_message(text: str = "credits are back, keep going") -> dict[str, str]:
return {"from": "user", "content": text, "type": "instruction"}
@pytest.mark.asyncio
async def test_a_user_message_revives_the_whole_fleet() -> None:
"""The reported scenario: everything is down and only the root is reachable."""
coordinator = await _fleet()
for agent_id in ("root", "child0", "child1", "child2"):
await coordinator.set_status(agent_id, "failed", error="provider said no")
assert all(coordinator.runtimes[f"child{i}"].user_wake_required for i in range(3))
await coordinator.send("root", _user_message())
for index in range(3):
child = f"child{index}"
assert coordinator.statuses[child] == "waiting", f"{child} was left for dead"
assert coordinator.runtimes[child].user_wake_required is False
assert coordinator.pending_counts[child] == 1
assert child not in coordinator.errors
# The addressed agent is woken by the user's own message, not a notice.
assert coordinator.runtimes["root"].user_wake_required is False
assert coordinator.runtimes["root"].mailbox == [_user_message()]
@pytest.mark.asyncio
async def test_the_revived_agent_wakes_on_the_notice() -> None:
"""An empty mailbox parks the agent straight back where it was."""
coordinator = await _fleet(children=1)
await coordinator.set_status("child0", "failed", error="down")
await coordinator.send("root", _user_message())
assert await coordinator.wait_for_message("child0", timeout=0.1) is True
count, _items = await coordinator.consume_pending("child0")
assert count == 1
@pytest.mark.asyncio
async def test_revival_is_not_rationed() -> None:
"""A human deciding to try again is the only limit there needs to be."""
coordinator = await _fleet(children=1)
for attempt in range(6):
await coordinator.set_status("child0", "failed", error="down again")
await coordinator.send("root", _user_message())
assert coordinator.statuses["child0"] == "waiting", f"attempt {attempt} was refused"
@pytest.mark.parametrize("status", ["failed", "crashed"])
@pytest.mark.asyncio
async def test_revival_does_not_care_how_the_agent_died(status: str) -> None:
coordinator = await _fleet(children=1)
await coordinator.set_status("child0", status, error="whatever it was")
await coordinator.send("root", _user_message())
assert coordinator.statuses["child0"] == "waiting"
@pytest.mark.parametrize("status", ["running", "waiting", "completed", "stopped", "budget_paused"])
@pytest.mark.asyncio
async def test_agents_that_did_not_die_are_left_alone(status: str) -> None:
"""A deliberate stop - budget, user request - is not a failure to recover from."""
coordinator = await _fleet(children=1)
await coordinator.set_status("child0", status)
await coordinator.send("root", _user_message())
assert coordinator.statuses["child0"] == status
assert coordinator.pending_counts["child0"] == 0
assert coordinator.runtimes["child0"].mailbox == []
@pytest.mark.asyncio
async def test_messaging_a_child_revives_its_failed_siblings() -> None:
"""A person is present either way; which agent they addressed is incidental."""
coordinator = await _fleet(children=2)
await coordinator.set_status("child0", "failed", error="down")
await coordinator.set_status("child1", "failed", error="down")
await coordinator.send("child0", _user_message("look at this instead"))
assert coordinator.statuses["child1"] == "waiting"
assert coordinator.runtimes["child0"].user_wake_required is False
@pytest.mark.asyncio
async def test_an_agent_message_revives_nobody() -> None:
"""Only a human clears the gate; agents talking among themselves must not."""
coordinator = await _fleet(children=2)
await coordinator.set_status("child1", "failed", error="down")
await coordinator.send("child0", {"from": "root", "content": "status?"})
assert coordinator.statuses["child1"] == "failed"
assert coordinator.runtimes["child1"].user_wake_required is True
@pytest.mark.asyncio
async def test_a_message_to_an_unknown_agent_still_revives_the_fleet() -> None:
"""The user spoke; a stale target id should not swallow that."""
coordinator = await _fleet(children=1)
await coordinator.set_status("child0", "failed", error="down")
assert await coordinator.send("gone", _user_message()) is False
assert coordinator.statuses["child0"] == "waiting"