AI Agent Node
The AI Agent node runs each turn through the Maestro agent and its model catalog. The Maestro agent is coming soon: it is in development and testing and is not part of release 3.0. This page describes the node as it is being built.
Overview
The AI Agent node runs one agent turn per input message. It sends the message to a model from your Maestro model catalog, lets the agent call the connector functions you allow it, and returns a structured answer that downstream nodes read like any other node result.
It is the node to reach for when a decision needs judgement rather than a rule: classifying a fault from a free-text work order, summarising an hour of alarms, deciding which of several recipes a batch matches.
Every message that reaches this node is an LLM call with real latency and real spend. Never put it directly behind a high-rate trigger. Put a Buffer or an on-change trigger in front so the agent sees decisions, not samples. The node's default timeout is 120 seconds for this reason, and the pipeline save warns when nothing upstream is limiting the rate.
Core Functionality
What it does
- Resolves
systemPrompt,userMessageandmemoryKeyagainst the live message, so each run can be about the asset that produced it. - Calls the model on the configured account, with the tools you allowed.
- Returns the answer shaped by your
outputSchema, plus what the run cost and which tools it used.
Choosing the model
account and model are the two halves of one id in the model catalog,
split at the first slash. The designer's picker writes both from a single
choice; when writing a pipeline by hand, llm-prod/claude-sonnet-4-6
becomes account: llm-prod and model: claude-sonnet-4-6.
Giving the agent tools
toolFunctionIDs lists saved connector functions the agent may call. Two
things must both hold: the function exists in this organization, and the
pipeline's execution identity holds function:execute on it. An agent
with no tools can still answer — it just cannot act.
toolNames is separate: those are Maestro's own built-in tools, not
connector functions.
When the agent wants to write
A tool that publishes, writes or invokes is a write-shaped call, and
writeMode decides what happens:
writeMode | What happens when the agent calls a write |
|---|---|
deny (default) | The call is refused before the run starts. The agent can read and answer, never change anything. |
allow | The call executes immediately under the pipeline's own identity. |
approval | The call is parked for a human holding agent_approval:decide. The run finishes without it; the write happens only after someone says yes. |
The default is deny because failing closed is the only safe default for
a component whose next action is decided by a model. In approval mode,
audienceRoles names whose notification feed the request lands in.
In the designer these modes are Read-only (deny), Require
approval (approval) and Allow writes (allow). Parked writes are
decided under Orchestrate → Approvals, on the Agent writes tab:
Approve runs the write at once under the pipeline's own identity,
Reject discards it. A parked write that nobody decides within one
hour expires and never runs. The tab is visible only to people with
agent_approval:read. See Approvals
for how decisions, failed writes and unknown outcomes are handled.
Writes parked during a run are listed under _metadata.pendingApprovals,
so an operator reading the execution never mistakes a parked write for a
completed one.
Memory
By default the agent remembers nothing between runs. Set memoryKey and
each distinct resolved value gets its own conversation — {{ $input[0].result.tags.asset_id }}
gives one memory per asset, a fixed name gives one shared conversation.
Memory is always bounded. memoryMaxTurns counts entries and each run
appends two (the message and the answer), so the default of 20 keeps the
last ten runs. memoryTTLSeconds expires a conversation after its last
run; the default is seven days. There is deliberately no "remember
forever".
Input / Output
Input
Whatever the upstream node produced. With an empty userMessage, the
whole input is JSON-serialised and sent as the user message — which is
the right default for most pipelines.
Output
{
"result": { "fault": "bearing wear", "confidence": 0.82 },
"_metadata": {
"usage": {
"promptTokens": 1840,
"completionTokens": 96,
"costUsd": 0.0071,
"iterations": 2,
"latencyMs": 3120
},
"toolCalls": [
{ "name": "read_vibration", "durationMs": 240, "error": false }
]
}
}
result is the answer, shaped by outputSchema. Read it downstream as
$node["Agent"].result.fault. With no schema the answer is
{ answer: string }.
_metadata.usage is what the run cost — read $node["Agent"]._metadata.usage.costUsd
to route expensive runs somewhere for review. _metadata.toolCalls shows
what the agent actually did, not just what it concluded.
Cost control
budgetTokensPerHour is a breaker on this node alone. Once the node's
prompt plus completion tokens reach it within the current UTC hour,
further runs fail with BUDGET_EXCEEDED until the hour rolls over. The
failure is permanent for that run, so it will not be retried into more
spend. Zero, the default, disables the breaker; your account's daily
ceiling and the organization budget still apply.
maxIterations caps how many model calls one run may make before the
agent must answer. Every tool call costs one. Leaving it empty uses
Maestro's runtime default of 15.
Usage examples
Classify a work order
systemPrompt:You are a maintenance triage assistant. Classify the fault.outputSchema:{"type":"object","properties":{"fault":{"type":"string"},"urgency":{"type":"string"}}}- Downstream a Condition node branches on
$node["Triage"].result.urgency.
Summarise an hour of alarms
Put a Buffer in front with a one-hour window so the agent sees one batch per hour, not one call per alarm.
Let the agent act, with a human in the loop
toolFunctionIDs: the setpoint-write function.writeMode:approval.audienceRoles:Process Engineer.
The agent proposes; a process engineer approves; only then does the write run.
An outputSchema turns the answer into fields your pipeline can branch
on. Without one you get a single answer string that something
downstream has to parse.
Configuration reference
The fields below are generated from the node's config contract, so they match what the pipeline validator enforces and what the designer's form offers.
ai.agent
| Field | Type | Required | Default | Values | Description |
|---|---|---|---|---|---|
account | string | yes | — | — | Provider account from the Maestro model catalog the run authenticates with and is billed to, e.g. llm-prod — the part before the slash of the catalog's account/model id |
model | string | yes | — | — | Wire model id passed verbatim to the provider, e.g. claude-sonnet-4-6 — the part after the first slash of the catalog's account/model id |
systemPrompt | string | no | — | accepts an expression | Standing instructions that say what the agent is and does, sent as the system message on every run, e.g. "You are the fault classifier for {{ $input[0].result.tags.asset_id }}". Optional. |
userMessage | string | no | — | accepts an expression | The per-run user message. Empty sends the node's whole input payload JSON-serialised, which is the right default for most pipelines. |
outputSchema | string | no | — | — | JSON Schema of the structured answer the agent must return, as a JSON string, e.g. {"type":"object","properties":{"fault":{"type":"string"}}}; the answer becomes $node["Name"].result. Must be a JSON object. Empty means {answer: string} |
toolFunctionIDs | string[] | no | — | — | Ids of saved connector functions the agent may call as tools; each must exist in this org and the pipeline's subject needs function:execute on it. Empty = the agent can only return its answer. List each id once |
writeMode | string | no | deny | deny, allow, approval | What happens when the agent calls a write-shaped function (publish, write, invoke): deny refuses it before the run starts, the safe default; allow executes it directly under the pipeline's identity; approval parks it for a human holding agent_approval:decide and the write runs only after a yes |
audienceRoles | string | no | — | — | Comma-separated role names whose notification feeds this node's write-approval requests land in, e.g. "Process Engineer, Organization.Admin"; only used when writeMode is approval. Empty notifies everyone who can decide agent writes. A single string, not a list |
toolFunctionLabels | object | no | — | — | Display labels the designer caches per function id ({name, connectionName}) so its tool chips render without a refetch. Not read at run time — toolFunctionIDs is the only execution truth. Omit it when writing a pipeline by hand |
toolNames | string[] | no | — | — | Names of Maestro's own built-in tools the agent may additionally call, exactly as Maestro lists them. Empty = none; the structured-answer tool is always present. Connector functions do not go here — use toolFunctionIDs |
memoryKey | string | no | — | accepts an expression | Scopes conversation memory: one conversation per distinct resolved value, e.g. {{ $input[0].result.tags.asset_id }} for one memory per asset, or a fixed name for one shared conversation. Empty = the agent remembers nothing between runs. A key that resolves to an empty string fails the run rather than silently dropping continuity. |
memoryTTLSeconds | integer | no | 604800 | at least 1 | How long a memory key's conversation survives after its last run, in seconds; default 604800 = 7 days. An expired conversation starts fresh. At least 1 — memory is always bounded, there is no keep-forever |
memoryMaxTurns | integer | no | 20 | at least 1 | How many of the most recent memory entries are kept per key and replayed as history; each run appends two (the user message and the answer), so the default 20 keeps the last 10 runs. Older entries are dropped. At least 1 |
maxIterations | integer | no | — | at least 1 | Cap on LLM calls per run before the agent must answer; every tool call costs one. Empty = Maestro's runtime default (15). At least 1 |
budgetTokensPerHour | integer | no | 0 | at least 0 | Per-node breaker: once this node's prompt plus completion tokens in the current UTC hour reach this number, further runs fail with BUDGET_EXCEEDED until the hour rolls over. 0 (the default) disables it; the account's daily ceiling and the org budget still apply |