Node Types
Configuration reference for all 20 node types available in .swirls files.
Each workflow node has a type that determines its behavior. The 20 types cover these operations:
| Job | Types |
|---|---|
| Think | agent, ai |
| Compute | code |
| Reach out | http, integration, email, scrape, search, parallel |
| Branch and repeat | switch, map, fanout, while, wait |
| Remember | stream, disk, database, postgres |
| Compose | workflow |
| Ask a person | review (a config on any node, see Reviews) |
This page documents all 20 node types, their required and optional fields, and examples. The complete list of valid type values: agent, ai, code, decision, database, disk, email, fanout, http, integration, map, parallel, postgres, scrape, search, stream, switch, wait, while, workflow. Binary uploads use schema-marked file values (contentMediaType / swirls:file) — not a node type. type: bucket was removed.
parallel is not workflow concurrency. Despite the name, type: parallel calls Parallel.ai for web research (search, extract, findall). For query-centric web discovery via Firecrawl, use type: search. Use map for sequential per-item work or fanout for independent concurrent per-item work. Independent DAG branches need no special node type; ready branches run concurrently up to the workflow's concurrency: cap.
For how nodes connect to each other, see Workflows. For type-safe access to node outputs, see Context. For per-node credit costs on Swirls Cloud, see Credits.
Shared optional fields
Every node accepts these fields in addition to its type-specific fields:
| Field | Description |
|---|---|
label | Display name. Defaults to the node name. |
description | Longer description. |
secrets | Object literal mapping secret block names to var arrays: { block: [VAR1] }. |
review | Human-in-the-loop review config. See Reviews. |
failurePolicy | Retry, skip, or fallback on failure. See Failure policies. |
format | Output format hint for the dashboard: markdown, html, text, image, video, audio, mixed, or json. |
code
Run TypeScript in an isolated sandbox.
| Field | Type | Required | Description |
|---|---|---|---|
| code | @ts { } block | Yes | TypeScript to execute. Access context.nodes for upstream data. Return value becomes output. |
| schema | @json { } block | No | Output schema (JSON Schema). Use outputSchema on the root node only. |
node normalize {
type: code
label: "Normalize email"
schema: @json {
{ "type": "object", "required": ["email"], "properties": { "email": { "type": "string" } }, "additionalProperties": false }
}
code: @ts {
const email = context.nodes.root.input.email ?? ""
return { email: email.toLowerCase().trim() }
}
}A code node has no database access: context.db is not in scope here. Query or mutate a managed database from a database node and read its output downstream.
Budget: a code node's code: body has 30 seconds of wall-clock time to complete. Time spent awaiting I/O (an HTTP call, a downstream request) counts against it. Exceeding it fails the node. Split long-running work across multiple nodes, since each node starts its own fresh budget.
ai
Invoke an AI model. Supports multiple kinds: text, object, image, video, and embed.
| Field | Type | Required | Description |
|---|---|---|---|
| kind | string | Yes | One of: text, object, image, video, embed. |
| provider | string | No | Which API to call: openrouter (default), anthropic, openai, or google. |
| model | string or @ts | No | Model identifier or runtime expression; omission uses the provider/kind default. See runtime model selection. |
| prompt | @ts { } block | Yes (at runtime) | TypeScript expression returning the prompt string. |
| temperature | number | No | Sampling temperature. |
| maxTokens | number | No | Max output tokens. |
| options | object | No | Kind-specific options (e.g. { n: 1, size: "1024x1024" } for image). |
| schema | @json { } block | No | Output schema. Required for the object kind. |
Example (object kind):
node classify {
type: ai
label: "Classify intent"
kind: object
model: "anthropic/claude-sonnet-4.6"
prompt: @ts {
return `Classify this message: ${context.nodes.root.input.message}`
}
schema: @json {
{
"type": "object",
"required": ["intent", "confidence"],
"properties": {
"intent": { "type": "string" },
"confidence": { "type": "number" }
}
}
}
}Example (image kind):
node generate_image {
type: ai
label: "Generate image"
kind: image
model: "openai/dall-e-3"
prompt: @ts {
return `A professional illustration of: ${context.nodes.root.input.topic}`
}
options: {
n: 1
size: "1024x1024"
}
}decision
Evaluate a reusable decision declaration with Jev. Required fields: decision: <name> and state: (string/object/array or inline/file-backed @ts). Optional model: overrides the declaration for one invocation. Provider, explicit secret binding, questions, and schemas belong on the declaration.
Output is { decision, provider, model, answers, usage } with typed Choice, Score, and Noul answers. Use probabilities in a downstream switch and a separate review gate. Node schemas, credential overrides, and enabled reviews are rejected. See Decisions with Jev for the full contract and complete example.
switch
Conditional routing. Returns a case name to determine which branch executes.
| Field | Type | Required | Description |
|---|---|---|---|
| cases | string array | Yes | List of case names. |
| router | @ts { } block | Yes | TypeScript expression returning one of the case names. |
Use labeled edges in the flow block: route -["case_name"]-> target.
node route {
type: switch
label: "Route by priority"
cases: ["high", "medium", "low"]
router: @ts {
const score = context.nodes.root.output.score ?? 0
if (score > 80) return "high"
if (score > 40) return "medium"
return "low"
}
}http
Make HTTP requests to external APIs. Attach a credential profile with auth: for OAuth, API key, basic, or bearer auth, or a connection with connection: for brokered OAuth. Set one, not both. Avoid building Authorization headers by hand; also avoid hyphenated header keys like Content-Type in literal headers objects (they break the parser; see Syntax).
| Field | Type | Required | Description |
|---|---|---|---|
| url | @ts { } block or string | Yes | Request URL. |
| method | string | No | HTTP method. Defaults to GET. |
| auth | identifier | No | Name of a top-level auth credential profile. Mutually exclusive with connection:. |
| connection | identifier | No | Name of a top-level connection block for brokered OAuth. Mutually exclusive with auth:. |
| network | identifier | No | Name of a top-level Project Network. Requires a static absolute URL. |
| headers | object or @ts { } | No | Request headers. Use @ts for keys with hyphens. |
| body | @ts { } block | No | Request body. |
| schema | @json { } block | No | Output schema. |
Named execution gateways are not yet available. A request with network: currently fails before dialing and does not fall back to a public route.
The node's output is the parsed response body directly (JSON value or raw text); there is no { status, headers, body } envelope. Status, status text, content type, and duration are available at context.nodes.<name>.meta.
auth api_key_ex {
type: api_key
secrets: api_k
key: API_KEY
header: "X-Api-Key"
}
secret api_k {
vars: [API_KEY]
}
Inside a workflow:
node fetch_data {
type: http
label: "Fetch from API"
method: "POST"
auth: api_key_ex
url: @ts {
return "https://api.example.com/data/" + context.nodes.root.input.id
}
body: @ts {
return JSON.stringify({ query: context.nodes.root.input.query })
}
schema: @json {
{ "type": "object", "properties": { "results": { "type": "array" } } }
}
}integration
Call a provider API through a project connection. Swirls brokers a short-lived access token for the bound account and proxies the request, so no provider credentials live in your files. Prefer a typed top-level action block, installed from the integration catalog with swirls add; a raw path: is the untyped legacy form.
| Field | Type | Required | Description |
|---|---|---|---|
| connection | identifier | Yes | Name of a top-level connection block. |
| action | identifier | Preferred | Name of a top-level action block declaring the provider operation. Do not set method or path on the node when using action. |
| path | string | Legacy | Provider API path when action: is omitted (untyped params and output). |
| method | string | No | GET (default), POST, PUT, DELETE, or PATCH. Raw path: only. |
| params | @ts { } block | No | Returns the request body (POST/PUT/PATCH) or query params (GET). |
| schema | @json { } block | No | Types the response. Raw path: only; action blocks supply the output schema. |
connection team_slack {
label: "Team Slack"
provider: slack
}
action slack_post_message {
provider: slack
method: POST
path: "/chat.postMessage"
encoding: form
input: @json {
{ "type": "object", "required": ["channel", "text"], "properties": { "channel": { "type": "string" }, "text": { "type": "string" } } }
}
output: @json {
{ "type": "object", "required": ["ok"], "properties": { "ok": { "type": "boolean" } } }
}
}
Inside a workflow:
node post_slack {
type: integration
label: "Post to Slack"
connection: team_slack
action: slack_post_message
params: @ts {
return {
channel: context.nodes.root.output.channel,
text: context.nodes.root.output.text,
}
}
}Bind the connection to a real account in Cloud Connections before running the workflow. Use type: http with connection: instead when you need full URL control. See Connections.
Send email via Resend with dynamic content.
| Field | Type | Required | Description |
|---|---|---|---|
| from | @ts { } block or string | Yes | Sender address. |
| to | @ts { } block or string | Yes | Recipient address. |
| subject | @ts { } block or string | Yes | Subject line. |
| text | @ts { } block or string | No | Plain text body. |
| html | @ts { } block or string | No | HTML body. |
| replyTo | @ts { } block or string | No | Reply-to address. |
The output shape is vendor-managed. Setting schema: on an email node will be rejected by the validator.
node send_email {
type: email
label: "Send notification"
from: @ts { return "[email protected]" }
to: @ts { return context.nodes.root.output.email }
subject: @ts { return "Your request has been processed" }
text: @ts {
return `Hello ${context.nodes.root.output.name}, your request is complete.`
}
}stream
Read persisted rows from a top-level stream block anywhere in the workspace, pinned to one version. Reads return all matching rows, newest first; pagination and sorting are not configurable today. See Streams for stream { workflow: , version:, versions: { vN { schema, condition?, prepare } } } and context.output in switch workflows.
| Field | Type | Required | Description |
|---|---|---|---|
| stream | identifier | Yes | Name of a top-level stream block. |
| version | v1, v2, … | Yes | Which versions: entry to read from that stream. |
| filter | @ts { } block | Yes* | Filter object for rows. Operators: eq, ne, gt, gte, lt, lte, like, in. Return {} for no filter. |
| schema | @json { } block | Recommended | Typically type: "array" of row objects. |
*Required in practice; use return {} when unconstrained.
postgres
Query or write to a user-managed PostgreSQL database declared in a top-level postgres block. Use select: for reads and insert:, update:, or delete: for writes. Each node references exactly one of the four.
| Field | Type | Required | Description |
|---|---|---|---|
| postgres | string | Yes | Name of a top-level postgres block. |
| select | @sql { } block | Yes* | Read-only SELECT (or WITH CTE) query. |
| insert | @sql { } block | Yes* | INSERT statement. |
| update | @sql { } block | Yes* | UPDATE statement. The validator warns when it has no WHERE clause. |
| delete | @sql { } block | Yes* | DELETE statement. The validator warns when it has no WHERE clause. |
| params | @ts { } block | Yes for insert; for the others, required when the SQL has {{key}} placeholders | Returns an object whose keys match {{key}} tokens in the SQL. |
| condition | @ts { } block | No | Write nodes only (insert, update, delete): if present, the write runs only when this returns true. |
| schema | @json { } block | Recommended for select | JSON Schema for result rows (typically an array of objects). |
*Exactly one of select, insert, update, or delete is required.
Select example:
node load_rows {
type: postgres
label: "Load active rows"
postgres: my_db
select: @sql {
SELECT id, name FROM items WHERE status = {{status}} LIMIT 10
}
params: @ts {
return { status: context.nodes.root.input.status }
}
schema: @json {
{
"type": "array",
"items": {
"type": "object",
"properties": { "id": { "type": "string" }, "name": { "type": "string" } }
}
}
}
}Insert example:
node save_row {
type: postgres
label: "Insert row"
postgres: my_db
insert: @sql {
INSERT INTO items (name, score) VALUES ({{name}}, {{score}})
}
params: @ts {
return {
name: context.nodes.classify.output.name,
score: context.nodes.classify.output.score
}
}
}Update example:
node refresh_score {
type: postgres
label: "Refresh score"
postgres: my_db
update: @sql {
UPDATE items SET score = {{score}} WHERE name = {{name}}
}
params: @ts {
return {
score: context.nodes.classify.output.score,
name: context.nodes.classify.output.name
}
}
}Delete example:
node prune_rows {
type: postgres
label: "Prune stale rows"
postgres: my_db
delete: @sql {
DELETE FROM items WHERE score < {{min_score}}
}
params: @ts {
return { min_score: context.nodes.root.input.threshold }
}
}Declare the database and tables in a top-level postgres block: see Postgres.
database
This is how a workflow reads and writes a Swirls-managed database block. operation narrows the injected client (context.db.<name>) to the declared capability, enforced at runtime, so the node is visible in flow { }, gateable with review:, and traced. operation: transaction is the one exception: it exposes the full client, inside one atomic $transaction, for a multi-step change no narrowed operation can express.
| Field | Type | Required | Description |
|---|---|---|---|
| database | string | Yes | Name of a top-level database block. |
| operation | query | insert | update | delete | transaction | Yes | Narrows which client methods run can call. |
| condition | @ts { } block | No | If present and returns false, the node is skipped (output { skipped: true }). |
| run | @ts { } block | Yes | The typed Prisma body, executed against the operation-narrowed client. |
Operation → client capability:
| operation | Client exposes |
|---|---|
query | findMany, findFirst, findUnique, count, aggregate, groupBy |
insert | create, createMany |
update | update, updateMany, upsert |
delete | delete, deleteMany |
transaction | The full client, inside one atomic $transaction |
Governed delete example:
node purge_stale {
type: database
label: "Purge stale users"
database: my_db
operation: delete
review: { enabled: true }
run: @ts {
return context.db.my_db.user.deleteMany({
where: { lastSeen: { lt: context.nodes.root.output.cutoff } },
})
}
}Transaction example (the full client, atomically):
node settle_invoice {
type: database
label: "Settle invoice"
database: my_db
operation: transaction
run: @ts {
return context.db.my_db.$transaction(async (tx) => {
const invoice = await tx.invoice.update({
where: { id: context.nodes.root.output.invoiceId },
data: { status: "PAID" },
})
await tx.ledgerEntry.create({
data: { invoiceId: invoice.id, amount: invoice.total },
})
return invoice
})
}
}For operation: transaction, review: and traces apply to the whole transaction as one step.
Budget: a database node's run: body has 30 seconds of wall-clock time to complete, including the query itself. Exceeding it fails the node. Batch a large scan or update over many rows with a map node instead of one long-running query.
Size limits. Each context.db call can return at most 64 MB into the sandbox (the query result, including includes, before your run: body slims it). The node's returned output is separately limited to 2 MB (Temporal activity payload). Over either cap the node fails with a message that names the limit. Select fewer columns, paginate with take, or store large results in the database / disk and return ids or a summary.
Declare the database and its Prisma schema in a top-level database block: see Database. See Context for where context.db is in scope.
workflow
Execute another workflow as a subgraph.
| Field | Type | Required | Description |
|---|---|---|---|
| workflow | identifier | Yes | Name of the workflow to execute (bare identifier, not a quoted string). |
| input | @ts { } block | Yes | TypeScript expression mapping input for the subgraph. |
node run_enrichment {
type: workflow
label: "Run enrichment"
workflow: enrich_contact
input: @ts {
return { email: context.nodes.root.input.email, name: context.nodes.root.input.name }
}
}Subgraph output is available as context.nodes.<workflowNodeName>.output.<leafNodeName> where leafNodeName is a leaf node in the child workflow.
map
Run a child workflow once per element returned from items. Requires maxItems (positive cap). Use either inline subgraph { ... } (no colon after subgraph) or workflow: named_workflow: exactly one.
Inside the child workflow and on the map node’s items block, context.iteration includes index, item (typed from the subgraph root inputSchema), total, and previous. Output is an array in items order, one entry per item, each keyed by the child workflow's leaf node names: [{ <leafName>: <leafOutput> }, ...].
Iterations run one at a time, in items order; each iteration can read the previous one via context.iteration.previous. concurrency: is invalid on map; use fanout when iterations are independent.
| Field | Type | Required | Description |
|---|---|---|---|
| items | @ts { } block | Yes | Returns an array to iterate. |
| maxItems | number | Yes | Maximum items to process. |
| subgraph | block | One of subgraph or workflow | Inline child workflow with root, optional nodes, and flow. |
| workflow | identifier | One of subgraph or workflow | Reference to a named workflow anywhere in the workspace. |
node per_ticket {
type: map
label: "Process each ticket"
items: @ts {
return context.nodes.root.output.tickets
}
maxItems: 100
subgraph {
root {
type: code
label: "Normalize"
inputSchema: @json {
{ "type": "object", "required": ["id"], "properties": { "id": { "type": "string" } }, "additionalProperties": false }
}
outputSchema: @json {
{ "type": "object", "required": ["id"], "properties": { "id": { "type": "string" } }, "additionalProperties": false }
}
code: @ts {
const item = context.iteration.item
return { id: item.id.trim() }
}
}
}
}fanout
Run an independent child workflow for every element with bounded concurrency. Fanout requires concurrency from 1 through 64 and preserves original item order in its output even when children finish out of order.
Inside each child, context.iteration includes index, item, and total. It does not include previous; use sequential map if one iteration depends on the last.
| Field | Type | Required | Description |
|---|---|---|---|
| items | @ts { } block | Yes | Returns an array to iterate. |
| maxItems | number | Yes | Maximum items to process. |
| concurrency | number | Yes | Maximum child workflows running at once; integer from 1 through 64. |
| subgraph | block | One of subgraph or workflow | Inline child workflow with a typed root. |
| workflow | identifier | One of subgraph or workflow | Reference to a named workflow anywhere in the workspace. |
node research_queries {
type: fanout
items: @ts { return context.nodes.root.output.queries }
maxItems: 100
concurrency: 8
workflow: research_query
}while
Run a child workflow repeatedly until condition is false or maxIterations is reached. Requires input, condition, update, and maxIterations. Same subgraph { } vs workflow: choice as map.
context.iteration includes index, input (threaded state from root inputSchema), and previous (last iteration’s leaf outputs). Output shape is { iterations: number; lastOutput: ... }.
| Field | Type | Required | Description |
|---|---|---|---|
| input | @ts { } block | Yes | Initial state for the loop. |
| condition | @ts { } block | Yes | If true, another iteration runs (after update). |
| update | @ts { } block | Yes | Produces the next input for the child workflow. |
| maxIterations | number | Yes | Hard cap on iterations. |
| subgraph | block | One of subgraph or workflow | Inline child workflow. |
| workflow | identifier | One of subgraph or workflow | Named workflow anywhere in the workspace. |
node refine_loop {
type: while
label: "Refine draft"
input: @ts {
return { draft: context.nodes.merge.output.text }
}
condition: @ts {
return context.iteration.index < 2
}
update: @ts {
return { draft: context.iteration.previous?.polish?.text ?? context.iteration.input.draft }
}
maxIterations: 5
subgraph {
root {
type: code
label: "Polish"
inputSchema: @json {
{ "type": "object", "required": ["draft"], "properties": { "draft": { "type": "string" } }, "additionalProperties": false }
}
outputSchema: @json {
{ "type": "object", "required": ["text"], "properties": { "text": { "type": "string" } }, "additionalProperties": false }
}
code: @ts {
return { text: context.iteration.input.draft + " (refined)" }
}
}
}
}Downstream code can read context.nodes.refine_loop.output.lastOutput.<leafName>.
scrape
Fetch and extract content from web pages via Firecrawl.
| Field | Type | Required | Description |
|---|---|---|---|
| url | @ts { } block | Yes | URL to scrape. |
| onlyMainContent | boolean | No | Strip navigation and boilerplate when true. |
| formats | array of strings | No | Response formats (e.g. markdown). |
| maxAge | number | No | Cache max age hint. |
| parsers | array of strings | No | Parser hints for the API. |
The output shape is vendor-managed. Setting schema: on a scrape node will be rejected by the validator.
search
Web discovery via Firecrawl (default) or Telem (provider: telem). Query-centric: pass a query string (or @ts returning one) and get a normalized { query, results[] } with each result tagged source: "web" | "news" | "images". The output shape is vendor-managed; setting schema: on a search node is a validator error.
| Field | Type | Required | Description |
|---|---|---|---|
| provider | firecrawl or telem | No | Defaults to Firecrawl; existing workflows retain their behavior. |
| query | @ts { } block or string | Yes | Search query. |
| limit | number | No | Default 5, max 20. Firecrawl: per source; Telem: total unique results. |
| sources | array | No | web, news, and/or images. |
| categories | array | No | github, research, and/or pdf. |
| location | string | No | Location bias. |
| country | string | No | Country code (e.g. US). |
| tbs | string | No | Time-based filter (e.g. qdr:w); web only. |
| includeDomains | array of strings | No | Only include these domains. |
| excludeDomains | array of strings | No | Exclude these domains. |
| scrapeFormats | array of strings | No | When set, attach page content via Firecrawl scrape options. |
node find {
type: search
query: @ts { return context.nodes.root.output.topic }
limit: 10
sources: ["web"]
}Firecrawl requires FIRECRAWL_API_KEY (same as scrape) and supports every field above. Telem requires TELEM_API_KEY and supports query, limit, and omitted sources or exactly ["web"]. Other filters and scrapeFormats are validation errors with Telem; use provider: firecrawl for those options.
Declare a top-level secret block:
secret web_research {
vars: [TELEM_API_KEY]
}Then use this node inside your workflow:
node find {
type: search
label: "Search the web"
provider: telem
query: "managed IT providers in Colorado"
limit: 10
}Set web_research::TELEM_API_KEY in project secrets. Enterprise projects can use type: managed on the secret block instead. Merely declaring this key does not change any node's provider.
Telem searches Brave and Exa, alternates their results starting with Brave, removes duplicate normalized URLs (ignoring fragments), and returns at most limit results overall. Results remain { query, results[] }, with web source tags, snippets in description, final list positions, and the engine in metadata.provider. A failed engine leaves the other engine's results usable and sets node metadata partial: true; no successful engines fails the node. A successful search with no matches returns an empty list.
scrape continues to use Firecrawl. Declare both keys when combining Telem discovery with Firecrawl scraping. For Parallel.ai’s research product (objective + searchQueries, extract, findall), use parallel instead.
parallel
Web search, URL extraction, and entity discovery via Parallel.ai. Not for running workflow steps concurrently. Use fanout for concurrent per-item iteration, map for sequential iteration, or multiple edges in flow { } for independent branches. The output shape is vendor-managed; setting schema: on a parallel node is a validator error.
| Field | Type | Required | Description |
|---|---|---|---|
| operation | search | extract | findall | Yes | API mode. |
| objective | @ts { } block | Yes | Return a string describing the goal. |
| searchQueries | @ts { } block | Search | Return string[] of keyword queries. |
| urls | @ts { } block | Extract | Return string[] of URLs. |
| mode | string | No | Search: one-shot, agentic, or fast (default fast). |
| excerptsMaxCharsPerResult | number | No | Search: excerpt size hint per result. |
| excerptsMaxCharsTotal | number | No | Search: total excerpt budget. |
| excerpts | boolean | No | Extract: include excerpts. |
| fullContent | boolean | No | Extract: include full content. |
| entityType | @ts { } block | FindAll | Entity type label. |
| matchConditions | @ts { } block | FindAll | Match rules array. |
| matchLimit | number | No | FindAll: max matches. |
| pollInterval | number | No | FindAll: polling interval. |
| pollTimeout | number | No | FindAll: max wait before timeout. |
| pollIntervalUnit | string | No | FindAll: seconds or minutes. |
| pollTimeoutUnit | string | No | FindAll: seconds or minutes. |
| generator | string | No | FindAll: base, core, pro, or preview. |
| excludeList | @ts { } block | No | FindAll: URLs or entities to skip. |
wait
Pause execution for a duration.
| Field | Type | Required | Description |
|---|---|---|---|
| amount | number | No | Numeric delay amount. |
| unit | string | No | One of: seconds, minutes, hours, days. Use with amount. |
| secondsFromConfig | @ts { } block | No | Returns the wait duration in seconds. Dynamic alternative to amount/unit. |
disk
Run a shell command on Archil (operation: exec, default) or copy a ready file handle onto a relative path (operation: copy_file). Declare the disk resource in a top-level disk block first.
| Field | Type | Required | Description |
|---|---|---|---|
| disk | identifier | Yes | Name of a top-level disk block. |
| operation | string | No | exec (default) or copy_file. |
| command | @ts { } block or string | exec only | Shell command to run on the mounted disk. |
| path | @ts { } block or string | copy_file only | Relative destination path on the disk. |
| file | @ts { } block | copy_file only | Must resolve to a ready file handle (kind: swirls.file). |
| overwrite | string | No | replace for atomic replace when destination exists (copy_file only). |
Exec output is fixed: stdout, stderr, exitCode, and timing. Setting schema: on a disk node is a validator error.
disk project_disk {
label: "Project disk"
}
Inside a workflow:
node list_files {
type: disk
label: "List files"
disk: project_disk
command: @ts {
return "ls /mnt"
}
}agent
Invoke a top-level agent block. The agent runs a tool-call loop: it calls tool workflows and built-in workspace tools until it produces a final answer or reaches maxSteps.
For configuration details (the agent block, profiles, sandbox configuration, structured output, tool workflow contract, and common mistakes), see Agents.
| Field | Type | Required | Description |
|---|---|---|---|
agent | identifier | Yes | Name of a top-level agent block. |
model | string or @ts | No | Model for this invocation; defaults to the declaration. Selection is checkpointed before inference and stays fixed through the tool loop. See runtime model selection. |
prompt | @ts { } block | Yes | Returns the user message string. |
profile | string | No | Name of a profile declared in the agent block. |
system | @ts { } block | No | Overrides the system prompt for this invocation. |
tools | identifier array | No | Narrows the effective tool set. Must be a subset of the profile's or agent's tools. |
schema | @json { } block | No | Structured output schema. Use schema, not outputSchema. |