Skip to main content

Modes for Background Agents

A mode is a named extra system prompt for a background agent. You write modes once, then pick one for a Slack channel, a Slack workflow step, an API trigger or a scheduled trigger. The agent runs under that mode's prompt, so one agent can investigate in #incidents and implement in #eng-bugs without two sets of instructions fighting in one prompt.

A mode can also define the shape of the agent's result. The agent then submits its result as data, Slack shows it as a formatted message, a workflow step can pass its fields on, and API callers can fetch it.

Beta

Modes are in beta. Ask your Willow contact to enable them for your organization. Until they are enabled, the Modes tab and the mode pickers are hidden, and channels and triggers run as they always have.

How it works​

When a run starts under a mode, Willow puts the mode's prompt at the start of the message it delivers to the agent, in a block with a clear start and end:

[Mode: Investigate] Follow these additional instructions for this run:
You are investigating. Do not change code or data.
Report findings with evidence and a confidence level.
[End of mode instructions]

Checkout is returning 500s since the 14:05 deploy. Can you take a look?

A few things follow from that:

  • The mode is added to the message. It does not replace or edit the agent's own system prompt, which stays as it is on the agent's Settings tab.
  • It applies the same way for every trigger type, on every platform that supports it.
  • Every message in a conversation is delivered under the mode its trigger selects. In a Slack thread, each reply picks up the channel's mode again.
  • A channel, step or trigger with no mode runs exactly as before.
  • Deleting a mode never breaks a trigger that used it. The trigger keeps running with no mode.

Create a mode​

Open the agent and select the Modes tab, then Add mode.

FieldWhat to enter
NameUp to 60 characters. Names are unique on an agent, ignoring case, because an API caller can pick a mode by name.
DescriptionOptional, up to 280 characters. Shown on the mode's card.
System promptThe instructions, up to 8,000 characters.
Structured resultOptional. See Structured results.

An agent can have up to 20 modes. Each card shows how many triggers use the mode. Deleting a mode that is in use tells you how many triggers will fall back to no mode.

Use a mode​

Pick a mode wherever the agent is started. Each picker is optional and reads No mode by default.

WhereHow
Slack channelIn the agent's Triggers tab, open a channel row and choose a Mode. Every run in that channel uses it. The All other channels and All Direct Messages rows don't have a picker yet.
Slack workflow stepOpen the step's settings in the Triggers tab and choose a Mode. See Workflow step outputs.
API triggerChoose a Mode when you add or edit the trigger. A caller can override it for one call. See Pick a mode per API call.
Scheduled triggerChoose a Mode when you add or edit the trigger.
Webhook triggerIn the webhook workflow that delivers events to the agent, choose a Mode under the agent. Every event the workflow sends runs under it. The picker appears when you edit the workflow from the agent's Triggers tab or from the integration's Webhooks settings.

Each trigger row shows a badge with the mode it uses.

Pick a mode per API call​

An API trigger accepts a mode field in the request body. It overrides the trigger's own mode for that call.

curl -X POST https://<your-gateway>/agent-api/incident-responder/trigger/<triggerId> \
-H "Authorization: Bearer $CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{ "message": "Fix the null check in checkout", "mode": "implement" }'
FieldTypeBehavior
modestringA mode's name (case-insensitive) or its id. Overrides the trigger's mode for this call.

A mode the agent doesn't have returns a 400 that lists the agent's modes, so a typo never runs the agent with no mode:

Unknown mode "investgate". This agent's modes: "Investigate", "Implement", "Weekly summary".

Structured results​

Turn on Structured result in a mode to have the agent hand back its result as data that matches a shape you define, instead of free text.

Define the shape​

Build the shape as a list of fields. Each field has a name, a type, a description for the agent, and a required or optional setting.

TypeResult value
TextA string.
NumberAny number.
Whole numberAn integer.
Yes / notrue or false.
List of textAn array of strings.

Field names use letters, digits and underscores, and can't start with a digit.

For anything richer, such as nested objects or a fixed set of allowed values, switch to the JSON view and write a JSON Schema. The root must be an object, and the schema is limited to 16,000 characters. A schema the field list can't show stays in the JSON view.

How the agent submits it​

