Domain events
Deze inhoud is nog niet vertaald.
What a domain event is
Section titled “What a domain event is”Domain events are the decoupling mechanism that lets features communicate without direct dependencies. Instead of feature A calling feature B, feature A publishes an event and feature B subscribes to it.
The DomainEventBus is an in-process broadcast publish/subscribe bus. Publishers
call publish(event); subscribers consume a typed on<T>() stream. Every event
implements DomainEvent and carries occurredAt.
The bus runs inside cc_server. Every publisher and every live subscriber
lives there. No client sees this bus. What a client sees is a curated subset the
server re-emits as notifications/* JSON-RPC frames.
Why a bus at all
Section titled “Why a bus at all”Without events, every cross-feature reaction is an import. The notification path would have to import the agents feature, the pull-request feature, ticketing, and meetings just to know when to raise a toast — and each of those would then have to know that notifications exist. Ticketing would import pipelines so a completed ticket could advance a run. The dependency graph closes into a knot, and the shared kernel stops being shared.
Publishing an event breaks that. The notification path subscribes to twenty-one event classes and imports none of the features that raise them. The pipeline trigger dispatcher subscribes to the whole bus and knows nothing about pull requests, tickets, or meetings. Observability aggregates run outcomes without reaching into dispatch.
The cost is the usual one: a publisher cannot tell whether anyone is listening,
and a listener that is never constructed is silently inert. TicketRemoteSyncHandler
is the standing example: it is declared, covered by tests, and never wired in
production.
What the bus is not
Section titled “What the bus is not”It is worth being precise about one thing the bus does not own.
The audit trail is not a projection of the event stream. Every mutating RPC
operation is audited by default — RepoOp.audited defaults to true and an
operation opts out only by declaring audited: false, a deliberate act visible
on the operation itself. The dispatcher appends the “who did what, from where”
row directly after a successful call. There is no DomainEventAuditBridge.
ActivityLogPersister writes activity_log rows from ActivityLogged events;
that is a separate feed from the RPC audit.
The event bus is the transport for notifications and for automation. It is not the bookkeeping system of record.
What the bus does drive
Section titled “What the bus does drive”- Notifications. Twenty-one event classes are turned into
notifications/*wire frames by the server, recorded into a durable per-workspace feed and broadcast to connected sessions. A frame whoseworkspace_idnames a workspace the session user is not a member of is dropped on the server; the client then filters the remainder to the workspace it is looking at. - Event-driven automation. Pipeline triggers subscribe to the bus and auto-start matching pipelines (see below).
- Lifecycle reactions. Creating a workspace seeds its CEO and specialist agents, the built-in pipeline templates, and the starter eval suites. A completed agent run resumes a suspended pipeline step, closes a task, feeds the goal supervisor, can wake a named checker, and can re-trigger a team leader. A merged PR or a deleted space triggers worktree garbage collection.
- Cross-vendor sync. Local ticket changes are pushed out to configured vendors by a coordinator listening for five ticket events.
Event categories
Section titled “Event categories”Events span the whole product surface. At a high level:
- Workspaces, agents and repos — workspace creation, run completion, repo registration, skill updates
- Pull requests — publishing, status changes, merges, review requests, mentions, authored-PR watch, externally detected PRs
- Messaging — messages, spaces and space provisioning progress
- Tickets — create, assign, reassign, details, status and the three terminal outcomes
- Tasks — sequenced lifecycle frames for one dispatched run, so clients can order and de-duplicate them
- Pipelines — terminal run events only (completed, cancelled, failed)
- Observability — activity-log entries and budget thresholds
- Identity and membership — member added, removed, role changed
- Calendar and meetings — auth expiry, meetings starting and recordings finishing
- Rigs — take-over, reap and unexpected close
Memory, orchestration, plan documents, artifacts, approvals, ticket-sync intake, calendar refresh, worktree merges, user creation, invites and device revocation are not domain events; see the reference.
For the complete catalog — all 53 concrete event classes, their payloads and the 24 subscribers that consume them — see the Domain events reference. That page is the inventory; this one is the model.
Live access control rides two different mechanisms
Section titled “Live access control rides two different mechanisms”Membership and device events look symmetrical and are not, which matters if you are reasoning about how fast a revocation takes effect.
Revoking a device does terminate its sessions. The server watches the paired device table and drops any live session whose device left the active set, within seconds.
Removing a member does not close the socket. WorkspaceMemberRemoved is
consumed: LocalRpcServer and RemoteRelayHost drop that user’s attached
workspace subscriptions, and RemoteEventForwarder invalidates its membership
cache so further frames for that workspace are not pushed. What denies the next
RPC is still the role gate, which re-resolves membership on every call — so
their next request is refused with “Not a member of this workspace”. The effect
is immediate for anything they try to do; it just is not achieved by hanging up
on them.
Pipeline triggers
Section titled “Pipeline triggers”Pipeline triggers subscribe to domain events and auto-start matching pipelines.
PipelineTriggerDispatcher listens to the whole bus, then:
- Short-circuits unless the event type is one it knows how to map to a payload
- Looks up enabled triggers matching that event type, across every workspace
- Filters each to its own workspace and applies its payload match filter
- Starts a run for each trigger that survives
The important constraint is step 1. The bus carries 53 event types; the
add-trigger picker starts a run for these fourteen (EventPayloadMapper
payload branches):
ExternalPrDetected, PullRequestPublished, PullRequestStatusChanged,
PrMerged, MessageReceived, TicketAssigned,
TicketCompleted, TicketFailed, TicketCancelled,
BudgetThresholdCrossed, RepoAdded, MeetingRecordingStopped, SkillUpdated
and SpaceDeleted. Nothing else can start a pipeline.
How a client sees any of this
Section titled “How a client sees any of this”RemoteEventForwarder translates the notification-class events into wire frames
and pushes them over RPC to connected sessions, where a frame mapper turns them
into in-app notifications. A thin client therefore sees a projection of the
server’s event stream without owning any execution — which is the whole point of
the thin-client split.
Not every pushed frame is a notification, though. The task-lifecycle stream and ticket reassignment are forwarded live to drive UI, but they are not recorded into the notification feed and raise no toast.
See also
Section titled “See also”- Pipelines and automation: what a trigger starts
- Tickets and delegation: assignment to an agent vs a team
- Architecture: how events fit the dependency rule
- Domain events reference: the complete catalog and its subscribers
- Set up pipeline triggers
- Configure notifications