Events
Events are inbound signals from external platforms that inject input into a running task. They are the inbound counterpart to actions: where actions are outbound calls the LLM initiates, events are things that happen in the world that the agent needs to respond to.
When a tool declares events, an agent subscribes to them automatically just by listing the tool as a capability. The same capability declaration that exposes the tool's actions also subscribes to its events — no additional configuration is required.
How Events Are Subscribed
When an agent's capabilities block references a tool, the agent is automatically subscribed to all events declared in that tool's events list. The tool's message template provides the default input, and the tool's receive.filter expression scopes which events are routed.
# Minimal: subscribe to all github_pr actions AND events
capabilities:
github_pr:
bindings:
owner: "buoyant-systems"
repo: "agent-mesh"
That's the complete declaration. The agent receives comment, review, and pr_merged events for buoyant-systems/agent-mesh automatically — once the scoping conditions are met.
The Action Allow List
Event routing is controlled by the action allow list — a per-tool, per-task structure maintained by the runtime. It is a flat namespace keyed by parameter name, shared across root parameters, per-action parameters, and per-event parameters.
How It Works
-
When the LLM calls any action on a tool, all resolved parameter values (root and per-action) are added to the allow list set for their respective parameter names. A name accumulates multiple values if the LLM calls actions with different values.
-
When an event arrives,
receive.filterexpressions that referenceparameters.*are evaluated against the allow list. Forparameters.X, the filter passes if the payload value is a member of the allow list set forX. -
If a parameter's allow list is empty (no action has been called with that name yet), the event is discarded.
Syncing Per-Action and Per-Event Parameters
If a per-action parameter and a per-event parameter share the same name, they are the same allow list entry. This is how the tool author links specific action inputs to specific event filters — through naming.
# Tool manifest (excerpt)
actions:
- name: create_pr
parameters:
properties:
author: # LLM provides this when calling create_pr
type: string
events:
- name: pr_merged
parameters:
properties:
author: # same name → shares the allow list entry for 'author'
type: string
receive:
webhook:
filter: "... && event.payload.pull_request.user.login == parameters.author"
When the LLM calls create_pr with author: "alice", the allow list for author becomes {"alice"}. The pr_merged event is then only routed for PRs by Alice. If the LLM later creates a PR for Bob, the set becomes {"alice", "bob"} and events for either are routed.
Bindings: Sealing the Allow List
When a parameter has an agent binding, the allow list for that parameter name is sealed at task start to the resolved binding value:
- The set is sealed — the runtime MUST NOT append to it from LLM action calls.
- The LLM cannot supply a value for this parameter (it is hidden from the LLM by the binding).
- Events referencing this parameter in
receive.filterare routable immediately, scoped to exactly the bound value.
# Agent binds owner + repo → allow lists sealed; events routable from task start
capabilities:
github_pr:
bindings:
owner: "buoyant-systems" # binding → sealed to this value
repo: "agent-mesh" # binding → sealed to this value
require_binding: true on a parameter is a tool-side validation constraint that ensures the agent MUST configure a binding. It does not itself seal the allow list or hide the parameter — that is what the binding does. If a parameter has require_binding: true but no binding, the configuration is invalid.
Using include to Filter Events
The include list on a capability filters both actions and events by name. Any name not in the list is excluded:
capabilities:
github_pr:
bindings:
owner: "buoyant-systems"
repo: "agent-mesh"
include: [create_pr, comment, review] # pr_merged event excluded
If include is absent, all actions and all events from the tool are active.
Events in before Middleware
The agent capability's before middleware applies to all activations of the capability — both action invocations and incoming events. When an event arrives, the CEL scope gains an event variable with the raw payload. When an action is invoked, event is not in scope.
Use has(event) to write assertions that only apply to events:
capabilities:
github_pr:
bindings:
owner: "buoyant-systems"
repo: "agent-mesh"
before:
- assert: "context.capabilities['github_pr'].count_successful < 10" # applies to actions only
- assert: "!has(event) || event.payload.comment.user.login != 'dependabot[bot]'" # applies to events
The pattern !has(event) || <condition> evaluates as:
- During an action invocation:
true(short-circuits — action is unaffected) - During an event activation: evaluates
<condition>against the event payload
Reshaping Event Input
Use after middleware with has(event) to transform the input only for event-originated turns:
capabilities:
github_pr:
after:
- transform: "has(event) ? '[ACTION REQUIRED] ' + input.message : input"
Event Activation vs Task Creation
Events can activate an agent in two ways:
| Mode | Resource | Effect |
|---|---|---|
| Warm resume | Tool event + agent capability | Injects input into an existing, idle task |
| Cold start | Trigger manifest | Creates a new task for the agent |
Tool event subscriptions resume existing tasks. Triggers act on raw event payloads and bypass the action allow list — they are for creating new tasks from external signals.
Subscription Lifecycle
A subscription's lifetime is the task's lifetime — there is no separate expiry to configure or reason about.
A subscription is created when a task starts and the agent's capabilities include a tool with events, and it is restored automatically whenever the task is rehydrated (for example after the runtime scales to zero). It is removed only when:
- The task reaches a terminal phase.
- The task is deleted.
An idle task keeps receiving events indefinitely — inbound events are a first-class way to resume a conversation (warm resume), and a long-quiet task waking to a webhook is expected behaviour, not a leak. Interrupting a task does not remove its subscriptions: the task returns to idle, where event-driven inputs remain live. Runtimes bound the overall lifetime through the task lifecycle itself (for example an idle-lifespan policy that completes long-dormant tasks), never through per-subscription timers.
In order to unsubscribe a task, transition it to a terminal phase.
Webhook Signature Verification
The webhook receive type supports an optional secret field for HMAC signature verification:
receive:
webhook:
secret: "{connection('github').service_auth().webhook_secret}"
filter: "event.payload.action == 'created'"
When secret is present, the runtime validates the inbound webhook's X-Hub-Signature-256 header against the resolved HMAC secret before evaluating the filter. The secret is resolved through the same connection surface that supplies a tool's credentials — a shared secret is a named field on the connection rather than anything written into the manifest. If absent, no signature verification is performed.
Because different tools may use different secrets, verification happens during event routing at the per-tool level, not at the API ingestion endpoint.
Full Example: Autonomous Code Review Agent
kind: "commonagents.info/v1beta2/agent"
name: "coder_agent"
description: "An autonomous software engineer that responds to PR feedback."
prompt: |
You are a software engineer. Open PRs, push code, and address
review feedback by pushing new commits.
capabilities:
github_file:
bindings:
owner: "buoyant-systems"
repo: "agent-mesh"
github_pr:
bindings:
owner: "buoyant-systems"
repo: "agent-mesh"
include: [create_pr, comment, review]
before:
- assert: "!has(event) || event.payload.comment.user.login != 'agentmesh-bot'"
guardrails:
before:
- assert: "size(context.input) > 0"
error_message: "Input cannot be empty."
When this agent calls create_pr, the resolved owner and repo values (fixed by binding) are in the allow list from task start. comment and review events for buoyant-systems/agent-mesh arrive immediately. The pr_merged event is excluded by include. After 48 hours of task inactivity, event subscriptions expire.