Skip to content

notify_observers drains a process-global task set, so one loop's on_loop_end blocks on another loop's passive hooks #2

Description

@zhanghanduo

agent_core/loop_types.py keeps fire-and-forget observer tasks in a module-global set:

_background_tasks: set[asyncio.Task[None]] = set()
...
if method == "on_loop_end":
    await drain_background_observers()   # drains every loop's tasks

drain_background_observers() gathers all pending tasks, not the ones this loop created. With two agent loops in one process — the ordinary sub-agent case — loop A's on_loop_end waits on loop B's still-running passive hooks.

Observed: a passive on_turn_end sleeping 3s in one logical loop made an unrelated loop's on_loop_end return 3.0s late. A passive hook awaiting something that is never signalled turns this into an indefinite hang of an unrelated loop's shutdown.

Fix direction: a per-loop task registry, or scope the drain to the tasks this loop scheduled. Note the drain exists for a real reason (a caller reading the event sink right after run() would otherwise race pending writes), so it must stay — just be scoped.

Provenance: pre-existing in ApodexHarness/miroharness/core/loop_types.py, ported verbatim by #1 — not introduced by the refactor. Fixing it here is the source-of-truth fix; ApodexHarness carries the same code and needs the same change until it consumes AgentCore.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions