Skip to content

feat(pipeline): multi-model plan → code → review pipeline task type#629

Open
bborn wants to merge 7 commits into
mainfrom
task/4640-herdr-borrow-plancodereview-multi-agent
Open

feat(pipeline): multi-model plan → code → review pipeline task type#629
bborn wants to merge 7 commits into
mainfrom
task/4640-herdr-borrow-plancodereview-multi-agent

Conversation

@bborn

@bborn bborn commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Workflow task type: one goal → a small DAG of steps

Adds a workflow: a single goal is broken into a small DAG of step tasks that run on one shared git branch, each routed to its own executor and model, advancing automatically. Steps are sequential where they depend on each other and parallel where they don't.

The default plan-code-review workflow:

Plan ──▶ Code ──▶ Review A ─┐
                  Review B ─┴─▶ Collect ──▶ PR
Step Default Job
Plan Claude / Opus Explore, write PLAN.md, push. No code.
Code Claude / Sonnet Implement the plan, push.
Review A, Review B Claude / Opus, Claude / Sonnet Two independent reviewers, in parallel, each on its own branch. Different models + independent context catch different classes of issue and avoid single-context self-review.
Collect Claude / Sonnet Read both reviews, apply the fixes worth applying, open the PR.

How it works

Built on primitives TaskYou already has — no bespoke execution engine; the normal daemon runs each step task:

  • The DAG is the task dependency graph. Each step is a task; a step's Deps become auto_queue dependencies. Fan-out (two reviewers depending on Code) and join (Collect depending on both reviewers) fall out for free — ProcessCompletedBlocker queues a step once all its blockers complete.
  • One shared branch. The root step pins the branch; every other step checks it out via SourceBranch, handing work forward through git. The two reviewers push to their own branches (single push, no rebase) so they can't clobber each other, and Collect reads each explicitly.
  • Autonomous, with a human gate only when needed. Steps advance on their own; the workflow lands in blocked when Collect opens the PR (awaiting a human merge) or when a step calls taskyou_needs_input.
  • One board card, not N. A workflow's step tasks collapse into a single lead card showing the goal and current step + progress (⇄ goal / Review ∥ · 3/5) instead of cluttering the board with a card per step.

Configurable per project

Each step's executor and model is configurable per project and persisted, so you set it once and every workflow in that project uses your choices:

ty pipeline config -p myapp                          # show current config
ty pipeline config -p myapp --set "Review B=codex"   # cross-executor review
ty pipeline config -p myapp --set "Code=claude/haiku"
ty pipeline config -p myapp --reset                  # back to defaults

Steps are data, so a workflow can be extended (add a QA step, a third reviewer, a different flow) without touching the executor.

Entry points

  • CLI: ty pipeline "<goal>" (--definition, --list, --no-execute, --permission-mode, --json) and ty pipeline config.
  • TUI: a Workflow selector in the new-task form; selecting one turns submit into a workflow, with a start-now / save-for-later confirm and a board toast.
  • MCP: taskyou_create_pipeline so an agent can spawn a workflow mid-task.

Each step commits and pushes the shared branch, so a workflow needs a git-worktree project with a remote to push to.

Changes

  • internal/pipeline/ (new/reworked): DAG model (Step + Deps, validated for single-root / acyclic / unique names), the built-in plan-code-review definition, Create() that builds the linked-task DAG, per-project step config, and GroupWorkflows for the collapsed board card.
  • internal/ui/: workflow board collapse (kanban.go, app.go), the Workflow form selector, the confirm/toast.
  • cmd/task/main.go: ty pipeline + ty pipeline config.
  • internal/mcp/server.go: taskyou_create_pipeline.
  • internal/executor/executor.go: setupWorktree honors a caller-pinned BranchName so the root step can own the shared branch.

Tests & QA

  • Unit tests: DAG build + validation, parallel fan-out + join auto-advance, the per-branch review handoff, per-project config round-trip, and workflow grouping / lead / progress.
  • Exercised end-to-end with live agents (Plan → Code → two parallel reviewers → Collect) on a real git repo with a remote — which is how a reviewer-clobber bug in the review handoff was found and fixed (reviewers now push to their own branches). Collect read both reviews, applied one and deferred the other with reasoning, and pushed.
  • All package tests + golangci-lint v2.8.0 green.

