Event Subscriptions
Lightweight pub/sub system for inter-agent event pipelines. Agents emit named events, and subscribing agents receive async tasks with the payload.
Concepts
agent_events table.agent_event_subscriptions table.target_message of a subscription. Supports {{payload.field}} interpolation. The subscriber's task message is built from the event payload.agent_permissions. The subscribing agent must have permission to call the source agent.How It Works
Agent A emits an event: emit_event(event_type="report_ready", payload={"url": "...", "summary": "..."})
Trinity checks all subscriptions matching agent A + event type report_ready.
For each matching subscription, an async task is dispatched to the subscribing agent.
The task message is built from the subscription's template with payload fields interpolated.
Events are persisted and visible via API.
WebSocket broadcast provides real-time event visibility.
Task-Completion Events (System-Emitted)
Trinity deterministically emits agent.task.completed and agent.task.failed at every execution terminal of an agent. These are system-emitted: the platform synthesizes them at the execution chokepoint with no agent in the loop. They ride the same subscription machinery as agent-emitted events.
The win: wake instead of poll. Subscribe to a worker's agent.task.completed (or agent.task.failed) and you get an automatic report-back task the moment that worker's execution finishes — no need to hold a call open or poll get_execution_result. This is the async-first alternative to chat_with_agent(async=true) followed by polling (see Agent Network).
subscribe_to_event(
source_agent="research-worker",
event_type="agent.task.completed",
target_message="research-worker finished task {{payload.execution_id}} ({{payload.status}}): {{payload.summary_or_error}}"
)Reserved Namespace
The agent.task.* namespace is reserved for the platform:
agent.task.* themselves (emit_event rejects it). Only Trinity produces these events.agent.task.*. Subscribing is cross-agent only — you subscribe to another agent's completions.Payload Fields
Interpolate these into your message template:
| Field | Meaning |
|---|---|
| {{payload.execution_id}} | The execution that terminated (correlation key) |
| {{payload.status}} | success or failed |
| {{payload.triggered_by}} | What triggered the source execution (schedule, chat, event, ...) |
| {{payload.summary_or_error}} | The worker's response text on success, or the error on failure (credential-sanitized, truncated) |
| {{payload.duration_ms}} | Wall-clock duration of the source execution |
| {{payload.cost}} | Cost of the source execution |
| {{payload.fan_out_id}} / {{payload.loop_id}} | Set when the source execution was part of a fan-out or loop |
Delivery Caveat
Delivery is best-effort. The report-back task wakes a subscriber whose container is running. If the subscriber agent is stopped, the wake is dropped — the event is still recorded in agent_events, but no task is dispatched. This is not a durable queue; do not rely on it for guaranteed hand-off between stopped agents.
Additive and Inert
With zero matching subscriptions, nothing happens: no event row is written and no task is dispatched. There is no new config, endpoint, or flag — task-completion events reuse the existing event-subscription tools below (subscribe_to_event / list_event_subscriptions / delete_event_subscription), passing event_type="agent.task.completed" or "agent.task.failed".
Reporting Back to the Person Who Asked
Subscriptions wake an agent. Independently of them, a job that started from a conversation and finished later — because the agent delegated it to another agent, or kicked off background work — reports its outcome back to the conversation it came from, success and failure alike. Four places can receive that report:
| Where the job started | Where the outcome lands |
|---|---|
| A Slack channel or thread | A note in that channel/thread — see Slack |
| A Telegram chat | A reply threaded to your message, from the bot you talked to — see Telegram |
| A Workspace chat | A message from the agent in that chat. It is filed under the agent you were talking to and names the agent that did the work when that differs; it opens with Finished or Didn't finish and the status, so a failure never vanishes. The chat moves to the top of the sidebar as any new message would, and the report waits up to two minutes for a reply already in progress before landing |
| A scheduled run addressed to someone | That person's Main chat — see Scheduling |
An ordinary turn that already answered inline is never reported a second time. Each finished job reports at most once, and a report is never sent to a conversation the work was not started from. The Workspace does not push the report while you watch: it is there on the next load or chat switch.
MCP Tools
| Tool | Description |
|---|---|
| emit_event(event_type, payload) | Emit a named event with data |
| subscribe_to_event(source_agent, event_type, target_message) | Create a subscription |
| list_event_subscriptions(agent_name) | List subscriptions |
| delete_event_subscription(subscription_id) | Remove a subscription |
API Endpoints
| Endpoint | Method | Description |
|---|---|---|
| /api/agents/{name}/event-subscriptions | POST | Create subscription |
| /api/agents/{name}/event-subscriptions | GET | List subscriptions |
| /api/event-subscriptions/{id} | GET | Get by ID |
| /api/event-subscriptions/{id} | PUT | Update |
| /api/event-subscriptions/{id} | DELETE | Delete |
| /api/events | POST | Emit event (agent-scoped) |
| /api/agents/{name}/emit-event | POST | Emit for specific agent |
| /api/agents/{name}/events | GET | Event history |
| /api/events | GET | All events |