Parameter Pipeline
The parameter pipeline describes how values reach a tool capability at execution time, and how those values control event routing. Understanding this pipeline is essential for writing secure tools that prevent prompt injection and enforce data integrity.
Parameter Hierarchy
A tool has three levels of parameters, each with distinct scope:
| Level | Field | Scope |
|---|---|---|
| Root | tool.parameters | Universal — available to ALL actions (in execute interpolation) AND ALL events (in receive.filter). Only parameters that apply across the entire tool belong here. |
| Per-action | action.parameters | Specific to one action — additional inputs the LLM provides when invoking that action. |
| Per-event | event.parameters | Specific to one event — additional filter inputs scoped to that event's receive.filter. |
All three levels share a single allow list namespace keyed by parameter name. See Action Allow List below.
ParameterSchema
All three levels use the same ParameterSchema format:
properties:
<name>:
type: string | number | boolean | object | array
description: str
default: any | None # presence determines required/optional
require_binding: bool # default: false
format: str | None # e.g. "password", "uri", "date-time"
enum: list[any] | None
# ... standard JSON Schema properties
Required vs Optional
- A property without a
defaultfield is required — it must be provided by the LLM or a binding before execution. - A property with a
defaultfield is optional — the runtime backfills the default if the value is absent.
require_binding: true
require_binding: true is a tool-side validation constraint. When set:
- The API server rejects any agent that references this tool without providing a binding for the parameter.
- If no binding is configured, the configuration is invalid and the runtime MUST error.
Note: it is the binding (not this flag) that:
- Hides the parameter from the LLM-facing schema.
- Seals the action allow list entry so it cannot be expanded by LLM action calls.
A binding can exist without require_binding: true — the parameter is still hidden and the allow list entry is still sealed. require_binding: true only adds the validation guarantee that the binding is not accidentally omitted.
Parameters with require_binding: true are well-suited for use in event filters: the constraint guarantees a binding is always present, and the binding ensures the value is reliably agent-controlled and the allow list entry is sealed from task start.
Parameter Sources
A capability parameter can originate from four sources, in priority order (highest wins):
1. Agent Bindings ← highest priority, always wins; hides parameter from LLM
2. Middleware Bindings ← overrides capability-level bindings in invoke steps
3. LLM Generation ← blocked for parameters that have an agent binding
4. Default Value ← from the parameter schema's default field
Action Allow List
The allow list is maintained per-tool, per-task, keyed by parameter name. It is a flat namespace — root, per-action, and per-event parameters all share the same allow list if they share a name.
Growing the allow list:
- Every time the LLM calls an action on a tool, ALL resolved parameter values (root + per-action) are added to the allow list set for their parameter name.
- A name accumulates multiple values over a task if the LLM calls actions with different values.
Using the allow list in event filters:
receive.filterexpressions that referenceparameters.*are resolved against the allow list.- The filter passes if the payload value is a member of the allow list set for that name.
- If a parameter's allow list is empty (no action has been called with that name yet), events referencing it are discarded.
Sealing the allow list:
- A parameter with an agent-defined binding has its allow list entry sealed at task start. The runtime MUST NOT append to it from action calls.
require_binding: trueis a validation constraint that ensures a binding is present — it is not itself what seals the entry.
Per-action / per-event sync:
- If a per-action parameter and a per-event parameter share the same name, they are the same allow list entry. This is how tool authors link specific action inputs to specific event filters — through naming.
Interpolation
After parameters are resolved, their values are interpolated into tool spec fields using {variable.key} syntax. See CEL Reference for the full interpolation roots.
# parameters.path resolved from LLM, settings.github.token from settings
url: "https://api.github.com/repos/{settings.github.owner}/{parameters.repo}/contents/{parameters.path}"
headers:
Authorization: "Bearer {settings.github.token}"
Event message templates use {event.payload.*} interpolation — referencing the raw inbound payload directly. There is no extraction alias layer.
Settings
Settings are namespace-level configuration values declared in the tool's settings schema. They are:
- Configured by namespace administrators, not provided by the LLM.
- The runtime MUST NOT allow the LLM to set any key declared in
settings. - Referenced in tool fields via
{settings.<key>}.
settings:
properties:
api_key:
title: "API Key"
format: "password" # masked in UIs
base_url:
title: "API Base URL"
default: "https://api.example.com"
Auth Tokens
The {auth.<provider>} interpolation root injects tokens generated by the runtime's pluggable authentication system. Auth providers are configured at the server level, not in the tool manifest.
headers:
Authorization: "Bearer {auth.oauth2}"
Pipeline Example
# Tool manifest
parameters:
properties:
# Root: applies to all actions + all events
repo_id:
type: string
require_binding: true # validation constraint: binding MUST be provided
actions:
- name: create_issue
parameters:
properties:
# Per-action: LLM provides this; also populates allow list for 'assignee'
assignee:
type: string
description: "GitHub login of the assignee."
events:
- name: issue_assigned
parameters:
properties:
# Per-event: shares 'assignee' allow list with create_issue action
# Events only route for assignees the LLM has previously specified
assignee:
type: string
receive:
webhook:
filter: >
event.payload.action == 'assigned'
&& event.payload.repository.id == parameters.repo_id
&& event.payload.assignee.login == parameters.assignee
# Agent manifest
capabilities:
my-tool:
bindings:
repo_id: "context.input[0].repo_id" # sealed — allow list immutable
# assignee: not bound → starts empty; grows as LLM calls create_issue
Execution flow for this example:
repo_id→ sealed from binding at task start.issue_assignedevents for this repo are routable from the beginning, scoped torepo_idonly.assignee→ allow list starts empty.issue_assignedevents for any assignee are discarded.- LLM calls
create_issuewithassignee: "alice"→ allow list forassigneebecomes{"alice"}. issue_assignedevents for Alice now route. Events for other assignees are discarded.- LLM calls
create_issuewithassignee: "bob"→ allow list becomes{"alice", "bob"}. Events for either now route.