OceanCode
Workflows
A workflow is a file of tasks that oceancode runs in a fixed order: which agent does what, which tasks run at once, where a check or your approval stops the run. No model picks the next step. Five ship with oceancode — Spec, Build, Review, Secure and Research — beside Goal and Race, and you can write your own or have the agent write one.
A workflow and a mode
A work mode decides how agents work together on your prompt (23). There are two kinds:
- A way of working (
kind: mode: Model, Auto, Solo, Delegate, Pair, Orchestrate) shapes the lead's ordinary turn: which sub-agents it may call, whether it edits, which limits apply. You talk to the lead as usual. - A workflow (
kind: workflow) takes the turn's place. Every prompt you send is one run: the prompt becomes the run's input ({input}), the turn answers at once (spec ×3 — started run <id>), the tasks run in the background, and the result comes back into the session when the run ends (crates/ocean-session/src/run/workflow/goal.rs:57).
Under a workflow a follow-up question, or /workflow new, starts a run too. To talk to the agent again, switch back:
F4 → Model, or /mode default.
Picking one
F4 or /mode opens the picker (How should the agents work?). The list has three parts: the six ways of working;
after a ── workflows ── divider the built-in workflows Spec, Build, Review, Secure, Research, Goal and Race; after
── custom ── your own files, modes and workflows alike, by name. The highlighted entry's picture and cost are on the
right and its description under the list.
| Key | What it does |
|---|---|
Enter | Run this workflow for this session's prompts. |
d | Make it the current agent's default (agents.<id>.mode in your user oceancode.json). |
e | Copy a built-in to edit: a workflow to .oceancode/workflows/<id>-copy.yaml, a mode to .oceancode/modes/<id>-copy.yaml, renamed <id>-copy and labelled <Label> (copy). |
n | Close the picker and put /workflow new in the prompt, before anything you had typed (see Writing your own). |
Esc | Close without a change. |
Typed forms: /mode spec sets this session's workflow and /mode default clears it. "mode": "spec" in
oceancode.json, or agents.<id>.mode, makes one the default (02).
The five
Spec — research, then a spec you approve
you: task
┌────────▼────────┐
│ Brief · general │
└────────┬────────┘
┌──────┼──────┐
┌─▼──┐ ┌─▼──┐ ┌─▼───┐
│code│ │docs│ │prior│ explore ×3
└─┬──┘ └─┬──┘ └─┬───┘
└──────◇──────┘ validate · review ↺ ≤ 2
┌────────▼────────┐
│ Spec ✎ · general│ → .oceancode/plans/
└────────┬────────┘ ◇ you approve- brief (
general) restates the task: the goal in one sentence, what is in and out of scope, the questions a spec must answer. No code. - research: three
exploreagents at once, evidence aspath:lineonly — code (where it is handled, what it calls, which tests cover it), docs (the rules that apply:CLAUDE.md,AGENTS.md,docs/), prior (a feature shaped like this one, its tests, its guide page). - validate (
review, a verdict) opens everypath:lineand passes only when each says what the scout claims. On a failure the three scouts correct their evidence against the findings and validate checks again; two passes at most. - spec (
general) writes the spec in the plan-file shape — Goal · Research findings · Approach · one fencedtodoblock, one buildable step per row · Verification per to-do · Open questions — and saves it as.oceancode/plans/<date>-spec-<run>.md. - approve: a question card shows the spec. Approve goes on; Stop ends the run
blocked · not approved, and the file stays. - report (
general): three lines — where the spec is, how many to-dos, and that Build with that path builds it.
Cost ≈ 3–5×. Needs an interactive session. Limits: 4 at once, 20 per turn, depth 1.
Build — to-dos from the spec, build, tests, review
you: spec path
┌─────────▼─────────┐
│ To-dos · general │ → the builder's list
└─────────┬─────────┘
┌─────────▼─────────┐
│ Build ✎ · builder │◀──┐◀──┐
└─────────┬─────────┘ │ │
◇ tests ──fail┘ │ ↺ ≤ 3
┌─────────▼─────────┐ │
│ Review · review │──fix──┘ ↺ ≤ 2
└─────────┬─────────┘
▼ report · general ──▶ you- plan (
general) reads the spec your prompt names, else the newest file in.oceancode/plans/, and replies with its path and itstodoblock (adding a verification line to any row without one). It edits nothing. - build (
builder) gets those rows as its to-do list and carries them out in order, running every check the spec names. While rows are open it is resumed, three times at most; a row still open fails the run. - tests, three passes at most: your
test_commandruns, and on a failurebuilderfixes the cause, never the test. After the third pass the run goes on with the last result, which review and the report read. - review, two passes at most:
reviewtries to refute the change against the spec — did every row land, is anything outside the spec, can every new test fail? — andbuilderfixes those findings, nothing else. - report (
general): the files changed, the commands with their pass lines, what is open.
Cost ≈ 2–5×. Needs test_command in oceancode.json; runs headless. Limits: 4 at once, 30 per turn, depth 2 (the
builder may call a helper such as explore).
Review — three lenses, a judge, a verdict
you: the change
┌──────────┼──────────┐
┌─────▼─────┐┌───▼────┐┌────▼────┐
│correctness││ scope ││ tests │ review ×3
└─────┬─────┘└───┬────┘└────┬────┘
└──────────┼──────────┘
┌───▼───┐
│ judge │ merges, evidence only
└───┬───┘
▼ verdict ──▶ you- lenses: three
reviewagents at once, each answering a verdict — correctness (it reads the diff, staged and unstaged, and the code it touches: the wrong assumption, the missing case, the error path), scope (does the change do what was asked and only that?), tests (can each test fail, does one cover each new branch, is any check only claimed?). - verdict (
judge): approve, request changes or block in one line, then the findings aspath:line, worst first, without duplicates or anything uncited.
Your prompt names the change and what was asked. Nothing edits. Cost ≈ 3–4×. Runs headless. Limits: 3 at once, 10 per turn, depth 1.
Secure — threat model, four lenses, a verdict
you: change
┌────────▼────────┐
│ Surface·explore │ inputs, secrets, sinks
└────────┬────────┘ review ×4 with
┌──────┼───────┬──────┐ security-audit
┌──▼───┐┌─▼────┐┌─▼───┐┌─▼────┐
│secret││inject││authz││supply│
└──┬───┘└─┬────┘└─┬───┘└─┬────┘
└──────┼───────┴──────┘
┌───▼───┐
│ judge │──▶ approve · changes · block
└───────┘- surface (
explore) maps the change's attack surface without judging it: every input from outside, secret or credential, auth or tenant check, shell, SQL or HTML sink and external call, aspath:line. - lenses: four
reviewagents at once, each given thesecurity-auditskill and answering a verdict — secrets (keys in code, logs or responses, weak crypto, bad randomness), injection (shell, SQL, path traversal, templates, unsafe deserialization, XSS: each input traced to its sink), authz (authentication, authorization and tenant boundaries), supply (a new dependency, a changed lockfile, a download or install step, an external call the change does not explain). - verdict (
judge): approve, request changes or block, then each finding aspath:linewith its exploit path, keeping only findings a lens cited with more than 80 % confidence.
Nothing edits. Cost ≈ 4–6×. Runs headless; the lenses want the security-audit skill (Requirements).
Limits: 4 at once, 10 per turn, depth 1.
Research — a sweep, verified claims, a cited report
you: question
┌────────▼────────┐
│ Plan ≤ 4 asks │
└────────┬────────┘
┌──────┼──────┐
┌─▼──┐ ┌─▼──┐ ┌─▼──┐ researchers, read-only
│ q1 │ │ q2 │ │ q3 │ claims with sources
└─┬──┘ └─┬──┘ └─┬──┘
└──────◇──────┘ verifiers re-check
┌────────▼────────┐ every claim
│ Report · cited │──▶ you
└─────────────────┘Research has no tasks of its own: it runs the built-in script deep-research (workflow: deep-research), which
splits your question into independent ones (four by default), sends one read-only researcher per question for claims
with their sources, has every claim re-checked against its source, and writes a report that cites only what
survived. Its runs are listed as deep-research.
Cost ≈ 4–8×. Runs headless. It sets no limits: the workflow bounds apply (workflows.max_concurrent_agents,
workflows.agent_budget, 02).
Goal, Race and Pipeline
Goal (loop until proven done) and Race (attempts, a judge ranks, you pick) are the two older workflows; they are
described in 23. Pipeline (spec, your approval, build, review) left the picker: Spec and
Build do its two halves with more checks. It is still built in, and "mode": "pipeline" in oceancode.json still
runs it; /mode pipeline is refused, as /mode takes only the picker's ids.
While it runs
- The badge. The prompt's top border names the workflow and how many agents its first parallel task runs —
spec ×3,build,review ×3,secure ×4,research— and, while a run is active, the task it is on:spec ×3 · validate(crates/ocean-protocol/src/diagram.rs:841). - The transcript row.
Workflow spec: <your prompt> [brief ✓ · research ● …]follows the tasks as they start (✓done,●running) and ends asWorkflow spec done in 2m: …,failed,cancelledorpaused. - The dock. Its Workflows section (
Workflows 1) lists each running run with its agents beneath it; open an agent's row to watch it (19). - Workflow Runs.
/workflow runsopens the runs pane (a text overview in minimal mode);/workflow pause,resumeandstopcontrol a run, and a paused or stopped run keeps its journal, so it can be resumed. - Question cards. An
approvetask puts a card in the prompt's place: Approve?, the result it shows (Spec's spec), and the options Approve and Stop. Apicktask (Race) lists the attempts in the judge's order with its reasons, and None.1–9choose (19). - The hand-back. When the run ends, its status and its last task's output land in the session as a
<workflow>message, and the agent is woken to read it, so the session's next answer is the run's result. A workflow that cannot run here answers your prompt at once with the reason and starts nothing.
Requirements
| Workflow | Needs | Headless |
|---|---|---|
| Spec | an interactive session, for the approve card | no |
| Build | test_command in oceancode.json, for the tests | yes |
| Review | nothing more | yes |
| Secure | nothing more; the security-audit skill, see below | yes |
| Research | nothing more | yes |
| Goal | nothing more | yes |
| Race | a git project, test_command, an interactive session for the pick | no |
Every workflow needs workflows on (workflows.enabled, the default). An interactive session is one that can show you
a question card (the pager, an editor); a headless one (oceancode run, a client without cards) cannot. A workflow
whose need is unmet answers the prompt with the reason — spec needs an interactive session, build needs a test_command in oceancode.json — and Auto never picks it (crates/ocean-session/src/run/mode.rs:123).
Secure's skill. Each of Secure's four lenses names the skill security-audit. oceancode ships no skill bodies:
it finds this one where it finds every skill (09) — the project's .oceancode/skills/ or
.claude/skills/ (this repository ships security-audit in its root .claude/skills/), or your
~/.oceancode/skills/ or ~/.claude/skills/ (crates/ocean-capabilities/src/discover.rs:74). Its SKILL.md body
goes in front of each lens's prompt. Without it each lens gets one line instead —
(Skill "security-audit" was not found; go on without it.) — and runs on its prompt alone
(crates/ocean-session/src/run/workflow/host.rs:626).
Auto and workflows
Under Auto, Jev picks a mode for each prompt from those that can run here, the workflows among them; each option
shows its summary and its when_to_use (Spec: "a task needs a spec before anything is built"). When Jev cannot
answer, the rules pick, and five rules on the prompt's first word come before the four of Auto
(crates/ocean-protocol/src/auto_route.rs:31):
spec,specify,design→ Spec;build,implement, when the prompt also names.oceancode/plans/or the wordspec→ Build;review→ Review;secure,security,audit→ Secure;research,compare,investigate→ Research.
The first word counts whole and in any case. A rule whose workflow cannot run here is passed over, so a headless
design the cache falls through to the other rules. The badge says who decided: auto → review · rules: asks for a review. Auto decides per prompt, so only the prompts it gives a workflow start a run.
Writing your own
A workflow is one YAML file, .yaml or .yml:
.oceancode/workflows/<name>.yamlin the project: every session and everyone who works in the repository gets it.~/.config/oceancode/workflows/<name>.yaml(the user config directory): yours, in every project.
A way of working (kind: mode) goes in a modes/ folder beside it instead, which also still reads JSONC (*.jsonc,
*.json, with flow written out and no kind). The folders are watched: a saved file is listed under
── custom ── at once. A file is skipped, with the reason in the engine log, when its name is taken (a built-in; a
workflow script's name: a .rhai beside it with the same stem, any .rhai whose meta.name it is, or the built-in
scripts deep-research, goal, pipeline and diagram), when it is the other kind for its folder, or when an
earlier file holds its name (the project's wins). A
file that does not parse or validate is left out, and the workflow tool's unknown-diagram error names it with the
reason (crates/ocean-app/src/modes.rs:36).
Three ways to start
- Copy one.
eon a built-in in the picker writes.oceancode/workflows/<id>-copy.yaml. - Ask for one.
/workflow new <what it should do>(ornin the picker) asks the agent to write it. It follows theworkflow-authorskill, which this repository ships in its root.oceancode/skills/workflow-author/; in another project, copy the skill into that project's.oceancode/skills/or your~/.oceancode/skills/. The agent writes the YAML, checks it with theworkflowtool (source: {type: "yaml", yaml}withvalidate_only: true), fixes what the check names (three rounds at most), then saves it withsave: {scope: "project"}or"user". - Write it.
oceancode workflow schemaprints the JSON Schema of a workflow or mode file.
The check reads the file as the catalog does, validates it against the agents this session knows, checks what the
session can run, then runs it once on a stand-in host: every question takes its first option, every command exits 0,
every verdict passes (crates/ocean-session/src/run/workflow/launcher_yaml.rs:73). A save does the same, asks you
once for the file, and writes it. It is refused when the file exists and replace: true is not set, when the name is
taken (a built-in, a workflow script, a pager /workflow op — new, runs, pause, resume, stop, save — or a
file listed from elsewhere), outside the two workflow folders, and when a line looks like it holds a key (sk-,
AKIA or Bearer followed by 16 or more key characters, or -----BEGIN;
crates/ocean-session/src/run/workflow/launcher_yaml.rs:104). A workflow is shared text: it never holds a key, and a
command takes its secrets from the environment it runs in.
To run a saved workflow: /mode <name> and a prompt, or the workflow tool with
source: {type: "name", name: "diagram"}, args: {diagram: "<name>", task: "<the input>"}.
The file
kind: workflow # workflow (tasks, or a script) or mode (the lead's turn)
name: lint-review # the id: the file's stem, /mode lint-review, the badge
label: Lint review # the picker's title
description: lint, three lenses, a verdict
when_to_use: a change exists and the linter must pass first
agents: { review: {}, judge: { model: openai/gpt-5 } }
tasks: [] # the tasks, in order (next section)
limits: { at_once: 3, per_turn: 10, depth: 1 }
picture: [] # lines drawn in the picker
cost: "≈ 3–4×"| Key | What it holds |
|---|---|
kind | Required in YAML: workflow or mode. It must agree with flow. |
name | Required: ^[a-z][a-z0-9-]*$, the key for /mode, the config's mode, the badge and the file's stem. |
label | Required: the picker's title. |
description | The picker's line and the text under the list. summary, when set, is a shorter line beside the label; one of the two is required. |
when_to_use | One sentence; Jev reads it under Auto. |
flow | Leave it out: script for a workflow, lead for a mode (auto is Auto's alone). |
agents | Every agent a task names, each with options: model and effort (pins, see Models), isolate: copy (the agent works in its own copy of the project: needs git, never inside a loop; every writing agent in a parallel needs it), max (at most this many at once), keep (a mode's lead resumes the same child). |
tasks | The tasks, in order. |
workflow | Instead of tasks: a workflow script's name (Research: deep-research). Never both. |
limits | at_once 0–64, per_turn 1–1000, depth 1–4 (1: task agents start no sub-agents), race_n 1–8 (copies of a parallel that names neither n nor models). Your subagent.max_* settings win. |
picture | Lines drawn in the picker; keep to 12 lines of 44 cells, as the built-ins do. Left out, the picker draws one line per task. |
cost | Shown in the picker, against working alone. |
picker | false hides it from the picker. |
lead_writes, lead_prompt | A mode's only: false takes the lead's edit tools; text added to the lead's prompt. |
An agent is any agent oceancode knows; an unknown one's error lists them. general and builder write; explore,
review and judge are read-only; your own markdown agents count too. The shipped workflows use only these five.
The four task types
- type: task # one agent, one prompt
id: build # its result's name; default: the agent's id
agent: builder
prompt: |
Carry out the to-dos:
{plan}
schema: verdict # verdict, ranking or deliverables: the reply is that JSON
model: openai/gpt-5 # this task only
condition: '!tests.pass' # runs only when it holds; quote a leading !
skills: [security-audit] # SKILL.md bodies put before the prompt
todo: { from: plan } # the todo block of an earlier reply, or a list of rows
save: spec # writes the reply to .oceancode/plans/<date>-spec-<run>.md
- type: parallel # different tasks at once
id: scouts # required
schema: verdict # for subtasks that name none
subtasks:
- { type: task, id: code, agent: explore, prompt: "Read the code. {input}" }
- { type: task, id: docs, agent: explore, prompt: "Read the docs. {input}" }
- type: parallel # copies: one agent, one prompt, n times
id: checks
agent: review
prompt: "Refute the change. {input}"
n: 3 # 1–8; else one copy per model; else limits.race_n; else 3
models: [openai/gpt-5, openai/gpt-5-mini]
- type: loop # repeats its tasks
max: 3 # passes, at least 1
until: tests.pass # ends it when this holds
tasks:
- { type: gate, gate: tests }
- { type: task, id: fix, agent: builder, condition: '!tests.pass', prompt: "Fix the cause: {tests.output}" }
- type: gate # approve, pick, tests or check
gate: check
id: lint
command: cargo clippy --all-targets -- -D warnings
expect: Finished # passes on exit 0 with this text in the output's last 4 KiB- task:
agentandpromptare required.todoneeds an agent that writes; the agent is resumed while rows are open, three times at most, and a row still open fails the task.saveruns once, so never on a subtask or inside a loop, and never replaces a file.effortis read on a task too, but only the agent'seffortis applied. - parallel:
idis required. Withsubtasks, each atype: task, every result is saved under its own id and the set under the parallel's; it takes noagent,prompt,normodels, and every subtask id is unique in the file. Without, it runs copies of oneagentwith oneprompt. - loop:
maxandtasksare required;untilis optional. - gate: see Gates. A gate and a parallel take
conditiontoo.
A failed task or subtask ends the run as failed; copies end it only when none succeeds. A failing gate does not stop
the run: follow it with a fix under condition: '!<id>.pass' inside a loop until: <id>.pass.
Templating, conditions and schemas
{input}is the prompt the run started with.{id}is a saved result as text: a plain reply as itself, a schema reply as JSON, a parallel as a JSON list.{id.field}is one field ({review.findings},{tests.output}; on a parallel, each member's), and{id.file}the plan file asavewrote. A name nothing has saved stays as typed, braces and all.- A condition reads
name.fieldor!name.field, both words lowercase letters and_only — so a result a condition reads needs such an id (fix_two, notfix2). It holds when the field istrue; on a parallel, when it is true for every member that answered in a schema; for a name nothing has saved, never, so!review.passruns before any review. There is no&&,==or number. Quote it: a bare!starts a YAML tag, and a tag is refused. schema: verdictasks for{pass, findings},rankingfor{order, reasons}(attempt numbers, best first: what apickgate shows),deliverablesfor{deliverables}. The reply is held to its schema, asked again a bounded number of times.
Gates
Gates are local: the cloud engine never waits for a person.
gate | Keys | Result | Needs |
|---|---|---|---|
approve | show: a saved result shown with the question (else the input) | Stop ends the run blocked · not approved | an interactive session |
pick | from: an earlier parallel's id; show: a ranking result | applies the chosen copy and ends the run | an interactive session; copies (isolate: copy) |
tests | command (else the config's test_command); each: run in every copy of a parallel | {pass, exit, output} under its id, else tests | test_command when it names no command |
check | command (required), expect | {pass, exit, output} under its id, else check | nothing |
With each the result stays on each copy, so that gate takes no expect and no id.
Aliases
The work-mode files before 2026-11-02 used other words. They are still read, so every older file parses; write the first column. A word and its alias together are refused.
| Write | Also read |
|---|---|
name | id (top level) |
tasks | steps |
a task's id | as |
prompt | do |
condition: x (runs the task when x holds) | unless: '!x' (skips it when x holds) |
{input} | {task} |
type: task, type: gate | a task keyed by agent, a gate keyed by gate |
type: parallel | parallel: { … } with as beside it |
type: loop | loop: { max, until, steps } |
An error names where it is and what was meant, counting the top-level list as tasks even when the file wrote
steps (crates/ocean-protocol/src/diagram_read.rs:21):
mode draft: tasks[2].unles: unknown field, expected one of `type`, `id`, …; did you mean `unless`?From the cloud engine's words
A local file has the shape of a bare CNL document (orchestration/cnl-engine/). What the local engine lacks is the
registry, SharedMemory and CEL; what it adds is loop and gate.
| Cloud (CNL) | This file |
|---|---|
the workflow: envelope with name, description, tasks | top-level kind: workflow, name, label, description, tasks |
type: agent, type: harness (goal, mission) | type: task with agent and prompt |
a task's id | id |
parallel with subtasks | type: parallel with subtasks |
validate | a verdict task or parallel in a loop with until: <id>.pass |
condition (CEL) | condition: "name.field" or "!name.field" |
retry | type: loop with max |
{input}, {task_id} | {input}, {id}, {id.field} |
a harness's skills | skills |
allowed_tools, posture: read_only | the agent: explore, review and judge are read-only |
sequential | the top-level tasks, which run in order |
batch, dynamic, debate, output, version, inputs, followup, timeout, budget | none (budgets: the config's workflows.*) |
Models
A cheap model for the scouts and a strong one for the builder or the judge:
agents.<id>.modelandeffortpin an agent in every task;- a task's
modelpins that task; models: [a, b, c]on a copiesparallelruns one copy per model;- the
workflowtool'sargs.models: {<agent id>: <model>}, when it launchesdiagram, wins over an agent's pin.
A model is provider/model, as /model lists it. The session's /subagent model wins over every pin; with no pin,
a task runs the config's agents.<id>.model, else subagent.model, else the session's model. The picker tags each
agent with its pin.
What is and is not enforced
The file is data the engine runs (crates/ocean-session/src/run/workflow/builtins/diagram.rhai), so these hold
whatever an agent says:
- The order. The tasks run as written; no model picks the next one, and a replayed run never decides again. A failed task ends the run.
- The gates. An approve gate waits for you, and Stop ends the run. A tests or check gate passes only on exit 0
(and
expectin the output). A pick applies only the copy you chose. - The answers. A reply in a schema is checked against it, and a condition or
untilreads its fields; a loop ends atmax. - The to-dos. Rows are in the agent's list before it starts, it is resumed while they are open, and a row still open fails the task.
- The saves. One new file under
.oceancode/plans/, never elsewhere, never over another. - The limits. At once, per turn and depth bound the run's agents (23);
explore,reviewandjudgehave no edit tools. - The rules of the file. An unknown key or agent, a malformed condition,
todoon a read-only agent,savein a loop or a subtask, a writer in a parallel withoutisolate: copy: each is refused before anything runs, and a workflow whose needs are unmet never starts.
These are judgement, which no file can hold:
- the quality of a spec, a review or a report;
- a builder's "done" beyond the tests and its to-do rows, which it closes itself;
- the honesty of
verdict.pass: a model can pass what it should not, so a second lens or acheckgate is the guard; - what a skill's text does:
skillsadds text to the prompt, not rules; - a
{name}that names nothing: it stays in the prompt as typed, and validation does not catch it; - which sub-agents a task's agent calls: a mode's allow-list shapes only the lead's own calls
(
crates/ocean-session/src/run/orchestration.rs:679), which is why Spec, Review and Secure setlimits.depth: 1(Build: 2).
See also
- 23 · Work modes — the ways of working, Goal and Race, limits, Auto.
- 02 · Configuration —
mode,test_command,agents.<id>.mode,workflows.*. - 09 · Capabilities and plugins — where skills are found.
- 19 · Ocean pager and 22 · Ocean pager reference — the picker, the badge, the dock and question cards.
- 21 · CLI reference —
oceancode workflow.