# Parallel Dispatch Pattern

Operational recipes for spawning per-task teams. Default sizing is light (one worker per task); full-crew (multi-worker + mandatory Codex) is the opt-in.

## Run-mode detection (Step 1)

```bash
# Check for !max / --full-power override FIRST (case-insensitive, whole-word)
if echo "$USER_PROMPT" | grep -qiE '(^|[[:space:]])(!max|--full-power)($|[[:space:]])'; then
  RUN_MODE=full-crew
  CODEX_FORCED=verify
  FULL_POWER=1
  # Strip the keyword from task texts before classification
else
  FULL_POWER=0
  RUN_MODE=light  # default

  # Glob the active manifest
  manifest=$(ls -t .forge/work/*/*/manifest.yaml 2>/dev/null | head -1)

  if [ -n "$manifest" ]; then
    # Manifest exists — full-crew requires BOTH codify AND production-build active.
    # The lightweight /feature default (per P2-2) writes `production-build: active`
    # with `codify: skipped` on every new manifest, so testing production-build alone
    # would defeat the lightweight default. Codify means the work crossed the
    # prototype-to-production bridge — that's the signal that earns full-crew.
    codify_active=$(yq '.phase_plan.codify // ""' "$manifest")
    prod_build_active=$(yq '.phase_plan.production-build // ""' "$manifest")
    if [ "$codify_active" = "active" ] && [ "$prod_build_active" = "active" ]; then
      RUN_MODE=full-crew
    fi
  fi

  # Also escalate if project.mode is production
  if [ -f .claude/CLAUDE.md ] && grep -qE '^\s*mode:\s*production' .claude/CLAUDE.md; then
    RUN_MODE=full-crew
  fi
fi
```

**Magic keyword reference:**

