refactor: extract agent loop into plugin, unify hooks and extension points #34

Merged
k9ert merged 6 commits from nazim/cobot:refactor/loop-plugin-architecture into main 2026-02-20 17:00:18 +00:00
Contributor

Summary

Extracts the agent message loop from agent.py into a loop plugin, and replaces the 11 hardcoded hooks with extension points. One dispatch mechanism. Agent becomes a loop runner.

Closes #33

What changed

New: Loop plugin (cobot/plugins/loop/)

  • Owns the poll → context → LLM → tools → send cycle
  • Defines 11 extension points (loop.on_message, loop.before_llm, etc.)
  • Other plugins participate via meta.implements declarations
  • Multiple loop plugins can run concurrently (agent loop, heartbeat, DVM, cron)

Simplified: agent.py (394→80 lines)

class Cobot:
    async def run(self):
        loops = self.registry.all_with_capability("loop")
        await asyncio.gather(*[loop.run() for loop in loops])

New: call_extension() and call_extension_chain() on base Plugin

  • call_extension(point) — collect results from all implementers
  • call_extension_chain(point, ctx) — flow ctx through implementers (like old hooks)
  • Replaces manual get_implementations() + iteration boilerplate

Auto-injected registry

  • start_all() sets plugin._registry = self on every plugin
  • Eliminates all set_registry() methods and get_registry() calls
  • Every plugin can use self._registry and self.call_extension() immediately

Ported plugins

Plugin Old (hook override) New (implements)
logger 7 hook methods implements: {"loop.on_message": "log_message_received", ...}
persistence on_message_received, transform_history, on_after_send Same methods via implements
compaction transform_history via implements
security on_message_received via implements
pairing on_message_received via implements
ppq on_before_llm_call, on_after_llm_call via implements
telegram on_before_llm_call via implements

Deprecated

  • run_hook() in registry (stub, prints warning)
  • HOOK_METHODS list (kept as reference, now lists extension point names)
  • run() convenience function removed from __init__.py

Stats

  • 17 files changed, 534 insertions, 572 deletions
  • Net reduction: 38 lines (while adding a whole new plugin + two new base class methods)
  • Zero new dependencies
