Cori
Guides

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 v1

Hard 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.

On this page