Choose an activity kind
How to pick the right activity kind for each step — the decision tree and hard rules.
Decision tree
Start here for every step:
What did this step do?
│
├─ Ran a shell command, script, or CLI binary?
│ └─ cli
│
├─ Called an MCP tool (Notion, GitHub, Sheets, etc.)?
│ └─ mcp_tool
│
├─ Did pure computation in TypeScript — no network, no filesystem?
│ └─ code
│
├─ Called an LLM to process content that varies each run?
│ └─ llm
│
└─ Used branching, fan-out, parallel execution, or waiting?
└─ builtin ⚠️ deferred in v1Hard rules
Prefer cli or mcp_tool over code.
If a step calls an external binary or service, it belongs in cli or mcp_tool — not wrapped in a code step that shells out. Code steps are for pure computation only.
Never put I/O in a code step.
Do not make network calls, read files, run shell commands, or call any external service inside a code step. Code steps run in the Temporal workflow execution context. Any non-deterministic or side-effectful behavior will break Temporal's replay guarantee. All I/O must live in cli, mcp_tool, or llm steps.
Use llm only for genuinely new runtime data.
llm steps are appropriate when the output depends on content that wasn't known at design time — translating a description, classifying a ticket, extracting fields from a document. If the logic is fixed regardless of what the input contains (e.g., "filter rows where manufacturer is non-empty"), use code.
Declare tools_required and mcp_servers in the manifest.
These are mandatory for cli and mcp_tool steps respectively. Cori validates them at cori check time. If a required binary or MCP server isn't declared, the check fails.
llm steps must declare a typed output schema.
Every llm step must export an output Zod schema. This is enforced at compile time.
builtin is deferred in v1
builtin steps (map, for_each, branch, parallel, wait) are accepted by the compiler but the v1 runtime does not execute them. Do not use builtin in any workflow you intend to run today.
If you need fan-out, model it as a single llm or code step that processes the whole collection. If you need branching, model it as sequential steps that handle both cases (with a condition in a code step that returns an empty result for the inactive branch).
See Reference: Activity kinds for the full template for each kind.