When a run starts under a mode with a structured result, Willow adds the schema and a one-time run token to the mode's instructions. The agent hands back its result in one of two ways:

  • In Slack, it calls reply_to_slack_thread with the result in output instead of text. One call checks the result, stores it, and posts it to the thread.
  • Everywhere else, it calls submit_output with the result. This tool is available only on agents that have at least one mode with a structured result.

Either way, Willow checks the result against the schema the run started with. If it doesn't match, the agent gets a list of what to fix and sends it again:

The output does not match the schema:
- output must have required property 'evidence'
- /confidence must be string
Fix these and call submit_output again.

Editing a mode later never changes what a run already in progress is held to. Results are limited to 200,000 characters.

In Slack​

The result is posted as a message with one block per field, in the order of the schema. Field names become readable labels, lists become bullets, yes/no values read Yes or No, and nested values appear as a code block. Empty fields are left out.

Summary: Checkout fails after the 14:05 deploy.
Confidence: high
Evidence
• 500s start at 14:06
• Null check removed in #4821
Needs page: Yes

Fetch a result with the API​

An API trigger call returns a session_key. Use it to read the structured results of that conversation:

curl "https://<your-gateway>/agent-api/<slug>/output?session_key=<session_key>" \
-H "Authorization: Bearer $CLIENT_ID:$CLIENT_SECRET"
{
"data": [
{
"id": "7b1e…",
"session_key": "custom:9f2c",
"mode_id": "…",
"mode_name": "Investigate",
"status": "submitted",
"output": {
"summary": "Checkout fails after the 14:05 deploy.",
"confidence": "high"
},
"created_at": "2026-10-09T14:07:50Z",
"submitted_at": "2026-10-09T14:09:12Z"
}
]
}

Results come back newest first. Use limit (1 to 50, default 10) to change how many. A run's status is pending until the agent submits, so poll until it reads submitted. A run whose agent never submits stays pending.

Send results to a webhook​

A mode can send its structured result to a webhook when the agent submits it. Willow sends a signed POST, so your own system gets the result without polling. This suits automation that must always run. For a step that needs judgment, such as paging someone only when severity is high, let the agent do it with its own tools instead.

Create a destination. In the Modes tab, under Destinations, select Add destination.

FieldWhat to enter
NameA label, for example Incident tracker.
Webhook URLAn https URL reachable from the internet.
Signing secretSelect Generate, copy it, and keep it in your receiver. Willow stores it encrypted and can't show it again. Enter a new one to replace it.
Send results to this destinationSwitch off to pause the destination.

Send test posts a sample event, so you can check your receiver and its signature check before real results arrive. An agent can have up to 10 destinations.

Fill part of the URL from the result. When the receiving address depends on the result, put {field} placeholders in the path or query. Each one is replaced with the result's top-level field of that name.

https://tracker.example.com/projects/{project_id}/events?team={team}
RuleWhy
Placeholders work in the path and the query only. The scheme, host and port stay fixed.The agent writes the result, so it can choose a project or team on your server, but never a different server.
A value may use letters, digits and . _ ~ -, or be a number.A value can't add a path segment, a parameter or another host. . and .. are refused.
A missing, empty or unusable value fails that delivery. Nothing is sent.Willow never falls back to a different address. The failure shows under the result in Sessions with the reason.
The finished URL is checked like any other: https, public address.The same safety checks apply after the placeholders are filled.

In the destination dialog, the placeholders Willow found and a preview of the URL appear under the field. In a mode's Send the result to list, a destination whose URL uses a field the result doesn't define, doesn't require, or can't hold a plain value shows a warning. Define that field as a required text, number or choice field. The Sessions tab shows the exact URL each delivery used. Send test fills every placeholder with test.

For addresses that differ by host rather than by an id, define one destination per host and choose between them per mode or trigger.

Choose where a mode sends. Turn on a mode's Structured result, then tick the destinations under Send the result to.

Change it for one trigger. When a trigger uses a mode with a structured result, its settings show Send the result to:

ChoiceEffect
Same as the modeThe default. The trigger sends to the mode's destinations.
NowhereThe result is stored and readable, but not sent anywhere.
Choose destinationsThe trigger sends to the ones you tick instead of the mode's.

This is available on Slack channels, Slack workflow steps, API triggers, scheduled triggers and webhook workflows. The choice is fixed when a run starts, so editing a mode or trigger later never changes a run already in progress. A destination you delete or pause is skipped.

What your receiver gets. A POST with a JSON body:

