Epic: Evolving the Loop Extension Points into a Full Event Bus #124
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Competitor
Kind/Documentation
Kind/Enhancement
Kind/Epic
Kind/Feature
Kind/Security
Kind/Story
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Scope/Core
Scope/Cross-Plugin
Scope/Plugin-System
Scope/Single-Plugin
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ultanio/cobot#124
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Status Quo: What We Already Have
Cobot already has a well-designed plugin system with lifecycle hooks. The loop plugin (priority 50) defines 12 extension points that other plugins implement via
PluginMeta.implements:session.poll_messagesloop.on_messageloop.transform_system_promptloop.transform_historyloop.before_llmloop.after_llmloop.before_toolloop.after_toolloop.transform_responseloop.before_sendloop.after_sendloop.on_errorChain handlers receive and return
ctx. Settingctx["abort"] = Truestops the chain and can providectx["abort_message"]as a fallback response.Other plugins also define extension points:
filedrop.before_write,filedrop.after_read,subagent.before_spawn,subagent.after_spawn,cron.before_job,cron.after_job.The registry routes calls via
call_extension()(collect results) andcall_extension_chain()(middleware-style chaining with abort support).This is already a solid event bus. The question is: what's missing?
What's Missing: The Gaps
1. No observe-only subscription mode
Every handler in a chain must return
ctx. There's no lightweight way to just watch events without participating in the chain. An audit/logging plugin has to be careful not to accidentally mutatectx.Proposal: Support a
modeon implementations:"chain"(default, current behavior) vs"observe"(receives a frozen copy, return value ignored). Observers run after the chain completes.2. No event bus outside the loop
The loop plugin's 12 hooks cover the message→LLM→tool→response cycle well. But agent lifecycle has more events that plugins might want to hook:
agent.startagent.stopsession.startsession.endsession.compactmodel.switchplugin.loadedThese exist implicitly (via
configure/start/stoplifecycle) but can't be hooked by third-party plugins.3. Extension points are static declarations
Currently, a plugin declares
implements: {"loop.before_llm": "my_method"}inPluginMetaat load time. You can't:Proposal: Keep the declarative
PluginMeta.implementsfor the common case, but addregistry.subscribe(event, handler)for runtime registration.4. No event typing or payload contracts
The
ctxdict is untyped. Each extension point has implicit expectations about what's inctx(e.g.,loop.before_toolexpectsctx["tool_name"],ctx["tool_args"]). This makes it hard for plugin authors to know what data is available.Proposal: Define typed event payload dataclasses per extension point. Handlers receive a typed event object instead of a raw dict. The raw dict can remain as a backwards-compatible fallback.
5. No cross-loop events
Multiple loop plugins can run concurrently (
main loop,cron loop,heartbeat loop). Currently there's no way for one loop to emit events that another loop (or non-loop plugins) can observe.Proposal: A global event bus on the registry itself, separate from extension points. Any plugin can emit; any plugin can subscribe.
The Vision: Three Layers
Layer 1 is solid. Layer 2 works well for the loop pipeline. Layer 3 is the gap — a way for any plugin to talk to any other plugin via events, without being in the same loop.
Concrete Use Cases Enabled
1. Safety gate plugin (uses existing
loop.before_tool)Already possible today! A plugin can implement
loop.before_tooland abort dangerous commands. The architecture supports this. But: it requires reading the loop plugin's README to know what's inctx. Typed payloads would make this self-documenting.2. RAG injection plugin (uses existing
loop.transform_history)Already possible today. A plugin implements
loop.transform_historyand injects relevant chunks. But: there's no way to know why the history is being built (is it a compaction? a retry? a normal turn?). Richer context in the payload would help.3. Telemetry plugin (needs observe mode)
Wants to log every event without modifying anything. Today it must implement chain handlers that pass
ctxthrough unchanged. With observe mode, it subscribes as read-only and can't accidentally break the pipeline.4. Session memory plugin (needs session lifecycle events)
Wants to auto-save a memory snapshot when a session ends. Today this is done via the
session-memoryhook in OpenClaw, but Cobot's loop plugin doesn't emit session lifecycle events.5. Multi-loop coordination (needs global event bus)
The cron loop spawns a subagent. The main loop wants to know when it finishes. Today, cron uses
subagent.after_spawnbut this is scoped to the subagent plugin. A global event bus would let any plugin observe any event across loops.What This Is NOT
This is not a rewrite. Cobot's plugin system is well-designed:
This epic is about filling the gaps in the existing architecture, not replacing it.
Sub-Issues
ctxagent.start/stop,session.start/end/compactregistry.emit()/registry.subscribe()for cross-plugin eventsregistry.subscribe(event, handler)alongside staticPluginMeta.implementsPrior Art
ExtensionAPIwith ~20 lifecycle events, block/modify semantics. See #123. Theircontextevent (modify messages before LLM) maps to ourloop.transform_history. Theirtool_callblock maps to ourloop.before_toolabort. Theirbefore_agent_startmaps to ourloop.before_llm.Related: #123 (pi competitor analysis)
Epic: Agent Lifecycle Event Busto Epic: Evolving the Loop Extension Points into a Full Event Bus