Page navigation
API REFERENCE · EVENT LAYER

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.

OVERVIEW

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.

Status:Active call means the registry and emit/bridge/gate path are both identifiable;Registry only means currently declared only in the registry, not proof that runtime execution produces it.
SYSTEM MAP

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.

OpenProgram event-layer relationship diagramAgent, tools, context, memory, channels and the auth bridge enter EventBus. Consumers subscribe or log events; ws.frame forwards to WebUI, while tool.before and turn.stop return synchronous gate results.SOURCESPROCESS BUSCONSUMERSagent / dispatcherturn · model · tool · questionuser.prompt_submittedTools and collaborationfile · subagent · branchesagents.listedcontext / memory / channelscompaction · ingest · inboundcredential.* is unregisteredEventBusemit · subscribeemit_gate · synchronousregistry admission boundarytyped subscriberssubscribe(handler, types=…)notify handlers isolatedevent_logsessions/<sid>/events.jsonllogs/events.jsonl · 5 MB rotatews.frame envelopeUnregistered typed eventWebUI subscribes and forwards to the browsertool.before / turn.stop gateAllow or denial reason
Cyan: typed events; purple: logging/observation; amber: synchronous gates.ws.frame and credential.* is unregistered and excluded from the 28 entries.
DATA MODEL

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: dict
openprogram/events/bus.py:45-105
REGISTRY · 28 ENTRIES

Currently registered events

Entries below come strictly from EVENTS. Unregistered types, internal WS frame types and proposed names are excluded.

MIGRATION / BOUNDARY

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.

TypeSourceActual semanticsStatus
ws.frameopenprogram/events/bus.py:122-136emit_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-45Three typed Events produced by the AuthEvent bridge; none currently has a registry entry.Unregistered bridged events
Boundary: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.
API · CORE

Bus API and helpers

get_event_bus() → EventBus
Return the singleton bus for the current worker process;create_event_bus() provides an independent instance without disk writes.
openprogram/events/bus.py:353-374
make_event(...) → Event
Construct a frozen Event, filling id/ts and ContextVar correlation.
openprogram/events/bus.py:87-105
emit_safe(type, origin, payload=None, metadata=None)
Construct and emit a typed event; event-layer failures are swallowed.
openprogram/events/bus.py:108-119
emit_ws_frame(frame)
Send an unregistered ws.frame envelope with payload {frame: {type, data, …}}.
openprogram/events/bus.py:122-136
subscribe(handler, *, types=None) → unsubscribe()
types=None receives every typed event; otherwise subscribe only to the specified type. Notify-handler exceptions are isolated.
openprogram/events/bus.py:208-227
emit_gate(event, timeout_s=None) → GateOutcome
Invoke gates synchronously in registration order; any reason causes allowed=False. Exceptions and soft timeouts fail open in the current implementation.
openprogram/events/bus.py:249-300
GUIDE · OBSERVE

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"}
)
GUIDE · GATE

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)
Execution constraints:Gates run on the synchronous path; avoid model calls and slow I/O. Exceptions, soft timeouts and same-type reentry currently fail open.
DEBUG

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
Common registered typed events for a top-level turn include 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.
APPENDIX

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.

Excluded from this catalog:Internal frames broadcast directly by WebUI, unregistered auth-bridge types, proactive policies and proposed event names. Only types in EVENTS belong in the Registered events catalog.