Processes
A process is a compiled, deterministic SOP the platform executes step by step. It is NOT an agent chat: there is no free-running loop, and a run is a standalone object you watch with the run tools below (not a conversation). Reach for a process when the same procedure must run the same way every time — repeatable, auditable, with LLM judgment confined to small scoped steps. Reach for an agent when the work is open-ended and conversational.
Anatomy of a definition
Section titled “Anatomy of a definition”A definition is plain data (YAML or JSON) — no expressions, no templates:
process— the process’s name (slug). Must match the process it’s saved to.envelope_schema— JSON Schema for the envelope, the run’s input contract.steps— executed in order. Each step has:id— unique, referenced by other steps’ wiring.sop— required prose: the SOP sentence(s) this step implements.block— exactly one block kind (below).params— plain JSON for the block. Values may be reference strings ("$envelope.external_id","$steps.extract.count","$flags","$item"inside a fan-out) — references are the only interpolation.inputs— wiring from prior outcomes:"envelope","<step_id>","<step_id>.<field>","$flags".when— optional gate:{step: classify, equals: update}or{step: extract, has: shifts}. Presence / enum equality only.map_over— optional fan-out: one child per item of the referenced list (e.g.envelope.files).on_fail—stop(fail the run — the default) orflag(record a flag and keep going).
outcome— optional final message;{step_id.field}placeholders resolve from step outcomes:outcome: {message: "Done — {create.url}"}.
Files in the envelope
Section titled “Files in the envelope”An envelope can carry file ids — declare them as plain type: string
fields in envelope_schema and wire them into steps like any other value
(params: {schedule_file: "$envelope.schedule_file"}). When the target is a
code tool whose parameter is declared type: file, the platform resolves the
id and hands the tool the actual file content at execution time — the same
injection agents rely on in chat. Any file uploaded to the organization works
(chat uploads included); get an id from list_files / search_files, or
upload one with create_upload_url. Ids from another organization do not
resolve and fail the step.
For agent steps there are two ways to give the agent the file itself:
attachmentson the agent block (e.g.attachments: ["$envelope.timereport_file"]) attaches the resolved file(s) to the bounded turn exactly like a chat attachment: images (scans, photos) are shown to the agent inline so it can read them visually; other files are announced in the task and read viaread_file. An attachment that does not resolve fails the step rather than letting the agent run blind. When the step runs as a named agent (so it has a mirror chat), the attached files also appear in that chat’s Files panel.read_fileinside an agent step resolves any org file id (not just the step’s attachments), so an agent step handed a file id through wired inputs can always open it on demand.
Block kinds
Section titled “Block kinds”- action — a built-in platform operation (deterministic code):
block: {action: core.create_idempotent}withparams: {tool: create_draft, idempotency_key: $envelope.external_id, ...}. - code_tool — one of your org’s custom code tools, called with wired args:
block: {code_tool: extract-shifts}. Reference the tool by its slug (the hyphenated identifier fromlist_code_tools). - llm — a single tool-less structured model call:
block: {llm: {instruction: "Classify the request...", output_schema: {...}}}. - prompt — same as
llm, but the instruction text comes from an org prompt:block: {prompt: {ref: classify-request, output_schema: {...}}}. The step still ownsoutput_schema. - agent — a bounded agent turn, the only kind that can use tools:
block: {agent: {ref: generic, tools: [search_customer], instruction: "...", output_schema: {...}, max_turns: 8, attachments: ["$envelope.scan"]}}.ref: genericis an anonymous worker: it gets only the step’stools:list and the platform’s default process model. To delegate the step to one of YOUR agents instead, setrefto the agent’s name (fromlist_agents) — it then runs as itself, with its own model and its own configured tools (the step’stools:list is not needed). Either way the step’s instruction + wired inputs form the task, the loop is capped bymax_turns, and the agent delivers the result itself by calling acomplete_steptool whose arguments must match the step’soutput_schema. Optionalattachments(file-id values or refs) attach files to the turn like chat attachments — images are shown to the agent inline (see “Files in the envelope”). - process — run another process as a child step:
block: {process: other-process}. The step’sparamsARE the child’s envelope — its keys must match the child process’senvelope_schemadirectly (params: {topic: "$envelope.topic"}, NOT wrapped in anenvelope:key).
Tool references
Section titled “Tool references”Agent steps’ tools: lists and action steps’ params.tool accept slugs from
the org tool catalog — browse it with list_org_tools(query=?). Tools
from an integration (MCP server) are catalogued under a prefixed slug
{prefix}_{tool}: an ERP server’s create_draft appears as
erp_create_draft. Look the exact slug up rather than guessing — the
validator rejects anything not in the catalog (and suggests close matches).
Custom code tools are the exception: a code_tool block references the
hyphenated slug from list_code_tools. In action params (params.tool,
params.tools.*, map_dispatch variants) a custom code tool may be named
by either that hyphenated slug or its catalog name (source custom in
list_org_tools). Every tool reference is a plain slug string — never a
block object.
Actions catalog
Section titled “Actions catalog”The action block kind runs one of these built-in core.* operations.
Every tool call inside an action is schema-gated, retried on transient
failures, and repaired by a bounded one-shot agent on payload mismatch — the
happy path involves no model calls.
core.call_tool— one deterministic tool call.params: {tool, args}.core.create_idempotent— create through a tool; theidempotency_key(usually an envelope ref) guarantees a re-run never duplicates.capturelifts result fields onto the step outcome;protectedregisters payload fields no later update may re-send.params: {tool, payload, idempotency_key?, capture?, protected?}.core.update_complement— follow-up update restricted to what the create couldn’t set:fromis the create step’s outcome (ref),echo_exactcopies fields from it verbatim, protected fields are refused.params: {tool, from, payload?, echo_exact?, require?, args?}.core.verify_fields— fetch and assert:expect(field → exact value),require_not_null(fields present and non-null),count({field, equals}), optional single-shotrepair: {tool, args}then re-assert.params: {tool, args?, expect?, require_not_null?, count?, repair?}.core.upload_attach— ordered sequence create URL → push bytes → attach; the byte push between the two tool calls is automatic, with fresh-URL retry.params: {tools: {create_url, attach}, file, create_url_args?, attach_args?, url_field?, upload_ref_field?, retry_fresh_url?}—fileis a ref to an item withfile_idandname.core.replace_collection— delete-all + one bulk add + count assertion.params: {tools: {delete, add}, items, items_field?, args?, delete_args?, add_args?, expected_count?, count_field?}.core.map_dispatch— dispatch each item to a tool picked by a discriminator field; the item itself is passed underargs_fieldautomatically; unknown discriminator values flag the item.params: {items, discriminator, variants, args_field?, args?}. Eachvariantsvalue is a plain tool-slug STRING ("docx": "extract-shifts"), never a block object like{code_tool: …}.
Validation errors report each action’s exact params schema when a step’s
params don’t match — validate_process_definition is the authoritative
reference for the current contracts.
Lifecycle: draft, test, deploy
Section titled “Lifecycle: draft, test, deploy”create_process(name, title, description=?)— creates the empty shell.nameis the permanent lowercase-hyphen slug;titleis the display name. No definition exists yet.- Write the definition and dry-run it with
validate_process_definition(definition)— returns{ok, errors, warnings}. Fix errors and re-run untilokis true. update_process_definition(process, definition)— saves it as a new draft revision (returns its version). Never deploys.test_process_run(process, envelope, revision_version=?)— runs a revision end-to-end; pass the draft’s version to test it before anyone deploys.deploy_process_revision(process, version=? | revision_id=?)— makes that revision LIVE: it is re-validated against the current catalog, the previously deployed revision is archived, and every future run uses the new one immediately. Always test the draft first. (The same deploy is available from the CLI asagentdepot process deploy. The web app’s Processes pages are read-only — there is no deploy button there.)
A process is not live until a revision is deployed: a freshly created shell, or a definition that was drafted and tested but never deployed, never runs. Deploying is the step that turns your work on.
Revisions have status draft / deployed / archived. Inspect them with
list_process_revisions(process) and get_process_revision(process, version=N); get_process(process) returns the currently deployed definition,
and list_processes() is the org-wide starting point.
A run’s status is running, completed, flagged (finished, but steps
raised flags worth a look), failed, or cancelled. To watch one:
list_process_runs(process=?, status=?)— recent runs, newest first.get_process_run(run_id)— the full picture: every step with its status, timing, error and outcome, plus the run’s flags, trigger envelope, andrevision_version.get_process_run_calls(run_id, step_id=?, include_messages=?)— the model calls behind the steps, merged into one timeline; setinclude_messages=trueto see the actual (truncated) request/response.restart_process_run(run_id)— re-runs a finished run’s envelope as a brand-new run on the current deployed revision.cancel_process_run(run_id, reason=?)— stops a run that is still running.
Troubleshooting a run
Section titled “Troubleshooting a run”get_process_run— find the first failed step; read itserrorand the outcomes of the steps before it.get_process_run_callsscoped to thatstep_idwithinclude_messages=true— see exactly what the model was asked and answered.repairentries mean a tool call’s payload failed validation; their verdict shows whether the automatic fix was accepted.- Check the run’s flags — non-blocking issues steps recorded while
proceeding (
on_fail: flag); they explain aflaggedrun. - Fetch exactly what ran:
get_process_revision(process, version=<revision_version from the run>)— the live definition may have moved on since. - Fix the definition →
update_process_definition(new draft) →test_process_runpinned to the draft →deploy_process_revisionto make the fix live.