From 9e4ebb4cf4b6a7ac2ad011c717604b480a03d8f6 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Sun, 19 Jul 2026 23:23:10 +0200 Subject: [PATCH] =?UTF-8?q?feat(agents):=20W5=20seo/geo=203-mode=20pipelin?= =?UTF-8?q?es=20=E2=80=94=20collect=20sonnet=20/=20judge=20opus=20pin=20/?= =?UTF-8?q?=20template=20sonnet=20(BDR-077)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit seo-analyzer + geo-analyzer gain MODE: collect|judge|template around the dispatcher (mode-based, zero body-text moves — seo-data fetch-wiring locks survive; opus pin kept = fail-safe direction, a forgotten override over-tiers but never downgrades judgment). Run-scoped gitignored signals handoff (.audit/*-signals-.md + COLLECTION COMPLETE sentinel), judge fails closed on absent/mismatched/unsealed signals. /seo rewired to 3 phases (domains parallel per phase) + DISPATCHER ERROR CONTRACT (mute/ERROR judge never carried into templating; retry once, escalate); /geo same single-domain; legacy no-MODE single-shot kept on the opus pin for /harden narrow-scope + /onboard report-only. Dropped /geo's 'ask and I relay' fiction (dispatched agents cannot ask). In-wave smokes PASSED disk-verified: collect signals+sentinel; judge ERROR-verdict on wrong RUNID; real judge = honest N/A + deterministic engine + full scoring grammar; template = complete envelope + verbatim sentinel + zero re-derivation. Census §18 (125 pass — one vacuous line-wrapped lock caught by the census itself and fixed), make test exit 0. --- agents/geo-analyzer.md | 33 +++++++++++ agents/seo-analyzer.md | 40 +++++++++++++ lib/tests/model-routing.test.sh | 20 +++++++ skills/geo/SKILL.md | 56 +++++++++++++----- skills/seo/SKILL.md | 101 +++++++++++++++++++++++++------- 5 files changed, 213 insertions(+), 37 deletions(-) diff --git a/agents/geo-analyzer.md b/agents/geo-analyzer.md index c787427..8c63520 100644 --- a/agents/geo-analyzer.md +++ b/agents/geo-analyzer.md @@ -94,6 +94,31 @@ $ARGUMENTS --- +## MODE DETECTION (BDR-077 — pipeline modes around the dispatcher) + +Mirror of seo-analyzer's pipeline contract. Parse the MODE line: + +- **`MODE: collect`** — dispatched `model: "sonnet"`. STEP 0-5 ONLY + (context, crawler policy probes, llms.txt checks — raw results), written + to the run-scoped, gitignored `.audit/geo-signals-.md`, terminated + by `COLLECTION COMPLETE — RUNID: `; emit a `COLLECT REPORT` + (`STATUS`, RUNID, COVERAGE counts) and STOP. +- **`MODE: judge`** — opus frontmatter pin. Fail-closed load of + `.audit/geo-signals-.md` (absent / RUNID mismatch / missing + sentinel → `GEO JUDGE — VERDICT: ERROR()`, STOP — never score + stale or partial signals). Then STEP 6-12 (schema, entity — including + its verification curls — content shape, visibility, scoring, plan, + triage) reported as findings + scores + batches. No bundle, no GEO.md. +- **`MODE: template`** — dispatched `model: "sonnet"`. INPUT: dispatcher + context + judge report VERBATIM (never re-derive). STEP 13-15: FIX + BUNDLE + sentinel, report file, envelope, console. +- **No MODE line** — legacy single-shot on the opus pin (/onboard + report-only). + +Every mode receives the full dispatcher CONTEXT block (LRN-126). + +--- + ## STEP 0 — AUDIT DEPTH **First action.** If not already determined by a parent skill (`/seo` @@ -324,6 +349,10 @@ RECOMMENDATION : CREATE | UPDATE | OK | SKIP (low value for this site type) --- +> **MODE BOUNDARY — `MODE: collect` ends at STEP 5**: signals file + +> `COLLECTION COMPLETE — RUNID: ` written, COLLECT REPORT emitted, +> stop. STEP 6-12 below are `MODE: judge` territory. + ## STEP 6 — SCHEMA.ORG FOR AI `[both]` Load: `~/.claude/agents/resources/geo-schemas.md` @@ -804,6 +833,10 @@ one level up, where the plan is printed and the user can interrupt. --- +> **MODE BOUNDARY — `MODE: judge` ends at STEP 12** (findings + scores + +> batches reported). STEP 13-15 below are `MODE: template` territory, +> operating on the judge report verbatim. + ## STEP 13 — EMIT FIX BUNDLE `[both]` **You do NOT apply fixes and you do NOT dispatch any sub-agent.** Same diff --git a/agents/seo-analyzer.md b/agents/seo-analyzer.md index 6f6f384..9c09f16 100644 --- a/agents/seo-analyzer.md +++ b/agents/seo-analyzer.md @@ -24,6 +24,38 @@ $ARGUMENTS --- +## MODE DETECTION (BDR-077 — pipeline modes around the dispatcher) + +The dispatcher (/seo) runs this agent as a 3-stage pipeline; /harden and +/onboard may still run it single-shot. Parse the MODE line in the prompt: + +- **`MODE: collect`** — dispatched `model: "sonnet"` (mechanical/standard + collection; the call-site override takes precedence over the opus pin). + Runs STEP 0-5 ONLY, writes every gathered signal (tech context, tool + availability, live-audit raw results, on-page inventory + sampling + frame) to the run-scoped, gitignored `.audit/seo-signals-.md`, + terminated by the line `COLLECTION COMPLETE — RUNID: `, then + emits a short `COLLECT REPORT` (`STATUS: DONE | BLOCKED`, RUNID, + COVERAGE counts) and STOPS. No scoring, no findings, no bundle. +- **`MODE: judge`** — runs on the opus frontmatter pin (audit judgment). + FIRST loads `.audit/seo-signals-.md`: absent, RUNID mismatch, or + missing `COLLECTION COMPLETE` sentinel → emit + `SEO JUDGE — VERDICT: ERROR()` and STOP (fail closed — NEVER + score stale or partial signals). Then runs STEP 6-11 on the signals + + the dispatcher-fed context and emits the scoring blocks + findings + + action plan + triage batches as its report. No bundle, no SEO.md. +- **`MODE: template`** — dispatched `model: "sonnet"`. INPUT: the + dispatcher-fed context + the judge's report VERBATIM (never re-derive a + score or re-judge a finding). Runs STEP 12-14: FIX BUNDLE + sentinel, + report file, envelope. +- **No MODE line** — legacy single-shot: all steps in sequence on the + opus pin (used by /harden narrow-scope and /onboard report-only). + +Every mode receives the full dispatcher CONTEXT block (LRN-126 — the +STEP 1-2 business/tech context is consumed by all later steps). + +--- + ## STEP 0 — AUDIT DEPTH **First action.** If a parent skill (`/seo` dispatcher) passed depth @@ -744,6 +776,10 @@ Validate: --- +> **MODE BOUNDARY — `MODE: collect` ends at STEP 5**: write the signals +> file + `COLLECTION COMPLETE — RUNID: ` terminal line, emit the +> COLLECT REPORT, stop. STEP 6-11 below are `MODE: judge` territory. + ## STEP 6 — EXTERNAL PRESENCE AUDIT `[FULL only, local business only]` **Skip if not a local business** (pure SaaS, content-only → jump to STEP 7). @@ -1182,6 +1218,10 @@ Do not proceed to STEP 12 until this plan is printed. --- +> **MODE BOUNDARY — `MODE: judge` ends at STEP 11** (scoring + findings + +> plan + batches reported, nothing serialized). STEP 12-14 below are +> `MODE: template` territory, operating on the judge report verbatim. + ## STEP 12 — EMIT FIX BUNDLE `[both]` **You do NOT apply fixes and you do NOT dispatch any sub-agent.** Same diff --git a/lib/tests/model-routing.test.sh b/lib/tests/model-routing.test.sh index c502fd7..7e56af8 100755 --- a/lib/tests/model-routing.test.sh +++ b/lib/tests/model-routing.test.sh @@ -140,6 +140,26 @@ has "agents/handover-doc-writer.md" 'MODE: render' has "agents/handover-doc-writer.md" 'DRAFT COMPLETE' has "agents/client-handover-writer.md" 'MODE: synthesize' has "agents/client-handover-writer.md" 'handover-doc-writer", model="opus"' +# 18) BDR-077 W5 — seo/geo 3-mode pipelines: collect/template = sonnet at +# the call site, judge = opus PIN (fail-safe direction: a forgotten +# override over-tiers, never downgrades judgment). Run-scoped signals +# handoff + completeness sentinel + fail-closed judge + dispatcher +# ERROR contract (mute/ERROR judge never carried into templating). +# Body text unmoved — seo-data.test.sh fetch-wiring locks survive. +has "agents/seo-analyzer.md" 'MODE: collect' +has "agents/seo-analyzer.md" 'MODE: judge' +has "agents/seo-analyzer.md" 'MODE: template' +has "agents/seo-analyzer.md" 'COLLECTION COMPLETE' +has "agents/geo-analyzer.md" 'MODE: collect' +has "agents/geo-analyzer.md" 'MODE: judge' +has "agents/geo-analyzer.md" 'MODE: template' +has "agents/geo-analyzer.md" 'COLLECTION COMPLETE' +has "skills/seo/SKILL.md" 'MODE: collect' +has "skills/seo/SKILL.md" 'seo-analyzer", model="sonnet"' +has "skills/seo/SKILL.md" 'never re-derive a score' +has "skills/seo/SKILL.md" 'DISPATCHER ERROR CONTRACT' +has "skills/geo/SKILL.md" 'MODE: collect' +has "skills/geo/SKILL.md" 'geo-analyzer", model="sonnet"' printf 'model-routing census: %d pass, %d fail\n' "$pass" "$fail" [ "$fail" -eq 0 ] diff --git a/skills/geo/SKILL.md b/skills/geo/SKILL.md index 2aae7a8..f988cac 100644 --- a/skills/geo/SKILL.md +++ b/skills/geo/SKILL.md @@ -36,26 +36,50 @@ terminated by `READY TO APPLY — awaiting dispatcher confirmation`, and this skill applies it. Applying from here (one dispatch level, no nested spawn) is what makes fixes land on any Claude Code version. -## STEP 1 — Dispatch geo-analyzer (audit + bundle) +## STEP 1 — Run the geo pipeline (collect → judge → template, BDR-077) +Gather depth + business context HERE first (ask the user in this loop if +needed — a dispatched agent cannot ask). Mint `RUNID=$(date +%s)-geo`. +Pass the same CONTEXT block ($ARGUMENTS + gathered context) VERBATIM to +every phase (LRN-126). Clean `.audit/geo-signals-.md` after apply. + +**A — collect (sonnet):** +``` +Agent(subagent_type="geo-analyzer", model="sonnet") +prompt: "MODE: collect +RUNID: +Dispatched from /geo. Context: +Execute STEP 0-5 per your spec, write the signals file + COLLECTION +COMPLETE sentinel, emit the COLLECT REPORT, stop." +``` + +**B — judge (opus pin, no override):** ``` Agent(subagent_type="geo-analyzer") -prompt: """ -Dispatched from /geo. Execute your full spec at -~/.claude/agents/geo-analyzer.md (STEP 0 onward — gather depth + business -context as needed; if you must ask the user, ask and I relay). +prompt: "MODE: judge +RUNID: +Context: +Load .audit/geo-signals-.md (fail closed per your spec), run STEP +6-12, report scoring + findings + action plan + triage batches." +``` +**ERROR CONTRACT:** `GEO JUDGE — VERDICT: ERROR(…)` or a mute judge → +STOP: no template, no apply. Surface verbatim, retry ONCE with a fresh +collect+judge, then escalate. Never carry a mute/ERROR judge into +templating. -Produce your report: -- If .claude/audits/SEO.md already exists → merge findings into its - §7 — Optimisation GEO / IA. -- Else write .claude/audits/GEO.md. - -Then emit the `## FIX BUNDLE` (STEP 13) terminated by the verbatim -`READY TO APPLY — awaiting dispatcher confirmation` sentinel. Do NOT apply -any fix and do NOT dispatch any sub-agent — /geo applies your bundle. - -$ARGUMENTS -""" +**C — template (sonnet):** +``` +Agent(subagent_type="geo-analyzer", model="sonnet") +prompt: "MODE: template +Context: +JUDGE REPORT (verbatim, ground truth — never re-derive a score): + +Run STEP 13-15. Produce your report: if .claude/audits/SEO.md already +exists → merge findings into its §7 — Optimisation GEO / IA; else write +.claude/audits/GEO.md. Then emit the `## FIX BUNDLE` terminated by the +verbatim `READY TO APPLY — awaiting dispatcher confirmation` sentinel. +Do NOT apply any fix and do NOT dispatch any sub-agent — /geo applies +your bundle." ``` ## STEP 1b — CHALLENGE THE FIX BUNDLE (advisory, before apply) diff --git a/skills/seo/SKILL.md b/skills/seo/SKILL.md index 50b02e5..843bf6b 100644 --- a/skills/seo/SKILL.md +++ b/skills/seo/SKILL.md @@ -302,14 +302,31 @@ still carries this rule for its applier: If `Edit` is insufficient (full-template refactor), the item is escalated as a cross-agent note → §11 user action instead. -## STEP 1 — Spawn both agents IN PARALLEL +## STEP 1 — Run both domain pipelines (3 phases; domains parallel per phase) -Issue both `Agent` tool calls **in the same message** (parallel tool -calls). The harness runs them concurrently. +BDR-077: each domain runs collect (sonnet) → judge (opus pin) → template +(sonnet), the two domains IN PARALLEL at every phase (both `Agent` calls +in the same message). Mint run ids first: `RUNID_SEO=$(date +%s)-seo`, +`RUNID_GEO=$(date +%s)-geo`. The CONTEXT payloads below are the SHARED +CONTEXT of each domain — pass them VERBATIM to every phase dispatch of +that domain (LRN-126). Clean both `.audit/*-signals-*.md` files after +STEP 2. + +**DISPATCHER ERROR CONTRACT (fail-closed at the pipeline, not just the +judge):** a `SEO JUDGE — VERDICT: ERROR(…)` / `GEO JUDGE — VERDICT: +ERROR(…)`, a mute judge, or a BLOCKED collect → STOP that domain: NO +template dispatch, NO L1 apply for it. Surface the error verbatim, retry +ONCE with a fresh collect+judge for that domain; a 2nd failure → +escalate to the human. A mute or ERROR judge is NEVER carried into +templating. + +**PHASE A — collect (both domains, one message):** ``` -Agent(subagent_type="seo-analyzer") +Agent(subagent_type="seo-analyzer", model="sonnet") prompt: """ +MODE: collect +RUNID: Dispatched from /seo. Context: AUDIT DEPTH: @@ -379,18 +396,15 @@ SHARED-FILE EDIT DISCIPLINE (carried into each bundle item): legal pages, new city/service pages. - If full-template refactor is needed, emit as a cross-agent note → §11. -Execute your agent spec at ~/.claude/agents/seo-analyzer.md starting -at STEP 2 (skip STEP 0 and STEP 1 — context is provided above). - -At STEP 13, emit the STRUCTURED ENVELOPE for merging (not a standalone -SEO.md), INCLUDING the `## FIX BUNDLE` section terminated by the verbatim -`READY TO APPLY — awaiting dispatcher confirmation` sentinel. Do NOT apply -any fix, do NOT dispatch any sub-agent, do NOT write SEO.md — /seo applies -your bundle in STEP 1.5 and merges the reports. +Execute MODE: collect per your spec — STEP 2-5 only (context above +replaces STEP 0-1). Write the signals file + COLLECTION COMPLETE +sentinel, emit the COLLECT REPORT, stop. No scoring, no bundle. """ -Agent(subagent_type="geo-analyzer") +Agent(subagent_type="geo-analyzer", model="sonnet") prompt: """ +MODE: collect +RUNID: Dispatched from /seo. Context: AUDIT DEPTH: @@ -436,17 +450,62 @@ SHARED-FILE EDIT DISCIPLINE (carried into each bundle item): llms-full.txt. - If full-template refactor is needed, emit as a cross-agent note → §11. -Execute your agent spec at ~/.claude/agents/geo-analyzer.md starting -at STEP 2 (skip STEP 0 and STEP 1 — context is provided above). - -At STEP 14, emit the STRUCTURED ENVELOPE for merging (not a standalone -GEO.md), INCLUDING the `## FIX BUNDLE` section terminated by the verbatim -`READY TO APPLY — awaiting dispatcher confirmation` sentinel. Do NOT apply -any fix, do NOT dispatch any sub-agent, do NOT write GEO.md/SEO.md — /seo -applies your bundle in STEP 1.5 and merges the reports. +Execute MODE: collect per your spec — STEP 2-5 only (context above +replaces STEP 0-1). Write the signals file + COLLECTION COMPLETE +sentinel, emit the COLLECT REPORT, stop. No scoring, no bundle. """ ``` +**PHASE B — judge (both domains, one message, AFTER both COLLECT REPORTs +are DONE):** no `model=` override — the opus frontmatter pins apply. + +``` +Agent(subagent_type="seo-analyzer") +prompt: "MODE: judge +RUNID: + +Load .audit/seo-signals-.md (fail closed per your spec), run +STEP 6-11, report scoring + findings + action plan + triage batches." + +Agent(subagent_type="geo-analyzer") +prompt: "MODE: judge +RUNID: + +Load .audit/geo-signals-.md (fail closed per your spec), run +STEP 6-12, report scoring + findings + action plan + triage batches." +``` + +Apply the DISPATCHER ERROR CONTRACT above on each returned verdict. + +**PHASE C — template (both domains, one message, only for domains whose +judge is DONE):** + +``` +Agent(subagent_type="seo-analyzer", model="sonnet") +prompt: "MODE: template + +JUDGE REPORT (verbatim, ground truth — never re-derive a score): + +Run STEP 12-14: emit the STRUCTURED ENVELOPE for merging (not a +standalone SEO.md), INCLUDING the `## FIX BUNDLE` section terminated by +the verbatim `READY TO APPLY — awaiting dispatcher confirmation` +sentinel. Do NOT apply any fix, do NOT dispatch any sub-agent, do NOT +write SEO.md — /seo applies your bundle in STEP 1.5 and merges the +reports." + +Agent(subagent_type="geo-analyzer", model="sonnet") +prompt: "MODE: template + +JUDGE REPORT (verbatim, ground truth — never re-derive a score): + +Run STEP 13-15: emit the STRUCTURED ENVELOPE for merging (not a +standalone GEO.md), INCLUDING the `## FIX BUNDLE` section terminated by +the verbatim `READY TO APPLY — awaiting dispatcher confirmation` +sentinel. Do NOT apply any fix, do NOT dispatch any sub-agent, do NOT +write GEO.md/SEO.md — /seo applies your bundle in STEP 1.5 and merges +the reports." +``` + ## STEP 1b — CHALLENGE THE FIX BUNDLE (advisory, before apply) Both envelopes now carry a `## FIX BUNDLE` — worth attacking before any edit lands. **Skip if intervention mode = conservative** (nothing is applied). Else persist both