Files
correx/examples/workflows/role_pipeline.toml
T
kami 12d5b9d7dc feat(tasks): analyst opens the task; freestyle threads it into implementation
Two coupled gaps from tracing a real run:

1. Freestyle implements in phase 2 via stages compiled from the architect's
   execution_plan (ExecutionPlanCompiler sets allowedTools = stage.tools), so the
   static allow-lists never reach it and architect_freestyle.md banned every tool
   but the file four. Teach the architect to thread an analysis-referenced task
   through the plan: implementing stages get task_context/task_update and claim +
   submit_for_review; the final/review stage completes it. No task referenced → no
   task tools, and the plan never creates one.

2. Give the analyst task_create so the work is framed as a tracked item up front
   (role_pipeline + freestyle). "Read-only" for the analyst means it writes no
   files; a task is an event-log entry, not a file write — task_create is T2, so
   opening one is approval-gated. The analyst names the new id in the analysis so
   the implementer claims it and the reviewer completes it; the implementer now
   creates only as a fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 15:13:52 +00:00

167 lines
6.2 KiB
TOML

# Role pipeline: analyst → architect → planner → implementer → static_check ⇄ reviewer
#
# Each stage produces a typed artifact that the next stage `needs`, so work flows forward
# without a human relaying notes. The decision journal (pinned into every stage's context)
# carries steering/approvals/verdicts across the whole run, so the reviewer sees the same
# ground truth as the planner.
#
# The implementer⇄reviewer loop is gated by review_report.verdict:
# approved → done
# changes_requested → back to implementer (capped by implementer.max_retries, then escalates)
#
# Requires these artifact kinds in ~/.config/correx/config.toml (schemas under docs/schemas/):
# [[artifacts]]
# id = "analysis"; schema_path = "schemas/analysis.json"; llm_emitted = true
# [[artifacts]]
# id = "design"; schema_path = "schemas/design.json"; llm_emitted = true
# [[artifacts]]
# id = "impl_plan"; schema_path = "schemas/impl_plan.json"; llm_emitted = true
# [[artifacts]]
# id = "review_report"; schema_path = "schemas/review_report.json"; llm_emitted = true
#
# Prompt files (prompts/*.md, relative to this workflow) must exist for a real run.
id = "role_pipeline"
start = "analyst"
# 1. Understand the request and the relevant code — writes no files (ground_references checks
# every workspace path the analysis names; a hallucinated path fails the stage and retries).
# It also owns task framing: task_search/task_context (read-only) find existing work, and
# task_create (T2, approval-gated — a task is an event-log entry, not a file write) opens a
# task for this work up front, named in the analysis so the implementer claims it and the
# reviewer completes it.
[[stages]]
id = "analyst"
prompt = "prompts/analyst.md"
produces = [{ name = "analysis", kind = "analysis" }]
allowed_tools = ["file_read", "ShellTool", "task_search", "task_context", "task_create"]
ground_references = true
token_budget = 16384
max_retries = 2
# 2. Decide the approach and component boundaries.
[[stages]]
id = "architect"
prompt = "prompts/architect.md"
needs = ["analysis"]
produces = [{ name = "design", kind = "design" }]
token_budget = 16384
max_retries = 2
# 3. Break the design into ordered, verifiable steps.
[[stages]]
id = "planner"
prompt = "prompts/planner.md"
needs = ["design"]
produces = [{ name = "impl_plan", kind = "impl_plan" }]
token_budget = 16384
max_retries = 2
# 4. Implement the plan. Writes files (jailed to the workspace). The loop target —
# max_retries here caps how many review→implement refinement rounds are allowed.
# Optional: a `writes` manifest (workspace-relative globs) hard-bounds where this
# stage may write — a FILE_WRITE outside it is blocked as scope creep. Left open
# here because the targets are task-specific; a task-scoped workflow would set e.g.
# writes = ["core/sessions/**", "testing/sessions/**"]
# The task tools let it own the work item across the loop: claim before starting,
# submit_for_review once verification passes, and add notes (create one first if the
# work warrants tracking and none exists — see the task policy in .correx/project.toml).
[[stages]]
id = "implementer"
prompt = "prompts/implementer.md"
needs = ["impl_plan"]
produces = [{ name = "patch", kind = "file_written" }]
allowed_tools = [
"file_read", "file_write", "file_edit", "ShellTool",
"task_create", "task_update", "task_context", "task_search",
]
token_budget = 32768
max_retries = 3
# 4b. Deterministic static analysis (NO LLM). Runs a configured tool (compiler/ktlint/detekt)
# over the patch and records its findings as a StaticFindingsRecordedEvent, which the
# reviewer-context filter then strips from the reviewer's context so the LLM reviewer spends
# its attention on semantic review rather than re-finding what the build already reports.
# No-op until `static_argv` is set (and a CommandRunner is wired) — safe to carry unconfigured.
# Activate e.g. with: static_argv = "./gradlew detekt --console=plain"
[[stages]]
id = "static_check"
stage_type = "static_check"
static_tool = "detekt"
token_budget = 0
max_retries = 0
# 5. Review the patch against the plan AND the analyst's acceptance criteria. The reviewer
# needs `analysis` so it judges the diff against concrete, pre-stated criteria (§5 narrow
# question) rather than whole files against taste.
# 5b. task_context lets the reviewer judge the patch against the task's own acceptance
# criteria; task_update lets it complete the task on an approved verdict (and only then).
[[stages]]
id = "reviewer"
prompt = "prompts/reviewer.md"
needs = ["patch", "impl_plan", "analysis"]
produces = [{ name = "review_report", kind = "review_report" }]
allowed_tools = ["task_context", "task_update"]
token_budget = 32768
max_retries = 2
# --- forward edges ---
[[transitions]]
id = "analyst-to-architect"
from = "analyst"
to = "architect"
condition_type = "artifact_validated"
condition_artifact_id = "analysis"
[[transitions]]
id = "architect-to-planner"
from = "architect"
to = "planner"
condition_type = "artifact_validated"
condition_artifact_id = "design"
[[transitions]]
id = "planner-to-implementer"
from = "planner"
to = "implementer"
condition_type = "artifact_validated"
condition_artifact_id = "impl_plan"
# implementer → static_check (once the patch is validated) → reviewer. The static_check stage
# produces no artifact, so its exit edge is unconditional (always_true).
[[transitions]]
id = "implementer-to-static_check"
from = "implementer"
to = "static_check"
condition_type = "artifact_validated"
condition_artifact_id = "patch"
[[transitions]]
id = "static_check-to-reviewer"
from = "static_check"
to = "reviewer"
condition_type = "always_true"
# --- verdict-gated loop exit / re-entry ---
[[transitions]]
id = "review-approved"
from = "reviewer"
to = "done"
condition_type = "artifact_field_equals"
condition_artifact_id = "review_report"
condition_field = "verdict"
condition_value = "approved"
condition_operator = "eq"
[[transitions]]
id = "review-changes-requested"
from = "reviewer"
to = "implementer"
condition_type = "artifact_field_equals"
condition_artifact_id = "review_report"
condition_field = "verdict"
condition_value = "approved"
condition_operator = "neq"