Loop 3 is the third and final loop in codoop-flow's Triple-Loop Model. It is a fully automated, end-to-end ticket execution pipeline that reliably implements, verifies, reviews, and ships software tickets.
What it solves: Getting an AI coding agent to implement a ticket without hallucinating infrastructure decisions or shipping untested code. Loop 3 splits labor deliberately: intelligence (agent) writes code and reasons about design; determinism (guardrail CLI) manages git, runs tests, and archives artifacts. The only thing the agent can do wrong without breaking the pipeline is write bad code — and that's caught by review personas.
Position in pipeline: Reads Loop 2 outputs from docs/tickets/pending/<ticket_id>/ → executes in an isolated git worktree on branch dev/<ticket_id> → produces code commit + updated living docs + archives to docs/tickets/done/ (or failed/).
In any AI coding tool, say:
Use the codoop-execute skill to run a ticket against /path/to/codoop_flow.toml
Or schedule it to run continuously:
/loop 5m run the codoop-execute skill against /path/to/codoop_flow.toml
The skill picks the oldest pending ticket, builds it in an isolated worktree, runs tests, gathers review feedback, self-heals on failure, and ships the result when ready.
python3 <SKILL>/scripts/codoop_tools.py --config <toml> pick
The CLI:
- Checks
in_progress/first. If a ticket is already there, rebuilds its worktree (if deleted) and reports it for resume (don't start a new one) - If
pending/is empty, reports "no pending tickets" - Otherwise, picks the oldest pending ticket, creates an isolated
git worktreeon branchdev/<ticket_id>, and returns full ticket metadata
Output JSON:
{
"picked": true,
"ticket_id": "ticket_001",
"title": "Add user search feature",
"ticket_dir": "/abs/path/to/in_progress/ticket_001",
"worktree": "/abs/path/to/worktrees/ticket_001",
"branch": "dev/ticket_001",
"modules": ["backend", "web"],
"ui_capture": false,
"screenshot_dir": null
}The agent's job: Parse the JSON. If already in_progress, resume. If no pending tickets, stop. If picked is true, proceed to Build.
The agent reads the ticket package from ticket_dir:
module_prd.md— 100% business descriptionspec.md— API contract, data schema, UI interactionspreview.html— when present, the human-reviewed local visual flow and key interactions; use it as design context, not as production codeplan.md— step-by-step execution plantodo.md— atomic checkbox tasks
The agent also reads project architectural boundaries from docs/tech/project-structure.md and docs/tech/tech-standards.md.
Implementation discipline: Use the /skill incremental-implementation workflow: implement one thin vertical slice, test, verify, then move to the next. Work through todo.md items in order, checking them off as you go (- [x]).
Edit-scope rule: A Scope heading in spec.md or bug_report.md is guidance: prefer it, but make the smallest necessary root-cause change outside it when a required gate demands it, and record the reason. Do not infer a hard file allowlist from Scope text.
Before editing, the agent records a baseline by running the declared validation
as independent ordered steps: lint, build, focused test, and UI capture when
required. A chained shell command such as lint && build && test is not a
valid verification plan. A failed lint step must not skip independent build,
focused-test, capture, or review evidence.
Each step records its command, stdout/stderr, exit code, and any screenshots.
For every diagnostic, the agent stores a normalized fingerprint: module,
step/command, file path, line (when available), rule/error code, and normalized
error text. After the change it runs the same steps and compares those
fingerprints with git diff --name-only:
- New/changed diagnostics, or diagnostics in changed files, are ticket failures.
- An exactly matching diagnostic outside changed files is a
baseline_blocker. It remains fully reported but cannot spend a healing attempt, require a fix, or by itself fail the ticket.
There is no broad lint ignore or permanent allowlist. A different file, line, rule/code, normalized message, or a new diagnostic fails again.
python3 <SKILL>/scripts/codoop_tools.py --config <toml> verify <ticket_id>
UI screenshot gate (only if ui_capture: true) checks
ticket_dir/public/qa-screenshots/ for at least one file with a recognized
image extension (.png, .jpg, .jpeg, .webp, .gif). It fails if none exist.
Output JSON:
{
"ticket_id": "ticket_001",
"ok": true,
"reasons": []
}Or on failure:
{
"ticket_id": "ticket_001",
"ok": false,
"reasons": ["ui_capture ticket produced no screenshots"]
}Exit code: 0 if ok: true, 1 if ok: false.
On verify failure, the agent applies /skill debugging-and-error-recovery:
- Read the reported ticket failure, never a baseline blocker
- Fix only the root cause with a minimal change; follow Scope guidance and report a necessary exception
- Re-run the independent validation steps and
verify
Budget: Up to max_healing_attempts retries (default 3, per ticket in metadata.json). Only ticket failures and review rejections count; baseline blockers do not. If still failing after exhausting retries → Fail Path.
When all functional gates, UI evidence, and review pass with only baseline
blockers remaining, completion is allowed and the finish handoff includes
baseline_warnings with the exact fingerprints and outputs. If a target
configuration explicitly requires repository-wide green, record
blocked_by_baseline instead of failed; keep the worktree and evidence for
recovery.
The agent runs review personas from <SKILL>/_shared/agents/ against the git diff in the worktree.
Approval is unanimous — any Critical or Important finding blocks release.
Static personas (always run — 3 people):
-
code-reviewer — evaluates correctness, readability, architecture, security, performance. Output: APPROVE or REQUEST CHANGES (findings categorized as Critical / Important / Suggestion). Critical or Important = REJECT.
-
security-auditor — identifies exploitable vulnerabilities. Maps findings to OWASP Top 10 and OWASP LLM Top 10. Severity: Critical / High / Medium / Low / Info. Critical or High = REJECT.
-
test-engineer — analyzes test strategy, coverage, pyramid level, test quality. Output: Gaps identified, recommended tests by priority.
Dynamic UI/UX personas (only if ui_capture: true — 2 additional people):
-
evidence-collector — screenshot-obsessed QA. Gets
screenshot_dirto inspect rendered screens. Uses Playwright output, before/after interaction screenshots. Requires visual proof for every claim. Default assumption: 3–5 issues minimum on first implementation. Automatic fail triggers: zero-issues claims, perfect scores. -
reality-checker — integration and deployment readiness. Gets
screenshot_dirto cross-validate QA findings. Defaults to "NEEDS WORK" unless overwhelming evidence supports READY. Checks end-to-end user journeys, spec compliance, performance (>3s load = fail).
If any reviewer rejects: Findings are fed back to the agent for a fix → re-verify → re-review (still within healing budget).
After technical approval, a runnable ticket with user-visible behavior may load
codoop-ux-walkthrough. It passes the ticket's PRD role, goal, scope, and
acceptance criteria as task context to an independently chosen persona, then
saves experience_report.md in the ticket directory. The report is a
qualitative product insight for human review: it never blocks release, triggers
self-heal, changes code, or creates a new ticket. Pure infrastructure,
refactoring, and internal-only tickets skip this step.
Before finishing, the agent syncs living documentation inside the worktree:
- Update
docs/prd/with changed business logic - Update
docs/tech/project-structure.mdfor new/moved files - Append concise entry to
docs/tech/changelog.md
Style: second person, present tense, active voice, one concept per section, no broken code examples.
python3 <SKILL>/scripts/codoop_tools.py --config <toml> finish <ticket_id> --message "<conventional commit>"
The CLI:
- Stages all changes excluding generated noise (
__pycache__,*.pyc,node_modules, etc.) - Commits with the provided message (or template fallback)
- Moves
in_progress/<ticket_id>todone/<ticket_id> - Removes the worktree with
git worktree remove --force
Output JSON:
{
"ticket_id": "ticket_001",
"state": "done",
"committed": true,
"commit": "abc1234deadbeef..."
}Pushing is your call. The agent tells you "branch dev/<ticket_id> is ready to push; decide whether to push to origin."
python3 <SKILL>/scripts/codoop_tools.py --config <toml> fail <ticket_id> --report "<summary>"
The CLI:
- Moves
in_progress/<ticket_id>tofailed/<ticket_id> - Writes the report to
failed/<ticket_id>/healing_report.md - Retains the worktree, including uncommitted changes, for recovery
The ticket and report travel to failed/ for manual human intervention.
All commands require --config <toml>. All output is JSON to stdout.
Output: Lists all ticket names in each pipeline stage.
{
"pending": ["ticket_a", "ticket_b"],
"in_progress": ["ticket_c"],
"done": ["ticket_d", "ticket_e"],
"failed": []
}Exit 0 always.
Behavior: Picks oldest pending ticket or resumes in_progress. Creates worktree on dev/<ticket_id>.
Output: Full ticket metadata (see Step 1).
Exit 0 always.
Input: Ticket ID (positional arg).
Behavior: Runs the deterministic UI screenshot hard gate (if applicable). The agent runs lint/build/focused-test commands as the independent validation steps described above; their results are classified against the baseline.
Output: Success or failure with specific reasons.
Exit 0 if OK, exit 1 if any gate fails.
Input: Ticket ID, optional --message.
Behavior: Stages (excluding noise), commits, archives to done/, removes worktree. When --message is omitted, the fallback message is <prefix>(<module>): <title> [<id>], where <prefix> is fix for ticket_type: "fix" and feat otherwise.
Output: Commit SHA and final state.
Exit 0 on success.
Input: Ticket ID, optional --report.
Behavior: Archives to failed/, writes healing_report.md, releases the
lease, and retains the worktree (including uncommitted changes) for human
recovery. The report records the worktree path and branch.
Output: Path to report.
Exit 0 on success.
Input: Failed ticket ID, after human approval.
Behavior: Moves failed/<ticket_id> to in_progress/, mints a new lease,
and reuses the retained worktree without reset. The former healing_report.md
is preserved as healing_report.previous*.md. If that worktree is gone, the
CLI recreates it from the ticket branch and reports worktree_recreated:true;
uncommitted recovery changes cannot be restored.
Use the returned lease token and continue the execute loop at Build with a new
self-healing budget. Do not route a failed ticket through pending/.
Each ticket executes in its own isolated git worktree — a full independent checkout on a separate branch, at a separate path.
Branch naming: dev/<ticket_id> (e.g., dev/ticket_001).
Lifecycle:
- On first
pick:git worktree add -b dev/<ticket_id> <path>(creates branch and attaches worktree) - On resume:
git reset --hard HEADto scrub any dirty state - On
finish:git worktree remove --force <path>(best-effort cleanup;git worktree prunereconciles refs on next run). Onfail, retain the worktree for recovery.
Worktree root location: <worktree_root>/<ticket_id> (default: ~/codoop_tickets/worktrees/ticket_001). Set via codoop_flow.toml.
Trigger: a new/changed diagnostic, a diagnostic in a changed file, a failed functional/UI gate, or a review persona rejection. Exact unchanged diagnostics outside changed files are baseline blockers, not triggers.
Budget: max_healing_attempts retries (default 3, set per ticket in metadata.json).
Process per attempt:
- Read the ticket failure to identify the root cause
- Fix the root cause (not symptoms), minimal change; follow Scope guidance and report a necessary exception
- Re-run all independent validation steps and
verify - If still failing, retry (if budget remains)
Budget exhausted: Call fail with denoised summary. Ticket moves to failed/.
1. code-reviewer (_shared/agents/code-reviewer.md)
- Checks: Correctness (logic, specs), Readability (naming, structure), Architecture (patterns, abstraction), Security (no obvious vulns), Performance (efficient algorithms)
- Verdict: APPROVE or REQUEST CHANGES
- Blocking: Critical (must fix) and Important (should fix)
- Non-blocking: Suggestion
2. security-auditor (_shared/agents/security-auditor.md)
- Checks: Input Handling, Auth/Authz, Data Protection, Infrastructure, Third-Party Integrations, AI/LLM Features
- Maps to: OWASP Top 10 and OWASP LLM Top 10
- Severity: Critical / High / Medium / Low / Info
- Blocking: Critical and High
3. test-engineer (_shared/agents/test-engineer.md)
- Checks: Test pyramid (unit/integration/E2E), behavior vs. implementation, test quality, coverage gaps
- Output: Coverage analysis and recommended tests by priority
4. evidence-collector (_shared/agents/testing-evidence-collector.md)
- Gets
screenshot_dirto inspect rendered screens - Runs Playwright capture, reviews
test-results.json - Requires visual proof for every claim; no fantasy approvals
- Default assumption: 3–5+ issues on first implementation
5. reality-checker (_shared/agents/testing-reality-checker.md)
- Gets
screenshot_dirto cross-validate QA findings - End-to-end journey analysis, cross-device consistency, performance checks (>3s load = fail)
- Default status: NEEDS WORK; only READY with overwhelming evidence
Unanimous approval required: ALL active personas must APPROVE / PASS. Any Critical/Important from any persona = REJECT.
Triggered by: "ui_capture": true in metadata.json.
What it requires:
- Delivery must place screenshots in
ticket_dir/public/qa-screenshots/ - At least one file with extension
.png,.jpg,.jpeg,.webp, or.gifmust exist after tests run - Absence of screenshots = verify fails
Extra personas: evidence-collector and reality-checker (the dynamic UI/UX group).
Screenshot archival: Screenshots travel with the ticket to done/ or failed/ for human inspection.
| Field | Type | Required | Default | Meaning |
|---|---|---|---|---|
target_repo |
string (path) | Yes | — | Absolute path to target git repo. Expanded via expanduser().resolve(). |
worktree_root |
string (path) | No | ~/codoop_tickets/worktrees |
Directory where per-ticket worktrees are created. Expanded via expanduser(). |
The ticket pipeline directories (pending/, in_progress/, done/, failed/) are derived from <target_repo>/docs/tickets/. They are created by codoop setup but never configured in the TOML.
| Field | Type | Required | Default | Meaning |
|---|---|---|---|---|
ticket_id |
string | Yes | — | Unique identifier; matches directory name and branch name dev/<ticket_id> |
title |
string | Yes | — | Human-readable title; used in fallback commit message |
ticket_type |
string | No | "feature" |
"feature" or "fix"; selects the fallback commit prefix (feat/fix) |
modules |
list[string] | Yes | — | Modules this ticket touches: backend, web, mobile, desktop |
coding_engine |
string or null | No | null | Informational; which AI tool handles this ticket |
max_healing_attempts |
int | No | 3 | Max self-heal retries; agent counts (CLI does not enforce) |
ui_capture |
bool | No | false | If true: activates screenshot gate and adds UI personas |
Loop 3 picks a ticket from docs/tickets/pending/<ticket_id>/ and consumes:
metadata.json— drives all scheduler decisionsmodule_prd.md+spec.md— progressively disclosed to the agent at startupplan.md+todo.md— agent reads and checks off items as completedpublic/qa-screenshots/— (for UI tickets) created at runtime; read by review personas
On success (finish):
- Committed on branch
dev/<ticket_id>: all changes + living doc updates + conventional commit message - Archived to
done/<ticket_id>/: full ticket directory +public/qa-screenshots/(if UI ticket) - Commit SHA returned; branch ready for push (human decides whether to push)
- If baseline blockers remain, the handoff includes
baseline_warningsand the archived verification report retains their exact fingerprints and output
On strict-baseline block:
- State is
blocked_by_baseline, neverfailed; the worktree and verification evidence are retained so the ticket can be resumed after the repository debt is cleared
On failure (fail):
- Nothing committed; worktree discarded
- Archived to
failed/<ticket_id>/: full ticket directory + newly writtenhealing_report.mdwith denoised failure summary
Committed to dev/<ticket_id> branch:
- All staged changes in the worktree (excluding generated noise)
- Living doc updates (
docs/prd/,docs/tech/) - Commit message: Conventional Commit format (type: scope: subject, optional body)
Archived to done/<ticket_id>/:
- The entire ticket directory:
metadata.json,module_prd.md,spec.md,plan.md,todo.md(all checkboxes ticked), and for UI tickets,public/qa-screenshots/with all visual evidence experience_report.mdwhen an optional user experience walkthrough ran; it is advisory product input for human review, not a release verdict
Not committed: No code commit runs.
Archived to failed/<ticket_id>/:
- The ticket directory as-is
- Newly written
healing_report.mdwith denoised failure summary from the agent
The project includes tests/test_skeleton.py which exercises only the deterministic guardrails (no AI, no mocking). Each test gets a fresh temporary git repo and worktrees directory.
Key tests:
test_pick_moves_and_creates_worktree— pick moves ticket from pending to in_progress and creates worktreetest_verify_has_no_test_command_gate— legacytest_commandentries are ignored by verifytest_ui_capture_gate— UI ticket without screenshots fails; with screenshots passestest_finish_commits_and_archives— finish commits ondev/<id>, archives to done/, removes worktreetest_ticket_lifecycle— Human-Centric path: init → fill → validate → promote
Run with: python tests/test_skeleton.py (no pytest needed).
- Determinism Over Cleverness — The CLI is small, fully deterministic, never guesses. The agent owns all intelligence.
- Independent Validation + Screenshot Gate — the agent records lint, build, focused-test, and UI evidence independently; the CLI enforces the required UI screenshot gate.
- Unanimous Approval — Review is only done when all personas agree. Any Critical/Important blocks.
- Self-Heal Within Budget — Failures trigger retry (if budget permits), not immediate failure. Budget exhaustion moves to failed/ for human intervention.
- Isolated Worktrees — Each ticket gets its own branch and checkout path; main repo is never touched.
- Living Docs Kept in Sync — After code ships, living docs (
docs/prd/,docs/tech/) are updated so they remain authoritative.