| name | dev-cycle |
| description | Full implementation cycle pipeline. Takes the highest-RICE action from the action queue and implements it through a structured test → implement → verify workflow. Primary skill for the implementation domain — invoked by evolve when implementation is selected. |
| ooda_phase | support |
| version | 1.2.0 |
| input | {"files":["agent/state/evolve/action_queue.json","agent/state/evolve/state.json","config.json","CLAUDE.md"],"config_keys":["safety.halt_file","safety.max_files_per_pr","safety.max_lines_per_pr","safety.protected_paths","test_command"]} |
| output | {"files":["agent/state/evolve/action_queue.json"]} |
| safety | {"halt_check":true,"read_only":false,"branch_prefix":"auto/dev-cycle/","cost_limit_usd":0.1} |
| domains | ["implementation"] |
| chain_triggers | [{"target":"scan-health","condition":"pr_created == true","note":"Recommend post-merge health check after implementation PR is merged"}] |
dev-cycle: Full Implementation Cycle Pipeline
The "builder" of the harness. Takes the highest-priority action from the action
queue, implements it in a dedicated branch, verifies it passes tests, and
creates a Draft PR for human review.
dev-cycle is the primary skill for the implementation domain. When evolve's
Orient step selects implementation as the winning domain, it calls dev-cycle.
All changes go through branch + PR — dev-cycle never commits directly to main.
- Creates Draft PRs by default — human review is mandatory for implementation
- Creates a ready (non-draft) PR ONLY when the change is auto-merge-eligible
(see "Auto-merge eligibility" below) AND the operator has opted in via
config.safety.enable_auto_merge. Default behavior is Draft / human merge.
- NEVER uses
git add -A — only explicit file staging to prevent secret leaks
- evolve (4-C) is the final merge authority — it re-checks every gate before any
auto-merge; dev-cycle only chooses draft vs ready.
Safety Rules
- HALT file — Mandatory first check. If present, print reason and stop.
- Level gate — Requires
progressive_complexity >= 3 or direct user invocation. Below Level 3, exit cleanly.
- PR size limits — Respect
config.safety.max_files_per_pr and config.safety.max_lines_per_pr. Exceeding either triggers a partial PR.
- Protected paths — Any change touching
config.safety.protected_paths forces Risk Tier 3 (already enforced — Draft only).
- Explicit staging —
git add {file} per file. git add -A is forbidden.
- Test retry cap — Maximum 3 fix attempts after a test failure. Beyond that, mark action as
"blocked" and exit.
Step 0: Safety
0-A: HALT Check
if file exists at config.safety.halt_file:
Print "[HALT] dev-cycle stopped. Reason: {file_content}"
Print "Remove to resume: rm {config.safety.halt_file}"
EXIT immediately.
0-B: Level Gate
Read config.json → progressive_complexity.current_level (authoritative source)
Also check config.json → implementation.enabled
if implementation.enabled == false AND not manually invoked by user:
Print "Implementation domain is disabled. Enable it: set implementation.enabled=true in config.json (done automatically by /ooda-config level 3)"
EXIT cleanly (not an error).
if progressive_complexity.current_level < 3 AND not manually invoked by user:
Print "Implementation requires Level 3. Current: {current_level}"
EXIT cleanly (not an error).
Manual invocation means the user explicitly called /dev-cycle or /evolve
in this session (as opposed to being triggered automatically by evolve on a
schedule). When in doubt, treat as manual invocation and allow execution.
Step 1: Select Action from Queue
LEAP mode (v1.7.0). If evolve invoked this cycle with leap_mode=true (Step
3-K, fired by an artifact-quality plateau), do NOT pick the top-RICE feature.
Instead:
- Make a step-change on
targeted_dimension (the rubric axis that has stayed
weakest / below bar) — overhaul, rebuild, or refactor-for-cohesion of the core
responsible for that dimension. RICE is bypassed on purpose: the whole point is
to do the high-effort overhaul that RICE structurally suppresses.
- Use
config.leap.max_lines (default 1500) as the size budget for Step 3,
instead of config.safety.max_lines_per_pr — a re-founding of the core is
legitimately larger than a feature.
- The verification gate for a leap is the artifact critique (evolve 5-G /
4-C2): the targeted dimension must rise by
config.leap.min_dimension_delta or
the change is reverted. The unit test still runs (information) but a unit-test
failure alone does not block a leap (a restructured module may legitimately
break a smoke test that mocked its old shape).
- Title the branch/PR
leap: overhaul {targeted_dimension} so it is legible as a
step-change, not a feature.
Then skip the RICE selection below and go straight to Step 2 with this overhaul as
the selected action. FEATURE cycles (the default) proceed with RICE selection:
Read agent/state/evolve/action_queue.json.
Find the top pending item by highest effective_rice (after decay adjustment):
data = read action_queue.json as JSON object
if data is not a valid object:
Print "Action queue is empty or malformed. Nothing to implement."
EXIT cleanly.
# The canonical format uses {pending:[], in_progress:[], completed:[]}
# Read from data.pending (preferred). Fallback: if data.actions exists, filter
# items with status=="pending". Fallback: if data is a plain array, filter by status.
candidates = data.pending # or fallback as described above
if candidates is empty or not a list:
Print "No pending actions. Nothing to implement."
EXIT cleanly.
# Sort by effective_rice. If effective_rice is missing, fall back to rice_score.
for item in candidates:
if item.effective_rice is undefined:
item.effective_rice = item.rice_score * (1.0 - (item.decay_applied or 0.0))
selected = max(candidates, key=lambda x: x.effective_rice)
if selected.effective_rice <= 0:
Print "Top candidate RICE score is {selected.effective_rice} (≤ 0). Skipping."
EXIT cleanly.
Print selected action details:
Selected action: {selected.title}
RICE score : {selected.effective_rice}
Source : {selected.source_domain}
Related : {selected.related_files}
Update the action's status to "in_progress" in action_queue.json and write
the file before proceeding. If the file write fails, abort and print the error.
Step 2: Create Branch
Generate a URL-safe slug from the action title:
- Lowercase the title.
- Replace spaces and underscores with hyphens.
- Strip all characters except
[a-z0-9-].
- Collapse consecutive hyphens into one and trim leading/trailing hyphens.
- If the slug is empty after stripping (e.g., non-ASCII title like Korean),
use the action ID as the slug instead (e.g.,
action-001). If the action ID
is also empty, generate a random 8-char hex string.
- Truncate to 40 characters (cut at a hyphen boundary if possible).
Get today's date in YYYYMMDD format.
git checkout -b auto/dev-cycle/{date}-{action-title-slug}
If the branch already exists (e.g., retry or duplicate title), append -2,
-3, etc., up to -9. If all suffixes are taken, abort with an error.
Print:
Branch: auto/dev-cycle/{date}-{action-title-slug}
Step 3: Implementation
Read context files before writing any code:
- If
CLAUDE.md exists at the project root, read it for project conventions.
- For each path in
selected.related_files:
- If the file exists, read it to understand the affected code.
- If it does not exist, print
"WARN: related file not found: {path} — skipping" and continue.
- If
related_files is empty or not set, print "INFO: No related files listed — proceeding with action title and source report only.".
- Read
selected.source_domain report (if referenced) to understand the
motivation behind this action.
Step 3-PRE: Research grounding (v1.11.0 — the anti-maze step)
Why this exists. The f1 dogfood proved the loop can "iterate without
improving" — a maze/local-optimum — when generation is anchored to the model's
own priors instead of to external ground truth. The fix (AlphaCodium
arXiv:2401.08500, which raised pass@5 19%→44% with a structured pre-generation
stage; AutoCodeRover; Simon Willison's "concrete examples beat abstract
requirements"): ground every non-trivial change in an external reference
BEFORE writing code. This is Boyd's Observe extended to the world's knowledge,
not just local state.
For any leap / quality-improving / "make it better" action (skip for a trivial
mechanical edit), BEFORE writing code:
- Resolve a reference. Read
config.references (and agent/state/research/*
if present — a researched, cited playbook). Pick the reference target for this
technique/domain (e.g. a named real-product level, a reference implementation
URL, or a specific playbook move with its concrete API/parameters).
- Fetch the concrete block. WebFetch / curl the specific reference snippet
(the 30–50 lines that matter — the exact API calls, parameter values, order of
operations), not the whole repo. If a research playbook already contains the
cited concrete spec, use that.
- Derive acceptance criteria from the reference: "the implementation MUST
(a) call X with params Y, (b) produce effect Z visible at camera/probe C,
(c) not break the gate." Record them in the cycle's outcome as
reference_block + acceptance_criteria.
- Only then generate — implement the cited technique, adapting names to the
real code. The PR/outcome records WHICH reference grounded it (
grounded_in).
A leap with no grounded_in reference is a red flag for the maze: prefer
researching a concrete approach over reaching for the model's first idea.
Analyze what needs to change based on the action title, source report, and the
resolved reference block, then implement the changes (write and/or edit files).
Protected paths enforcement:
Before writing any file, check against config.safety.protected_paths.
This prevents dev-cycle from modifying safety-critical files that could
compromise the framework's integrity (self-modification prevention).
protected = config.safety.protected_paths -- e.g., ["agent/safety/*", "skills/evolve/*", "agent/contracts/*"]
before writing or editing any file:
for each pattern in protected:
if file path matches glob pattern:
Print "BLOCKED: {file} matches protected path '{pattern}'. Skipping."
Print "Protected paths cannot be modified by dev-cycle, even at Level 3."
Add to PR body notes: "⚠ Protected path {file} was NOT modified (blocked by safety policy)."
Set protected_blocked = true -- forces Draft / Risk Tier 3 below (#35)
DO NOT write/edit this file — continue to next file.
If ALL planned files are protected, mark the action as "blocked" with memo
"All target files are protected paths" and EXIT cleanly.
If protected_blocked is true (some — not all — target files were protected and
skipped), the PR is never auto-merge-eligible even if the remaining diff is
small and green: a partial change with safety-critical files silently dropped may
be incomplete or incoherent, so a human must review it (#35).
Size limit enforcement:
Track changes as you write. After each file edit, run git diff --stat on
the working tree to get authoritative counts (do not estimate):
files_changed = 0
lines_changed = 0 # counted as (additions + deletions) from git diff --numstat
Before writing each file:
if files_changed >= config.safety.max_files_per_pr:
Print "PR size limit reached ({max_files_per_pr} files). Creating partial PR."
Print "Remaining work noted in action-queue memos."
Add memo to action-queue: "Partial implementation — {files_changed} files changed."
GOTO Step 4 (verify what was done so far)
if lines_changed + estimated_lines_for_this_file > config.safety.max_lines_per_pr:
Print "PR line limit reached ({max_lines_per_pr} lines). Creating partial PR."
GOTO Step 4
When partial: create a NEW pending action for the unfinished scope (same
source_domain, title "{original title} (remainder)", rice_score inherited) and
note the split in the original action's memos field. The original action then
proceeds to "proposed" like any other PR — the remainder is independently
selectable next cycle and cannot be silently lost with the original stuck
in_progress.
Step 4: Verify
Gate integrity (v1.10.1 — earned by the f1 probe). A static check is
necessary but NOT sufficient, and a sub-agent's self-reported gate result is
not trustworthy — the orchestrator must verify from facts. Two real misses the
f1 overnight run surfaced, BOTH caught only by loading the artifact in its real
runtime, never by the unit gate:
node --check exits 0 on a same-scope const REDECLARATION that the browser
ES-module parser rejects — the game wouldn't boot, yet the cycle's
node --check + smoke gate "passed". For an ES-module/browser artifact, also
do a module-load check (import the changed modules in their module system,
e.g. node --input-type=module -e 'import("./src/x.js")', or load the page)
— that catches what node --check cannot.
- A cumulative visual regression (over-exposed-to-white frame) passed every
unit gate; only a rendered critique caught it.
Rule: for rich-runtime artifacts (browser/UI/graphics/game), the verification
MUST load the artifact the way its runtime does (module-load + render/screenshot
critique, i.e. evolve Step 5-G), and evolve re-checks the gate from recorded
facts — it does NOT take the build skill's word for "tests passed".
If config.test_command is not configured or is empty:
Print "No test_command configured. Skipping tests."
test_status = "skipped"
GOTO Step 5
If configured, run tests with timeout enforcement:
timeout {config.test_timeout_seconds or 300}s {config.test_command}
If the test command exceeds the timeout, treat as failure:
if exit_code == 124 (timeout):
Print "ERROR: Test command timed out after {timeout}s. Treating as failure."
test_output = "Test timeout after {timeout}s"
Track attempts:
attempt = 1
max_attempts = 3
timeout = config.test_timeout_seconds or 300
while attempt <= max_attempts:
run test_command with timeout
if exit_code == 0:
test_status = "passed"
break
else:
Print "Tests failed (attempt {attempt}/{max_attempts})."
if attempt < max_attempts:
Print "Attempting fix..."
targeted_fix(test_output):
1. Parse test runner output for the first failing test name and assertion message.
2. Read the source file containing the failing assertion.
3. Apply a single-location edit (≤ 15 lines) that addresses the assertion.
4. Do NOT modify test files — only fix production code.
5. If the failure is an import/module error, fix the import only.
attempt += 1
if test_status != "passed" after 3 attempts:
Print "[BLOCKED] Tests failed after 3 attempts. Action marked as blocked."
Print "Review test output above and fix manually."
Set action status to "blocked" and MOVE it to completed[] in action_queue.json
(pending[] holds only workable items; evolve's 6-C6 hygiene sweep enforces the
same rule — a blocked item must never sit in pending/in_progress forever)
git stash (preserve work without committing)
git checkout main (never leave the session on the dead feature branch —
the next evolve cycle's git operations assume main)
EXIT with non-zero status.
When fixing between attempts: make targeted changes only — address the
specific failing assertion or import error. Do not rewrite large sections.
Step 5: Create PR
Stage only the files that were explicitly changed in Step 3 and any files
edited during test-fix retries in Step 4. Maintain a cumulative
changed_files list across both steps.
Never use git add -A or git add ..
for file in changed_files:
if file exists on disk:
git add {file}
else:
Print "WARN: {file} no longer exists — skipping stage"
git commit -m "{selected.title}"
git push origin HEAD
If git push fails:
if error contains "conflict" or "rejected" or "non-fast-forward":
Print "ERROR: merge conflict detected — {error}"
Print "Resolve manually, then: git push origin {branch_name}"
Update action status to "blocked" with memo "merge conflict with main"
else:
Print "ERROR: git push failed — {error}"
Print "Push manually with: git push origin {branch_name}"
Record push error in action-queue memos.
EXIT with non-zero status.
Auto-merge eligibility (compute before creating the PR). The PR is
auto-merge-eligible iff ALL of these hold:
config.safety.enable_auto_merge == true -- opt-in, default false
AND config.progressive_complexity.current_level >= 3
AND no changed file matches config.safety.protected_paths
AND protected_blocked == false -- no protected file was skipped (#35)
AND changed_files_count <= config.safety.auto_merge_max_files -- default 5
AND changed_lines_count <= config.safety.auto_merge_max_lines -- default 100
AND test_status == "passed" -- the CANONICAL Step-4 value;
-- "skipped" (no test_command)
-- is NOT eligible — auto-merge
-- requires actually-green tests
If NOT eligible (the default), create the PR as Draft. If eligible, create it
ready (omit --draft) and stamp auto_merge_eligible=true in the meta
comment so evolve 4-C can recognize it — evolve still independently re-checks
every gate before merging (defense in depth).
Create the PR (--draft UNLESS auto-merge-eligible):
gh pr create \
--title "{selected.title}" \
$([ "$auto_merge_eligible" = true ] || echo --draft) \
--body "$(cat <<'EOF'
<!-- ooda:meta source_domain={selected.source_domain} rice={selected.effective_rice} action_id={selected.id} auto_merge_eligible={true|false} protected_blocked={true|false} -->
## Source
- **Domain**: {selected.source_domain}
- **RICE Score**: {selected.effective_rice}
- **Action ID**: {selected.id}
## Changes
| File | Description |
|------|-------------|
| `{file1}` | {one-line description} |
## Test Results
- **Status**: {test_status}
- **Command**: `{config.test_command}`
- **Attempts**: {attempt}/{max_attempts}
- **Output** (last run): `{last 5 lines of test output or "tests skipped"}`
## Notes
{any partial PR notes, size limit warnings, or protected-path flags}
---
Generated by OODA-loop dev-cycle v1.0.0
EOF
)"
(--draft is added by the conditional on the gh pr create line above — present
unless the change is auto-merge-eligible.)
If gh is not available:
Print "gh (GitHub CLI) not found. PR creation skipped."
Print "Push the branch and create a PR manually:"
Print " git push origin {branch_name}"
Print " gh pr create --draft --title \"{selected.title}\""
pr_number = null
Update action_queue.json:
{
"status": "proposed",
"pr_number": {number or null},
"pr_url": "{url or null}",
"proposed_at": "{ISO 8601}"
}
Step 6: Report
Print the final summary:
dev-cycle complete — {ISO timestamp}
Action : {selected.title} (RICE: {selected.effective_rice})
Branch : auto/dev-cycle/{slug}
PR : #{pr_number} ({Draft|ready}) | {pr_url}
Files : {files_changed} changed
Lines : {lines_changed} changed
Tests : {test_status}
Status : proposed
pr_created : {true|false}
pr_created is a REQUIRED report variable (true iff a PR was actually opened
this run) — it is what evolve's 4-B evaluates for this skill's chain trigger
(pr_created == true). Report variables are the evaluation source for skills
whose contract output file (here action_queue.json) doesn't carry the
condition fields at top level.
If PR was not created (gh unavailable):
PR : not created — push branch and create manually
Graceful Degradation
| Scenario | Behavior |
|---|
| HALT file present | Print reason, exit immediately |
| Level < 3, not manual | Print level message, exit cleanly |
| action_queue.json missing | Print "Action queue not found at agent/state/evolve/action_queue.json", exit cleanly |
| No pending actions | Print "No pending actions", exit cleanly |
| Branch already exists | Append suffix (-2, -3), continue |
| PR size limit hit | Create partial PR, note remaining scope in action memos |
| Tests fail after 3 tries | Mark action "blocked", stash changes, exit non-zero |
git push fails | Record error in memos, print manual instructions, exit non-zero |
gh not installed | Skip PR creation, print manual instructions, exit 0 |
test_command not configured | Skip tests, record "skipped", continue to PR |
related_files missing/empty | Proceed with action title and source report only |
| Protected path changed | Already in Draft mode — note in PR body as protected-path change |
| Merge conflict on push | Abort push, mark action "blocked" with memo "merge conflict with main", stash changes, exit non-zero |
action_queue.json malformed | Print parse error, exit cleanly |
Branch suffix exhausted (-9) | Print error, mark action "blocked", exit non-zero |