Agent Workflow Formats — Claude Code Dynamic Workflows vs Codex Automations¶
Reference notes on the two harness-native orchestration surfaces that sit above Anvil: Claude Code dynamic workflows (JavaScript orchestrating subagents) and OpenAI Codex automations (scheduled natural-language prompts). Part 1 is the dynamic-workflows anatomy; Part 2 is Codex automations plus the broader OpenAI orchestration landscape. The closing section, "Anvil as the Workflow Substrate," frames why this matters: these primitives are ephemeral and harness-specific, and Anvil is the durable, governed state + audit layer underneath them.
Compiled 2026-06-19. Companion to
intent-driven-development-landscape.md(the strategic map) and the local design draftanvil-workflow-substrate-design.md.
Part 1 — Claude Code Dynamic Workflows¶
What it is¶
A dynamic workflow is a small JavaScript program that orchestrates a fleet of subagents deterministically. The clean split:
- JS = control plane — loops, conditionals, fan-out, ordering. Deterministic, runs in the harness.
- Subagents = the work — each
agent(...)call spawns a fresh model instance that does one chunk and returns a result (raw data, not chat).
It runs in the background: the tool returns immediately with a runId plus a
persisted scriptPath, and a notification fires on completion. The script's
final return is the result.
Anatomy — two parts, always in order¶
A workflow file is exactly two pieces: a meta literal, then the body.
export const meta = { // MUST be a pure literal (no vars/calls/spreads/interpolation)
name: 'my-workflow', // required
description: 'one-liner shown in the permission dialog', // required
whenToUse: 'optional, shown in the workflow list',
phases: [ // optional; one entry per phase() call, titles matched exactly
{ title: 'Find', detail: 'fan out searchers' },
{ title: 'Verify', detail: 'adversarially check each hit' },
],
}
// ── body starts here (async context: top-level await + return are legal) ──
phase('Find')
const hits = await agent('Find all the X', { schema: HITS_SCHEMA })
phase('Verify')
const checked = (await parallel(hits.items.map(h => () =>
agent(`Verify: ${h.title}`, { schema: VERDICT_SCHEMA })
))).filter(Boolean)
return { checked }
The meta block is metadata only — it must be a pure object literal (no
variables, function calls, spreads, or string interpolation) because the harness
parses it statically before running anything. name and description are
required; whenToUse and phases[] are optional. Phase titles in meta.phases
must match the phase() calls in the body exactly.
The body runs in an async context, so top-level await and return are
legal. The globals below are injected — you do not import them.
Building blocks (globals in the body)¶
agent(prompt, opts?) -> Promise— spawn one subagent.- no
schema→ returns final text (a string). - with
schema(a JSON Schema) → forces structured output, validated at the tool layer (retries on mismatch), returns the parsed object. This is the workhorse. opts:label,phase,model/effort(tier overrides — usually omit),isolation:'worktree'(own git worktree for parallel file mutation; expensive),agentType(a named subagent, e.g.'Explore','code-reviewer').- returns
nullon skip/death → pair with.filter(Boolean). pipeline(items, stage1, stage2, …) -> Promise<any[]>— each item flows through ALL stages independently, with no barrier between stages. This is the DEFAULT shape. The stage callback gets(prevResult, originalItem, index). A throwing stage drops that item tonull.parallel(thunks) -> Promise<any[]>— run concurrently with a BARRIER (awaits all). A throwing thunk →nullin results. Never rejects.phase(title)— start a progress group.log(msg)— emit a narrator line.args— the value passed as the tool'sargs(parameterize reusable workflows).budget—{ total, spent(), remaining() }, a token target (for scaling depth).workflow(nameOrRef, args?)— run another workflow inline (ONE level deep).
Mechanics / runtime¶
- Background execution; returns
runId+scriptPath(the script is auto-persisted to a file). - Concurrency cap ≈
min(16, cores − 2)agents at once; excess queue. Lifetime cap of 1000 agents. Oneparallel/pipelinecall takes ≤ 4096 items. - Iterate: edit the persisted
scriptPath, then re-invoke with{ scriptPath }. - Resume:
{ scriptPath, resumeFromRunId }→ the longest unchanged prefix ofagent()calls returns cached results; the first edited/new call onward re-runs. - Subagents can reach session MCP tools via ToolSearch.
The one mechanic that matters most: pipeline vs parallel¶
parallelis a barrier — everything waits for the slowest agent.pipelinehas no barrier — item A can be in stage 3 while B is still in stage 1.- Use a barrier ONLY when a stage needs ALL prior results at once: dedup across
the whole set, early-exit on zero, "compare against the others." Otherwise
prefer
pipeline— you stop paying for idle fast agents.
Constraints (gotchas)¶
- Plain JS, not TS — no type annotations / interfaces / generics (they won't parse).
- No
Date.now()/Math.random()/ arglessnew Date()— they throw (they would break deterministic resume). Pass timestamps viaargs; vary by index instead of randomness. - No filesystem / Node APIs in the script (subagents can use tools, but the orchestration script itself cannot).
- A subagent's final text IS the return value — which is why
schemamatters for anything downstream code consumes. - Only runs when opted in (ultracode on, or "use a workflow" / a skill invokes it).
How to build one¶
- Decide the shape — what fans out, what verifies, what synthesizes. (Comprehensive = decompose + cover in parallel. Confident = independent perspectives + an adversarial check before committing.)
- Write the
metaliteral (phase titles == yourphase()calls). - Define a JSON Schema per structured step.
- Write the body with
phase/agent/pipeline/parallel. - Invoke via the Workflow tool (inline
script, orargsto parameterize).
Reusable patterns¶
- Fan-out → verify (pipeline): each dimension reviews, then each finding is adversarially verified as soon as its review completes.
- Adversarial verify: N skeptics per finding, each prompted to REFUTE; kill on majority. Perspective-diverse verifiers (correctness / security / repro) beat N identical refuters.
- Judge panel: N independent attempts from different angles → parallel judges score → synthesize from the winner.
- Loop-until-dry / until-count / until-budget: keep spawning finders until K
empty rounds / a target count / budget exhausted. Dedup against a
seenset, not against confirmed results (else rejected items reappear forever). - Completeness critic: a final agent asking "what's missing?"
Real example in this repo¶
.claude/skills/resolve-loop/resolve-item.workflow.js
— research (parallel, diverse angles) → judge (a ponytail gate) →
implement-in-worktree → adversarial review loop. It exercises meta + phase +
parallel + agent(schema) + a bounded loop.
Part 2 — OpenAI's Equivalent: Codex Automations + the Landscape¶
Headline¶
Codex "automations" are NOT a committed, hand-authorable workflow/template
format. They are scheduled natural-language prompts configured in the Codex
app (a UI form, or by asking Codex in a thread). There is no
.codex/automations/*.yaml, no codex.yaml, no repo-committed schema. (App
state persists locally at ~/.codex/automations/<id>/automation.toml — an
implementation detail, community-confirmed, not an authoring surface.)
What a Codex automation actually is¶
- "Recurring background tasks": run a prompt on a schedule, post findings to the Triage inbox, auto-archive empty runs.
- Two kinds: thread automations (heartbeat wake-ups attached to a thread; minute-level loops; keep context) and standalone/project automations (fresh independent scheduled runs → Triage; can span projects).
- One prompt per run — it can be multi-step inside the model's own reasoning
and can call skills via
$skill-name, but there is NO author-controlled fan-out / branching across subagents. - Triggers: schedule only — minute intervals, daily/weekly presets, or custom cron. No event triggers (no on-PR / on-push / on-issue / webhook).
- Runs in your local project or a background git worktree; write actions (including PRs) are gated by the selected approval mode (the canonical bug-triage automation is report-only).
The "templates" that DO exist (≠ a config format)¶
Starter prompt boilerplate with bracketed placeholders in the use-case docs —
e.g. bug-triage: Run a bug triage sweep for [repo/service/team]… Schedule:
[every weekday morning / daily]. This is prompt boilerplate, not a config-file
gallery.
Adjacent committed / event-driven surface (a DIFFERENT feature)¶
The Codex GitHub Action (openai/codex-action@v1) is committed YAML in
.github/workflows/, fires on real GitHub events (on: pull_request / push /
issue), runs codex exec, takes its prompt inline or via
prompt-file: .github/codex/prompts/*.md, and posts results as PR comments. This
is the committed + event-driven Codex surface — but OpenAI does not call it
"automations." Do not conflate the two: automations are UI-configured scheduled
prompts; the GitHub Action is committed, event-driven CI.
Don't conflate (other Codex files; none define automations)¶
AGENTS.md— freeform markdown guidance Codex reads before working..codex/config.toml/~/.codex/config.toml— run behavior (model, approval, sandbox, MCP). Unrelated to automations.
Broader OpenAI orchestration landscape (context)¶
- Agents SDK (Python/TS) — the truest code analog to dynamic workflows:
your code deterministically calls
Runner.runon sub-agents; fan-out viaasyncio.gather; primitives = Agent / handoffs / tools / guardrails / sessions / Runner. No template format (it's code). Runs in YOUR infra. - Agent Builder (AgentKit) — the visual analog; If/else + While via CEL, Human-approval / MCP nodes. DEPRECATED (shuts down 2026-11-30); one-way export to Agents SDK code; no confirmed importable declarative format.
- ChatGPT "Scheduled tasks" / "Workspace Agents" — natural-language "automations" plus a schedule; cron-for-prompts; no authorable template.
Comparison — Claude dynamic workflows vs Codex automations¶
| Dimension | Claude dynamic workflows | Codex automations |
|---|---|---|
| Authoring | Code (JS orchestrating subagents) | UI form / NL (schedule + prompt); no committed file |
| Unit of work | A program (steps, conditionals, data passing) | One prompt per run |
| Control flow / fan-out | First-class (parallel / pipeline / loops) |
None author-controlled (single agent run) |
| Determinism | High (the script is the harness) | Low (NL prompt + model) |
| Structured output | Yes (JSON Schema) | No (findings → Triage) |
| Triggers | In-session / background; you wire them | Schedule only (cron); no events |
| Where it runs | Local (session / background) | Codex app (local / worktree) or cloud container |
| Committed artifact | The .js (but session-authored, harness-specific) |
None for automations; the Codex GitHub Action YAML is the committed-but-separate surface |
The gap / opportunity¶
Nobody ships a portable, committed, declarative, multi-step workflow/automation FORMAT for coding agents.
- Claude dynamic workflows = code — powerful, but session-authored, harness-specific, not a committed cross-tool artifact.
- Codex automations = UI-configured scheduled prompts — single-agent, not committed.
- The only committed declarative surfaces are CI-shaped single-prompt (the Codex
GitHub Action YAML) or instruction files (
AGENTS.md).
→ Whitespace for a harness-neutral, repo-committed workflow spec (steps + fan-out + triggers + typed outputs) that multiple agents could execute. This rhymes directly with Anvil's cross-harness + durable-state positioning.
Anvil as the Workflow Substrate¶
The takeaway from Parts 1 and 2: every harness-native orchestration primitive — Claude dynamic workflows (JS orchestrating subagents), Codex automations (scheduled prompts), the OpenAI Agents SDK (code) — is ephemeral and harness-specific. They keep intermediate state in script variables and throw it away when the session ends. Anvil is the durable, governed state + audit layer underneath that runtime.
The second front door¶
Today Anvil's only front door is the PRD:
PRD → parse → review → plan/score → tasks → claim → packet → submit evidence →
apply. That is the greenfield path. The second front door is the
workflow/loop itself.
Key refinement: the PRD IS the spec. For the common case you do not author a workflow file from scratch — Anvil already turns the PRD into a ready queue. The job is to transfer/drive that queue into whatever loop or automation a runtime offers, so each step runs Anvil's governed transitions.
The seam — anvil next¶
The seam is the next command
(bin/src/anvil/cli/claim.py:500). It already
returns the next ready task, or — with --json — {"data": {"task": null}} on an
empty queue (exit 0). The only missing bit is a branchable exit code for
jq-less shells and automations: add a -q / --quiet flag → exit 0 if a task
is ready, exit 3 if the queue is empty. That single change makes the queue
drainable from any plain shell.
One primitive, two modes¶
The loop body already exists:
claim → packet (the contract that teaches the steps) → do the work →
submit --evidence → apply (the gate). The execute skill wraps this.
Both usage modes are the same primitive — "one governed task per invocation":
- Fire once — run the body once for
anvil next's task (a Codex automation fire, a CI job, a cron tick). - Drain until empty —
while anvil next -q; do <body>; done(Claude's self-paced/loop, or any shell).
Durable state makes both resumable and safe under concurrency (single-winner leases).
Why this strengthens Anvil¶
- Exercises the wedge under real load. Single-winner leases, file-conflict detection, and evidence gating get stress-tested under real parallel load.
- Covers the brownfield ~75%. A second entry point covering the ad-hoc / brownfield work the PRD front door misses.
- Collapses the fakoli-flow / fakoli-crew trinity. Coordination via Anvil events instead of grep-parsed status files (SL-4).
- Correct, not just fast. Leased + evidence-gated steps cannot double-claim or fake "done."
Where it sits on the roadmap¶
"Runtime-neutral workflow" — the next axis after runtime-neutral state. This is SL-7 (workflow-step) grown into a product axis, sitting on SL-3 (typed proof) and closing SL-4 (status-file → events).
Backlog items:
- WF-1 — the
anvil next -qexit-code seam. - WF-2 — committed loop adapters (Claude
/loop, Codex automation, CI drain) plus a how-to. - WF-3 —
anvil run-workflow+ a.anvil/workflows/*.yamldeclarative path — deferred / spec-first, only for ad-hoc loops NOT derived from a PRD. See the local design draftanvil-workflow-substrate-design.mdfor the full narrative.
Sources¶
Claude Code dynamic workflows — the Workflow tool's meta/body contract,
globals (agent/pipeline/parallel/phase/args/budget/workflow),
resume/iterate mechanics, and constraints, as observed in-harness and in the
in-repo example
.claude/skills/resolve-loop/resolve-item.workflow.js.
OpenAI / Codex:
- developers.openai.com/codex/app/automations
- developers.openai.com/codex/use-cases/automation-bug-triage
- developers.openai.com/codex/changelog
- developers.openai.com/codex/cloud
- developers.openai.com/codex/cloud/environments
- developers.openai.com/codex/github-action
- developers.openai.com/codex/guides/agents-md
- developers.openai.com/codex/config-reference
- openai.github.io/openai-agents-python
- developers.openai.com/api/docs/guides/agent-builder (+ /api/docs/deprecations)