Follow-ups

  • Opening a collapsed workflow card shows the lead step's detail; a dedicated workflow overview (all steps / the parallel branches at a glance) is a natural next step.
  • A TUI surface to edit pipeline config (today it's the CLI; the TUI already honors the saved config).

🤖 Generated with Claude Code

bborn and others added 2 commits July 6, 2026 10:16
Borrowed from herdr's herdr-plan-code-review: one goal is split into an
ordered chain of phase tasks, each routed to its own executor and model,
that hand work forward on one shared git branch and advance automatically.

The default `plan-code-review` pipeline runs three phases on one branch:
Plan (Claude/Opus, writes PLAN.md) → Code (Claude/Sonnet, implements) →
Review (Codex, reviews with fresh eyes and opens the PR).

Assembled entirely from existing primitives — no bespoke execution engine:
- per-task Executor + Model overrides give each phase a different agent;
- task dependencies with auto_queue chain the phases (completing one
  auto-queues the next via db.ProcessCompletedBlocker);
- a pinned BranchName on phase 1 + SourceBranch on the rest keeps every
  phase on one branch so each sees the previous phase's commits.

The normal daemon executor runs each phase task in turn.

New:
- internal/pipeline: Phase/Definition model, built-in plan-code-review
  definition, and Create() that builds the dependency chain.
- `ty pipeline <goal>` CLI command (+ --list, --definition, --no-execute,
  --permission-mode, --json).
- taskyou_create_pipeline MCP tool.
- setupWorktree now honors a caller-pinned BranchName so a pipeline can
  route several phases onto one shared branch.

Tests: pipeline build/chain/auto-queue/validation, branch-name honoring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the pipeline entry point to the TUI (TaskYou is TUI-first) and make the
default pipeline degrade gracefully when an executor isn't installed.

TUI:
- New "Pipeline" selector in the new-task form (advanced fields), mirroring
  the existing Executor/Permission selectors: "single task" (default) or a
  pipeline definition. Selecting one turns submit into pipeline.Create using
  the form's title/body as the goal and its project/permission for every phase.
- The queue confirm reads "Start the pipeline now? / stage all phases" for a
  pipeline, and a toast reports the created chain + branch.
- Hidden when editing an existing task.

Executor fallback:
- pipeline.Create takes an optional AvailableExecutors set; any phase whose
  executor isn't installed falls back to Claude (recorded in Result.Fallbacks
  and surfaced in the CLI/TUI). So a missing Codex no longer strands the Review
  phase — the pipeline still runs end-to-end. CLI and TUI pass the host's
  available executors; an empty set disables the check.

Tests: form pipeline field (default, cycle, visibility, render); pipeline
fallback + no-fallback. QA'd via the ty-qa VHS harness (board, form selector,
selected state, confirm, end-to-end TUI creation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bborn

bborn commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

Follow-up round 2: configurable phases, no Codex default, TUI + QA

Addressing the review feedback (d8243544, and TUI/QA in 96163f64):

No more Codex default / fallback

The default pipeline is now all ClaudePlan=opus, Code=sonnet, Review=opus — so it works on any host and never silently swaps executors. The old "default to Codex, fall back to Claude" behavior and the AvailableExecutors fallback machinery are gone.

Configurable per project, persisted (set once → always your choices)

Each phase's executor+model is configurable per project and saved (settings key pipeline_config:<project>:<definition>). Set it once and every pipeline in that project defaults to your choices — the CLI, TUI, and MCP paths all read the saved config via pipeline.EffectivePhases.

New ty pipeline config:

$ ty pipeline config -p myapp
plan-code-review pipeline for myapp (defaults)
  Plan     claude/opus
  Code     claude/sonnet
  Review   claude/opus

$ ty pipeline config -p myapp --set "Review=codex" --set "Code=claude/haiku"
plan-code-review pipeline for myapp (configured)
  Plan     claude/opus
  Code     claude/haiku
  Review   codex/default

# subsequent `ty pipeline "<goal>" -p myapp` and TUI pipelines use exactly this
$ ty pipeline config -p myapp --reset   # back to defaults

Partial edits merge onto current config; unknown phase names / executors error clearly.

TUI affordance (from the prior round)

