NeboAI · how to

Build a workflow

Declared steps that run the same way every time, with the parts that must not vary taken off the model entirely.

TimeHalf an hour
You needOne employee that works

A conversation that worked once is not a routine. A workflow is a declared sequence: the same steps, in the same order, with a trigger that starts it and a record of what happened. It is also where a step that must call a tool can be made to fail rather than quietly report success.

The shape

Workflows live in an employee's agent.json, keyed by name, each with a trigger and a list of activities.

"workflows": { "morning-briefing": { "trigger": { "type": "schedule", "cron": "0 7 * * *" }, "description": "Daily briefing", "activities": [ { "id": "gather", "intent": "Gather today's priorities", "steps": ["Check the calendar", "Scan the inbox"], "token_budget": { "max": 4096 } } ], "budget": { "total_per_run": 6000 } } }

That is the sequential form: each activity runs after the last, and its output becomes context for the next. Every activity is model-executed.

The two kinds of step

The moment a workflow has connections, it runs on the graph executor instead, and this is where it stops being a prompt chain. The engine owns the control flow, and typed nodes execute with no model in the path at all.

NodeWhat it does
commandRuns a shell command. Its stdout is the node output, byte for byte.
conditionA real test. Outgoing edges labelled True and False.
loopIterates an array, each pass with a fresh context.
httpMakes a request, returns the response.
waitPauses for a bounded time, up to an hour.
Which step is which

The model reasons; the machine transcribes. Any step whose output is data — a parsed file, a converted record, a committed total — belongs in a typed node. The model gets the work that needs judgment: classifying, matching, writing. Asking a model to reproduce data exactly is a hallucination trap even with precise instructions.

Wiring the graph

"connections": [ {"from": "__trigger__", "to": "fetch"}, {"from": "fetch", "to": "check"}, {"from": "check", "to": "parse", "label": "True"}, {"from": "parse", "to": "report"}, {"from": "report", "to": "__emit__"} ]

__trigger__ is where the run enters and __emit__ is where it announces it finished, which is what another employee listens for. Nodes read each other with {{nodes.<id>}}, and a node that printed JSON is parsed, so the next one can address its fields.

Large inputs

For anything bigger than a few dozen records, do not put the records in the conversation. Have a command node write chunk files and print only pointers, loop over the pointers, and let each pass read its own file. The data enters as tool output rather than as context, and the conversation never grows with the input.

{"id": "parse", "type": "command", "params": { "skill": "@you/skills/intake", "command": "python3 ${NEBO_SKILL_DIR}/scripts/parse.py in.xlsx --outdir ${NEBO_DATA_DIR}/chunks" }}, {"id": "each", "type": "loop", "params": {"source": "nodes.parse.chunks", "maxIterations": 50}}, {"id": "work", "intent": "Process the rows in this chunk", "steps": ["Run `cat {{item.file}}` — that is the only source of row data"]}

Triggers

TypeFires
scheduleOn a cron expression
heartbeatEvery interval, optionally only inside a window
eventWhen a named source emits
watchWhen a plugin streams a change
folderWhen matching files change on disk
manualWhen you ask

heartbeat with a window is the one people miss. It runs every interval but only during the hours you name, so a business-hours job is not burning through the night.

"trigger": { "type": "heartbeat", "interval": "30m", "window": "08:00-19:00" }

Two authoring rules

Between steps the engine evaluates progress and can end a run. Two habits avoid ending one by accident.

  • Fewer, denser steps, each finishing on something concrete. Put conditional sub-actions inside a step rather than making them their own.
  • End affirmatively. The last step returns the artefact — the path, the JSON, the summary. A final step that says "nothing to do" can be promoted into ending the whole run.
Budgets

Keep the sum of activity token budgets at or below the run total. Inline workflows are not checked at load time, but both limits are enforced while running, so a mismatch surfaces as a truncated run rather than a clear error.

Check it

  • Run it twice on the same input and diff; the typed nodes must match exactly
  • Break a command node deliberately and confirm the run fails rather than continuing
  • Confirm the last step returns something, on the path where there is nothing to do