# EXEC — ADR-132 agent-dispatch plan (which model agent does what)

**Authored:** 2026-08-06 (DL, Opus). **Companion to:** `PLAN-ADR-132-ai-provider-priority-rollout.md`
+ `ADR-132-ai-provider-priority-universal.md`. **Status:** READY TO DISPATCH — but execution is
**hkl-gated, dev-first**. This doc says *who runs each chunk and with what brief*; it does not run
anything.

## Dispatch principles

- **Orchestrator stays Sonnet** (this session). It spawns chunk agents via the Agent tool with an
  explicit `model:` override, collects their structured reports, and logs results back into
  `PLAN-…-rollout.md`'s Phase-2 tracker. (`feedback_plan_first_model_tiering`.)
- **Model tier by stakes, not by size:**
  - **Opus** — contract-defining or high-blast-radius correctness (the shared helper; adversarial review of it).
  - **Sonnet** — net-new fallback chains, Node key-swaps (ADR-114), prod-shared files, anything with real design choices.
  - **Haiku** — mechanical inserts that follow an existing in-file pattern (add one tier to an already-correct chain).
- **All edits are on the VPS over SSH** (host `72.60.97.173`), not a local git repo → **no worktree
  isolation needed**; parallel chunks are safe because each touches a distinct file + container.
- **Dev-first.** Any chunk whose container is prod-shared (`dispatch-ai`, and any "check" that resolves
  to prod) stops at the plan and waits for a per-target hkl go — the agent reports "PROD-GATED", makes
  no edit.
- **Every chunk agent gets the Common Preamble below** prepended to its task, so it is self-sufficient.

## Dependency graph / sequencing

```
P1 (gemini text endpoint) ─┐
P3 (verify harness)        ─┼──> G0 gate ──> Wave1 ─> Wave2 ─> Wave3 ─> Wave4 ─> Wave5 (hkl per-target)
P2 (shared ai_chain helper)┘        (P2 reviewed by Opus before G0 passes)
```

- **Phase 0 (P1,P2,P3) runs first, in parallel** (3 agents). P2 is then **adversarially reviewed by a
  4th agent (Opus)** before G0 opens — nothing downstream is trustworthy if the helper is wrong.
- **Within each wave, chunks run in parallel** (distinct proxies). **Waves run in sequence** so lessons
  from an early wave feed the next (the pipeline's slowest-item cadence, not a hard barrier — but keep
  the wave order for risk reasons).

---

## Model-assignment table

| Chunk | Task | Model | Why this tier |
|---|---|---|---|
| **P1** | Build `gemini-proxy` `/generate` text endpoint (mirror `analyze_via_cli` minus `@file`) | **Sonnet** | Small, well-specified, but touches a live proxy + OAuth env — not mechanical. |
| **P2** | Build shared `ai_chain.py` + `ai_chain.mjs` (5-tier, both variants, ADR-062 §5 shape + 503) | **Opus** | Contract-defining; every consumer depends on it. Highest correctness stakes. |
| **P2-REV** | Adversarially review P2 (refute: wrong tier order, silent all-fell-through, 503 semantics, env-toggle gaps) | **Opus** | Independent verify of the linchpin before anything builds on it. |
| **P3** | Build `verify_chain.py` force-fail harness (dead-port each tier, assert next answers, per variant) | **Sonnet** | Reusable acceptance gate; must be trustworthy. |
| **A1** tour-ai | Full T1→T5 chain from scratch (was Anthropic-only, no fallback) | **Sonnet** | Net-new chain, paid SPOF, no in-file pattern to copy. |
| **A2** ai-finance | Full T1→T4 vision chain from scratch (was Anthropic-only) | **Sonnet** | Net-new vision chain, paid SPOF. |
| **A5** health-ocr | Swap static `generativelanguage` key → gemini-proxy OAuth; reorder T1→T4 (Node) | **Sonnet** | ADR-114 correctness + Node; not mechanical. |
| **A6** excel-ai | Swap static Gemini key → gemini-proxy `/generate` OAuth; reorder (Node) | **Sonnet** | Same as A5. |
| **A3** ocr-proxy | Insert T2 Gemini `/analyze` between Claude & OpenRouter (pattern already shipped) | **Haiku** | Mechanical insert following the 2026-08-06 reference edit + shared helper. |
| **A4** hub-media | Add T2 Gemini + T3 OpenRouter below Claude /vision | **Sonnet** | Two new tiers + prod-shared (ImageBinding). |
| **A7** counter-ai | Insert T2 Gemini text into Claude→OR→Anthropic | **Haiku** | One-tier insert into an already-correct chain. |
| **A8** print-ai | Insert T2 Gemini text | **Haiku** | Same shape as A7. |
| **A9** blogsmith | Insert T2 Gemini text | **Haiku** | Same shape. |
| **A10** sitecap | Insert T2 Gemini text (Node) | **Haiku** | Same shape, Node. |
| **A13** videosmith | Add T1 Claude + T2 Gemini above existing Anthropic+OpenRouter | **Sonnet** | Adds two tiers incl. primary; more than an insert. |
| **A11** dispatch (address) | Insert T2 Gemini text; keep Ollama T5 | **Sonnet** | **PROD-shared — hkl-gated, LAST.** Additive, extra care. |
| **A12** dispatch (docket OCR) | OPTIONAL: add T1/T2 vision above Ollama llava | **Sonnet** | Optional (062 §1 self-host exempt); prod-shared. |
| **ORCH** | Spawn, collect, log to tracker, decide wave gates | **Sonnet** | Standing rule. |

