# 544: Agent Reflection Loop — durable kernel **Issue:** [#544](http://git.mosaicstack.dev/mosaicstack/stack/issues/544) **PRD:** [`docs/plans/agent-reflection-loop-PRD.md`](../plans/agent-reflection-loop-PRD.md) **Branch:** `feat/agent-reflection-loop` ## Context Build the **durable kernel** of the agent reflection loop: passive end-of-run capture of the doer's end-state as structured `reflection.v1` data, plus a deterministic diff **review risk-floor**. The closed calibration / skill-synthesis loop (design §7–§8) stays **gated** behind Phase-0 experiments P1/P2/P3 and is explicitly out of scope here. Source design: jarvis-brain `docs/planning/AGENT-REFLECTION-LOOP.md` (debate-hardened v2). Scope rule, non-goals, the full `reflection.v1` field list, and acceptance criteria live in the PRD. This file is the task breakdown + status. ## Work items | # | Item | Path | Status | | --- | ----------------------------------------------------- | --------------------------------------------------------- | ------ | | 1 | Diff risk-floor (pure, deterministic) + unit tests | `packages/macp/src/risk-floor.ts`, `risk-floor.spec.ts` | done | | 2 | `reflection.v1` JSON Schema (documented contract) | `packages/macp/src/schemas/reflection.v1.schema.json` | done | | 3 | `reflection.v1` zod schemas + self-report DTO + tests | `packages/types/src/reflection/*` | done | | 4 | Stop hook (fail-closed capture) | `packages/mosaic/framework/tools/qa/reflect-stop-hook.sh` | done | | 5 | Hook registration (`hooks.Stop`) | `packages/mosaic/framework/runtime/claude/settings.json` | done | | 6 | Phase-0 experiment harnesses (P1/P2/P3) | `scripts/analysis/reflect-*.sh` | done | ## Design decisions (this implementation) - **Mechanical vs self-reported split.** A bash Stop hook cannot author the agent's self-assessment, so it writes the mechanical fields (risk-floor verdict, `files_changed`, ids, provenance) and merges an optional agent-supplied `$REFLECTION_INPUT` self-report; absent/unreadable ⇒ those fields `null` and `provenance.degraded = true`. - **Risk-floor authority.** `evaluateRiskFloor` (TS, tested) is the source of truth. The hook ports the same surface table inline to avoid a node/build dependency on the hook path; the two are documented as kept in sync. - **Hook registration deviation.** `settings-overlays/` has no merge mechanism (docs-only), so a hooks overlay there would be inert. The Stop hook is registered in the canonical `runtime/claude/settings.json` — the same file the `mosaic` launcher reflects into `~/.claude/settings.json`. Still vendored in-repo. - **DTO without class-transformer.** `reflection.dto.ts` uses class-validator only (no `@Type`), matching `chat.dto.ts`, so the module imports without a `reflect-metadata` shim in the types-package test env. Deep nested validation is owned by the zod `ReflectionSelfReportSchema` (the runtime authority the hook uses). - **`.mosaic/` excluded** from the change surface — it is agent scratch (reflections, locks, self-report input), not part of the diff under review. ## Verification - `pnpm --filter @mosaicstack/macp test` → 88 passed (15 new risk-floor). - `pnpm --filter @mosaicstack/types test` → 64 passed (10 new reflection). - Root `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, `pnpm build` → green. - Stop hook smoke: fail-closed no-op (mode unset), solo capture (degraded), self-report merge (degraded=false), re-fire lock guard — all pass. - All bash (hook + 3 Phase-0 scripts) shellcheck-clean; Phase-0 scripts emit structured JSON/markdown and print their pre-registered kill conditions. ## Activation (post-merge, deployment concern — not a blocker) The Stop hook only activates when a launcher/profile sets `REFLECTION_MODE=solo|orchestrated`; unset/`off` is a strict no-op, so global registration is safe. `framework/install.sh` rsyncs the hook into `~/.config/mosaic/tools/qa/`, and the `mosaic` launcher reflects the updated `settings.json` (`hooks.Stop`) into `~/.claude/settings.json`.