Repository navigation
[FEATURE]: Add Dynamic workflows for repeatable multi-step automation (new Claude Code feature) #29059
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Feature request
Add project-local workflows for repeatable multi-step automation in OpenCode.
Context
This idea comes from an unannounced feature that briefly appeared in Anthropic's Claude Code called Workflows. It was only available for a short time via hidden flags, but the concept is amazing.
Instead of letting the AI orchestrate everything (which wastes tokens and makes the model lazy), we use a simple JavaScript/TypeScript file to control the steps, loops, and logic. Sub-agents run in completely isolated sessions, keeping the main chat clean.
I wanted this in the open-source world immediately, so I built a prototype for OpenCode.
References:
- Video Demo: YouTube Link
- Reddit Thread: Reddit Link
The Problem
Right now, if you want to run a multi-step task in OpenCode (e.g., fetch logs ➡️ fix code ➡️ run tests), you have to type it manually or build messy external bash scripts.
The main benefit is deterministic control over agent orchestration. Instead of asking the model to decide every next step inside one long conversation, the workflow file defines the sequence, branching, loops, and parallelism explicitly. This is often the missing piece for reliable multi-agent tasks: it gives users LangChain/LangGraph-style orchestration, but inside OpenCode's project-local, TUI-first environment.
Proposed Solution
Add workflow files under .opencode/workflows/ that you can run directly from the TUI.
What a workflow can do:
- Run in the background via
/workflow <name> arg=value(doesn't lock the chat). - Use an isolated context (
ctx) to spawn sub-agents without messing up your main chat history. - Run tasks in parallel (
ctx.parallel) or as a stream pipeline (ctx.pipeline). - Show up in terminal autocomplete.
- Save history so you can inspect what happened later.
Code Example (.ts / .js)
import { workflow } from "@opencode-ai/plugin/workflow"
export default workflow({
name: "Research Plan",
phases: ["brief", "review", "summary"],
arguments: {
topic: { type: "string", description: "Topic to research." },
},
async run(args, ctx) {
ctx.setPhase("brief")
const brief = await ctx.agent({
agent: "build",
prompt: `Create a short technical brief about: ${args.topic}`,
})
ctx.setPhase("review")
const [risks, implementation] = await ctx.parallel([
() =>
ctx.agent({
agent: "plan",
prompt: `Review this brief for risks:\n\n${brief.text}`,
}),
() =>
ctx.agent({
agent: "build",
prompt: `Suggest an implementation approach:\n\n${brief.text}`,
}),
])
ctx.setPhase("summary")
return ctx.agent({
agent: "general",
prompt: [
`Summarize the result for: ${args.topic}`,
"",
"BRIEF:",
brief.text,
"",
"RISKS:",
risks.text,
"",
"IMPLEMENTATION:",
implementation.text,
].join("\n"),
})
},
})New UI Features (TUI)
/workflow <name> ...args— Starts a workflow in the background./workflows— Opens a full-screen dashboard of all runs.- Detail View — A screen showing current phases, live logs, active sub-agents, retries, token count, and costs.
- Delete Option — Ability to remove old runs from history.
Implementation Status
I already have a working local prototype with discovery, background execution, TUI dashboard, history persistence, and docs. I can open a PR as soon as the design is approved!
Activity
VasyaYovbak commentedon May 28, 2026
https://code.claude.com/docs/en/changelog#2-1-154
Introducing dynamic workflows: ask Claude to create a workflow and it orchestrates work across tens to hundreds of agents in the background, so you can take on larger, more complex tasks. Run /workflows to view your runs
So claude code now added it officially
[-][FEATURE]: Add project-local workflows for repeatable multi-step automation[/-][+][FEATURE]: Add Dynamic workflows for repeatable multi-step automation (new Claude Code feature)[/+]randomdinosaur commentedon May 31, 2026
Is it reasonable to add an "ultracode" reasoning level like Claude code is doing? what would the architecture looks like pairing with your implementation?
VasyaYovbak commentedon May 31, 2026
This is possible , but i am not sure should we copy this style of workflow access. Under the core , "ultracode" it's just highest level of resoaning + provided prompt to model to always utilize workflows ( when model think it need it).
Because opencode support a lot of different providers , logic for setting highest resoning level could be a little complicated.
I have plans to introduce today model access to workflows. Model will have access to execute them , to create new, to run one without saving, to run in async mode (waiting for response), and i am planning to provide existing workflows into harness like it is done with SKILLS. So basically it would always have access to them, but i just don't want to force it always use them.
randomdinosaur commentedon May 31, 2026
Gotcha. Thank you. And cheering you up. This (workflows) would be a great addition.
VasyaYovbak commentedon Jun 5, 2026
basically everything is done. Ready for review and testing
mguttmann commentedon Jun 5, 2026
For anyone following this issue: the implementation in #29789 just got a substantial completion pass — real cancellation, crash recovery, resilient discovery, enforced budgets, real pipeline semantics, and broad test coverage. See VasyaYovbak#1 (a PR against the feature branch).
🤖 Generated with Claude Code
VasyaYovbak commentedon Jun 5, 2026
Thanks a lot for improvements! Merged your fix in the main branch!
mguttmann commentedon Jun 6, 2026
Update for followers: the implementation now covers the full Claude-Code-workflows surface —
ultracodekeyword + session mode, bundled/deep-research, pause/resume (survives restarts), run caps, per-run approval, direct/<name>commands — on top of the audited engine from the earlier passes. See VasyaYovbak#2 (rolling PR into the feature branch behind #29789).🤖 Generated with Claude Code
mguttmann commentedon Jun 7, 2026
This feature now exists and is actively maintained in PR #29789.
In short: it adds Dynamic Workflows to opencode — TypeScript workflow files discovered statically (no code execution at discovery time), an engine
ctxAPI for dispatching agents (ctx.agent), running them concurrently (ctx.parallel) or in stages (ctx.pipeline), with budgets, phases, logging, pause/resume via a persisted journal, a live TUI dashboard, a per-run approval gate, an HTTP API, and anultracodekeyword/toggle for orchestration. It also ships a single built-in workflow,deep-research.Feature matrix
ctx.agent({agent, prompt, model?, schema?, permissionSessionID?})returns{data, text}; aschemaforces structured output (failsWorkflowStructuredOutputErrorif none, never silent plaintext)packages/opencode/src/workflow/workflow.tsctx.parallelruns tasks under run scope, checkpoints before each task; default concurrency 20, explicit limit floored to 1packages/opencode/src/workflow/workflow.tsctx.pipeline(items, ...stages, options?)variadic stages(prev, item), runs under run scope, checkpoints before each stage; default unbounded, explicit limit floored to 1packages/opencode/src/workflow/workflow.ts; type inpackages/plugin/src/workflow.tsctx.log,ctx.setPhase, livectx.budgetRemaining(Infinity when no budget set)packages/opencode/src/workflow/workflow.tsmin(16, max(2, os.cpus().length − 2)), clamped 2..16, on top of any per-callconcurrencyLimit(narrower wins)packages/opencode/src/workflow/workflow.ts(agentConcurrencyCap, per-runagentSemaphore)DEFAULT_AGENT_LIMIT = 1000total agent dispatches per run (counts journal replays); exceeding raisesWorkflowAgentLimitErrorpackages/opencode/src/workflow/workflow.tsWorkflowInvalidError; unset = Infinity). Soft per-step gate: checked before eachctx.agent, settled after; parallel steps in flight can overspend. Gate order: abort → budget → agent-lifetime; raisesWorkflowBudgetExceededErrorpackages/opencode/src/workflow/workflow.tsrunning, completed, failed, cancelled, interrupted, paused. Terminal: completed/failed/cancelled/interrupted. Non-terminal: running/paused. AgentRun status narrower 3-value: running/completed/failedpackages/opencode/src/workflow/workflow.ts;packages/core/src/workflow/sql.tsrunningrows →interruptedand their nodes →failed; touches only the run row, never agents JSON;pausedleft untouchedpackages/opencode/src/workflow/workflow.ts(sweep, run on layer creation)pausekeeps the persisted agent journal (sessions aborted, scope closed, fiber interrupted);resume_ofreplays only paused/interrupted source runs, matching on prompt + resolved-agent-name + phase (not model/schema), replays completed agents ascached:true, charges cost once, survives restarts;invalidate_agents= 0-based source indices forced to re-run livepackages/opencode/src/workflow/workflow.ts; journal persisted inpackages/core/src/workflow/sql.tsMetaReader.readextracts meta purely from TS source AST — never imports/executes module code (code runs only atstartafter permission gate); broken file →valid:false, never aborts list; precedence project > global > builtin, first-name-wins; all ops directory-scoped (cross-dir remove → false)packages/opencode/src/workflow/meta-reader.ts,meta.tsbuiltin:<name>). Sole built-indeep-research, phasesplan/research/verify/synthesize, argquestion:string; no-web-tools gate throws hard error; loads via same temp-file import + permission gate as on-diskpackages/opencode/src/workflow/builtin.tsworkflow;action∈ read/start/wait/inspect/create. read=static meta (no ask); start=permissionworkflowask after side-effect-free pre-check, fg waits ≤DEFAULT_TIMEOUT1h orbackground=true; wait=block onrun_id(must startjob); inspect=summary/logs/agents/agent/result/all (pseudo-XML, escaped); create=writes.opencode/workflows/<name>.ts, two asks (workflowthenedit), static MetaReader validation. Always in registry builtin set (no ultracode/experimental gate)packages/opencode/src/tool/workflow.ts/workflow, 7 routes: GET/workflow(list), GET/workflow/run(runs), GET/workflow/run/:id(get), POST/workflow/:name/start, POST/workflow/run/:id/cancel, POST/workflow/run/:id/pause, DELETE/workflow/run/:id. Status mapping: invalid file → 400WorkflowApiError; unknown name/id → 404ApiNotFoundError; malformed branded id → 400 at decode. Group middlewares: InstanceContext, WorkspaceRouting, Authorizationpackages/opencode/src/server/routes/instance/httpapi/groups/workflow.ts+handlers/workflow.ts/workflow(trailing space) name +name=valuearg completion; args coerced by DECLARED type (onlytype:"number"→ number if finite, else raw string; bare flags → string"true"); quote-aware linear tokenizerpackages/tui/src/component/prompt/workflow-autocomplete.ts/<name>slash command (Claude-Code parity), excludingvalid:falsefiles and names colliding with existing built-in/server/MCP commandspackages/tui/src/component/prompt/workflow-autocomplete.tsDialogWorkflowpollsworkflow.runs()every 1000ms, newest-first, selection anchored by run.id (re-anchored after re-sort); detail view pollsworkflow.get()every 1000ms only whilerunning; status icons running●/completed✔/failed✖/cancelled⊗/interrupted⊘/paused⏸; lingering agent on terminal run shown failed with frozen durationpackages/tui/src/component/dialog-workflow.tsxswritesdefinition.sourceto.ts; project<worktree>/.opencode/workflows/<name>.tsor global<config>/workflows/<name>.ts(Tab toggles, default project); name sanitized to single safe segment; existing file = hard error, never overwritespackages/tui/src/component/dialog-workflow.tsx(DialogWorkflowSave)DialogWorkflowApproval.show()→ Promise ofonce/always/cancel; options Yes / Yes,always / View script / No (wraparound nav); backdrop/Esc on either screen →cancel(abort-safe); single-shot decide; viewing source usesnotifyClose:falseso it never resolvespackages/tui/src/component/dialog-workflow-approval.tsx+dialog-workflow-approval-helpers.tsultracodekeyword (Unicode word boundary, case-insensitive) → single-turnULTRACODE_PROMPT_DIRECTIVE, stripped from prompt;/ultracodesession toggle → persistentULTRACODE_SESSION_DIRECTIVE; both instruct orchestration via the workflow toolpackages/tui/src/component/prompt/ultracode.tsworkflows.approval∈always/first-run(default)/never;approvalDecision: never→start, always→ask, first-run/undefined→ask until consent persisted. Consent inworkflows.approvedlist + process-scopedsessionApprovedSet OR'd in for immediate honordialog-workflow-approval-helpers.tsworkflowpermission (after pre-check, before any module load/execution); create asksworkflowthenedit; name sanitized to^[A-Za-z0-9_-]+$so a glob char can't widen analwaysrule; HTTP auth via group Authorization middlewarepackages/opencode/src/tool/workflow.ts;.../httpapi/groups/workflow.tscaller:{sessionID, agent}+permissionSessionIDpropagate the session's deny/external_directory and edit-class (Plan Mode) rules to every spawned subagent (same ruleset as task tool, #26514);permissionSessionIDonly routes WHERE prompts surfacepackages/opencode/src/tool/workflow.ts; engineworkflow.ts(StartOptions.caller)MetaReader.read(AST only, never imports/executes); literals only (else fixed errorworkflow meta must be statically analyzable (literal values only)); pseudo-XML output escapes model-controlled fields against envelope-forgingpackages/opencode/src/workflow/meta-reader.ts;packages/opencode/src/tool/workflow.tsworkflow_run(canonical in core, engine one-line re-export, schemas compile-asserted assignable). DB process-global but endpoints per-directory (scoped bydirectorycol, default'').resultis plain text (NOT JSON) to distinguish SQL NULL=unset from text"null"=genuine null. 3 migrations: base+2 indexes → adddirectory→ addresume_ofpackages/core/src/workflow/sql.ts(+ migrations); re-exportpackages/opencode/src/workflow/workflow.tsPR: #29789 — please give it a try and let us know how it works for your use case.
mguttmann commentedon Jun 7, 2026
Update: the implementation in #29789 just gained a full review-roadmap round (via VasyaYovbak#3) — human-in-the-loop
ctx.question(with pause/resume), deterministicctx.shellsteps, depth-1ctx.workflownesting, per-step git-worktree isolation + tool/skill/file scoping, run lifecycle bus events, structured phases, a headlessopencode run --workflow, and ACP discovery. All TDD + adversarially reviewed (3,122 tests green). Worth a look if you're tracking this.dzianisv commentedon Jun 14, 2026
Heads-up for anyone tracking this: there are now two in-flight efforts, and they're the same lineage rather than competing rewrites.
.opencode/workflows/,@opencode-ai/plugin/workflow,ctx.agent/parallel/pipeline,/workflow+/workflowsTUI, persisted run history.git merge-base --is-ancestorconfirms feat(opencode): add Dynamic workflows (new Claude Code feature) #29789 is in its history; same migration timestamps). It adds nested sub-agent spawning (depth ≤5), worktree isolation, an approval gate, headlessopencode run --workflow, anultracodekeyword, and p2 hardening.So #32167 is a genealogical superset of this PR — the workflow core here is preserved and evolved, not discarded.
I put up a consolidation branch that takes the superset and refreshes it onto current
dev(tipde9aa9c8) — merges clean, zero conflicts: https://github.com/dzianisv/opencode/tree/feat/workflows-consolidatedGiven the relationship, the cleanest path forward is probably to land the nested-spawning bugfixes from #32167 first (they fix #23091/#13715), then the workflow subsystem from this lineage as a focused follow-up. cc @VasyaYovbak
VasyaYovbak commentedon Jun 14, 2026
Hi @dzianisv ! Thanks for your work! Looks like we unfortunately ended up doing roughly the same thing =(
I also pulled the necessary fixes from the #32166 branch, but only the ones directly related to workflows, and prepared a PR here: VasyaYovbak#4
I was just waiting for approval before merging them.
Could you clarify whether you also pulled those same workflow-related changes, or whether you did a separate pull/rebase on top of the latest
dev?docs: workflow TUI dashboard & detail view design
dzianisv commentedon Jun 19, 2026
For anyone tracking this: we shipped a working implementation of Claude Code-style dynamic workflows in our fork.
Commit: dzianisv@2073487
What works (end-to-end, against a real provider):
/workflow <path-to-file.js>slash command runs project-local JS workflows.agent(),parallel(),pipeline(),phase(),log(),args) with no boilerplate, alongside the nativeworkflow({...})form. Files written for Claude Code's hidden workflows feature run unmodified.schemaoption (model is forced to return validated JSON).parallel()fan-out, andpipeline()(per-item staging, no barrier).claude-sonnet-4→ the provider's versionedclaude-sonnet-4.6), and the resolved model is threaded through to the sub-agent call.SKILL.mdcontent + a per-dir catalog so the manager agent can plan over locally-discovered skills.13 files, includes test coverage (
bare-globals.test.ts,workflow-command.test.ts, plus tool/schema tests). Verified E2E: a 6-phase research workflow and isolation tests run to completion against GitHub Copilot-served Claude models.Happy to open a PR upstream or split it into reviewable chunks if there's interest.
tvvignesh commentedon Jun 27, 2026
Hi. Need this please. +1
Thanks in advance.
githwd2016 commentedon Jun 29, 2026
Thanks a lot for working on this amazing feature! I've been following the progress and the implementation looks really solid.
I'm wondering if there's any rough timeline for when this might get merged into the main branch and available in official releases?
MemoAsh commentedon Jul 5, 2026
The workflow engine shape here is strong, especially the persisted journal + phase/state model. One thing I would consider making first-class is the output contract after a workflow finishes, not just the execution mechanics.
For repeatable coding-agent workflows, the failure mode I keep seeing is: the workflow ran, logs exist, but the final claim is still prose. A small completion receipt makes the run auditable:
In an OpenCode workflow API, that could be a convention (
templates/completion-receipt.md) or something more typed (ctx.receipt({...})) so external tools can inspect runs consistently.I am testing this from the portable workflow-pack side here: https://memoash.github.io/agent-workflow-pack/try-pr-review.html
Useful critique for this feature: should completion receipts be outside the workflow engine as userland templates, or should a dynamic workflow system expose a typed receipt/result boundary from day one?
github-actions commentedon Sep 4, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.
Profazia commentedon Sep 11, 2026
Any updates ?