Page navigation
Event layer Event Layer
reference
Use the current openprogram/events/registry.py as the API admission list, then verify origins against emit_safe, emit_gate, bridges and passthrough calls. The registry has 28 entries: 26 have identifiable calls in current source; branches.listed and sessions.listed currently appear only in the registry. Unregistered events and WebSocket frame types are listed separately.
Scope and reading guide
The event layer is a process-wide EventBus. Producers construct a frozen Event and send it to the bus; consumers subscribe by type. gate events execute synchronously in the sender thread. The registry defines names, kinds and payloads; call sites establish production. Names, counts and source paths refer to the current checkout.
System diagram
Multiple producers enter the bus through openprogram/events/. Consumers subscribe to typed events or read logs; WebUI passthrough uses the unregistered ws.frame envelope. Amber paths are synchronous gates; the current registered gate types are tool.before and turn.stop.
ws.frame and credential.* is unregistered and excluded from the 28 entries.Event data model
Event is a frozen dataclass;make_event generates IDs and timestamps and fills session/turn metadata from store ContextVars.
@dataclass(frozen=True)
class Event:
id: str
ts: float
type: str
origin: str
payload: dict
metadata: dictopenprogram/events/bus.py:45-105Currently registered events
Entries below come strictly from EVENTS. Unregistered types, internal WS frame types and proposed names are excluded.
Unregistered but observable types
These types are constructed or sent in current code but are absent from registry.EVENTS. EventBus tolerates them and logs one warning per type.
| Type | Source | Actual semantics | Status |
|---|---|---|---|
| ws.frame | openprogram/events/bus.py:122-136 | emit_ws_frame places an existing WebSocket frame in payload.frame;question.replied, session_reload, programs:changed and similar values are payload fields, not registered typed events. | Unregistered passthrough envelope |
| credential.cooldown credential.rotated credential.exhausted | openprogram/events/bridges.py:19-45 | Three typed Events produced by the AuthEvent bridge; none currently has a registry entry. | Unregistered bridged events |
question.replied and question.rejected currently appears only in WS frames (openprogram/agent/permissions/lifecycle.py:149-152, openprogram/agent/questions.py:232-237), not as a registered typed event.Bus API and helpers
create_event_bus() provides an independent instance without disk writes.ws.frame envelope with payload {frame: {type, data, …}}.types=None receives every typed event; otherwise subscribe only to the specified type. Notify-handler exceptions are isolated.allowed=False. Exceptions and soft timeouts fail open in the current implementation.Observation: subscribe to current events
Subscriptions are asynchronous observation interfaces. Handler return values do not affect the emitter.
from openprogram.events import get_event_bus
def on_file_changed(event):
print(event.payload["path"], event.metadata.get("session"))
unsubscribe = get_event_bus().subscribe(
on_file_changed, types={"file.changed"}
)Synchronous gates: tool and turn-stop decisions
Registered gate types are tool.before and turn.stop. An empty return permits execution; a string denies it with a reason.tool.before also has a register_tool_gate compatibility entry.
from openprogram.events import get_event_bus
def guard(event):
if event.payload.get("tool") == "bash":
return "policy denied this tool call"
return None
remove = get_event_bus().subscribe_gate("tool.before", guard)Event log
The process-wide singleton bus writes typed dispatch to JSONL. With a session and existing directory, it writes to ~/.openprogram/sessions/<sid>/events.jsonl; otherwise to ~/.openprogram/logs/events.jsonl; rotate above 5 MB. Gate verdicts are stored in the same record’s gate field.
tail -f ~/.openprogram/logs/events.jsonl
user.prompt_submitted, turn.start, model.response_started, tool.before, tool.after, model.response_completed and turn.end. Actual order depends on failure, cancellation, tool calls and stop-hook branches.Boundaries
origin currently follows the convention user, agent, tool, system, proactive. If registry payload documentation differs from actual arguments, retain and identify that difference with its source location; do not invent API fields.
EVENTS belong in the Registered events catalog.