# agent-scope-11 — Situations hierarchy redesign (Global → Situations)

> Kickoff: Fable, 2026-07-24. Design settled through THREE mockup rounds —
> approved surface: `design/tiles/situations-round-3/`. Authority: DECISIONS
> 2026-07-24 entries "situations hierarchy" + round-1/2/3 reactions. Status
> lives in `plans/rethink/README.md`. **QUEUED** behind the Agent scope's
> open packages (WIP rule); execution on the orchestrator's call.

## The locked frame (all ledgered, DECISIONS 2026-07-24)

- **Situations = the 3 pipeline surfaces**: Conversations · Comment
  replies · Moderation (renamed from "Comment moderation"). Business cases
  (job seeker, client interaction, arms) are NOT situations — they become
  flows with the AI-agent node (channels-scope-5).
- **"Mind" → "Global"** (the central mind). Tabbar shows parentage: filled
  Global tab + divider + "Situations" group label (round-3 ② approved).
- **Five standard headers ADOPTED**: Role · Goal · Tone · Knowledge ·
  Guardrails. Today's 7 headings (`instruction-headings.ts:29-37`)
  consolidate; Qualification + Offers retire as headers.
- **Flow-shaped seeded content comes OUT of situations NOW** (round-3 ①,
  reverses "parked stays composed"): moved to a **Parked for Flows** box —
  stored aside, visible, **NOT composed into prompts**. Safe because the
  Conversations situation is not in live use before cutover (DMs are
  ManyChat's). Each piece returns as a flow when flows ship.
- **Per-channel children are APPEND-ONLY, normally empty** — the overwrite
  ("Replace it") affordance is DELETED. Zero data migration: no stored
  channel override uses overwrite (`struck:true` only in tests — round-2
  scout, verify again at build time before deleting the code path).
- Fewer headers, plain sections — Global headers must NOT render like
  situation cards/rows.
- **Contact-knowledge system is NOT this package** — its design round
  follows `plans/rethink/contact-memory-research.md` separately.

## Stages (each: TDD, own oracle, independent review before gate)

**Stage 1 — read model + headers.** Consolidate 7 headings → the 5
standard headers in shared composition + seeds; retire Qualification/
Offers as headers (content re-homed under Goal/Knowledge per round-3
sheets). Oracle: shared tests green; **goldens WILL move — re-pin with a
per-change diff note in the stage log** (byte-identical is impossible
here; every moved golden must be explained, none unexplained).

**Stage 2 — Parked for Flows.** Storage for flow-shaped content lifted out
of situations (visible in UI, excluded from composition), lift-out of the
seeded Conversations content per the round-3 sheet. Oracle: composition
tests prove parked text absent from every composed prompt; parked items
render in the UI; a restore path exists (operator can pull one back).

**Stage 3 — surface.** Global tab + parentage tabbar, 3 situation tabs,
5-header layout, append-only channel children (Replace affordance
deleted), Moderation rename. Tokens are law; no raw controls. Oracle: web
tests + render pass at 320/375/768; zero engine-label leaks (scope-7
convention).

**Stage 4 — gate.** Full oracle sweep (lint/typecheck/builds/per-package
tests), deploy loop + health, web published. Board/README reconciled.

## Out of scope

Contact-knowledge (own design round) · flows themselves (channels-scope-5)
· replay-gate-on-base-edits policy (open operator question, board) ·
Stage 6c routing.ts retirement (agent-scope-4).

## Risks

- Golden churn is the meat: header consolidation rewrites composed
  prompts. The oracle discipline above (explained re-pins) is the gate bar.
- Live behaviour must NOT change for Comment replies / Moderation
  composition except the header consolidation itself — the lift-out only
  touches Conversations content (verify which situation each parked piece
  belongs to before moving it).

## Stage log

- 2026-07-24: kickoff written (Fable).

## Stage 1-3 — EXECUTED, unverified against production DB (executor seat, wip/agent-scope-11)

**Stage 1 — headings.** `INSTRUCTION_HEADINGS` (`packages/shared/src/agent/
instruction-headings.ts`) is now `["Role","Goal","Tone","Knowledge",
"Guardrails"]`. Mapping (`HEADING_BY_ID`/`HEADING_BY_TYPE`): Identity→Role;
Conversation, Qualification, Offers→Goal; Safety limits→Guardrails; Tone and
Knowledge unchanged. **No golden snapshot moved from Stage 1 alone** —
`situations-golden.test.ts` targets `buildSystemPrompt`, which joins section
`.text` only (never `.title`/heading), and the section-id-order pin asserts
ids, not headings — headings are invisible to that pin by construction.
Heading literals in test fixtures (`situations.test.ts`, `modules.test.ts`,
api's `situations.controller.test.ts`/`agent-admin.controller.test.ts`/
`comment-policy.service.test.ts`, web's `Situations.test.tsx`) were
mechanically renamed to the new 5 (old→new: Identity→Role,
Conversation/Qualification/Offers→Goal or Guardrails per the literal used,
Safety limits→Guardrails).

**Stage 2 — Parked for Flows.** New `PARKED_FOR_FLOWS` registry in
`modules.ts`: `dms: [job-seekers, lead-capture, lead-temperature,
qualification-slots, extra-contact, services, offering-choices]` — every
seeded module the old Qualification/Offers headers carried, all confirmed
members of `SITUATIONS.dms` (asserted in `modules.test.ts`). Comment-replies
and comment-moderation carry NOTHING parkable (unchanged). `assembleSituation`
now filters these ids out of the dms stack UNLESS `opts.restoredParked` names
them back in; `SituationsConfig.situations[dms].restoredParked` is the stored,
per-module restore list (bounded `z.array().max(20)` server-side). Wired into
`run-turn.ts` (the live DM turn) and `agent-admin.controller.ts`'s `prompt`
endpoint (the "under the hood" view) — both now read `situationRestoredParked`
so neither can drift from the engine.

**Golden diff table** (`situations-golden.test.ts.snap` + `channel-profile.
golden.test.ts.snap` + `routing-equivalence.golden.test.ts.snap`, 19 snapshots
moved, re-pinned with `-u`): every moved snapshot is an `external`/`dms`
prompt (whatsapp/instagram/facebook/web) and its ONLY change is the removal
of the 7 parked modules' text (services catalogue, job-seekers paragraph,
lead-capture paragraph, never-silent — unaffected, lead-temperature rubric,
qualification-slots paragraph, extra-contact paragraph, offering-choices
block). Comment/moderation snapshots: byte-identical, zero moved (Parked for
Flows is DMS-only by design). Section-id-order snapshots: the same 7 ids
disappear from the `external` id list; `comment`/`moderation` id lists
unchanged.

**Stored-overwrite verification (kickoff's explicit gate before deleting the
Replace UI): NOT independently verified from this seat.** This executor runs
in an isolated worktree with no VPS/production-DB access (the task explicitly
forbids deploy). The operator's own ruling stands unchanged (DECISIONS
2026-07-24 round-1: "I don't think there are overwrites"), and the engine's
existing `struck` semantics are UNCHANGED (a stored `struck:true` still
renders correctly — see the `Situations.test.tsx` "PRE-EXISTING struck
override" test) — only the UI CONTROL that creates a new one is removed, so
even if a live overwrite exists today it keeps rendering exactly as before;
nothing is silently dropped. Recommended before merge: a session with VPS
access run `SELECT value FROM "Setting" WHERE key = 'agentSituations'` and
grep the JSON for `"struck":true` to close this out with real data.

**Stage 3 — surface.** Global tab (filled + divider + "Situations" group
label) added to the tabbar; Global tab edits `config.global.modules` for
every non-parked seeded DMS module (identity/goal/triage/never-silent/pivots/
tone/boundaries), rendered as plain headed sections (no situation-card
chrome). "Comment moderation" → "Moderation". The per-channel "Replace it"
control is deleted from `ChannelVariantEditor` — channel children are
append-only; a pre-existing stored `struck` override still renders and
reverts via "Undo my change" (test-covered). Parked-for-Flows UI: a dashed
box lists a situation's parked modules with a Restore action; restoring moves
a module into its normal heading group and the composed drawer; "Move back
to Parked for Flows" undoes an unsaved restore within the session.

**Oracles run, all green:** `packages/shared` vitest (1061/1061),
`apps/api` vitest (1537/1537), `apps/web` vitest (670/670, includes the
token-conformance sweep), `tsc --noEmit` × 3 packages, `eslint src` (web —
0 errors, 7 pre-existing warnings unrelated to this work), `vite build`
(apps/web), `tsc` build × 3 (shared/api/web).

**Deviations / forks not answered by the kickoff, resolved conservatively:**
- Global tab's editable module set is a NEW web-side curation (dms's non-
  parked seeded modules) — the kickoff didn't specify which ids Global
  edits; chosen as "everything DMS still composes by default" so Global
  stays a strict superset-parent of what a situation can override.
- "Restore" is per-situation (matches the stored shape,
  `situations[id].restoredParked`) — the kickoff didn't say whether a
  restored module should also be un-restorable from the UI; added a "Move
  back to Parked for Flows" affordance for an UNSAVED restore only (a
  previously-saved restore has no re-park control yet — flagged as a
  possible Stage 4/follow-up gap, not required by the stated oracle).
- `FacebookChannelCard.tsx`/`InstagramChannelCard.tsx` still say "Comment
  moderation" (linking to the separate, still-present `/agent/behaviour`
  route) — left untouched: out of the kickoff's stated scope (Situations
  screen only), and touching an unrelated screen risked scope creep.

**Not done (this seat's task explicitly excluded it):** Stage 4 (gate — full
oracle sweep is done above, but deploy loop + health-check + web publish +
board/README reconciliation are NOT run — another package, channels-scope-5
Stage B, lands first per the orchestrator's plan).

## GATE: PASSED (orchestrator, 2026-07-24)

Landed on rethink at `0bf96e9` (rebase clean), deployed, health green, web
published. Independent review: CLEAN, no MAJORs. Own oracles on the MERGED
tree (with scope-12 + scope-5 Stage B): shared 1071/1071 · api 1560/1560 ·
web 696/696 · typecheck clean · vite build clean. **Stored-overwrite check
CLOSED with prod evidence** (orchestrator SQL: zero rows containing
`struck` in prod `settings`) — deleting the Replace affordance touched
nothing live. Reviewer byte-verified the golden table: comment/moderation
prompts untouched; every moved snapshot is external/dms missing exactly
the 7 parked ids.

Follow-ups (boarded): re-park control for a SAVED restore (today only an
unsaved-this-session restore can be re-parked; content isn't lost, just
becomes a normal editable module) · FacebookChannelCard/
InstagramChannelCard still say "Comment moderation" (host-sweep flag,
different route) · Global-tab curation (DMS non-parked seeded modules) is
a seat choice the kickoff didn't specify — reviewer confirmed nothing
became unreachable; operator glance rules on the taste.