{
"event": "agent.run.output",
"id": "7b1e…",
"agent": { "id": "…", "slug": "incident-responder", "name": "Incident Responder" },
"mode": { "id": "…", "name": "Investigate" },
"session_key": "slack:C0123:1728484800.000100",
"status": "submitted",
"output": { "summary": "Checkout fails after the 14:05 deploy.", "confidence": "high" },
"submitted_at": "2026-10-09T14:09:12.000Z"
}
HeaderValue
X-Willow-Eventagent.run.output, or agent.run.output.test for a test.
X-Willow-DeliveryAn id for this delivery.
X-Willow-TimestampUnix seconds when Willow sent it.
X-Willow-Signaturet=<timestamp>,v1=<signature>. Present when the destination has a secret.

Verify the signature. The signature is the hex HMAC-SHA256, keyed with your signing secret, of the timestamp, a dot and the raw request body. Reject a request whose timestamp is more than a few minutes old.

import { createHmac, timingSafeEqual } from "node:crypto"

function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")))
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex")
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300
return (
fresh &&
v1.length === expected.length &&
timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
)
}

Use id together with submitted_at to ignore a delivery you have already handled. If the agent submits a corrected result for the same run, you receive it again with the same id and a later submitted_at.

Delivery and retries. Any 2xx answer counts as delivered. Otherwise Willow tries again after 10 seconds and again after a minute, three tries in all. After that the delivery shows Failed under the result in the Sessions tab, with a Retry button. Willow does not keep retrying in the background beyond those three tries.

Limits and safety.

  • Only https URLs are accepted, with no credentials in the URL.
  • Willow only connects to public addresses. It refuses localhost, private networks and cloud metadata addresses, and checks the address again when it connects.
  • Redirects aren't followed, and a request that takes longer than 10 seconds counts as failed.
  • Only admins who can edit the agent can add, change or test destinations.

Workflow step outputs​

A Slack workflow step can pass the result on to the next step in the workflow. When a step uses a mode with a structured result and is set to continue When the agent answers, each of the step's Slack outputs is matched to the result field with the same name, if Slack's declared type for the output can hold that field's value:

Result fieldFits a Slack output of type
Textstring
Whole numberinteger or number
Numbernumber
Yes / noboolean
List of textarray
Nested valuestring, passed on as JSON text

Matching outputs are set to Field from the mode's result when you pick the mode. You can change any output to another option, such as Link to the thread, and your choice is kept. The match needs When the agent answers, because the result exists only once the agent replies. If you change a step back to continue as soon as it starts, those outputs are cleared.

Slack checks each output against the type the step declares. The settings only offer fields that fit, but if you later change the schema or the output's type in Slack, a value that no longer fits makes Slack reject the step.

Platform support​

PlatformModes
Willow Agents, Claude Managed Agents, AWS AgentCore, Cursor, kagentSupported.
Custom and custom-managedSupported. Structured results also need your harness to connect to Willow's gateway so the agent can call submit_output.
Claude Tag, LangGraphNothing to apply a mode to. These platforms don't run conversations started by triggers.
Snowflake Cortex AgentsNot supported. Cortex runs only Snowflake's own tools and can't call Willow's, so there is nothing for a mode to ride on. The Modes tab and pickers are hidden, and saving modes on a Cortex agent is rejected.

Troubleshooting​

SymptomWhat to check
No Modes tab or pickersModes are in beta. Ask your Willow contact to enable them. The tab is also hidden on Snowflake Cortex agents.
A channel or trigger runs without the modeConfirm a mode is selected on that channel or trigger. A trigger whose mode was deleted shows Deleted mode and runs with none.
400 Unknown mode from an API callThe mode value doesn't match a mode name or id on this agent. The error lists the modes that exist.
A result stays pendingThe agent didn't submit. Check the session's events for a submit_output call or a Slack reply with output. On a custom harness, confirm it can reach Willow's gateway.
The agent keeps getting "output does not match the schema"Read the errors in the session. The field descriptions are the agent's only guide to what each field holds, so tighten them.
A result isn't reaching the webhookOpen the session. The result shows each destination's status and the last error. Check the destination isn't paused and that the mode or trigger sends to it (Send the result to). Use Send test on the destination to check the URL and your signature check.
A workflow step output stays emptyCheck the step is set to When the agent answers, that a field has the same name as the output, and that the types fit.