Rough spend shape: 1 Opus build + 1 Opus review (P2), ~7 Sonnet, ~5 Haiku. Waves 1-4 ≈ 11 agents;
P0 ≈ 4 agents. Wave 5 held for hkl.

---

## Common Preamble (prepended to EVERY chunk agent)

> You are implementing ADR-132 on the 360lm VPS. Read `/var/www/360lm/docs/adr/ADR-132-ai-provider-priority-universal.md`
> and `/var/www/360lm/docs/PLAN-ADR-132-ai-provider-priority-rollout.md` first. SSH to the VPS as
> `72.60.97.173` for all edits. **Target chain:** T1 Claude OAuth ($0, claude-proxy:8788 /generate|/vision)
> → T2 Gemini OAuth ($0, gemini-proxy /generate|/analyze) → T3 OpenRouter (paid, free models first) →
> T4 Anthropic REST (approval-gated) → T5 Ollama (last resort, only where already present).
> **Rules:** (1) Use the shared helper `/opt/ai-chain/ai_chain.{py,mjs}` — do not re-copy the chain
> inline unless the file is prod-shared and a full refactor is riskier than an additive insert (say which
> you chose and why). (2) Preserve ADR-062 §2–§6 exactly — human-review gate, loading UX, `{ok,data,mode}`
> response shape, `need_api_permission` 503, graceful degradation. Do not regress them. (3) Back up first:
> `cp <file> <file>.bak-20260806-adr132`. (4) Deploy with `docker restart <container>` — **never**
> `docker compose up` (it can reattach networks; `traefik-net` is an alias for `root_default`). (5)
> **Acceptance is mandatory and real:** run `/opt/ai-chain/verify_chain.py` against the live endpoint —
> force-fail T1 and confirm T2 answers; force-fail T1+T2 and confirm T3; confirm a normal call reports
> `mode: claude-oauth`; record actual per-tier latency. "Returns 200" is NOT acceptance. (6) If the
> container is prod-shared or you cannot confirm it is dev-only, STOP before editing and report
> `outcome: PROD-GATED` with what you found. **Report back this JSON:** `{chunk, file, container,
> action_taken, tiers_before, tiers_after, acceptance: {t1_to_t2, t2_to_t3, normal_mode, latency_ms},
> adr062_preserved: bool, prod_gated: bool, backup_path, notes}`.

---

## Per-chunk specifics (only what the preamble doesn't already cover)

- **P1 gemini text endpoint** — add `POST /generate {system,user,model}` to `/opt/gemini-proxy/main.py`;
  in `gemini.py` add `generate_via_cli(system,user,model)` = `subprocess.run(['gemini','-m',model,'-p',
  f'{system}\\n{user}'], env=… with GEMINI_API_KEY popped, HOME=/root, NO_BROWSER=true)`, parse `{…}` or
  return raw text. Return `{ok, tier:'gemini-cli/oauth', result}`. Restart gemini-proxy. Confirm logs
  show `gemini-cli/oauth`, not `vertex/sa`.