New-task form gained a Pipeline selector (advanced fields); selecting a definition turns submit into a pipeline build, with a pipeline-aware confirm and a board toast. QA'd via the ty-qa VHS harness on an isolated instance:

Form selector form
Selected + hint selected
Confirm confirm
Created end-to-end (toast + chain on board) created

(The board card labels show [Plan]/[Code]/[Review]; executor/model live on each phase task and are what pipeline config controls.)

All package tests + golangci-lint v2.8.0 green.

Follow-up: a TUI screen to edit pipeline config (today it's the CLI; the TUI already honors the saved config).

…default

Address review feedback: the default pipeline no longer hardwires Codex with a
Claude fallback. Instead:

- Defaults are all-Claude (Plan=opus, Code=sonnet, Review=opus) — works on any
  host, never silently swaps executors.
- Each phase's executor+model is configurable per project and persisted, so you
  set it once ("Review runs on codex for this repo") and every pipeline there
  defaults to your choices. Stored in settings under pipeline_config:<proj>:<def>.
- New `ty pipeline config` command: view / --set "Phase=executor[/model]" /
  --reset. pipeline.Create applies the saved config via EffectivePhases; the
  TUI and MCP paths pick it up automatically.
- Removed the AvailableExecutors fallback machinery (no longer needed now that
  the default is always-available Claude and executor choice is explicit config).

Tests: config round-trip (save/merge/clear), Create honors saved config, chain
defaults updated to all-Claude. Verified the config CLI end-to-end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bborn bborn force-pushed the task/4640-herdr-borrow-plancodereview-multi-agent branch from d824354 to ed350ef Compare July 7, 2026 12:49
… card)

Reworks the linear pipeline into a proper workflow DAG, addressing review
feedback that a straight chain couldn't express the parallel dual-review that
is the herdr flow's whole point, and that N linked tasks cluttered the board.

Model (internal/pipeline):
- Steps form a DAG: each Step has Deps; sequential where steps depend on each
  other, parallel where they don't. Default plan-code-review is now
  Plan → Code → [Review A ∥ Review B] → Collect, built on the existing task
  dependency graph (fan-out + join via auto_queue; validated for single-root,
  acyclic, unique names). Collect opens the PR — the one human gate.
- Parallel reviewers push to their OWN branch (single push, no rebase), and
  Collect reads each via an injected {{reviews}} list. This replaces a
  shared-branch rebase-push that real-agent QA proved a weak model botches by
  clobbering the other reviewer's file.
- Per-project step executor/model config carried over (StepConfig).

Board (internal/ui):
- A workflow's step tasks collapse into ONE lead card showing the goal and
  current step + progress ("⇄ goal" / "Review ∥ · 3/5") instead of N cards.
  Done via a pre-pass (pipeline.GroupWorkflows) feeding the board; the Kanban
  render cache signature includes the workflow badge.
- Form selector renamed Workflow; the confirm drops the confusing "stage all
  phases" wording for plain start-now / save-for-later.

CLI/MCP updated for steps + DAG; `ty pipeline config` edits per-step models.

Tests: DAG build/validation, parallel fan-out + join auto-advance, per-branch
review handoff, config round-trip, workflow grouping/lead/progress.

QA: ran the real flow end-to-end with live Claude agents (plan → code → two
parallel reviewers → collect) on a git repo with a remote — which is how the
reviewer-clobber bug was found and fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bborn

bborn commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

Round 3: rebuilt as a workflow DAG — parallel review, one board card, real-agent QA

Went back to how herdr actually works (Plan → Code → two parallel reviewers → collect) and reworked the linear pipeline into a real workflow DAG. Commit f01c8acf.

What changed

  • It's a DAG, not a chain. Each step has Deps; the default plan-code-review is Plan → Code → [Review A ∥ Review B] → Collect. Sequential where steps depend on each other, parallel where they don't — the two independent reviewers (different models, independent context) are the whole point, catching different issues and avoiding self-review bias. Built on the existing task dependency graph (fan-out + join via auto_queue).
  • One board card, not N. A workflow's step tasks collapse into a single lead card — ⇄ <goal> with the current step + progress (Review ∥ · 3/5) — instead of cluttering the board with 5 cards. ("stage all phases" wording is gone too.)
  • Configurable / extensible. Steps are data — add a QA step, point a reviewer at codex, etc. — per project via ty pipeline config.
  • Autonomous, human gate only at the end. Steps advance on their own; the workflow drops to blocked only when Collect opens the PR (or a step genuinely needs input).

