Let the supervisor stop one flow's tasks and clear its quarantine

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01StpRc2C6au1WJ1EUU7fsfu
This commit is contained in:
2026-08-23 17:43:15 +02:00
co-authored by Claude Opus 5
parent 294b72d989
commit 3bb23fbd3e
2 changed files with 74 additions and 0 deletions
+28
View File
@@ -44,6 +44,10 @@ class Supervisor:
def __init__(self, events: EventBus | None = None) -> None:
self._events = events
self._tasks: dict[str, asyncio.Task[None]] = {}
#: Which flow each task belongs to. Recorded rather than read off the
#: task's name, because names are a flow and a node joined by a dot and
#: matching on that prefix would let 'hea' cancel 'heating'.
self._flows: dict[str, str] = {}
self._failures: dict[str, deque[float]] = {}
self.quarantined: set[str] = set()
@@ -51,6 +55,7 @@ class Supervisor:
"""Run `factory()` and keep running it until told to stop."""
if name in self._tasks:
return
self._flows[name] = flow
self._tasks[name] = asyncio.create_task(
self._supervise(name, flow, factory), name=f"supervised:{name}"
)
@@ -116,6 +121,29 @@ class Supervisor:
"""Stop supervising. Idempotent, and safe to call mid-restart."""
tasks = list(self._tasks.values())
self._tasks.clear()
self._flows.clear()
await self._cancel(tasks)
async def cancel_flow(self, flow: str) -> None:
"""Stop one flow's supervised tasks and give it a clean slate.
Rebuilding the whole pipeline throws the supervisor away and builds
another, so a flow quarantined by the last build gets another chance.
Rebuilding one flow has to say the same thing about that flow alone, or
every other flow's quarantine would go with it.
"""
names = [name for name, owner in self._flows.items() if owner == flow]
tasks = [self._tasks.pop(name) for name in names if name in self._tasks]
for name in names:
del self._flows[name]
await self._cancel(tasks)
# The clean slate: whatever this flow spent before, the build that
# follows starts its budget again.
self.quarantined.discard(flow)
self._failures.pop(flow, None)
async def _cancel(self, tasks: list[asyncio.Task[None]]) -> None:
"""Ask these tasks to stop, and wait no longer than the grace period."""
for task in tasks:
task.cancel()
if not tasks: