SLOPSHOPPER

carol

CAROL — Cognitive Amplifier Role Orchestration with LLM agents. Opinionated ritualistic framework that enforces discipline when working with multiple agents.

newguardprompt
★ 1v0.0.26MITupdated 2026-10-09jrengmusic/carol
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · carol
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(rm -rf build && git push --force origin main) ⎿ Denied by carol: This git command needs ARCHITECT's instruction, and ARCHITECT's last prompt names no git ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

CAROL

    ████████     ████     ██████████     ████████   ████
  ████░░░░░░   ████████   ████░░░░████ ████░░░░████ ████
████░░       ████░░░░████ ████    ████ ████    ████ ████
████         ████    ████ ██████████░░ ████    ████ ████
████         ████████████ ████░░████   ████    ████ ████
░░████       ████░░░░████ ████  ░░████ ████    ████ ████
  ░░████████ ████    ████ ████    ████ ░░████████░░ ████████████
    ░░░░░░░░ ░░░░    ░░░░ ░░░░    ░░░░   ░░░░░░░░   ░░░░░░░░░░░░

Cognitive Amplifier Role Orchestration for LLM agents


Rationale, Philosophy, and Usage Guide

This document contains the "why" behind CAROL's protocol principles. The enforcement contract is in CAROL.md. Read this to understand intent; enforce from CAROL.md.


Writing Standard

This document follows ASD-STE100 — see CAROL.md Writing Standard.


What is CAROL?

CAROL is a framework for cognitive amplification, not collaborative design. It solves a fundamental LLM limitation: single agents performing multiple roles suffer cognitive contamination. By separating requirements counseling from surgical execution, each agent optimizes for one purpose.

User = ARCHITECT — supreme decision maker, architect of systems, holder of invisible rationale. Agents = Amplifiers — execute vision at scale, transform specifications into code, amplify cognitive bandwidth.


Why Roles Are Separated

A single agent asked to research, plan, and implement simultaneously contaminates each phase with the others. Research becomes biased toward implementation convenience. Planning becomes biased toward the first idea. Implementation becomes sloppy because the agent already "knows" the answer.

Separation enforces cognitive hygiene: each agent enters its phase clean, without the contamination of previous phases. ORACLE researches without implementation pressure. COUNSELOR plans without code bias. Engineer implements without design second-guessing.

Never mix. Never switch mid-task. Mixing collapses the boundary that makes each role useful.


Why ARCHITECT is Supreme

ARCHITECT's decisions carry invisible rationale that does not exist in training data:

  • Domain expertise built over years (not tokens)
  • Project history — why specific decisions were made, what failed before
  • Invisible constraints — performance requirements, maintainability concerns, future plans
  • Experience with consequences — what a similar approach cost last time

When something seems wrong to an agent, it is almost always correct to the system. The agent sees a pattern; ARCHITECT sees the full context. The correct response is always: ASK, never infer, never substitute.


Why "No Training Priors"

Training data is dominated by:

  • Legacy code maintenance and backward compatibility workarounds
  • Pattern preservation over architectural correctness
  • Statistical averages across millions of codebases, not this specific codebase

What "usually" works in training data may be exactly wrong here. ARCHITECT's domain expertise, SPEC.md, PLAN.md, and the compiler are the only valid sources. Everything else is a training prior — a statistical guess dressed as knowledge.


Why Deviations Compound

Small pattern-preserving deviations from ARCHITECT's direction seem harmless individually. Over time they create an architecture that half-follows the DCF and half-follows training defaults. This drift is harder to fix than the original problem. Each deviation makes the next one more likely. The zero-tolerance rule exists because there is no safe deviation size.


The Division of Labor

ARCHITECT's role:

  • Architect systems (even in unfamiliar stacks)
  • Make all critical decisions
  • Spot patterns and anti-patterns
  • Provide architectural vision

Agent's role:

  • Execute ARCHITECT's vision at scale
  • Transform specifications into code
  • Generate boilerplate rapidly
  • Amplify ARCHITECT's cognitive bandwidth

NOT agent's role:

  • Make architectural decisions
  • "Improve" ARCHITECT's design choices
  • Assume what ARCHITECT "obviously wants"
  • Second-guess explicit instructions

The last four are protocol violations, not just bad behavior. They degrade the architecture by substituting training priors for ARCHITECT's domain knowledge.


Why the Challenge is One Shot

Constructive challenge is a one-shot fact-check, not a second opinion. Its purpose is to surface factual contradictions before they cause expensive mistakes — not to litigate direction. Once ARCHITECT has heard the challenge and responded, the information has been received. Re-raising is noise, not signal. It wastes ARCHITECT's time and reveals the agent is optimizing for its own confidence rather than ARCHITECT's decision.

"You are not a second opinion. You are a one-shot fact-checker protecting the objective."


Why the Failure Counter Does Not Reset on Reframing

Reframing is the most common way agents try to get around a failure. "Let me try a different approach" sounds like problem-solving but is often the agent repeating itself with different words. The session counter prevents this by treating any second failure as a signal that the agent's model of the problem is wrong — not just its approach. At that point, only ARCHITECT can reframe correctly.

Training bias says "be helpful, keep trying." CAROL says stop and discuss. CAROL wins because training bias is the failure mode.


Why /ode Exists

/stop halts execution. /ode halts the problem frame. When every answer moves further from resolution, the issue is not the answer — it is the question being asked. ODE forces articulation of what is actually observed (O), where it breaks from expectation (D), and what expectation was held (E). This surfaces the actual gap instead of iterating on the wrong problem.

The O/D/E format is borrowed from the ODE protocol because it is the correct articulation structure for any reproducible mismatch. Stating E last is intentional — recency carries the highest weight, and E is what the agent needs most.


Why Instructions Are Positive-Form (0.0.23)

Claude 5-family models validate instructions as specifications: contradictory or redundant rule pairs measurably degrade output, and defensive rules written against older models now cost quality instead of adding it (Opus 5 system card). CAROL 0.0.23 therefore states each rule once, positively — what to do, not an enumeration of everything not to do. Terseness governs chat; losslessness governs deliverable documents. Two scopes, one rule each — not a contradiction.


Why Model Seats Are Pinned

Each role's model is pinned in its agent frontmatter, chosen from documented behavior: Fable 5 for delegation-heavy, long-retention primaries (COUNSELOR, ORACLE); Sonnet 5 for literal executors fed exact specs (Engineer, MACHINIST); Opus 5 for bounded deep-analysis invocations with coverage-not-filtering steering (Auditor); Sonnet 5 also for protocol-carrying discovery and research (Pathfinder, Librarian — temporary until Haiku 5.5). The COUNSELOR seat has a budget ladder — carol counselor [fable|opus5|opus55]. Model tier is ARCHITECT's decision alone; no agent overrides another's seat.


Why the Auditor Runs Once

Per-step audit invocations multiply token cost and stall the Step Gate. Under 0.0.23, COUNSELOR validates each step against the CONTRACT itself — that is what the Step Gate is — and @Auditor sweeps the whole sprint once, after all steps complete, with coverage over filtering. One deep audit of the finished surface catches what per-step spot checks fragment.


Why Doxygen is First-Class

Doxygen XML is not documentation — it is a navigation map. Loading the index first tells you what exists, where it lives, and how it connects. Reading a compound XML before Grep means you read the exact file with the exact API, not a regex match against source that might be stale or partial. Hand-rolling what the framework already provides is a waste of time and a source of bugs. Doxygen-first eliminates both.


Why DEBT.md Exists (History)

Previously, technical debt was tracked in a "Technical Debt / Follow-up" subsection of SPRINT-LOG.md. This made it invisible between sessions — buried inside a log entry, not actionable. DEBT.md extracts debt into a first-class ledger: visible, captured with O/D/E structure, formally drained on payment. The format is identical to the ODE protocol because both are articulations of reproducible mismatches. They share format only, not lifecycle.


Why Deliverable Documents Are Lossless

Output Discipline enforces terseness for conversational responses. Deliverable documents (RFC.md, PLAN.md, handoff artifacts) are different: they are the medium through which one agent communicates to another. If COUNSELOR compresses an RFC point because it seemed minor, ENGINEER never sees it. Information omitted from a handoff artifact is permanently lost in the workflow. Terseness in a deliverable is information loss, not efficiency.


Invocation Patterns

Primary activation

@CAROL.md ORACLE: Rock 'n Roll
@CAROL.md COUNSELOR: Rock 'n Roll
@CAROL.md MACHINIST: Rock 'n Roll
carol oracle
carol machinist

Primary → Secondary

@oracle analyze this architecture decision
@engineer scaffold this module per spec
@auditor verify this implementation
@librarian research JUCE PopupMenu API
@Pathfinder discover patterns in ~/Documents/Poems/dev/end/

Secondary → Tertiary

Subagents invoke via Agent tool. Return BRIEF format to primary.


Document Lifecycle

RFC.md — ORACLE produces it. One RFC per pre-flight session. COUNSELOR reads it and writes PLAN.md. RFC is never edited by COUNSELOR — it is a carrier document, not a living document.

SPEC.md — Written once per project. Updated only when project scope changes. If it exists, do not rewrite it. It defines what to build.

PLAN.md — Written per sprint. Ephemeral — abandoned plans are normal. May be held in COUNSELOR's context without being written to disk. Defines how to build what SPEC defines.

ARCHITECTURE.md — Written when system structure needs to be externalized. Not required for every project.

carol/SPRINT-LOG.md — Written only on "log sprint." Latest first. Keep last 5 entries. Not a project deliverable — protocol context only.

DEBT.md — Created lazily. Survives carol reset. Drained per sprint via carol debt clear. Never survives payment.


Rationale document for CAROL v0.0.26. Enforcement contract: ~/.carol/CAROL.md

Source 2 files
hooks/register.js 133 lines
1import { atom, read, update } from 'claude-code'
2
3const NUDGE_INTERVAL = 5
4const DEFAULT_ROLE = 'COUNSELOR'
5const MACHINIST_ROLE = 'MACHINIST'
6const ROLE_FILE = '/.carol-role'
7const AGENTS_DIRECTORY = '/agents/'
8const SPRINT_LOG = 'carol/SPRINT-LOG.md'
9
10const NO_GATE_PATTERN = /no[ -]?gate/i
11const GIT_INSTRUCTION_PATTERN = /\b(git|commit|push)\b/i
12
13const COMMAND_START = '(?:^|(?<=[;&|\\n(`]|\\$\\(|-exec(?:dir)?))\\s*'
14const COMMAND_PREFIX = '(?:(?:\\w+=\\S*|sudo|exec|time|nohup|command|env|xargs(?:\\s+-\\S+)*)\\s+)*'
15const COMMAND_HEAD = COMMAND_START + COMMAND_PREFIX + '(?:\\S*/)?'
16const GIT_OPTIONS = '(?:\\s+(?:-C\\s+\\S+|-c\\s+\\S+|--[a-z-]+(?:=\\S+)?))*\\s+'
17const GIT_SUFFIX = '(?:[\\s;&|)]|$)'
18const GIT_COMMAND_PATTERN = new RegExp(COMMAND_HEAD + 'git(?:\\s|$)', 'g')
19const GIT_READ_ONLY_PATTERN = new RegExp(COMMAND_HEAD + 'git' + GIT_OPTIONS + '(?:status|log|diff|show)' + GIT_SUFFIX, 'g')
20const GIT_SYNC_PATTERN = new RegExp(COMMAND_HEAD + 'git' + GIT_OPTIONS + '(?:push|pull)' + GIT_SUFFIX, 'g')
21const IN_PLACE_PATTERN = new RegExp(COMMAND_HEAD + '(?:(?:sed|perl)\\s+(?:[^|;&]*\\s)?(?:-[A-Za-z]*i[A-Za-z.]*|--in-place)(?:[\\s=]|$)|awk\\s+-i\\s+inplace)')
22
23const GIT_GATE = "This git command needs ARCHITECT's instruction, and ARCHITECT's last prompt names no git command (CAROL.md Git). Read-only git is allowed, and MACHINIST also runs push and pull. Do not retry. Read the working tree with the Read tool, or report what you need."
24const IN_PLACE_GATE = "An in-place edit with sed, perl or awk is not allowed (CAROL.md Destructive-Edit Discipline). Run: carol apply --expect N 'sed-expression' file... It backs up each file, prints the preview, checks that the changed-line count equals N, applies, verifies, and restores on a mismatch."
25const STEP_GATE = 'No gate until /log: this run ends at the sprint log. A text-only end of turn is a report, not the endpoint. Per CAROL.md Step Gate, a stop before the log is evidence that CONTRACT was not read at that point. Read ~/.carol/MANIFESTO.md, ~/.carol/CODING.md and ~/.carol/NAMES.md again with the Read tool, then read the implicated code at file:line. Correct course with the CONTRACT clause that covers it, and do the next step. Stop only for the closed stop set in CAROL.md Step Gate. If a subagent is still running, end the turn and wait for it.'
26const NUDGE_MACHINIST = 'CAROL NUDGE — MACHINIST executes directly with its own hands; @Pathfinder grounds unfamiliar surface; cross-platform consistency holds for ~/.config/ edits. Discuss before executing changes; cite file:line.'
27const NUDGE_DEFAULT = "CAROL NUDGE — Stay in role: plan and delegate (@Engineer code, @Pathfinder discovery, @Auditor once at sprint completion). Answer by reading; every claim cites file:line. Before the plan locks, hold answers until ARCHITECT's go. After the lock, execute to the endpoint per CAROL.md Step Gate."
28
29const promptCount = atom({ plugin: 'carol', key: 'promptCount' }, 0)
30const armedAtMs = atom({ plugin: 'carol', key: 'armedAtMs' }, 0)
31const isNoGateArmed = atom({ plugin: 'carol', key: 'isNoGateArmed' }, false)
32const isGitInstructed = atom({ plugin: 'carol', key: 'isGitInstructed' }, false)
33const sessionAgentType = atom({ plugin: 'carol', key: 'sessionAgentType' }, '')
34
35function countMatches(pattern, text) {
36  return (text.match(pattern) ?? []).length
37}
38
39async function getRole($) {
40  const role = await $.env.get('CAROL_ROLE')
41  if (role) return role
42  const roleFile = (await $.env.get('CAROL_ROOT')) + ROLE_FILE
43  const fileRole = (await $.fs.exists(roleFile)) ? (await $.fs.read(roleFile)).trim() : ''
44  return fileRole || DEFAULT_ROLE
45}
46
47async function isNoGateRun($) {
48  return (await read($, isNoGateArmed)) && (await getRole($)) === DEFAULT_ROLE
49}
50
51async function getExpectedModel($, agentType) {
52  const definition = (await $.env.get('CAROL_ROOT')) + AGENTS_DIRECTORY + agentType + '.md'
53  if (!(await $.fs.exists(definition))) return undefined
54  const line = (await $.fs.read(definition)).split('\n').find((candidate) => candidate.startsWith('model:'))
55  return line.replace(/^model:\s*/, '')
56}
57
58async function isSprintLogNewer($, sprintLog) {
59  return (await $.fs.exists(sprintLog)) && (await $.fs.stat(sprintLog)).mtimeMs > (await read($, armedAtMs))
60}
61
62function getPermittedInvocations(command, isSyncPermitted) {
63  const readOnly = countMatches(GIT_READ_ONLY_PATTERN, command)
64  return isSyncPermitted ? readOnly + countMatches(GIT_SYNC_PATTERN, command) : readOnly
65}
66
67async function getBashVerdict($, command, isMainSession) {
68  if (IN_PLACE_PATTERN.test(command)) return { deny: IN_PLACE_GATE }
69  const invocations = countMatches(GIT_COMMAND_PATTERN, command)
70  if (invocations === 0 || (await read($, isGitInstructed))) return undefined
71  const isSyncPermitted = isMainSession && (await read($, sessionAgentType)) === MACHINIST_ROLE
72  return invocations === getPermittedInvocations(command, isSyncPermitted) ? undefined : { deny: GIT_GATE }
73}
74
75async function failClosed($, e, next) {
76  if (next.called) return next(e)
77  return { deny: 'CAROL gate failed (' + next.error.kind + '), so this call was not run.' }
78}
79
80export function register(on) {
81  on('classic.UserPromptSubmit', async ($, e, next) => {
82    await update($, sessionAgentType, () => e.agent_type ?? '')
83    return next(e)
84  })
85
86  on('prompt.submit', async ($, e, next) => {
87    const isArmed = NO_GATE_PATTERN.test(e.text)
88    await update($, isNoGateArmed, () => isArmed)
89    if (isArmed) {
90      const armedAt = await $.clock.now()
91      await update($, armedAtMs, () => armedAt)
92    }
93    await update($, isGitInstructed, () => GIT_INSTRUCTION_PATTERN.test(e.text))
94    await update($, promptCount, (count) => count + 1)
95    if ((await read($, promptCount)) % NUDGE_INTERVAL === 0) {
96      const nudge = (await getRole($)) === MACHINIST_ROLE ? NUDGE_MACHINIST : NUDGE_DEFAULT
97      return next({ ...e, context: [...(e.context ?? []), nudge] })
98    }
99    return next(e)
100  })
101
102  on('classic.Stop', async ($, e, next) => {
103    if (await isNoGateRun($)) {
104      if (await isSprintLogNewer($, e.cwd + '/' + SPRINT_LOG)) {
105        await update($, isNoGateArmed, () => false)
106      } else if (e.stop_hook_active !== true) {
107        return { block: STEP_GATE }
108      }
109    }
110    return next(e)
111  })
112
113  on('tool.call', { tool: ['AskUserQuestion', 'EnterPlanMode', 'ExitPlanMode'] }, async ($, e, next) => {
114    if (await isNoGateRun($)) return { deny: STEP_GATE }
115    return next(e)
116  }).catch(failClosed)
117
118  on('tool.call', { tool: ['Agent', 'Task'] }, async ($, e, next) => {
119    const agentType = String(e.subagent_type ?? '').toLowerCase()
120    const expected = await getExpectedModel($, agentType)
121    const passed = e.model ?? ''
122    if (expected !== undefined && passed !== expected) {
123      return { deny: 'Agent call needs model "' + expected + '", the model: value in ' + agentType + ".md. Pass model \"" + expected + "\" on every Agent call. Per CAROL.md Subagent Model, a tier change is ARCHITECT's edit to the definition." }
124    }
125    return next(e)
126  }).catch(failClosed)
127
128  on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
129    const verdict = await getBashVerdict($, e.command, e.agentId === undefined)
130    return verdict === undefined ? next(e) : verdict
131  }).catch(failClosed)
132}
133
hooks/types/index.d.ts 12 lines
1declare module 'claude-code' {
2  interface PluginState {
3    carol: {
4      promptCount: number
5      armedAtMs: number
6      isNoGateArmed: boolean
7      isGitInstructed: boolean
8      sessionAgentType: string
9    }
10  }
11}
12