mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
refactor(gateway): extract kanban watcher loops into GatewayKanbanWatchersMixin (god-file Phase 3)
gateway/run.py is the largest god file (20k LOC, GatewayRunner with 220
methods). This lifts the cohesive kanban-watcher cluster — _kanban_notifier_watcher,
_kanban_dispatcher_watcher, _kanban_advance/unsub/rewind, _deliver_kanban_artifacts
(~1,035 LOC, 6 methods) — into gateway/kanban_watchers.py as a mixin that
GatewayRunner inherits.
Mixin (not free functions) because the methods use only self state: inheriting
keeps every self._kanban_* call site working unchanged via the MRO, making this
a behavior-neutral move. The methods' lazy imports (_kb, _decomp, _load_config,
Platform) travel with them; the mixin needs only stdlib + a matching
logging.getLogger('gateway.run').
run.py 20187 -> 19157 LOC; GatewayRunner direct methods 220 -> 214.
Behavior-neutral: gateway test suite 6582 passed / 0 failed; start() still wires
both watchers via self._kanban_*; MRO resolves all 6 to the mixin. One test
(corrupt-board quarantine retry) keyed its time-travel mock on the caller's
filename being gateway/run.py — updated to also accept gateway/kanban_watchers.py.
Establishes the mixin-extraction pattern for further GatewayRunner decomposition
(the 2406-LOC _run_agent and 1164-LOC _handle_message remain, but their callback
closures need a context-object redesign — deferred).
This commit is contained in:
parent
6459b3d991
commit
1c68f6f81f
4 changed files with 1121 additions and 1038 deletions
1064
gateway/kanban_watchers.py
Normal file
1064
gateway/kanban_watchers.py
Normal file
File diff suppressed because it is too large
Load diff
1044
gateway/run.py
1044
gateway/run.py
File diff suppressed because it is too large
Load diff
45
tests/gateway/test_kanban_watchers_mixin.py
Normal file
45
tests/gateway/test_kanban_watchers_mixin.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""Tests for the extracted GatewayKanbanWatchersMixin (god-file Phase 3).
|
||||
|
||||
The kanban watcher loops were lifted out of gateway/run.py into a mixin that
|
||||
GatewayRunner inherits. These tests confirm the mixin exposes the methods and
|
||||
that GatewayRunner picks them up via the MRO (behavior-neutral relocation).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
|
||||
from gateway.kanban_watchers import GatewayKanbanWatchersMixin
|
||||
|
||||
KANBAN_METHODS = [
|
||||
"_kanban_notifier_watcher",
|
||||
"_kanban_dispatcher_watcher",
|
||||
"_kanban_advance",
|
||||
"_kanban_unsub",
|
||||
"_kanban_rewind",
|
||||
"_deliver_kanban_artifacts",
|
||||
]
|
||||
|
||||
|
||||
def test_mixin_defines_kanban_methods():
|
||||
for m in KANBAN_METHODS:
|
||||
assert hasattr(GatewayKanbanWatchersMixin, m), f"mixin missing {m}"
|
||||
|
||||
|
||||
def test_gateway_runner_inherits_mixin():
|
||||
# Import here so a heavy gateway import only happens if the first test passed.
|
||||
from gateway.run import GatewayRunner
|
||||
|
||||
assert issubclass(GatewayRunner, GatewayKanbanWatchersMixin)
|
||||
# Each kanban method resolves to the mixin's implementation via the MRO.
|
||||
for m in KANBAN_METHODS:
|
||||
owner = next(c for c in GatewayRunner.__mro__ if m in c.__dict__)
|
||||
assert owner is GatewayKanbanWatchersMixin, (
|
||||
f"{m} resolved to {owner.__name__}, expected the mixin"
|
||||
)
|
||||
|
||||
|
||||
def test_watcher_loops_are_coroutines():
|
||||
# The two long-running watchers are async loops.
|
||||
assert inspect.iscoroutinefunction(GatewayKanbanWatchersMixin._kanban_notifier_watcher)
|
||||
assert inspect.iscoroutinefunction(GatewayKanbanWatchersMixin._kanban_dispatcher_watcher)
|
||||
|
|
@ -3754,11 +3754,15 @@ def test_gateway_dispatcher_retries_corrupt_board_after_quarantine(
|
|||
caller = inspect.currentframe().f_back # type: ignore[union-attr]
|
||||
code = caller.f_code if caller is not None else None
|
||||
filename = code.co_filename if code is not None else ""
|
||||
if filename.endswith("gateway/run.py"):
|
||||
# The kanban dispatcher/notifier watcher loops were extracted from
|
||||
# gateway/run.py into gateway/kanban_watchers.py (god-file Phase 3),
|
||||
# so accept either filename for the time-travel mock.
|
||||
if filename.endswith("gateway/run.py") or filename.endswith("gateway/kanban_watchers.py"):
|
||||
return next(time_values, 1301.0)
|
||||
return real_monotonic()
|
||||
|
||||
monkeypatch.setattr("gateway.run.time.monotonic", _monotonic_for_gateway_dispatcher)
|
||||
monkeypatch.setattr("gateway.kanban_watchers.time.monotonic", _monotonic_for_gateway_dispatcher)
|
||||
|
||||
calls = {"tick": 0}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue