DESIGN · FRAMEWORK EVOLUTION

From “independent mechanisms”
to “a unified event bus”.

How the framework changes after introducing the event layer: current and target states, changes to each subsystem and migration order.No wholesale rewrite: run old and new paths together and migrate one source at a time.

↑ PromoteEventBus

From unused code to the sole event hub: process singleton, unified Event and type-based subscriptions

↓ Reduce responsibilitywebui server

From a forced signal hub to an ordinary subscriber: subscribe to the bus → forward to frontend WS

+ IntroduceProactive and future features

Simply another subscriber. Connect with one subscribe(types=…) call; auditing, statistics and replay work the same way.

01 · AS-IS

Current state: webui is forced to coordinate signals

Almost every signal exists to make information visible to the frontend, so all are directly connected to webui's _broadcast. Five line styles represent five distinct mechanisms. Auth has the right model but no listeners; memory and file changes have no signals; hooks observe but cannot intercept; EventBus is unused. A new consumer must integrate separately with five or six mechanisms, while some useful moments have no signal at all.

Current architecture diagram
02 · TO-BE

Target: a unified event bus

Component positions correspondone to one with the current-state diagram; only connections and roles change. All sources use emit(Event) and all consumers use subscribe(types=…); the framework's only interception point is tool.before , a synchronous query point reusing existing approval and applying to subagents. Everything else is asynchronous observation.

Target architecture diagram
03 · DELTA

Subsystem changes

SubsystemNowFutureChange size
EventBusUnused, zero subscriptionsUpgrade to type subscriptions and a process singleton, then enableCore
agent loopInternal AgentEvent stream;hook return values are discardedAlso emit to the bus at key points; add a synchronous query for tool.before(interception becomes possible)Small
webui serverDirectly connected by everyone; the de facto hubSubscribe to the bus and forward to the frontend; retire direct paths source by sourceMedium
task runnerDirect _broadcast job_status connectionemit subagent.*, with webui subscribing and forwarding broadcastsSmall
dispatcheron_event callback chain directly to webuiRetain during transition and additionally emit user.prompt_submitted and othersSmall
channelsDirect broadcast_channel_turn connectionemit channel.*; retain direct connection during transitionSmall
authIts own _emit/subscribe(already correct)Leave the source unchanged and translate into the bus through a small bridgeVery small
contexton_event callbackAlso emit in the callback context.*Very small
memoryPeriodic polling with no signalEmit at start/end of processing and represent timer triggers as eventsVery small
File changesSilent backup with no signalEmit at checkpoint_before_edit file.changedVery small
plugin hooksObserve-only, 6 firing pointsUse the bus internally; retain hooks as a plugin API or retire them graduallyDecision point
04 · MIGRATION

Migration: parallel paths, one source at a time

The first three steps areentirely additive: the bus runs beside existing paths with no behavior change and can be stopped or reverted at any time. Only step four changes old paths, protected by shadow comparison.

Enable the bus and connect class-A sourcesAdditive only✅ Implemented 2026-06-13

Upgrade EventBus with type subscriptions and a singleton; agent loop / task runner / dispatcher emit in parallel at key points. Existing paths continue unchanged.

Acceptance: subscribe to the bus and print the complete event sequence for a real turn
Fill two gapsAdditive only✅ Implemented 2026-06-13

file.changed event at checkpoint_before_edit;tool.before synchronous query point reusing _approval and applying to subagents.

Acceptance: file edits produce events; a test subscriber can actually block a specified command
Bridge class-B sourcesAdditive only✅ Implemented 2026-06-13

One unidirectional bridge each for auth / context / channels / memory translates into a unified Event on the bus. Source logic remains unchanged.

Acceptance: trigger a credential rate limit and a compaction, and receive both from the same bus
Switch webui to a subscriberChanges the old path✅ Implemented 2026-06-13

Start in shadow mode: bus forwarding and old direct connections run together and outputs are compared. After confirming equivalence, disconnect each old source path. This is transparent to the frontend.

Acceptance: switch after zero differences in shadow comparison; frontend behavior remains unchanged
Connect new consumersAdditive only

Connect the proactive rule layer; every future feature then needs only the bus and a single subscribe call.

Acceptance: proactive works through subscriptions without accessing subsystem internals
05 · UNCHANGED

Intentionally unchanged

What changes and what stays unchanged both matter. The following areoutside this evolution:

Dispatcher's seven-stage orchestrationThe public process_user_turn signature remains unchanged Session git DAG storageContextgit and the node model remain unchanged JobRunner thread-pool modelSubagent execution remains unchanged ApprovalRegistryReused, not rewritten AuthStore itselfBridge it without changing it Frontend WS protocolThe webui switch is transparent to the frontend
Text source:framework-evolution.md · Event-layer design:event-layer.html · Current wiring:openprogram/events/{registry,bus,bridges}.py and actual emit sites