Activity kinds
The five kinds of workflow steps — cli, mcp_tool, code, llm, and builtin — and when to use each.
Every step in a Cori workflow has a kind that determines how it executes. There are five kinds. The first four are available in v1; the fifth is deferred.
Decision table
| What the agent did in the conversation | Kind to use |
|---|---|
| Ran a shell command or CLI tool | cli |
| Called an MCP tool (Notion, GitHub, Sheets, etc.) | mcp_tool |
| Wrote pure TypeScript computation with no I/O | code |
| Called an LLM to classify, translate, summarize, or extract | llm |
| Used branching, fan-out, looping, or wait logic | builtin ⚠️ deferred |
cli — shell commands
Use cli for any step that runs a shell command or invokes an external binary. This is the most common kind for steps that interact with the filesystem or external tools.
import { step } from '@cori-do/sdk';
import { z } from 'zod';
export const input = z.object({ input_file: z.string() });
export const output = z.object({ rows: z.array(z.record(z.string())) });
export default step.cli({
description: 'Read rows from the source CSV',
command: ({ input }) => `csvkit --csv ${input.input_file}`,
parse_output: (stdout) => ({ rows: JSON.parse(stdout) }),
});mcp_tool — MCP tool calls
Use mcp_tool for steps that call an MCP server (Notion, GitHub, Google Sheets, etc.). Declare the MCP server in the manifest's mcp_servers field — this is mandatory and enforced at cori check time.
import { step } from '@cori-do/sdk';
import { z } from 'zod';
export const input = z.object({ spreadsheet_id: z.string() });
export const output = z.object({ sheet_id: z.number() });
export default step.mcp_tool({
description: 'Ensure the FR tab exists in the spreadsheet',
server: 'google-sheets',
tool: 'create_sheet',
args: ({ input }) => ({ spreadsheet_id: input.spreadsheet_id, title: 'FR' }),
parse_output: (result) => ({ sheet_id: result.sheet_id }),
});code — pure computation
Use code for steps that do pure TypeScript computation with no external I/O — data transformation, validation, filtering, mapping.
Never put external I/O (network calls, filesystem reads, shell commands) in a code step. Code steps run in the Temporal workflow context, which has strict determinism requirements. All I/O must go in cli, mcp_tool, or llm steps.
import { step } from '@cori-do/sdk';
import { z } from 'zod';
export const input = z.object({ rows: z.array(z.record(z.string())) });
export const output = z.object({ valid_rows: z.array(z.record(z.string())), issues: z.array(z.string()) });
export default step.code({
description: 'Filter rows that pass GPSR compliance checks',
run: ({ input }) => {
const issues: string[] = [];
const valid_rows = input.rows.filter(row => {
if (!row.manufacturer) { issues.push(`Row ${row.id}: missing manufacturer`); return false; }
return true;
});
return { valid_rows, issues };
},
});llm — language model calls
Use llm for steps that require a language model to process genuinely new runtime data — translation, classification, summarization, extraction. An llm step must declare a typed output schema.
Use llm steps sparingly. If a step's logic is fixed (it always does the same thing regardless of input variation), it should be code or cli. llm is for steps where the content of the output depends on new data each run.
import { step } from '@cori-do/sdk';
import { z } from 'zod';
export const input = z.object({ rows: z.array(z.record(z.string())) });
export const output = z.object({ translated_rows: z.array(z.record(z.string())) });
export default step.llm({
description: 'Translate description column to French',
model: 'gpt-4o-mini',
prompt: ({ input }) => `Translate the "description" field in each row to French. Return JSON array.\n\n${JSON.stringify(input.rows)}`,
output_schema: output,
});builtin — control flow (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 workflows you intend to run today. The syntax is documented in Reference: Activity kinds for future use.
Builtins represent workflow-level control flow: fan-out over a list, conditional branching, parallel execution, and waiting for external events.
See Reference: Activity kinds for the full API for each kind.