- Short form: `!max` (preferred for daily use)
- Canonical form: `--full-power` (verbose, self-documenting)
- Both case-insensitive
- Must be whole words (bounded by whitespace or string boundary)
- Either form forces `RUN_MODE=full-crew` and `CODEX_FORCED=verify`
- Stripped from task text before classification (it's a run-level flag)

Reject silent variants — do NOT match `max` alone (no bang), `!maximum`, `full power` (no dashes), `--fullpower` (no internal dash), `-full-power` (one dash). Strict matching prevents false positives from casual English.

## Per-task risk escalation (Step 3)

Even when `RUN_MODE=light`, a single task escalates to a full-crew team if its `risk: high`:

```python
HIGH_RISK_TERMS = re.compile(
    r"(?i)\b(auth|authentication|authorization|login|oauth|saml|jwt|session|"
    r"password|credential|secret|secret rotation|payment|billing|refund|"
    r"charge|pii|ssn|migration|schema migration|alter table|drop column|"
    r"encryption key|rbac|iam|kms|permission)\b"
)

def classify_risk(task_type, task_text):
    if task_type == "security":
        return "high"
    if task_type in ("dev", "complex", "review", "debug") and HIGH_RISK_TERMS.search(task_text):
        return "high"
    return "low"

task_team = "full-crew" if (risk == "high" or RUN_MODE == "full-crew") else "light"
```

## Task classification (Step 3)

Apply regex/heuristic match in order, first-match wins:

```python
classifiers = [
    (r'(?i)add (a |an )?new feature|build .* product|implement .* end-to-end', 'command:/feature'),
    (r'(?i)fix bug|investigate failure', 'command:/bugfix'),
    (r'(?i)refactor|restructure', 'command:/refactor'),
    (r'(?i)hotfix production|rollback', 'command:/hotfix'),
    (r'(?i)scaffold .* (project|repo)|start new project', 'command:/greenfield'),
    (r'(?i)audit security|owasp|vuln', 'security'),
    (r'(?i)review (this )?(code|pr|diff)', 'review'),
    (r'(?i)explore|compare .* (vs|or)|find best|research|investigate options', 'research'),
    (r'(?i)why does .* fail|debug|trace', 'debug'),
    (r'(?i)write (a |the )?readme|document|add comments|docstring', 'docs'),
    (r'(?i)fix typo|bump dep|rename var|rename const', 'quick'),
    # falls through to: dev (default for code work), complex (if multi-subsystem), ambiguous
]
```

## Team assembly (Step 4) — by run-mode × type, with per-task high-risk escalation

### Light mode teams (default — one worker, no Codex)

```
dev        → 1 agent: prototype-builder (if pocs/* exists) or builder — no Codex
research   → 1 agent: architect | tracer | doc-writer (by scope) — no Codex
debug      → 1 dispatch: support-debug skill — no Codex
docs       → 1 agent: doc-writer — no Codex
review     → 1 dispatch: quality-code-review (low-risk tier)
security   → escalates to full-crew (security is always high-risk)
quick      → 1 agent inline (no dispatch) — no Codex
complex    → warn + escalate this task to full-crew (recommend /feature or !max)
```

### Full-crew teams (production manifest, high-risk task, or !max)

```
dev        → 1 agent (builder) + 1 dispatch (quality-code-review) + 1 Codex
research   → 2-3 agents (architect, tracer, doc-writer/spec-reviewer) + 1 Codex
debug      → 1 dispatch (support-debug) + 1 agent (gotcha-hunter) + 1 Codex
docs       → 1 agent (doc-writer) + 1 Codex verify
review     → 1 dispatch (quality-code-review full chain) + 1 Codex adversarial
security   → 1 dispatch (quality-security-audit) + 1 agent (security-reviewer) + 1 Codex adversarial
quick      → 1 agent + 1 Codex verify
complex    → architect + tracer + builder + dispatch quality-code-review + spec-reviewer + security-reviewer (if risk) + e2e-runner (if E2E plan) + Codex adversarial
```

**Mixed-risk run:** a single high-risk task inside a light run picks from the full-crew row for that one task only.

## Per-agent prompt template

```
You are agent {role} in parallel-run task {n} of {N}, run-id {run-id}.

Task type: {dev|research|debug|docs|review|security|quick|complex}
Task risk: {low|high}
Run mode: {light|full-crew}
Codex consent: {verify|takeover|skip|never}

TASK:
{verbatim task text from user list}

Repository context:
- cwd: {abs path}
- active manifest: {path or "none"}
- last commit: {sha}

Your role: {builder|reviewer|explorer|tracer|doc-writer|...}
Your scope: {paths, subsystem, or "whole repo"}

OUTPUT:
- Result of doing the task (code diffs, findings, document, etc.)
- One-line verdict for the parallel-run aggregator: PASS / CONCERNS / FAIL
- Hand off any followups by writing them to .forge/parallel/{run-id}/task-{n}/followups.md

Cap your output at {N} words. Save artifacts under .forge/parallel/{run-id}/task-{n}/{role}.{ext}.
```

## Batched dispatch ceiling

20-25 Agent tool calls per `<function_calls>` block. For 10 tasks × avg 3 agents = 30 agents, plan 2 dispatch rounds.

## Per-task aggregator (Step 7)

```python
verdicts = [parse_verdict(role_output) for role_output in role_outputs]

if all(v == "PASS" for v in verdicts):
    status = "complete-consensus"
elif any(v == "FAIL" for v in verdicts) and "PASS" in verdicts:
    status = "complete-divergent"
elif all(v == "FAIL" for v in verdicts):
    status = "failed"
elif len(verdicts) < expected_count:
    status = "complete-single-source"
else:
    status = "complete-with-concerns"
```

## Failure modes

| Failure | Symptom | Action |
|---|---|---|
| Background agent timeout | Notification never arrives | Mark agent failed, fall through to single-source verdict |
| Codex sandbox can't write | "operation blocked in sandbox" | Codex outputs uncommitted; Claude commits or main session commits |
| Two agents touch same file | Edit tool conflict | Partition more aggressively |
| Classifier picks command-shaped but rest heterogeneous | Some routed, some not | Surface routing decisions, continue with remainder |
| User cancels mid-run | Receives interrupt | Stop in-flight tasks; preserve completed outputs |

## Sizing examples

| Punch list | Tasks | Run mode | Agents fired |
|---|---|---|---|
| 3 docs items | 3 | light | 3 |
| 5 quick chores | 5 | light | 5 |
| 5 quick chores | 5 | full-crew (e.g. `!max`) | 10 |
| 2 dev + 1 research + 1 debug + 1 docs | 5 | light | 5 (very lean) |
| Same 5 mixed | 5 | full-crew | 12-14 |
| 4 low-risk + 1 "audit auth handler" (high-risk) | 5 | light (auto-escalates task 5) | 4 + 3 = 7 |
| 10 heterogeneous | 10 | full-crew | 25-40 |
| 15 tasks (soft cap warning) | 15 | full-crew | 40-60 — offer batch |
| 25 tasks (over hard cap) | 25 | any | REFUSE — split |

## Cleanup

`.forge/parallel/{run-id}/` directories accumulate one per run. Retention spec: **30 days**, then eligible for prune.

```bash
# Dry run — list what would be removed
find .forge/parallel -maxdepth 1 -mindepth 1 -type d -mtime +30 -print

# Actually prune
find .forge/parallel -maxdepth 1 -mindepth 1 -type d -mtime +30 -exec rm -rf {} +
```

**Future CLI hook (spec, not yet implemented):** `forge parallel clean [--older-than 30d] [--dry-run]` should walk `.forge/parallel/`, list run directories older than the cutoff, and (without `--dry-run`) `rm -rf` them. Default cutoff `30d`. The subcommand belongs alongside `forge wiki *` in `src/cli.ts`. Deferred to a follow-up work item.

**Consolidation before prune (optional):** before pruning, `support-dream` can summarize per-run findings into aiwiki entries so lessons survive. Today this is opt-in — run `support-dream` with `scope: .forge/parallel/` before invoking the prune command.
