PDCA campaigns

Sequential Plan → Do → Check → Act runs over ordered campaign items. Each item drives the L1 driver in an isolated git worktree, runs quality gates, writes a model reliability report, and optionally promotes to a live…

Updated

Quick start

bash scripts/setup.sh
bash scripts/run-pdca-campaign.sh campaigns/poc-boundary.yaml
bash scripts/run-pdca-campaign.sh campaigns/lenses-production.yaml

Campaign outputs live under runs/campaigns/<campaign_id>/<item_id>/.

Campaign YAML schema

Top-level keys:

Field Type Required Default Purpose
campaign_id string no YAML stem Campaign directory name under runs/campaigns/
items list yes Ordered campaign items

Per-item fields (parsed by pdca.load_campaign):

Field Type Required Default Purpose
id string yes Stable item id (directory name)
goal string yes PLAN goal passed to the driver
target string yes Sandbox or repo path (worktree root)
level string no L1 Assay target level (L1, L2, L3, …)
sublevel string no Assay sub-level id (L1.1L4.3); see AUTONOMY-LEVELS.md
worker_ladder list of strings no ["local", "cursor"] Ordered backends to try (see worker matrix below)
patch_units list no Ordered units: goal, optional allowed_files, optional layer (logic / docs / ui)
promote_live string no Live checkout path to copy into when CHECK passes and tree is clean
verification_argv list no Extra argv run after driver pass (pytest, link checker, …)
fixture string no Fixture JSON for fake backend
pre_pytest_red bool no false Require red pytest before the run
allowed_files list no Path allowlist for single-unit items
verification_cwd string no Working directory for verification_argv
verification_argv_extra list of lists no Additional verification argv tuples
promote_scope string no repo Promotion scope hint

Example fragment from campaigns/poc-boundary.yaml:

campaign_id: poc-boundary
items:
  - id: poc-sandbox-offline
    goal: fix failing multiply
    target: sandbox/calculator
    level: L1
    worker_ladder: [fake]
    fixture: sandbox/fixtures/multiply_fix.json
    pre_pytest_red: true

  - id: poc-sandbox-granite
    goal: fix failing multiply test in calculator.py
    target: sandbox/calculator
    level: L1
    worker_ladder: [local, cursor]
    pre_pytest_red: false

Worker ladder

Backend Role Env / skip conditions Typical ladder position
fake Fixture-backed patch (FORGE_DARK_FACTORY_FIXTURE or item fixture) No LLM, no network Offline CI (poc-boundary item 1); forced when worker_backend=fake and no custom ladder
deterministic Rule/fixture map for clear-domain tasks No LLM CI campaigns (lenses-production-*-ci.yaml); router tier deterministic
local Granite / OpenAI-compatible via forge_workcells.local_llm_worker FORGE_DARK_FACTORY_GRANITE_TIMEOUT_SEC (default 180s, max 300); FORGE_DARK_FACTORY_LLM_TIMEOUT_SEC for other profiles Default sign-off ladder step 1
cursor Cursor CLI agent -p --model <CURSOR_MODEL> CURSOR_AGENT_BIN, CURSOR_MODEL, CURSOR_AGENT_NON_INTERACTIVE=1; skipped with cursor_skipped if agent missing Escalation after local failure or Granite timeout

Escalation order: campaign worker_ladder is tried left-to-right (next_backend in tier_backend.py). On CHECK fail, PDCA retries once with the next tier. Router-only default for local tier: ("local", "cursor"); deterministic tier: ("deterministic",); escalate tier: empty ladder (human).

Campaign pattern Typical worker_ladder
Offline boundary [fake] then [local, cursor]
Sign-off (Granite/Cursor) [local, cursor]
CI deterministic [deterministic]

Sign-off gate: Granite or Cursor verify_ok per item (see scripts/campaign-signoff.sh).

Fail-fast LLM (campaign runs)

export FORGE_LCDL_CHAT_HTTP_RETRIES=0          # default in run-pdca-campaign.sh
export FORGE_DARK_FACTORY_LLM_TIMEOUT_SEC=45   # default 45s (max 120)
export FORGE_DARK_FACTORY_GRANITE_TIMEOUT_SEC=180  # local worker + harness only (max 300)

On Granite timeout the driver skips further local attempts and escalates to Cursor.

For long-timeout Granite calibration (no Cursor), see GRANITE-HARNESS.md and campaigns/granite-calibration.yaml.

Set CURSOR_AGENT_BIN, CURSOR_MODEL, CURSOR_AGENT_NON_INTERACTIVE=1 for Cursor. If agent is missing, the cursor step is skipped (cursor_skipped in model report).

Per-item artifacts