Board: one collapsed card (was 5)

board

QA — actually ran it end-to-end with live agents

Ran the real rendered step prompts through live Claude agents on a git repo with a remote — Plan (sonnet) → Code (sonnet) → Review A (haiku) ∥ Review B (sonnet), concurrently → Collect (sonnet), goal: "add fizzbuzz.py + a pytest test".

This caught a real bug. With the reviewers pushing to a shared branch, the weaker model botched the rebase-push and clobbered the other reviewer's file. Fixed: each reviewer now pushes to its own branch (single push, no rebase, no race), and Collect reads each explicitly. Re-ran — both reviews survive:

code branch:   plan → code → collect
  4d5eca5 plan: Add fizzbuzz.py ...
  daa6f69 Add fizzbuzz.py and pytest suite per PLAN.md
  25e81e9 Add .gitignore for Python artifacts        ← Collect applied a review nit

parallel review branches (independent, no clobber):
  …-review-a  (haiku)  → review-review-a.md
  …-review-b  (sonnet) → review-review-b.md   ← both intact

Collect read both reviews, applied Review B's .gitignore nit, deferred its requirements.txt suggestion with reasoning (matched scope to PLAN.md), noted no reviewer disagreement, and pushed. (The final gh pr create no-op'd only because the QA sandbox uses a local bare remote, not GitHub — PR creation itself is standard.)

All package tests + golangci-lint v2.8.0 green.

Follow-ups

  • Codex was installed but its binary is broken in my env (bad mise install), so both QA reviewers ran on Claude (different models) — cross-executor is config-driven and unit-tested; a working codex slots in via pipeline config --set "Review B=codex".
  • Board: opening a collapsed card shows the lead step's detail; a dedicated workflow overview (see all steps / the parallel branches) is a nice next step.

Configuring a workflow's per-step executor/model was CLI-only; add a TUI
editor so it's doable in the board (TaskYou is TUI-first).

- Press `W` on the board to open a compact per-project workflow config modal:
  one row per step (Plan, Code, Review A, Review B, Collect) with a horizontal
  executor/model selector, showing each step's dependencies (← Code, ← Review
  A+Review B) so the DAG is visible. ↑/↓ pick a step, ←/→ change, enter saves.
- Saves to the same persisted per-project config as `ty pipeline config`, so a
  change here is what every workflow in the project defaults to (verified: the
  CLI reads back what the TUI writes, and vice versa).
- Prefills from the project's current effective config; project is the selected
  task's project, else the last-used project.

WorkflowConfigModel is a small self-contained bubbletea model (compact one-line
selectors, not a tall huh form). Tests cover the executor/model option list and
the value round-trip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bborn

bborn commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

Full TUI flow for workflows — including in-TUI config (3da6f4a2)

Configuring a workflow's per-step models was CLI-only; added a proper in-TUI editor so the whole flow is doable on the board. QA'd by driving the real TUI (isolated instance, VHS).

1. Create a workflow — the new-task form's Workflow selector:
form

2. Configure it — press W on the board. One row per step with an executor/model selector; the DAG deps (← Code, ← Review A+Review B) render inline. ↑/↓ step · ←/→ change · enter save:
config

3. Cross-executor review — set Review B to codex (←/→):
edit

4. Save — persists to the per-project config and toasts on the board:
saved

It edits the same persisted config as ty pipeline config — verified round-trip: after saving Review B → codex in the TUI, ty pipeline config -p qa reads back Review B codex/default, and vice versa. So the CLI and TUI are two views of one setting, and every workflow in the project defaults to it.

All package tests + golangci-lint v2.8.0 green.

bborn and others added 2 commits July 7, 2026 14:02
… authoring

Answers "how do I define/configure the steps": custom workflows are now
first-class. You can define a whole new flow (add a QA step, a spike→build→test
shape, extra reviewers) without touching Go.