## Summary Extracts the agent message loop from `agent.py` into a **loop plugin**, and replaces the 11 hardcoded hooks with **extension points**. One dispatch mechanism. Agent becomes a loop runner. Closes #33 ## What changed ### New: Loop plugin (`cobot/plugins/loop/`) - Owns the poll → context → LLM → tools → send cycle - Defines 11 extension points (`loop.on_message`, `loop.before_llm`, etc.) - Other plugins participate via `meta.implements` declarations - Multiple loop plugins can run concurrently (agent loop, heartbeat, DVM, cron) ### Simplified: `agent.py` (394→80 lines) ```python class Cobot: async def run(self): loops = self.registry.all_with_capability("loop") await asyncio.gather(*[loop.run() for loop in loops]) ``` ### New: `call_extension()` and `call_extension_chain()` on base Plugin - `call_extension(point)` — collect results from all implementers - `call_extension_chain(point, ctx)` — flow ctx through implementers (like old hooks) - Replaces manual `get_implementations()` + iteration boilerplate ### Auto-injected registry - `start_all()` sets `plugin._registry = self` on every plugin - Eliminates all `set_registry()` methods and `get_registry()` calls - Every plugin can use `self._registry` and `self.call_extension()` immediately ### Ported plugins | Plugin | Old (hook override) | New (implements) | |--------|-------------------|------------------| | logger | 7 hook methods | `implements: {"loop.on_message": "log_message_received", ...}` | | persistence | `on_message_received`, `transform_history`, `on_after_send` | Same methods via `implements` | | compaction | `transform_history` | via `implements` | | security | `on_message_received` | via `implements` | | pairing | `on_message_received` | via `implements` | | ppq | `on_before_llm_call`, `on_after_llm_call` | via `implements` | | telegram | `on_before_llm_call` | via `implements` | ### Deprecated - `run_hook()` in registry (stub, prints warning) - `HOOK_METHODS` list (kept as reference, now lists extension point names) - `run()` convenience function removed from `__init__.py` ## Stats - 17 files changed, 534 insertions, 572 deletions - Net reduction: **38 lines** (while adding a whole new plugin + two new base class methods) - Zero new dependencies
refactor: extract agent loop into plugin, unify hooks and extension points
Some checks failed
CI / test (3.11) (pull_request) Failing after 43s
CI / test (3.12) (pull_request) Failing after 4s
CI / test (3.13) (pull_request) Failing after 4s
CI / lint (pull_request) Failing after 2s
CI / build (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
E2E Tests / e2e (pull_request) Failing after 3s
9a51d1c42d
- New loop plugin (cobot/plugins/loop/) owns the message cycle
- agent.py reduced from 230+ lines to ~80 lines — just a loop runner
- All 11 hooks replaced by extension points on the loop plugin
- Plugins declare participation via meta.implements instead of method overrides
- Registry auto-injects _registry into all plugins (no more set_registry/get_registry)
- New call_extension() and call_extension_chain() on base Plugin class
- Multiple loop plugins run concurrently via asyncio.gather
- run_hook() deprecated (kept as stub for backwards compat)

Closes #33
k9ert force-pushed refactor/loop-plugin-architecture from 9a51d1c42d
Some checks failed
CI / test (3.11) (pull_request) Failing after 43s
CI / test (3.12) (pull_request) Failing after 4s
CI / test (3.13) (pull_request) Failing after 4s
CI / lint (pull_request) Failing after 2s
CI / build (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
E2E Tests / e2e (pull_request) Failing after 3s
to b7c7346bab
Some checks failed
CI / test (3.11) (pull_request) Failing after 18s
E2E Tests / e2e (pull_request) Failing after 4s
CI / test (3.12) (pull_request) Failing after 16s
CI / lint (pull_request) Failing after 10s
CI / test (3.13) (pull_request) Failing after 16s
CI / build (pull_request) Has been skipped
2026-02-20 16:35:54 +00:00
Compare
nazim force-pushed refactor/loop-plugin-architecture from b7c7346bab
Some checks failed
CI / test (3.11) (pull_request) Failing after 18s
E2E Tests / e2e (pull_request) Failing after 4s
CI / test (3.12) (pull_request) Failing after 16s
CI / lint (pull_request) Failing after 10s
CI / test (3.13) (pull_request) Failing after 16s
CI / build (pull_request) Has been skipped
to 0b4922abb5
Some checks failed
CI / lint (pull_request) Failing after 9s
E2E Tests / e2e (pull_request) Failing after 3s
CI / test (3.11) (pull_request) Successful in 21s
CI / test (3.12) (pull_request) Successful in 22s
CI / test (3.13) (pull_request) Successful in 22s
CI / build (pull_request) Has been skipped
2026-02-20 16:48:08 +00:00
Compare
fix: resolve ruff lint and format errors
Some checks failed
CI / lint (pull_request) Successful in 10s
E2E Tests / e2e (pull_request) Failing after 3s
CI / test (3.11) (pull_request) Successful in 21s
CI / test (3.12) (pull_request) Successful in 22s
CI / test (3.13) (pull_request) Successful in 22s
CI / build (pull_request) Failing after 8s
d35a21381d
- Remove unused import: time (loop/plugin.py)
- Remove unused import: HOOK_METHODS (registry.py)
- Fix F821: add TYPE_CHECKING import for PluginRegistry forward ref (base.py)
- Format: agent.py, loop/plugin.py (ruff format)
nazim force-pushed refactor/loop-plugin-architecture from d35a21381d
Some checks failed
CI / lint (pull_request) Successful in 10s
E2E Tests / e2e (pull_request) Failing after 3s
CI / test (3.11) (pull_request) Successful in 21s
CI / test (3.12) (pull_request) Successful in 22s
CI / test (3.13) (pull_request) Successful in 22s
CI / build (pull_request) Failing after 8s
to 19da7d83f9
Some checks failed
CI / lint (pull_request) Successful in 10s
E2E Tests / e2e (pull_request) Failing after 3s
CI / test (3.11) (pull_request) Successful in 23s
CI / test (3.12) (pull_request) Successful in 23s
CI / test (3.13) (pull_request) Successful in 24s
CI / build (pull_request) Successful in 7s
2026-02-20 16:54:30 +00:00
Compare
Merge branch 'main' into refactor/loop-plugin-architecture
All checks were successful
CI / lint (pull_request) Successful in 10s
CI / test (3.11) (pull_request) Successful in 23s
CI / test (3.12) (pull_request) Successful in 25s
CI / test (3.13) (pull_request) Successful in 26s
E2E Tests / e2e (pull_request) Successful in 14s
CI / build (pull_request) Successful in 7s
b091689854
k9ert approved these changes 2026-02-20 17:00:09 +00:00
k9ert left a comment
Owner

LGTM

LGTM
k9ert merged commit 0619aecd64 into main 2026-02-20 17:00:18 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ultanio/cobot!34
No description provided.