- **P2 shared helper** — `chain_text(system,user,*,allow_api=False)` and `chain_vision(image_b64,mime,
  prompt,*,allow_api=False)`. Adapt each tier's wire contract: claude /generate|/vision are JSON+tokenless;
  gemini /generate is JSON, gemini /analyze is **multipart file+task + `Authorization: Hub <b64 session>`**
  (mint an internal Hub session like `a0_verify.py`/hub-media do). Per-tier env toggles
  (`AI_DISABLE_CLAUDE`, `AI_DISABLE_GEMINI`, `AI_DISABLE_OPENROUTER`). On T1+T2+T3 all failing and
  `allow_api` false → raise the 503 `need_api_permission` signal for the caller to surface. Log
  `mode:<tier>` per call.
- **A1 tour-ai / A2 ai-finance** — replace the lone `api.anthropic.com` call with the shared helper
  (`chain_text` / `chain_vision`). These have no existing fallback, so this is the highest-value change;
  test the full fall-through, not just the happy path.
- **A5 health-ocr / A6 excel-ai** — the ADR-114 fix: delete the static-key `generativelanguage` call,
  route Gemini through gemini-proxy OAuth (`/analyze` for A5 vision, `/generate` for A6 text). Confirm no
  `generativelanguage.googleapis.com` string remains.
- **A3 ocr-proxy** — the reference edit already added Claude /vision → OpenRouter with `OCR_USE_CLAUDE`
  toggle + `extract_json`. Just insert the T2 Gemini `/analyze` call between them (add `AI_USE_GEMINI`
  toggle, `call_gemini_vision()` mirroring `call_claude_vision()`), same fall-through pattern.
- **A11 dispatch (address)** — PROD-shared: prefer an **additive** insert of T2 over a full refactor;
  keep Ollama T5. Do not touch the docket-OCR path (A12) in the same edit. hkl-gated.

---

## Appendix — ready-to-run orchestration script (DO NOT RUN without hkl go)

Given explicit go-ahead, Phase 0 + Waves 1-4 can be executed as one Workflow (Wave 5 held separately).
Sketch (pipeline: P0 barrier → waves in sequence, chunks parallel within a wave):

```
phase('P0'); const helper = await parallel([
  ()=>agent(P1_brief,{model:'sonnet',label:'P1-gemini-text'}),
  ()=>agent(P2_brief,{model:'opus',  label:'P2-ai-chain'}),
  ()=>agent(P3_brief,{model:'sonnet',label:'P3-verify-harness'}),
]);
phase('P2-review'); const rev = await agent(P2_REVIEW_brief,{model:'opus',schema:VERDICT});
if(!rev.ok) return {halted_at:'G0', reason:rev.reason};   // linchpin must pass before any consumer
phase('Wave1'); await parallel([A1,A2].map(b=>()=>agent(b.brief,{model:'sonnet',label:b.id,schema:REPORT})));
phase('Wave2'); await parallel([A5,A6].map(b=>()=>agent(b.brief,{model:'sonnet',label:b.id,schema:REPORT})));
phase('Wave3'); await parallel([{A3:'haiku'},{A4:'sonnet'}].map(...));
phase('Wave4'); await parallel([A7,A8,A9,A10].map(b=>()=>agent(b.brief,{model:'haiku',schema:REPORT}))
                                .concat([()=>agent(A13.brief,{model:'sonnet',schema:REPORT})]));
// Wave5 (A11/A12 dispatch, PROD) NOT included — separate, per-target hkl approval.
```

Each `REPORT` matches the Common-Preamble JSON schema; the orchestrator writes them into the plan's
Phase-2 tracker as they land. This appendix is illustrative — I will only author/run the real Workflow
on your explicit go, and only after you've accepted ADR-132.

## Go/no-go checklist for hkl

1. Accept ADR-132 (Proposed → Accepted) — or send edits.
2. Confirm dev-first is fine and name which targets are prod (esp. dispatch-ai) so Wave 5 is scoped.
3. Say "go P0" to build the two prereqs + helper in dev; I'll report P0 + the Opus review before any wave.