- **YAML files.** One workflow per file in ~/.config/task/workflows/*.yaml
  (override $TY_WORKFLOWS_DIR) and per-project .taskyou/workflows/. Discovered at
  runtime and merged with the built-in; a custom name shadows a built-in.
  Schema: name/description + steps[{name, executor, model, deps, prompt}].
- **Authoring is just prompts.** You write what each step does + its deps; the
  git handoff is DERIVED from the step's DAG position (compose.go): a step with a
  parallel sibling pushes to its own branch, a join reads its inputs' branches,
  the sink opens the PR. So authors never write commit/push/PR boilerplate — the
  same class of bug the earlier real-agent QA caught.
- **Free text → workflow.** `ty pipeline new "<describe it>"` runs the
  description through Claude to emit a validated YAML you can edit, with a
  self-repair retry when the model violates the single-root rule.
- `ty pipeline --list` marks built-in vs custom; custom workflows appear in the
  TUI new-task selector automatically.

Tests: YAML parse/validate/marshal round-trip, registry merge + shadowing,
DAG-derived handoff (parallel/join/sink/linear), Create honoring a custom
workflow. QA'd end-to-end: hand-written YAML + a live LLM-generated 9-step DAG
(plan → 3 parallel spikes → select → build → review∥test → finalize).

Follow-ups: multi-root workflows (true parallel entry points) still need shared-
branch bootstrapping; the TUI `W` model-config modal targets the default
definition (custom workflows configure models in their YAML / via CLI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bborn

bborn commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

User-defined workflows — text-file first, LLM optional (ace3f0d3)

Answering "how are steps defined / who sets them / how do I configure them": custom workflows are now first-class. You can define a whole new flow (add a QA step, a spike→build→test shape, more reviewers) without touching Go.

YAML files (the source of truth)

One workflow per file in ~/.config/task/workflows/*.yaml (override $TY_WORKFLOWS_DIR), plus per-project .taskyou/workflows/. Discovered at runtime, merged with the built-in. Hand-editable:

name: build-and-qa
steps:
  - {name: Plan, model: opus, prompt: "Plan {{goal}}."}
  - {name: Build, deps: [Plan], prompt: "Build it."}
  - {name: Security, deps: [Build], prompt: "Security review; write security.md."}
  - {name: QA, executor: codex, deps: [Build], prompt: "QA it; write qa.md."}
  - {name: Finalize, deps: [Security, QA], prompt: "Address findings, finalize."}

Authoring is just prompts — the git plumbing is derived from the DAG

You write what each step does + its deps; the handoff is composed from the step's DAG position. QA'd — the generated body for a parallel step vs the join/sink:

  • Security (parallel with QA) → "push your output to YOUR OWN branch {{branch}}-security … Do NOT open a pull request."
  • Finalize (joins Security+QA, sink) → "read origin/{{branch}}-security and origin/{{branch}}-qa … open a pull request with gh pr create."

So authors can't get the parallel-branch handling wrong — the exact bug the earlier real-agent QA caught.

Free text → workflow

ty pipeline new "<describe it>" runs it through Claude → a validated, editable YAML (with a self-repair retry). Live QA — one sentence produced a 9-step DAG:

$ ty pipeline new "spike three approaches in parallel, pick the best, build it,
                   then review and test in parallel, then finalize"
Created workflow 'spike-build-verify'
  Plan               claude/opus
  Spike Approach A   claude/sonnet  ← Plan
  Spike Approach B   claude/sonnet  ← Plan
  Spike Approach C   claude/sonnet  ← Plan
  Evaluate and Select claude/opus   ← Spike A+Spike B+Spike C
  Build              claude/opus    ← Evaluate and Select
  Code Review        claude/opus    ← Build
  Test               claude/sonnet  ← Build
  Finalize           claude/opus    ← Code Review+Test

In the TUI

Custom workflows appear in the new-task Workflow selector automatically:
selector

Tests cover parse/validate/marshal round-trip, registry merge + shadowing, DAG-derived handoff, and Create honoring a custom workflow. All green + golangci-lint v2.8.0.

Honest limitations (follow-ups)

  • Multi-root workflows (true parallel entry points, e.g. 3 spikes with no shared root) aren't supported yet — the root pins the shared branch, so the generator adds a single root the parallel steps depend on. Real multi-root needs shared-branch bootstrapping.
  • The TUI W model-config modal still targets the default workflow; custom workflows set their models in YAML (or ty pipeline config -d <name>).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant