feat(agents): W5 seo/geo 3-mode pipelines — collect sonnet / judge opus pin / template sonnet (BDR-077)

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-<RUNID>.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.
This commit is contained in:
Bastien Chanot
2026-07-19 23:23:10 +02:00
parent 6886622ecf
commit 9e4ebb4cf4
5 changed files with 213 additions and 37 deletions
+33
View File
@@ -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-<RUNID>.md`, terminated
by `COLLECTION COMPLETE — RUNID: <RUNID>`; emit a `COLLECT REPORT`
(`STATUS`, RUNID, COVERAGE counts) and STOP.
- **`MODE: judge`** — opus frontmatter pin. Fail-closed load of
`.audit/geo-signals-<RUNID>.md` (absent / RUNID mismatch / missing
sentinel → `GEO JUDGE — VERDICT: ERROR(<reason>)`, 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 ## STEP 0 — AUDIT DEPTH
**First action.** If not already determined by a parent skill (`/seo` **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: <RUNID>` written, COLLECT REPORT emitted,
> stop. STEP 6-12 below are `MODE: judge` territory.
## STEP 6 — SCHEMA.ORG FOR AI `[both]` ## STEP 6 — SCHEMA.ORG FOR AI `[both]`
Load: `~/.claude/agents/resources/geo-schemas.md` 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]` ## STEP 13 — EMIT FIX BUNDLE `[both]`
**You do NOT apply fixes and you do NOT dispatch any sub-agent.** Same **You do NOT apply fixes and you do NOT dispatch any sub-agent.** Same
+40
View File
@@ -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-<RUNID>.md`,
terminated by the line `COLLECTION COMPLETE — RUNID: <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-<RUNID>.md`: absent, RUNID mismatch, or
missing `COLLECTION COMPLETE` sentinel → emit
`SEO JUDGE — VERDICT: ERROR(<reason>)` 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 ## STEP 0 — AUDIT DEPTH
**First action.** If a parent skill (`/seo` dispatcher) passed 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: <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]` ## STEP 6 — EXTERNAL PRESENCE AUDIT `[FULL only, local business only]`
**Skip if not a local business** (pure SaaS, content-only → jump to STEP 7). **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]` ## STEP 12 — EMIT FIX BUNDLE `[both]`
**You do NOT apply fixes and you do NOT dispatch any sub-agent.** Same **You do NOT apply fixes and you do NOT dispatch any sub-agent.** Same
+20
View File
@@ -140,6 +140,26 @@ has "agents/handover-doc-writer.md" 'MODE: render'
has "agents/handover-doc-writer.md" 'DRAFT COMPLETE' has "agents/handover-doc-writer.md" 'DRAFT COMPLETE'
has "agents/client-handover-writer.md" 'MODE: synthesize' has "agents/client-handover-writer.md" 'MODE: synthesize'
has "agents/client-handover-writer.md" 'handover-doc-writer", model="opus"' 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" printf 'model-routing census: %d pass, %d fail\n' "$pass" "$fail"
[ "$fail" -eq 0 ] [ "$fail" -eq 0 ]
+40 -16
View File
@@ -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) skill applies it. Applying from here (one dispatch level, no nested spawn)
is what makes fixes land on any Claude Code version. 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-<RUNID>.md` after apply.
**A — collect (sonnet):**
```
Agent(subagent_type="geo-analyzer", model="sonnet")
prompt: "MODE: collect
RUNID: <RUNID>
Dispatched from /geo. Context: <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") Agent(subagent_type="geo-analyzer")
prompt: """ prompt: "MODE: judge
Dispatched from /geo. Execute your full spec at RUNID: <RUNID>
~/.claude/agents/geo-analyzer.md (STEP 0 onward — gather depth + business Context: <CONTEXT>
context as needed; if you must ask the user, ask and I relay). Load .audit/geo-signals-<RUNID>.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: **C — template (sonnet):**
- If .claude/audits/SEO.md already exists → merge findings into its ```
§7 — Optimisation GEO / IA. Agent(subagent_type="geo-analyzer", model="sonnet")
- Else write .claude/audits/GEO.md. prompt: "MODE: template
Context: <CONTEXT>
Then emit the `## FIX BUNDLE` (STEP 13) terminated by the verbatim JUDGE REPORT (verbatim, ground truth — never re-derive a score):
`READY TO APPLY — awaiting dispatcher confirmation` sentinel. Do NOT apply <the judge report>
any fix and do NOT dispatch any sub-agent — /geo applies your bundle. Run STEP 13-15. Produce your report: if .claude/audits/SEO.md already
exists → merge findings into its §7 — Optimisation GEO / IA; else write
$ARGUMENTS .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) ## STEP 1b — CHALLENGE THE FIX BUNDLE (advisory, before apply)
+80 -21
View File
@@ -302,14 +302,31 @@ still carries this rule for its applier:
If `Edit` is insufficient (full-template refactor), the item is escalated If `Edit` is insufficient (full-template refactor), the item is escalated
as a cross-agent note → §11 user action instead. 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 BDR-077: each domain runs collect (sonnet) → judge (opus pin) → template
calls). The harness runs them concurrently. (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: """ prompt: """
MODE: collect
RUNID: <RUNID_SEO>
Dispatched from /seo. Context: Dispatched from /seo. Context:
AUDIT DEPTH: <LOCAL|FULL> AUDIT DEPTH: <LOCAL|FULL>
@@ -379,18 +396,15 @@ SHARED-FILE EDIT DISCIPLINE (carried into each bundle item):
legal pages, new city/service pages. legal pages, new city/service pages.
- If full-template refactor is needed, emit as a cross-agent note → §11. - If full-template refactor is needed, emit as a cross-agent note → §11.
Execute your agent spec at ~/.claude/agents/seo-analyzer.md starting Execute MODE: collect per your spec — STEP 2-5 only (context above
at STEP 2 (skip STEP 0 and STEP 1 — context is provided above). replaces STEP 0-1). Write the signals file + COLLECTION COMPLETE
sentinel, emit the COLLECT REPORT, stop. No scoring, no bundle.
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.
""" """
Agent(subagent_type="geo-analyzer") Agent(subagent_type="geo-analyzer", model="sonnet")
prompt: """ prompt: """
MODE: collect
RUNID: <RUNID_GEO>
Dispatched from /seo. Context: Dispatched from /seo. Context:
AUDIT DEPTH: <LOCAL|FULL> AUDIT DEPTH: <LOCAL|FULL>
@@ -436,17 +450,62 @@ SHARED-FILE EDIT DISCIPLINE (carried into each bundle item):
llms-full.txt. llms-full.txt.
- If full-template refactor is needed, emit as a cross-agent note → §11. - If full-template refactor is needed, emit as a cross-agent note → §11.
Execute your agent spec at ~/.claude/agents/geo-analyzer.md starting Execute MODE: collect per your spec — STEP 2-5 only (context above
at STEP 2 (skip STEP 0 and STEP 1 — context is provided above). replaces STEP 0-1). Write the signals file + COLLECTION COMPLETE
sentinel, emit the COLLECT REPORT, stop. No scoring, no bundle.
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.
""" """
``` ```
**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: <RUNID_SEO>
<the seo SHARED CONTEXT verbatim>
Load .audit/seo-signals-<RUNID_SEO>.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: <RUNID_GEO>
<the geo SHARED CONTEXT verbatim>
Load .audit/geo-signals-<RUNID_GEO>.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
<the seo SHARED CONTEXT verbatim>
JUDGE REPORT (verbatim, ground truth — never re-derive a score):
<the seo judge report>
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
<the geo SHARED CONTEXT verbatim>
JUDGE REPORT (verbatim, ground truth — never re-derive a score):
<the geo judge report>
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) ## STEP 1b — CHALLENGE THE FIX BUNDLE (advisory, before apply)
Both envelopes now carry a `## FIX BUNDLE` — worth attacking before any edit lands. Both envelopes now carry a `## FIX BUNDLE` — worth attacking before any edit lands.
**Skip if intervention mode = conservative** (nothing is applied). Else persist both **Skip if intervention mode = conservative** (nothing is applied). Else persist both