Author a workflow
The complete eight-step procedure for turning a conversation into a Cori workflow — whether done by you or by a coding agent.
This is the core authoring procedure. A coding agent with the cori_save_workflow skill follows these steps automatically; you can also follow them manually.
Prerequisite: you've completed a multi-step task in a conversation (or have a clear plan). See Capture from an agent conversation for the typical flow.
Re-read the conversation
Go through the conversation and categorize each action:
- Productive — an action that contributed to the final result
- Dead end — a failed attempt, a retried command, a wrong direction
- Scaffolding — one-time setup that isn't part of the repeatable workflow
Only productive actions become workflow steps. Dead ends and scaffolding are excluded.
Decide where the folder lives
The workflow folder lives in your repository (or wherever you want to version it). Pick a location and a name that describes what the workflow does:
repos/my-workflows/translate_product_sheets_fr/You'll run it with:
cori run ./translate_product_sheets_frOr publish it to a git repo and run by ref:
cori run github.com/org/workflows/translate_product_sheets_fr@v1.0.0Decide parameters
For each value the workflow uses, ask: would this change on the next run?
- Yes → it's a parameter (declared in the manifest, passed on the CLI)
- No → it's a constant (hardcoded in the step)
Common parameters: input file paths, target languages, spreadsheet IDs, environment flags (like dry_run). Avoid over-parameterizing — if something never changes in practice, make it a constant.
See Define parameters for the full guide.
Decompose into activity kinds
For each productive action, pick the right kind:
| What happened | Kind |
|---|---|
| Shell command or CLI tool | cli |
| MCP tool call (Notion, Sheets, GitHub…) | mcp_tool |
| Pure TypeScript computation, no I/O | code |
| LLM call for new runtime data | llm |
| Fan-out, branch, or wait | builtin ⚠️ deferred |
See Choose an activity kind for the decision tree and rules.
builtin steps are deferred in v1. If your workflow needs branching or fan-out, model it as sequential steps for now.
Write the step files
Create one file per step, numbered with a two-digit prefix:
steps/
01_read_source_rows.ts
02_translate_rows.ts
03_check_gpsr.tsEach file exports a typed input Zod schema, a typed output Zod schema, and a default step.* export. See Reference: SDK for the step constructors and Reference: Activity kinds for templates.
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) }),
});Also create types.ts for any shared types used across steps.
Write the manifest
Create manifest.md in the workflow root. The frontmatter declares the workflow's identity, parameters, and required tools:
---
id: translate-product-sheets-fr
name: Translate product sheets to French
description: Reads a product CSV, translates the description column to French using an LLM, and writes results to a Google Sheet.
version: "1.0.0"
created: "2025-01-15"
parameters:
- name: input_file
type: path
description: Path to the source CSV file
- name: spreadsheet_id
type: string
description: Google Sheets spreadsheet ID
- name: dry_run
type: boolean
default: false
description: Skip the write step if true
tools_required:
- csvkit
mcp_servers:
- google-sheets
---See Reference: Manifest for the full field reference.
Review before disk write
Before the folder is written (or if you're reviewing an agent-written folder), check:
- Parameters are correct — not too many, not too few
- Each step's kind matches what it actually does
- Required tools and MCP servers are declared in the manifest
- Step file names are numbered sequentially
-
types.tscovers all shared types
Nothing is written until you approve. When using the agent skill, this review happens interactively.
Run cori check, then suggest cori run --dry-run
cori check ./translate_product_sheets_frThis validates the manifest, compiles the step files, and verifies declared tools and MCP servers — without executing. Fix any errors it reports.
Then do a dry run to confirm the workflow structure without executing the destructive steps:
cori run --dry-run ./translate_product_sheets_fr input_file=products.csv spreadsheet_id=1BxiM...When that looks right, run for real:
cori run ./translate_product_sheets_fr input_file=products.csv spreadsheet_id=1BxiM...See the cookbook example for a complete worked-through workflow that follows this procedure.