Skip to content

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.

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_failstop (fail the run — the default) or flag (record a flag and keep going).
  • outcome — optional final message; {step_id.field} placeholders resolve from step outcomes: outcome: {message: "Done — {create.url}"}.

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:

  • attachments on 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 via read_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_file inside 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.
  • action — a built-in platform operation (deterministic code): block: {action: core.create_idempotent} with params: {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 from list_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 owns output_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: generic is an anonymous worker: it gets only the step’s tools: list and the platform’s default process model. To delegate the step to one of YOUR agents instead, set ref to the agent’s name (from list_agents) — it then runs as itself, with its own model and its own configured tools (the step’s tools: list is not needed). Either way the step’s instruction + wired inputs form the task, the loop is capped by max_turns, and the agent delivers the result itself by calling a complete_step tool whose arguments must match the step’s output_schema. Optional attachments (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’s params ARE the child’s envelope — its keys must match the child process’s envelope_schema directly (params: {topic: "$envelope.topic"}, NOT wrapped in an envelope: key).

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.

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; the idempotency_key (usually an envelope ref) guarantees a re-run never duplicates. capture lifts result fields onto the step outcome; protected registers 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: from is the create step’s outcome (ref), echo_exact copies 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-shot repair: {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?}file is a ref to an item with file_id and name.
  • 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 under args_field automatically; unknown discriminator values flag the item. params: {items, discriminator, variants, args_field?, args?}. Each variants value 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.

  1. create_process(name, title, description=?) — creates the empty shell. name is the permanent lowercase-hyphen slug; title is the display name. No definition exists yet.
  2. Write the definition and dry-run it with validate_process_definition(definition) — returns {ok, errors, warnings}. Fix errors and re-run until ok is true.
  3. update_process_definition(process, definition) — saves it as a new draft revision (returns its version). Never deploys.
  4. test_process_run(process, envelope, revision_version=?) — runs a revision end-to-end; pass the draft’s version to test it before anyone deploys.
  5. 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 as agentdepot 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, and revision_version.
  • get_process_run_calls(run_id, step_id=?, include_messages=?) — the model calls behind the steps, merged into one timeline; set include_messages=true to 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.
  1. get_process_run — find the first failed step; read its error and the outcomes of the steps before it.
  2. get_process_run_calls scoped to that step_id with include_messages=true — see exactly what the model was asked and answered. repair entries mean a tool call’s payload failed validation; their verdict shows whether the automatic fix was accepted.
  3. Check the run’s flags — non-blocking issues steps recorded while proceeding (on_fail: flag); they explain a flagged run.
  4. Fetch exactly what ran: get_process_revision(process, version=<revision_version from the run>) — the live definition may have moved on since.
  5. Fix the definition → update_process_definition (new draft) → test_process_run pinned to the draft → deploy_process_revision to make the fix live.