Run Metadata
Every agent run carries metadata: a small set of named values that describe the run, alongside its input. The input is what the agent works on, such as a question, an article or an email body. Metadata is the context around it, such as who asked, which customer, which channel, the email's sender or the article's URL.
Steps read metadata with {{metadata.<key>}}, in any field that takes string substitutions, and in conditions, filters and Call Agent mappings:
You are helping {{metadata.customer_name}} on the {{metadata.plan}} plan.
Reply in {{metadata.language}}.
Every key is declared
A trigger declares the metadata keys its agent's steps may use. The declaration is the agent's metadata interface: a caller can read it to learn what to send, and the platform checks runs and saves against it.
- A step may use only a key that one of the agent's triggers declares. Saving an agent whose steps use an undeclared key is refused, and the error names the key and where it is used. An import, and some API and MCP saves, declare such keys for you instead.
- Each declared key is optional or required.
- A declared key that a run doesn't carry reads as empty in a template, and as null in a condition.
A declaration entry has three fields:
{ "key": "customer_id", "origin": "custom", "required": true }
| Field | Meaning |
|---|---|
key | The name steps use, as in {{metadata.customer_id}} |
origin | system, source or custom — see Where keys come from |
required | Only a custom key can be required; a run started without it is refused |
A key can be up to 255 characters, and can't be indexing_state or start with _seclai., which the platform uses internally. A key containing { or } can't be referenced, so the editor refuses one.
Where keys come from
| Origin | Who sets it | Can be required |
|---|---|---|
| System | The platform, on every run: trigger_type | No |
| Source | The platform, from what started the run: the email, the Slack message, the cloud-drive file or the content item | No |
| Custom | A caller who starts the run, a trigger's default value, or a Slack form field | Yes |
System and source keys reach the run, and show in its trace, whether or not they are declared.
System keys
| Key | Value |
|---|---|
trigger_type | The type of the trigger that started the run, such as email_received |
Source keys
Each trigger type that starts from an event has its own set:
- Email Received — the sender, recipients, subject, threading headers and authentication results. See Captured Metadata.
- Slack command — the workspace, channel, thread, the person and the text they sent. See Captured Metadata.
- Cloud file triggers — the file's name, path, size, type and the drive event. See Cloud File Triggers.
- Content triggers — the platform keys below, plus any field of the content item's own metadata.
| Content key | Value |
|---|---|
content_guid | The item's identifier within its source, such as a feed entry's GUID. Set on every content run. |
url | A link to the item in Seclai |
source_connection_id | The id of the content source the item came from |
source_connection_content_version_id | The id GET /contents/{id} takes. It can link to a newer version than the run's input. |
A content trigger can also declare any field of the item's metadata by name, such as author or published_date. Which fields an item has depends on its source.
Custom keys and default values
A custom key gets its value from one of three places:
- The caller — whoever starts the run sends it. Apart from a Slack form's fields, only a Dynamic Input or Template Input trigger takes custom keys from a caller, because those are the only types the API and MCP start directly.
- A default value set on the trigger. When a caller also sends the key, the caller's value wins; the default is used only when the caller sends nothing.
- A Slack form field — each field's key is a custom key, required when the field is.
On every other trigger type (email, Slack, content, cloud file), nothing sends custom keys, so a custom key there needs a default value or a Slack form field.
A key with a default value is always present, so it can't also be required.
When a run or a save is refused
A run started by a caller — the Run button, the API, MCP, a parent agent's Call Agent step or a Slack user — is refused before any step runs when it:
- lacks a required custom key, or
- sends a custom key the trigger doesn't declare.
The refusal names every such key, and the run uses no credits:
Run refused: missing required metadata: customer_id; metadata the trigger does not declare: cutomer_name
The API returns it as an HTTP 400, MCP as an error, a Call Agent step fails with it, and a Slack user sees it in the thread.
A run the platform starts — an email, a content event, a cloud file, a schedule — is never refused for its metadata. A declared key it doesn't carry reads as empty.
A save is refused when, after the change:
- a step uses a key no trigger declares,
- a scheduled Template Input trigger has a required key, or
- a Call Agent step's mapping names a key that isn't a custom key the called agent declares, or leaves out one it requires.
Undeclared metadata: 'region' (referenced at summarize.prompt_template) is not declared on any trigger
Declaring keys in the editor
The trigger's Metadata step
Open the trigger and go to its last step, Metadata:
- System metadata lists every platform key for the trigger's type, with what it holds, an example value, and whether it is set on every run or only when present. Tick a key to declare it. A content trigger also lists the fields its knowledge base's items carry, with On items: how many items have a value for each, out of all items or those that arrived in the last 30 or 7 days. Hover a count to see the field's most frequent values. A field with a low count is empty on most runs. Add content field declares a field no item carries yet.
- Custom metadata lists the trigger's custom keys. Each has a Default value, and on a Dynamic or Template Input trigger a Required box; setting a default turns Required off. A Slack trigger's form fields are listed here too, read-only.
- A key the agent's steps use is marked Used by steps. If a step uses a key the trigger doesn't declare, a warning lists it before you save.
From a step
In a step's field, the Insert Metadata Field picker lists the keys the agent's triggers declare, grouped as System, Source and Custom. A custom key shows Required, Optional, or its default value (= gold). Click one to insert it.
When the agent has one trigger, the picker can also declare as it inserts:
- Platform metadata you can add lists the trigger type's platform keys not yet declared; one click declares and inserts.
- Add a metadata field declares a new key, with an optional Default value and Required. On a trigger no caller sends custom keys to, the default value is required.
A {{metadata.<key>}} you type yourself for an undeclared key is flagged under the field as not declared and, when the agent has one trigger, offers Declare on trigger to fix it.
Declaring keys from the API and MCP
Read a trigger's declaration, and the platform keys it could declare:
curl https://api.seclai.com/agents/{agent_id}/triggers/{trigger_id}/metadata-keys \
-H "X-API-Key: YOUR_API_KEY"
Replace it — the body is the whole declaration:
curl -X PUT https://api.seclai.com/agents/{agent_id}/triggers/{trigger_id}/metadata-keys \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metadata_keys": [
{ "key": "trigger_type", "origin": "system", "required": false },
{ "key": "customer_id", "origin": "custom", "required": true },
{ "key": "channel", "origin": "custom", "required": false }
]
}'
For a content trigger, see how many of its knowledge base's items carry each field, over all time and the last 30 and 7 days, with each field's most frequent values:
curl https://api.seclai.com/knowledge_bases/{knowledge_base_id}/metadata-key-stats \
-H "X-API-Key: YOUR_API_KEY"
Default values are set on the trigger's Metadata step in the editor, and an exported agent carries them. A definition saved through the API with Seclai-Version: 2026-11-02 or later is refused if it uses an undeclared key; with an earlier version, each such key is declared optional for you. Importing an agent declares what its steps use the same way. Neither can declare a custom key on a trigger no caller sends custom keys to; that key needs a default value.
In MCP, get_trigger_metadata_keys and set_trigger_metadata_keys read and replace a declaration, and get_knowledge_base_metadata_key_stats counts the fields a content trigger's items carry. update_agent_definition takes declare_metadata: true to declare, as optional, the keys the new definition uses that no trigger declares yet, with the same exception.
Examples
A support bot called from your app
A Dynamic Input trigger declares customer_id as required and channel as optional, with channel defaulting to web.
curl -X POST https://api.seclai.com/agents/{agent_id}/runs \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "Where is my order?",
"metadata": { "customer_id": "cus_8841", "channel": "mobile" }
}'
- This run uses
channel=mobile, the caller's value. A run that leaveschannelout getsweb. - A run without
customer_idis refused, as is one that sendscustomerId, a key the trigger doesn't declare.
Look up orders for customer {{metadata.customer_id}}.
The customer is writing from our {{metadata.channel}} app.
A scheduled report
A Template Input trigger runs every Monday with this input template:
Write the weekly {{metadata.region}} sales summary for {{date UTC}}.
region has the default value EMEA. A schedule has no caller, so a scheduled trigger can't have a required key: give the key a default value instead. If a required key has no default, adding the schedule is refused. The same trigger run from the API can send "region": "APAC" to override the default for that run.
Replying to an email, routed by team
An Email Received trigger declares the source keys email_from and email_subject, and a custom key team with the default value billing. Nothing sends custom keys to an email trigger, so team needs that default.
Send Email (reply_to_sender: true,
subject: "Re: {{metadata.email_subject}}")
Prompt Call: You answer {{metadata.team}} questions for {{metadata.email_from}}.
Summarizing new articles with a link back
A Content Added trigger declares the platform key url and the item fields title and author, ticked from the fields its items carry.
New post: {{metadata.title}} by {{metadata.author}}
Read it in Seclai: {{metadata.url}}
An item whose source has no author still runs; {{metadata.author}} reads as empty.
Routing on a key
An If/Else step can branch on a declared key. Its condition target is metadata.plan, compared with $eq to enterprise:
if_else (conditions: [{ target: "metadata.plan", operator: "$eq", value: "enterprise" }])
then → Prompt Call (priority support prompt)
else → Prompt Call (standard prompt)
When a run doesn't carry plan, the target is null and the condition takes the else branch.
A Slack form
A Slack command trigger's form has a required Customer field with the key customer and an optional Priority field with the key priority. Both are custom keys with nothing more to declare:
Open a ticket for {{metadata.customer}} at {{metadata.priority}} priority.
Requested by {{metadata.slack_user_name}}.
slack_user_name is a source key, so it is declared on the trigger's Metadata step. A default value for priority, such as normal, is used when the person leaves the field blank.
Passing metadata to another agent
A Call Agent step sends the called agent only the keys its metadata_mapping names, as {"<called agent's key>": "<this run's key>"}. Nothing else passes through.
Call Agent (agent_id: "billing-lookup",
pass_input: true,
metadata_mapping: { "account_id": "customer_id", "lang": "language" })
When this run carries customer_id and language, the called agent receives them as account_id and lang. Each must be a custom key the called agent's trigger declares, and every key it requires must be mapped, or the step can't be saved. The called agent also gets its own trigger's default values and the platform's keys for its run. An empty mapping ({}) sends no metadata.