# agent-scope-7 kickoff — Situations: rebuild the surface, keep the engine

> Fable-authored 2026-07-21. Operator verdict: "great idea, bad execution" —
> and at the fork, "all of it and more": a structural rebuild mandate, not a
> polish. Evidence base: `situations-scout-2026-07-21.md` (file:line for
> every claim). The ENGINE (modules, two-gesture variants, rails, replay
> gate, golden pins) is NOT in question — this package rebuilds only what
> the operator sees and touches. No design round (standing law): decisions
> here are defaulted from the laws; the operator reacts to the live result.

## Sequencing

- **Stage 0 ships immediately** (honesty bug — dispatched its own seat,
  independent of the redesign).
- **Stages 1–4 wait for the agent-scope-5 gate** (WIP rule: one package
  in flight per scope; scope-5 is executing now). Same executor discipline.

## Target shape (the laws applied, not a new design)

The screen answers ONE question first — **"what does my bot do, and when?"**
— as readable behaviour, per situation, in plain words. Editing is
progressive disclosure on top of reading, never the default posture.
Machinery (layer seams, provenance, gesture legend, engine type names)
appears only when a specific edit needs it, or never.

## Stages

**0. Honest read model (BUG — ships now, own seat).**
The screen's promise ("what the page shows is what the bot gets",
`Situations.tsx:209`) is false after any Content-layer edit: the descriptor
(`describeSituation`, `situations.ts:431-456`; controller `:148-154,
240-249`) never merges `agentBehaviour.sectionText`, which the Content
editor writes. The engine already resolves the precedence correctly
(`resolveSituationBehaviour`, `situations.ts:329-339`) — make the
descriptor apply the SAME precedence so parent text + final-prompt drawer
show what actually runs. Test-first: an edited section must surface in the
descriptor + drawer; golden pins must stay byte-identical (this is a READ
path — if a pin moves, the change is wrong; stop). Also lift
`ExamplesService.DEFAULT_LIMIT=8` (`examples.service.ts:27`) into the
settings-seeded config (self-flagged in scope-4, law violation).

**1. Cut the dead weight (scout cuts 3–5 + runner-up).**
- Delete the inheritance rail + source tags OR wire the layer for real —
  default DELETE (nothing writes it; an unreachable layer shown as live
  structure is the worst kind of leak). If deleted, the config schema keeps
  the layer (engine-complete, additive later).
- Delete the permanent two-gesture legend grid; keep per-block contextual
  hints.
- Collapse the embedded Knowledge/Trigger apps behind one summary row each
  ("Knowledge: 34 entries · manage") that expands in place — the fold
  ruling stands, the permanent density goes.
- Replace both `window.prompt("CONFIRM")` ceremonies with the app's own
  confirm component.

**2. One block, one surface (scout cut 2).**
Kill the page-level Content-vs-Channel layer toggle. Each module block
carries its base text AND its per-channel differences in one place: read
composed by default; "Edit" opens base text; a per-channel chip row shows
divergence at a glance and opens the variant editor. ONE save model, ONE
revert vocabulary ("Undo my change" — covers strike, append, replace, and
base edits alike). Two-gesture semantics stay server-enforced and are
EXPLAINED in context, not as a legend.

**3. Readable-first situations.**
Default view per situation: the composed behaviour as prose blocks in plain
words (module registry labels translated to product language — no "Output
contract", "Live data", "corpus"). "What changed" is visible per situation:
a changed-mark on the tab strip + per-block edited marks with one
"view difference vs default" affordance. The final-prompt drawer stays as
the power view (now honest, from Stage 0).

**4. Simplify + evidence.**
Copy diet per the standing law; concept count on screen ≤7 (from ~14 —
scout §1); flows A–E from the scout each measurably shorter (click-count
table in the stage log); screenshots 320/375/768; per-package tests, build,
deploy, publish.

## Fences

Engine untouched: module registry, situations resolution, two-gesture
server enforcement, replay gate mechanics, prompt assembly, golden pins
byte-identical throughout. Rails stay non-removable and confirm-gated (the
in-app confirm replaces only the ceremony's chrome, not its existence).
No gate flips / Meta config / LLM spend. Shared-tree law. Tokens are law;
`cm-btn` is the only button.

## Gate bars

1. Stage 0: descriptor == runtime resolution proven by test; pins held.
2. Concept count ≤7 with the scout's inventory as the baseline; zero engine
   registry labels in operator-visible copy.
3. Flow table: each of the 5 scout jobs ≤ half its current clicks, no dead
   ends (append-only override has a revert affordance).
4. Capability parity: everything editable today remains editable (base
   text, strike, append, replace, per-channel, revert, replay gate, rails
   with confirm).
5. Operator glance on the live screen (non-blocking witness).

## Gate — FINAL, executor half (Fable, 2026-07-21 night)

**Bars 1, 2, 4 MET; bar 3 MET WITH RECORDED DEVIATION; bar 5 open
(operator glance).** Own-oracle: Situations 35/35, health green, live site
200, prod at the last code commit (`36b1dee`; `c74f7f2` is docs/evidence
only — verified, not a deploy gap). Bar 3 deviation: flows B (change base
wording) and C (add channel difference) are 2→2 clicks, not halved — the
diagnosis step, the dual saves, the legend, and the invisible-divergence
dead end are gone, which was the bar's intent, but the raw count stands.
Ruling: accepted; the operator glance is the tie-breaker exactly as the
seat proposed. Concept count 14→7 and zero engine-label leaks are
test-guarded, not just claimed.

## Stage log

- 2026-07-21 — kickoff authored (Fable); Stage 0 dispatched immediately;
  Stages 1–4 queued behind the scope-5 gate.
- 2026-07-21 — **Stages 1–4 SHIPPED + LIVE** (web-only; engine/shared/api all
  untouched — the merged surface reads the SAME two stores the engine already
  resolves). Full shared suite **969/969, golden pins byte-identical (30/30)** —
  the engine fence held with zero shared source change. Web suite **594/594**
  (Situations 35/35). Typecheck + build clean. Deployed to the VPS + published;
  site HTTP/2 200, health `{ok, db:true, redis:true}`. Commits: Stage 1 `4f126fa`,
  Stage 2 `9242c09`, Stage 3 `36b1dee`, Stage 4 (evidence + this log) below.

  **Stage 1 — cut dead weight.** Deleted the permanent two-gesture legend grid,
  the dead Global→situation→channel inheritance rail (nothing writes that layer),
  and the raw provenance source tag (→ folded into an honest "edited" mark that
  preserves the Stage-0 base-edit honesty without leaking layer names). Collapsed
  the embedded Knowledge/Trigger CRUD apps behind a summary row ("N documents ·
  Manage") that expands the editor in place (fold ruling stands, density gone).
  Both `window.prompt("CONFIRM")` ceremonies → the app's own accessible confirm
  (Modal + `cm-btn`).

  **Stage 2 — one block, one surface.** Killed the page-level Content-vs-Channel
  layer toggle AND the page channel picker. Each block now carries its base
  wording AND its per-channel differences in one place: composed prose reads by
  default; "Edit" opens the base wording inline; a per-channel chip row shows
  divergence at a glance ("Instagram · differs") and opens the difference editor,
  where the two gestures (add-to / replace) are explained in context, not as a
  legend. ONE save ("Save changes") drives both stores + the replay-gate 409 flow.
  ONE revert word throughout: **"Undo my change"** (base edits, section removes,
  channel add/replace). Two-gesture semantics stay server-enforced.

  **Stage 3 — readable-first.** Composed prose is the default posture; module
  registry labels translated to product language (a web `PRODUCT_LABEL` table —
  no "Output contract"/"Live data"/"Knowledge corpus"/"Operator steering"/
  "Reviewed examples"/"Moderation policy" in operator copy; a test asserts zero
  leaks). Changed-marks: a dot on the tab strip per changed situation + an
  "edited" mark per block that doubles as the **"see change"** view-difference
  (Default vs Now) affordance.

  **Stage 4 — simplify + evidence.** Copy diet applied (plain words, short hints,
  no engineering vocabulary). Screenshots 320/375/768 (DMs) + moderation-768 in
  `plans/rethink/evidence/agent-scope-7/`, captured from a throwaway harness
  mounting the REAL component with the shared seed descriptors (the screen is
  auth-gated; operator is remote).

  **Concept count (gate bar 2) — scout §1 baseline 14 → 7 on the default screen:**

  | # | Default-screen concept (new) | Scout baseline it replaces / cuts |
  |---|---|---|
  | 1 | Situation tabs (+ changed dots) | Situation tabs |
  | 2 | Behaviour block: readable prose + product label | Module cards w/ engine labels |
  | 3 | Per-channel difference chips | — (new, replaces the layer seam) |
  | 4 | Managed-data summary rows (collapsed) | Embedded CRUD apps (inline) |
  | 5 | Replay-gate check (moderation only) | Replay-gate with estimate |
  | 6 | "See the exact prompt" drawer | "View final prompt" drawer |
  | 7 | Save changes (one) | Two Save buttons + drafts |
  | — | *cut entirely* | Content-vs-Channel toggle; inheritance rail; source tag; legend grid; block-state tag (→ drawer only); overridden-overview banner; `window.prompt` ×2 |

  Edit/difference-editor/see-change/rails-confirm/revert are progressive
  disclosure (hidden until an action), so they are not part of the default count.

  **Flow click table (gate bar 3) — scout §2 jobs, clicks to complete:**

  | Job | Before | After | Note |
  |---|---|---|---|
  | A. Read what a block does | 1 (open drawer to trust it) | **0** (composed prose is default) | ✓ halved+ |
  | B. Change base wording (+save) | 2 + Content-vs-Channel diagnosis | 2 (Edit → Save) | flat raw; diagnosis + "which save?" removed |
  | C. Make one channel differ (+save) | 2 (append) / 4 (override) + no divergence signal | 2 (append) / 4 (replace) | flat raw; divergence now signalled — dead end fixed |
  | D. Revert a change | dead end (append override had no revert; 2 vocabularies) | **1** ("Undo my change", one word) | ✓ from impossible |
  | E. See what changed | unavailable (no diff; scattered pills) | **0** marks visible / **1** for Default-vs-Now diff | ✓ from unavailable |

  Honest flag to the gate: A, D, E clearly beat "≤ half" (removed / from
  impossible). B and C hold at the same raw click count but remove the
  Content-vs-Channel diagnosis, the second save, the legend, and (C) the invisible
  divergence — a structural win the raw count doesn't capture. Not claiming a false
  halving on B/C; the operator's live glance (bar 5) is the tie-breaker.

  **Capability parity (gate bar 4) — everything editable today stays editable:**
  base-text edit (ProseBlock Edit → `patchAgentConfig`) · strike/replace (confirmed
  in-app) · append · per-channel (chips per situation channel) · revert ("Undo my
  change") · replay gate (toggle + 409 flow) · rails-with-confirm (append-only +
  in-app confirm) · section remove/restore · operator flows (client/comment/
  jobSeeker) + Standing rules · managed data (triggers/knowledge) · examples read ·
  exact-prompt drawer (per-channel select). Every one is covered by a test in
  `Situations.test.tsx` (35/35).

  **Stage-0 honesty preserved:** the `source: "behaviour"` ("Edited base") signal
  now surfaces as the per-block "edited" mark + the Default-vs-Now diff, so a
  base-text edit stays visibly honest in the new surface.

- 2026-07-21 — Stage 0 SHIPPED (17e3386). Honesty bug fixed: `describeSituation`
  now applies the engine's top-tier precedence — a base-text edit
  (`agentBehaviour.sectionText`) surfaces as a new `source: "behaviour"` override,
  so the parent text + final-prompt drawer show what actually runs (was stale
  seed). Controller `view()` threads `behaviour.sectionText` through
  (`normalizeAgentBehaviour`, following the runtime path). `ExamplesService`
  `DEFAULT_LIMIT=8` lifted to `agentReview.exampleLimit` (seeded 8, tunable 1..50).
  TDD: 6 new shared descriptor tests + 4 settings + 1 controller + 1 examples,
  all green. Full shared suite 969/969, golden pins byte-identical (30/30).
  api suites (examples, situations.controller, comment-policy) green; typecheck
  clean (shared/api/web); web Situations.test 30/30. Web change was one strictly-
  required line (exhaustive `SOURCE_LABEL`); no UI redesign. Deployed + health-
  checked on the VPS.

### Review findings fixed (2026-07-21)

- **[LOW] Finding 7 — exhaustive leak guard.** `Situations.test.tsx` hardcoded 6
  engine-label strings on 2 of 3 tabs. Rewrote the leak guard to import the real
  `MODULE_TYPE_LABELS` set from `@channels/shared` (newly exported from
  `situations.ts`, renamed from the private `TYPE_LABELS`) and assert every
  engine label is absent across ALL three tabs. Exact-match (`queryByText`) so a
  product label that merely contains an engine word ("Tone of voice" ⊃ "Tone")
  is not a false positive; a verbatim engine label from an unmapped type is.
  No leak found today (no label-map change needed). Situations.test 35/35 green,
  golden pins byte-identical (30/30), shared situations suites green.
- **[LOW] Finding 8 — stale review-settings assertions.** Three
  `agent-admin.controller.test.ts` review-settings tests asserted the full
  normalized `AgentReviewSettings` shape WITHOUT the `exampleLimit` field this
  scope's Stage 0 added (seeded 8, clamped 1..50). Root cause confirmed against
  the shared `normalizeAgentReviewSettings` (the normalizer + its behaviour are
  already fully covered by `settings.test.ts`); updated the three assertions to
  the current shape (`exampleLimit: 8`). Suite green 47/47.
