Each step type an agent graph can use has one manifest in src/agents/steps/<type>.js. It is
plain data (a default export with no functions), so it serializes to JSON unchanged. One manifest
drives the step settings panel and canvas card in the Developer Portal, the step library, the AI
builder's notes, GET /v1/developer/agents/graph-schema (and so viable developer agents schema),
this harness's schemas/steps.json and the step docs. Runtime validation stays in
src/agents/graph.js; test/stepManifests.test.js keeps the two in agreement.
npm run steps:build writes schemas/steps.json; VIABLE's API writes the portal's copy from the
same manifests. Never edit either copy by hand.
A manifest
export default {
type: 'image', // the graph node type
version: 1, // raise when a setting changes meaning
label: 'Image', // shown on cards, the library and panels
category: 'ai', // start | ai | safety | logic | knowledge | tools | annotate
summary: 'Draw or change pictures: one prompt, or a list of them.', // one line in the step library
use: '…', // for the AI builder: what it is best for, its limits, model calls
guide: ['…'], // how-to bullets behind the panel's ? button
keywords: 'picture photo …',// extra words the step library search matches
calls_model: 'always', // always | some | omitted (never)
docs: '/docs/build/steps#image',
research: { title, summary, url }, // only for research-based steps (VETTING)
cannot_run_alone: '…', // why Run step refuses this type (Input, Loop, For each, Parallel, Ask, Note)
flow: { kind: 'next' }, // next | terminal | none | branches (keys, labels) | routes | paths | cases
sentence: [ … ], // the plain-language line at the top of the panel and on the card
settings: [ … ], // in panel order
parts: [ … ], // built-in editors placed among the settings
outputs: [ … ], // what later steps can read
presets: [ … ], // first choices when the step is added
defaults: { … } // config of a new step (before any preset)
}
Settings
{
key: 'max_items', // dot path inside node.config, such as "params.temperature"
label: 'Most pictures a turn',
widget: 'number', // see below
level: 'option', // setup | option | recovery | advanced
tip: 'Items after this many are left out. 1 to 6. Default: 4.',
placeholder: 'Default: 4',
default: 4, // what an unset value means
defaults: [{ when: { set: 'items' }, value: 'auto' }], // a default that depends on other settings
when: { set: 'items' }, // shown only when this holds
unused_note: 'Used only with a list of pictures.', // shown when it is saved but `when` fails
min: 1, max: 6, integer: true,
options: ['stop', 'continue'], option_labels: { stop: 'Stop the run', continue: 'Keep going' },
refs: true, // may insert fields from earlier steps
value: true, // a template that holds one value (edited as text, a field, by condition or an expression)
empty: 'the reply model writes it', // what an empty value means
empty_choice: ['The learner’s message', 'Custom input'], // a first choice between empty and a value
loader: 'voices', // options come from elsewhere: voices or reasoning_efforts (both from the step's model)
ai: 'Write rules the reviewer can check one by one.', // extra note for the AI builder
recorded_as: 'message' // the key a run's trace records this setting's worked-out value under, when it differs
}
Levels. setup is what every author of this step decides; it is always shown. option and
recovery are shown when set (or when their value differs from the default) and otherwise listed
under Add option in their section; recovery is the When something goes wrong section
(retries, fallbacks, what happens on an error). advanced behaves like option but sorts last in
Add option. Every setting a manifest lists must be reachable from the panel: a setting is never
hidden only because it is unset.
Widgets. text, textarea, prompt (long instructions, edited in the full-width editor),
json, expr (a condition expression), number, boolean, select, model,
knowledge_bases, agents, list (strings), routes, assignments, checks, fields
(named values), voice.
Tips say what the setting does, give an example where it helps, and end with the default:
"Items after this many are left out. 1 to 6. Default: 4." At most 160 characters. Labels use the
author's words, never config keys (Pictures per prompt, not n). Every sentence must be true of
the code.
Conditions
when (and defaults[].when, sentence[].when, outputs[].when) is data, so the runtime, the
portal, the CLI and the docs evaluate it the same way:
| Form | Holds when |
|---|---|
{ set: 'items' } |
the value is not null, undefined, '' or [] |
{ unset: 'items' } |
it is |
{ present: 'items' } |
the value is not null or undefined ('' counts: an Image step with items: '' is in list mode) |
{ absent: 'items' } |
it is null or undefined |
{ is: { mode: 'full' } } |
the value equals it; a list matches any; null in the list matches unset |
{ has: { checks: ['off_topic', 'custom'] } } |
the value is a list holding any of these |
{ not: … }, { all: [ … ] }, { any: [ … ] } |
combinations |
matchesWhen(when, config) in src/agents/steps/index.js is the reference implementation.
Parts
Built-in editors with behaviour no generic widget has, placed by level like a setting:
{ id: '@pictures', level: 'setup' }. Known parts: @pictures (an Image step's one prompt or
list), @outputs (an LLM's answer format), @cases (a Branch's cases), @loop (a Loop's count
or condition), @outcomes (VETTING's outcomes), @routes, @paths. A part may name the setting
it sits after: { id: '@pictures', level: 'setup', after: 'model' }. Parts that are a section of their own in the panel (@outputs as Answer format, @cases, @outcomes) show as that section; their level only says how essential they are.
The sentence
A list of segments joined in order. Each has text with {placeholders} and an optional when.
A segment whose placeholders are all empty is left out.
sentence: [
{ text: 'Draws the pictures in {items}', when: { set: 'items' } },
{ text: 'Draws {prompt}', when: { unset: 'items' } },
{ text: ', up to {max_items} a turn', when: { set: 'max_items' } },
{ text: '. If the safety check refuses one, tries once more', when: { set: 'fallback_prompt' } }
]
{key} is a setting, formatted by its widget: a reference shows as its step and field ("tutor →
pictures"), long text as its first clause, a select as its option label, a list as "a, b and c".
{@name} is a computed phrase the portal provides: @outputs, @routes, @paths, @cases,
@tools, @checks, @assignments, @values, @exits, @agent, and for a Component step
@component (its manifest's label) and @sends (what each field sends, and to which hosts).
Outputs
outputs: [
{ path: 'status', label: 'Status', type: 'string', tip: 'completed, partial, failed or skipped', when: { set: 'items' } },
{ path: 'pictures', label: 'Every picture', type: 'list', item: 'picture' }
]
path is read as {{<step id>.<path>}}. type is string, number, boolean, list or
any. item names the fields of a list's items (picture, unfinished). An LLM's named fields,
a State step's values, a Values step's values, a Loop's counter and a For each step's item come
from the step's own config and are added by the portal.
Presets
presets: [
{ id: 'planned', label: 'Draw what an LLM planned', tip: 'One picture per item of a list output.', config: { items: '', max_items: 3 } }
]
Shown when the step is added from the library. A preset only fills settings; the step is the same type either way.
Rules the tests enforce
- Every type in
NODE_TYPEShas exactly one manifest, and every manifest is a node type. - Every manifest serializes to JSON and back unchanged.
- Setting keys are unique; levels, widgets, part ids and condition forms are known.
- Every placeholder in a sentence names a setting or a known
{@name}. - Tips are at most 160 characters; labels at most 40.
- Numeric bounds agree with
graph.jswhere it checks them. schemas/steps.jsonequals whatnpm run steps:buildwrites.
This page comes from the open repository: engine/docs/step-manifests.md. To change it, change it there.
