The two observer surfaces disagree. LoopObserver (Protocol, agent_core/loop_types.py:293) stops at on_loop_end; BaseObserver (:326) additionally defines on_compaction (:363) and on_loop_cancelled (:370) — and the loop dispatches both.
Consequences:
LoopObserver is @runtime_checkable, so isinstance(obs, LoopObserver) passes for an observer with no compaction hook. notify_observers' getattr(obs, method, None) probe then silently drops every compaction record for it — no warning, no log.
- A strict-typed host holding
list[LoopObserver] cannot call on_compaction at all, even though the CompactionEvent record and the COMPACTION_SEQ_KEY contract in agent_core/runtime/loop/compact.py are shared and expect a consumer.
Decision needed: if the two hooks are optional, say so in the Protocol docstring so the silent-drop behaviour is intended and documented. If they are contract, declare them on LoopObserver — which makes BaseObserver a conforming default rather than a superset. Related: #4 (the cancellation hook's drain gap).
Provenance: pre-existing in ApodexHarness/miroharness/core/loop_types.py, ported verbatim by #1 — not introduced by the refactor. ApodexHarness carries the same code and needs the same change until it consumes AgentCore.
The two observer surfaces disagree.
LoopObserver(Protocol,agent_core/loop_types.py:293) stops aton_loop_end;BaseObserver(:326) additionally defineson_compaction(:363) andon_loop_cancelled(:370) — and the loop dispatches both.Consequences:
LoopObserveris@runtime_checkable, soisinstance(obs, LoopObserver)passes for an observer with no compaction hook.notify_observers'getattr(obs, method, None)probe then silently drops every compaction record for it — no warning, no log.list[LoopObserver]cannot callon_compactionat all, even though theCompactionEventrecord and theCOMPACTION_SEQ_KEYcontract inagent_core/runtime/loop/compact.pyare shared and expect a consumer.Decision needed: if the two hooks are optional, say so in the Protocol docstring so the silent-drop behaviour is intended and documented. If they are contract, declare them on
LoopObserver— which makesBaseObservera conforming default rather than a superset. Related: #4 (the cancellation hook's drain gap).Provenance: pre-existing in
ApodexHarness/miroharness/core/loop_types.py, ported verbatim by #1 — not introduced by the refactor. ApodexHarness carries the same code and needs the same change until it consumes AgentCore.