Path Purpose
plan.json PLAN: goal, target, level
run-*/machine/ Driver machine record (M)
run-*/human/report.md Generated human narrative (H)
run-*/machine/model_report.json Model usage + reliability scores
run-*/machine/assay.json Assay gate (tests_pass, acceptance, risks)
run-*/machine/promote.json Promotion result (when promote_live set)
check.json CHECK gate summary

Model reliability report

Written to machine/model_report.json and summarized in the H report.

Reliability per attempt (deterministic):

  • verify_ok1.0
  • draft_ok only → 0.5
  • else → 0.0

Campaign summary aggregates granite_success_rate and winner backend per item.

CHECK gates

  1. Dual-wiki freeze gate (H matches M)
  2. Assay gate (forge/forge.config.yaml core evidence)
  3. Driver final_status == pass
  4. Optional item verification_argv (pytest, link checker, inline asserts)

On CHECK fail: ACT retries with the next worker tier once; if still failing, the item is marked blocked or fail and the campaign continues (unless P1/P8 infra breaks).

Model reliability (PoC boundary campaign)

Example from a green poc-boundary Granite run:

Item Winner Granite success Notes
poc-sandbox-offline fake n/a Offline fixture
poc-sandbox-granite local 1.0 Granite drafted + verified on first attempt

Per-item JSON: runs/campaigns/<campaign>/<item>/run-*/machine/model_report.json.

Promotion policy

When promote_live is set (lenses campaigns):

  1. All CHECK gates passed on the worktree
  2. Live repo git status --porcelain is clean
  3. Changed files copied from worktree → live (allowed paths only)
  4. No auto-commit — operator commits manually

If the live tree is dirty, promotion is skipped and recorded in promote.json.

Campaign files

File Items
campaigns/poc-boundary.yaml Offline sandbox + Granite sandbox
campaigns/lenses-production.yaml Sign-off — Granite/Cursor on docs-health + handbook slice (L1)
campaigns/lenses-production-ci.yaml CI only — deterministic L1 fixtures
campaigns/lenses-production-l2.yaml Sign-off — L2 multi-file docs-health (Granite or Cursor)
campaigns/lenses-production-l2-ci.yaml CI only — deterministic L2 multi-unit fixtures
campaigns/lenses-production-l3.yaml Sign-off — L3 use-case slice (Granite or Cursor + Playwright)
campaigns/lenses-production-l3-ci.yaml CI only — deterministic L3 layered fixtures + Playwright

L2 multi-unit semantics

Campaign items may declare explicit patch_units (ordered goals + per-unit allowed_files). The driver runs draft → apply → verify sequentially; only the final unit runs verification_argv / verification_argv_extra. L2 assay requires ≥2 distinct changed files in the proof union.

L3 layered use-case semantics

L3 items add optional layer per patch unit (logic, docs, ui). Final verification uses scripts/verify-docs-health-l3.py (pytest + Playwright via scripts/run-l3-e2e.sh). L3 assay requires ≥2 layers, cross-layer files, and E2E pass.

Handbook slice: sandbox/lenses-handbook-slice/ (sync via bash scripts/sync-handbook-slice.sh). Docs-health L2 seed: sandbox/docs-health-l2/ (copy of forge-lenses fixture). Docs-health L3 seed: sandbox/docs-health-l3/ (bootstrap via bash scripts/bootstrap-docs-health-l3.sh).

L1 sign-off

bash scripts/campaign-signoff.sh           # pytest + campaigns + reliability doc
bash scripts/campaign-signoff.sh --granite-demo   # includes poc-demo-granite.sh
bash scripts/campaign-signoff.sh --granite-demo --granite-harness  # + probe/matrix harness

Writes docs/RELIABILITY-SIGNOFF.md from campaign model reports (includes L2 deterministic CI when run via sign-off script).

Granite harness

See GRANITE-HARNESS.md. Quick probe:

bash scripts/granite-harness.sh --probe-only
bash scripts/granite-harness.sh --skip-readme   # calculator calibration, long timeout

L2 complete when sign-off includes green lenses-production-l2-ci (offline) and, for full sign-off, green lenses-production-l2.yaml with Granite or Cursor winners per item.

L3 complete when sign-off includes green lenses-production-l3-ci (offline + Playwright) and, for full sign-off, green lenses-production-l3.yaml with Granite or Cursor per item.

CI

GitHub Actions (.github/workflows/ci.yml): setup.sh, pytest, dark_factory_check.py, poc-demo.sh, lenses-production-ci.yaml, lenses-production-l2-ci.yaml, bootstrap-docs-health-l3.sh, lenses-production-l3-ci.yaml (deterministic + Playwright).