A Claude Code Workflow that executes a cys:plan implementation plan, running
independent tasks in parallel via a dependency DAG inferred from each task's
Consumes/Produces block — instead of one task at a time like sequential plan
executors do.
The generated code is technology-agnostic — validated against both Node and Java/Spring Boot projects, nothing in the design is tied to a specific language.
- What is cys?
- See it in action (60 seconds)
- Quick Start
- Installing the cys plugin
- One-time permissions setup (merges)
- Using cys
- Building from source
- How it works
- Safety checks & known limitations
- Reporting bugs & contributing
cys is two things, sharing one repo:
- A portable plugin — five skills covering the whole flow
design → plan → run → check → ship, created by Christian Bacilio and named
after his twin daughters, Cielo y Sophia. Four of the five skills are plain
Markdown with no Claude-Code-specific coupling, so the same
skills/directory works as-is on Claude Code, Cursor, and Gemini CLI — see Installing the cys plugin. cys:run, the parallel execution engine (this repo'sWorkflowscript) — the actual differentiator, and Claude Code only. It's aWorkflowscript, a third kind of Claude Code extension distinct from plugins and skills: you invoke it by absolute path (scriptPath: <clone>/workflows/parallel-plan-executor.js), it can't pause mid-run to ask you anything, and everything it produces — task briefs, review verdicts,.cys/handoff.md— is written to disk instead.
| Skill | What it does |
|---|---|
cys:design |
idea → spec |
cys:plan |
spec → implementation plan |
cys:run |
this repo's Workflow — launched via /cys:run-plan or /cys:flow. Claude Code only. |
cys:check |
adversarial review / verification |
cys:ship |
commit / SemVer bump / PR |
cys:guide |
index — which skill to use when |
/cys:flow (Claude Code only) is the all-in-one entry point: give it a target
repo and an idea, and it walks the whole flow (design → plan → parallel run) with
your approval gates at each stage. Use /cys:run-plan instead when an approved
plan already exists.
Design spec: docs/cys/specs/2026-07-04-parallel-plan-executor-design.md.
flowchart TD
subgraph DESIGN["1 · cys:design"]
D1["User's idea"]
D2["Dialogue: context,\none question at a time,\n2-3 approaches"]
D3["docs/cys/specs/*.md"]
D1 --> D2 --> D3
end
GATE1{"Human gate:\ndoes the user\napprove the spec?"}
D3 --> GATE1
GATE1 -- "no, revise" --> D2
GATE1 -- "yes" --> PLAN
subgraph PLAN["2 · cys:plan"]
P1["Numbered tasks\nFiles + Consumes/Produces"]
P2["docs/cys/plans/*.md"]
P3["bin/parse-plan.js\ngraph dry-run"]
P1 --> P2 --> P3
end
PLAN --> RUN
subgraph RUN["3 · cys:run (Claude Code only)"]
R1["DAG inferred from the plan"]
R2["worktree + implement + adversarial\nreview + serialized merge,\nper task, in parallel where\nthe DAG allows it"]
R3["merged task-<id> branches\n+ .cys/ (briefs, reports, diffs)"]
R1 --> R2 --> R3
end
RUN --> CHECK
subgraph CHECK["4 · cys:check (optional)"]
C1["Extra review on a\nbranch that's ready"]
C2["Verdicts + findings\nto .cys/pending.md"]
C1 --> C2
end
CHECK --> SHIP
RUN -.-> SHIP
subgraph SHIP["5 · cys:ship"]
S1["Classifies the change,\ncomputes SemVer"]
S2["CHANGELOG + branch +\ncommit + PR"]
S1 --> S2
end
GATE2{"Human gate:\ndoes the user\nmerge the PR?"}
S2 --> GATE2
GATE2 -- "yes" --> DONE["Change integrated"]
style GATE1 fill:#8a6d1a,color:#fff
style GATE2 fill:#8a6d1a,color:#fff
style DONE fill:#1a6b2a,color:#fff
Source: docs/diagram/flujo-cys-ecosystem.mmd.
One request, in plain language:
/cys:flow ~/projects/persons-api "A CRUD REST API for managing person records, Java 17 / Spring Boot 3, MongoDB persistence"
cys turns that into a spec, a plan, and — the part that's actually different from other AI coding tools — a real parallel execution. From a pilot run of exactly that idea:
| Task | What it built | Ran |
|---|---|---|
| 1. Domain model & scaffolding | Person document, Maven project |
alone — everything else depends on it (9m13s) |
| 2. Repository | PersonRepository |
in parallel with Task 3 (2m01s) |
| 3. Service layer | business rules, validation | in parallel with Task 2 (1m46s) |
| 4. Controller | REST endpoints | after 1–3 (2m11s) |
| 5. Error handling | GlobalExceptionHandler |
after 1 & 4 (2m56s) |
Tasks 2 and 3 don't depend on each other — cys inferred that from their
Consumes/Produces blocks and ran them concurrently instead of one
after another. Every task went through its own isolated git worktree, an
adversarial code review, and a serialized merge — you get a PR with a
whole-branch review verdict, not just green tests. See
Reporting bugs below if anything looks off — the
final review already writes its own findings to .cys/pending.md for you.
The fast path, for Claude Code — no cloning, no building. Installing the plugin already materializes this whole repo (pre-built engine included) where Claude Code can run it:
/plugin marketplace add bacsystem/parallel-plan-executor
/plugin install cys@bacsystem
Then, from any Claude Code session:
/cys:flow /absolute/path/to/your-project "describe what you want built"
That's the whole flow — design, plan, and (Claude Code only) a real parallel run — with your approval at each gate. See Installing the cys plugin for Cursor and Gemini CLI, and Using cys for the full first-run walkthrough once you're past the quick version.
On other platforms (Cursor, Gemini CLI), the plugin gives you cys:design
and cys:plan; cys:guide tells you how to execute the resulting plan's
tasks yourself, since cys:run's parallel engine is Claude Code only.
cys's five non-engine skills (design, plan, check, ship, guide)
are plain Markdown with no Claude-Code-specific coupling, so they're
shared as-is — same skills/ directory, no forked copy — across every
platform below. cys:run (parallel execution, the DAG scheduler,
adversarial review, and serialized merging) is Claude Code only: on
any other platform, cys:guide tells you how to execute a plan's tasks
yourself once cys:plan has produced one.
Install from this repo's self-hosted marketplace:
/plugin marketplace add bacsystem/parallel-plan-executor
/plugin install cys@bacsystem
The first line resolves the short owner/repo GitHub form. Two equivalent
alternatives, if you need them:
# Full GitHub URL instead of the short form
/plugin marketplace add https://github.com/bacsystem/parallel-plan-executor
# A local clone instead of GitHub (e.g. to test uncommitted changes)
/plugin marketplace add /absolute/path/to/your/clone
Installing the plugin also exposes this repo's commands/run-plan.md as the
/cys:run-plan slash command, and commands/flow.md as /cys:flow — no
manual file copying needed.
Cursor doesn't have a /plugin marketplace add <repo>-style command like
Claude Code, but it does have a "from a local repo" install path in its
own Settings UI (confirmed working — Cursor's plugin UI changed after
this section was first written, so trust these steps over any older
screenshot you find elsewhere):
- Clone this repo (see Building from source below — for just the skills, cloning is enough, you don't need to build the workflow artifact or run its test suite).
- In Cursor: Settings → Plugins (or the Customize panel, if your version has moved plugin management there) → + Add → From Local Repo → point it at your clone's absolute path.
cysshows up under a "Bacsystem" group (fromplugin.json'sauthorfield) with an Add button — click it. Once it says Added, the skills are live, no reload needed.
Invoke skills the same way you would any other Cursor skill (e.g.
/design, /plan).
Fallback: symlink into Cursor's local plugins folder
If your Cursor version doesn't have the "From Local Repo" flow, the
older documented mechanism still works: link the clone into Cursor's
local plugins folder (~/.cursor/plugins/local/<name> on macOS/Linux,
per Cursor's plugin docs). A symlink
is preferred over a copy so future git pulls stay picked up — always
use the clone's absolute path as the link target, not a relative one
(a relative target resolves against the symlink's own directory, not
wherever you ran the command from, and silently breaks):
macOS / Linux:
mkdir -p ~/.cursor/plugins/local
ln -s /absolute/path/to/your/clone ~/.cursor/plugins/local/cys
Windows (PowerShell):
New-Item -ItemType Directory -Force "$env:USERPROFILE\.cursor\plugins\local" | Out-Null
New-Item -ItemType Junction -Path "$env:USERPROFILE\.cursor\plugins\local\cys" -Target "C:\absolute\path\to\your\clone"A junction (New-Item -ItemType Junction, or mklink /J from cmd.exe)
works without admin rights, unlike a regular directory symlink
(New-Item -ItemType SymbolicLink / mklink /D), which needs either an
elevated prompt or Developer Mode enabled.
Then reload Cursor (Command Palette → "Developer: Reload Window") to pick it up.
The five non-engine skills (design, plan, check, ship, guide)
also work in Gemini CLI, via its native Agent
Skills feature — no forked copy, skills/ is discovered as-is by
directory-name convention (no manifest field needed, unlike Cursor).
Install:
gemini extensions install https://github.com/bacsystem/parallel-plan-executor
This clones the whole repo to ~/.gemini/extensions/cys/ and makes the
skills available in every project — not just the one you ran the
command from. Since install copies rather than tracks the repo live, run
gemini extensions update cys to pick up future releases.
cys:run's parallel execution stays Claude-Code-only (see
Building from source below): on Gemini CLI,
cys:guide tells you how to execute a plan's tasks yourself instead.
Do this once, before your first real cys:run, so task merges don't get
blocked mid-run.
The workflow's merge agents run git merge inside your target repo. Claude
Code treats an agent merging code as a sensitive action, and what happens
depends on your permission mode:
- Default (normal) mode: nothing to configure. The first time a merge agent runs
git merge, you get Claude Code's native permission dialog — Allow once / Allow always / Deny. Pick "Allow always" on the first one and the rest of the run flows without asking again. - Auto mode: there is no dialog by default — an automatic classifier decides
alone, and it may block agent merges even when you authorized the run up front (see
the permissions note in branching topology for
why). To get the same yes/no dialog as normal mode, add an
askrule to the target project's.claude/settings.json(create the file if needed):
{
"permissions": {
"ask": [
"Bash(git merge:*)",
"Bash(git -C * merge *)"
]
}
}With that rule in place, every git merge from any agent pauses and asks you,
deterministically, regardless of mode — you just click, never type. If you'd rather
never be asked, use "allow" instead of "ask" (the run becomes fully hands-off; the
human gate moves to the final PR review).
This section walks the full first-run experience, then covers the
reference pieces (manual invocation, the /run-plan command, the
Handoff phase, branch topology) for when you need more control than
/cys:flow gives you.
This subsection is for anyone who hasn't run the workflow before and wants to go through it without getting lost. If you already know it, Manual invocation below is the quick reference.
- An approved implementation plan, with numbered tasks and their
Consumes/Producesblocks (the format produced by thecys:planskill). If you don't have one yet, ask Claude Code, from your project's repo: "help me write an implementation plan for [your feature]" — with the cys plugin installed that runscys:design→cys:planand leaves the plan file ready. - The repo you're automating, with a clean working tree (
git statusshows no pending changes) and, if you'll requestopenPr: trueat the end, a GitHub remote already configured withgh auth statusgreen. - The cys plugin installed (see Quick Start) — no manual cloning needed for this path. If you're driving the engine directly instead of through the plugin commands, see Building from source.
It can be in your project's folder, in this repo's folder, or anywhere else — the workflow doesn't depend on where your Claude Code session is running, as long as you give it absolute paths to the plan and the target repo.
You don't need to hand-write the args JSON. That's Claude Code's job: you just
tell it what you want in a sentence, with these pieces of information:
- the path to your plan (
planPath), - the path to your target project (
repoPath), - the name of the integration branch (
integrationBranch) — an ephemeral feature branch cut fromdevelop, neverdevelop/maindirectly (see the recommended topology below), - whether you want it to push and open the PR at the end (
openPr) and against which branch (pr.base), - your explicit authorization for the merges, naming the branches — this matters, see the box below.
Real example (similar to what was used while building this very fix):
"Launch the parallel-plan-executor workflow on my project at
D:/my-project. The plan is atdocs/plans/2026-07-16-my-feature.md, already approved. Integration branch:feature/my-feature. At the end, push and create the PR againstdevelop. I authorize merging branches task-1 through task-6."
Claude Code takes care of running bin/parse-plan.js on your plan, building the args,
and invoking the Workflow tool with this repo's script — you never touch JSON directly.
Why name the branches in your authorization? If the environment has Claude Code's permission classifier in auto mode, it may require a human to explicitly authorize merges — and that authorization needs to name the concrete action ("merge task-1 through task-6"), not a plain "yes" or "go ahead". Saying it upfront, with branches named, avoids the run getting stuck partway through. See the permissions note in branching topology for the technical detail.
The workflow runs in the background — it doesn't wait for your reply. You'll see:
- A text progress bar like
[####----] 2/6 tasks settledevery time a task finishes (merged, failed, or skipped). - A
Task N: started (implement)notice as soon as each task starts, so you know it isn't stuck during the minutes implementation takes.
You can ask Claude Code "how's the workflow going?" at any point — it will check the
real state and tell you which tasks finished, which are in progress, and whether
anything went wrong. You can also open Claude Code's /workflows panel to see the
per-phase detail (Implement, Review, Merge, Final review, Handoff), how many agents and
tokens each phase used, and each agent's timing.
The most common snag is a merge getting marked as blocked out of caution, even after you authorized upfront — that's an environment safety measure, not a flaw in your plan. If that happens:
- Ask Claude Code what happened — it should be able to explain the concrete cause.
- Repeat your authorization naming the specific branches still pending ("I authorize merging task-2 and task-3") and ask it to retry.
- The run is recoverable: nothing already done is lost. Tasks that already finished (implemented, reviewed, merged) don't re-run — only what's still pending retries.
- If at least one task merged, you'll have a
.cys/handoff.mdfile in your project with: the suggested PR title and body, the proposed SemVer bump, and a cleanup checklist (whichtask-Nbranches to delete and when) — see Handoff phase for the full detail. - If you requested
openPr: true, the PR is already created in GitHub against the branch you specified — review it yourself and merge it whenever you're satisfied. The workflow never merges the PR on its own; that decision always stays in your hands. - If any task failed or got blocked, the final report will tell you exactly which one and why — and which other tasks were skipped in cascade because they depended on it.
| What you see | What it means |
|---|---|
args.tasks must be a non-empty array |
The plan has no parseable tasks, or the plan wasn't parsed correctly. Check that your plan has ### Task N: blocks with Consumes/Produces. |
A merge comes back CONFLICT with no real git conflict |
Almost always the permission classifier asking for explicit authorization — see Step 4. |
| The run stops partway through | It's recoverable: Claude Code can resume it without losing the work already done. |
| The agent takes several minutes "doing nothing" when the first task starts | Normal — the first implement includes setting up the project's environment; you'll see the progress notice as soon as it's done. |
Once you know the flow, this is the raw shape of what /cys:flow//cys:run-plan
do for you — useful if you're scripting around cys or want to see every field:
# 1. Compute the task graph for your plan
# (stdout is pure JSON; ambiguity warnings — e.g. two tasks producing the same
# symbol — go to stderr and are also included in the JSON's "warnings" field)
node bin/parse-plan.js /path/to/your-plan.md > /tmp/plan-graph.json
# 2. Ask Claude Code to invoke the Workflow tool with:
# scriptPath: "<this repo>/workflows/parallel-plan-executor.js"
# args: { tasks: <the "tasks" field of plan-graph.json>,
# graph: <the "graph" field of plan-graph.json>,
# planPath: "/path/to/your-plan.md",
# repoPath: "/path/to/your/project",
# integrationBranch: "feature/my-plan", # the branch every task merges into (required)
# executorPath: "<this-repo>", # absolute path of this clone: the workflow
# # runs its bin/ scripts by exact path (required)
# openPr: true, # optional: push + open the PR at the end
# pr: { base: "develop", assignees: ["me"], labels: ["story"],
# milestone: "v1.2", closes: 42 }, # optional PR fields (git-flow contract)
# mergeAuthorization: "I authorize merging task-1 through task-N into <branch>",
# # optional but recommended: your explicit authorization, so the merge
# # agent doesn't have to guess whether consent was already given (see the
# # permissions note in branching topology below)
# maxConcurrency: 3 # optional, default unlimited — see below
# }maxConcurrency caps how many tasks cys:run executes at once within a DAG layer. The
Claude Code Workflow tool already queues excess agent() calls beyond its own
min(16, cores-2) cap, so this is mainly useful to go lower than that — e.g. to avoid
many simultaneous local git worktrees on your own machine for a plan with a wide layer of
independent tasks. /run-plan and /cys:flow offer to set it for you when the parsed
plan's inferred parallel width exceeds 6 — you don't need to compute this by hand.
If you'd rather not type out the natural-language request from the step-by-step guide
every time, this repo ships a Claude Code custom slash command that wraps it:
commands/run-plan.md.
-
Copy
commands/run-plan.mdfrom this repo to either:~/.claude/commands/run-plan.md— available in every project on your machine, or<your-project>/.claude/commands/run-plan.md— available only inside that one project.
Global (
~/.claude/commands/) is the right choice for most people, since this tool is meant to be invoked against other projects, not just the one it happens to live in. -
Open the copied file and replace the
REPO = ...placeholder near the top with the absolute path where you cloned this repo (parallel-plan-executor), e.g.REPO = /home/you/parallel-plan-executor. This is the one thing you must edit — the command has no other way to find the workflow script. -
That's it — no restart needed. Claude Code picks up commands under
.claude/commands/the next time you use them.
/run-plan /path/to/your-plan.md /path/to/your/project feature/my-plan
All three arguments are optional to type up front — the command will ask you for
anything you leave out, plus whatever manual invocation
above lists as optional (openPr, pr fields, your merge authorization). It never
invents your authorization text on your behalf; it always asks you to name the branches
yourself.
When at least one task merged, a final handoff agent prepares the git-flow closing
for you — without executing it. It writes .cys/handoff.md in the target
repo with: a suggested Conventional-Commit PR title, a full PR body (Summary / Type of
change / Main changes / Version / Checklist), the proposed SemVer bump derived from the
run's commits (git-flow rules, 0.x included), the final review verdict, and a post-run
cleanup checklist.
With openPr: true (explicit consent given at launch) it additionally pushes the
integration branch and creates the pull request via gh against pr.base (default
develop), applying the optional pr fields — assignees, labels, milestone, and
Closes #<closes> in the body. It never merges the PR: that gate is human, always.
Point integrationBranch at an ephemeral feature branch cut from develop — never
at develop/main directly:
main (release) ← never touched by agents
└── develop (integration) ← never touched by agents
└── feature/<plan> ← integrationBranch: task branches merge here ★
├── task-1 ← one isolated worktree per implementer
└── task-N
Why: mainline stays protected by construction (agent-written code never lands on a
shared branch without human review), a failed run costs one git branch -D, and the
human gate sits exactly where it belongs — the single feature/<plan> → develop PR you
open via git-flow after reviewing the finished branch.
Permissions note (read this if a merge gets blocked): under Claude Code's auto
mode, an automatic classifier judges each agent action on its own, and agent-performed
git merge is exactly the pattern it watches for. Passing your authorization text via
args.mergeAuthorization helps the merge agent itself not self-block out of caution
(finding F8 in docs/pilots/2026-07-15-pilot-stats-bitacora.md) — but it does not
bind the classifier: in a later real run the classifier explicitly rejected that relayed
text as "self-asserted, unverifiable" consent and blocked the merge anyway. The
deterministic fix is the one-time permissions setup
above: an ask (or allow) rule for git merge in the target project's
.claude/settings.json, added by you. Rules take precedence over the mode — with the
rule in place you get a plain yes/no dialog (or silent allow) instead of a classifier
judgment call.
Only needed if you're contributing to this repo, or want to run the raw
Workflow script without going through the plugin commands. If you just want
to use cys, Quick Start is enough — installing the plugin
already gives you a ready-to-run, pre-built copy.
- Claude Code, with access to the
Workflowtool. This is not optional or swappable for another AI assistant: the script inworkflows/parallel-plan-executor.jsis written against that tool's primitives (agent(),pipeline(),parallel(), etc.) — it isn't an open standard another assistant (ChatGPT, Gemini, etc.) can interpret. What is agnostic is the target project being automated: it can be Go, Node, Java, or whatever stack the plan describes. - The cys plugin (see Installing the cys plugin) for
authoring plans with
cys:plan. The engine is fully self-contained: the workflow ships its owntask-brief/review-packagescripts inbin/and records runs under.cys/. Any plan following the### Task N:+Consumes/Producesformat works, whatever tool wrote it. - Node.js >= 20 (for
bin/parse-plan.jsand the test suite — no runtime dependencies, just standard Node). - Git, and a clean working tree in the project you're automating.
gh(GitHub CLI) installed and authenticated, only if you'll useopenPr: true(so the workflow can create the final PR).
# 1. Clone this repo (where the workflow lives) onto your machine.
# WHERE: anywhere you like — your home folder, a tools directory, etc.
# It does NOT need to be inside .claude/, and it does NOT need to live next to
# the projects you'll automate; every path you pass it later is absolute.
git clone <this-repo-url> parallel-plan-executor
cd parallel-plan-executor
# 2. Check your Node version (must be >= 20)
node --version
# 3. Install (no runtime dependencies; this just wires up the npm scripts)
npm install
# 4. Run the test suite to confirm everything works in your environment
npm test
# 5. Build the workflow artifact (regenerates workflows/parallel-plan-executor.js
# from the template — also re-run this after any change under src/)
npm run buildThat's it — the workflow is invoked from a Claude Code session, no need to publish it to npm or install it globally. See Using cys above. Before your first real run, also do the one-time permissions setup so task merges don't get blocked mid-run.
bin/parse-plan.jsreads a plan file and computes its task list + dependency graph (pure Node, fully unit tested — seetests/).workflows/parallel-plan-executor.js(built fromworkflows-src/parallel-plan-executor.template.jsvianpm run build) takes that graph and runs each task in its own git worktree viaagent(), starting a task the moment its specific dependencies finish rather than waiting for a whole batch.- Each task gets an adversarial review agent instead of a human checkpoint per task,
since a
Workflowcan't pause mid-run to ask you anything. - Merges happen one at a time, serialized, respecting the dependency order.
- You get a single report at the end, and — if at least one task merged — a Handoff agent prepares the git-flow closing for you (see Handoff phase).
- Startup validation: the workflow validates
argsbefore launching any agent — a cyclic graph or an id present ingraphbut missing fromtasksfails fast with a clear error instead of deadlockingrunDagsilently. - Same-file chaining: tasks touching the same file are serialized as a chain (each depends on the last task to touch it), so they never run in parallel against each other.
- Duplicate-producer warnings: two tasks declaring the same
Producessymbol is surfaced as a warning (first producer still wins); it does not abort the run. - Skip reasons point at the root cause: a task skipped through a cascade reports the task that originally failed, not the intermediate skipped link.
- Only backtick-quoted symbols count in
Consumes/Produces(e.g.- Produces: the `createWidget()` factoryproducescreateWidget). Bare prose is ignored on purpose: extracting every identifier turned words like "the" or "None" into symbols and created spurious dependencies — even false cycles — between unrelated tasks.Consumes: Noneis therefore simply an empty list. - The
Consumes/Producesparser reads one line at a time — a value that wraps onto a second line in the plan's prose won't be captured. A missed dependency does not silently misorder tasks: the task starts without its real dependency in place, so it either fails loudly (or self-reportsBLOCKED) and its transitive dependents are skipped, all surfaced in the final report. A retry-later mechanism (re-attempt once more of the DAG has closed) was evaluated and deferred — see design spec §7 — so today the only mitigation is keepingConsumes/Produceson one line per entry. - No speculative re-execution of an abnormally slow task (evaluated and deferred, see design spec §7) — right-sizing tasks in the plan itself is the current mitigation.
task-<id>branches of failed or BLOCKED tasks survive the run on purpose: they preserve whatever partial state exists for diagnosis. Clean them up afterwards withgit branch -D task-<id>once you no longer need them.
Open an issue at github.com/bacsystem/parallel-plan-executor/issues (the bug report template will guide you). The single most useful thing you can attach is something cys already generated for you — no need to write a fresh repro from scratch:
.cys/pending.md, if the run's final review or acys:checkcall already logged a finding about this..cys/task-<id>-report.md, for the specific task that misbehaved.review-*.diff, if a review flagged something.- The exact stderr/stdout of a failing command (e.g.
node bin/parse-plan.js).
Want to contribute code or docs? See CONTRIBUTING.md for this repo's
evidence-driven discipline (every behavior change needs a test tracing to a real
finding, plus a comment explaining why) and the TDD/build workflow.