Building an Agent System
Inside Claude Code
How a solo developer turned Claude Code into a multi-agent development platform for a health-critical app with 19 features across iOS and Node.js.
01Live Demo — Start This Now
This prompt gets pasted into a fresh claude --agent ceeya-dev session. It runs in the background — by the end of this presentation, the full result will be ready.
Primary Demo Prompt
This triggers a Tier 1 task with specialist dispatch, visible hook execution, and structured output.
I want to add a "share your verdict" feature to the report card — let someone share their personalized verdict for a product as an image or link. Before implementing, check if this makes sense for the product, map the shared types that would need updating, and run a Codex cross-model review on the plan before presenting it to me.
What fires and why — step by step
What happens step by step:
- SessionStart hook — visible context injection: handoff from previous session + "This Week's Focus" priorities from guardian config
- Knowledge loading — ceeya-dev reads all 8 knowledge files (1,305 lines) before responding. 8+ Read tool calls fire in rapid succession.
- Brainstorming skill invocation — the model pauses implementation impulse and explores the feature space: what does "share" mean? Image? Link? Deep link? What privacy implications for sharing health-personalized data?
- product-thinker dispatch — "Does sharing align with Ceeya's identity? Ceeya is private by design — sharing creates a tension. What are the privacy implications?" The SubagentStart hook logs this dispatch.
- context-scout dispatch — "What shared types exist for the report card? What's the existing share infrastructure? What would need to change?" SubagentStart logs again.
- Codex cross-model review — GPT-5.x reviews the plan via
mcp__codex__codex. Different model family, different blind spots. Findings get ACCEPT/REBUT/INVESTIGATE treatment. - Synthesized plan — all agents' findings combined, Codex review addressed, presented with debate notes. You have final authority on whether to proceed.
Key moments to watch for:
- The hooks are visible: SessionStart prints the handoff. SubagentStart prints dispatch timestamps. These are not magic — they're shell scripts that run before/after tool calls.
- The model doesn't jump to code: It reads knowledge, brainstorms, dispatches specialists, and presents a plan. This is the 7-step pipeline at work — the model doesn't reach Step 4 (Implement) until you approve the plan.
- The specialists can disagree: product-thinker might flag privacy concerns while context-scout maps the technical surface. The orchestrator must reconcile both perspectives.
- The user has veto power: The plan is presented for approval. You can reject it, modify it, or accept it. The agent waits — it doesn't auto-implement.
Actual demo output (real run, April 13)
1. SessionStart hook fired automatically — injected handoff context and guardian priorities before the agent even responded.
2. Knowledge loading (Step 1) — the agent read handoff.md, guardian-config.md, conventions.md, and the full product reference (24 files discovered). Then it found existing infrastructure:
"I see there's already a ScanShareCard component mentioned in the handoff — standout positives/negatives are noted as 'share card only.' So there's existing share infrastructure."
3. Two specialists dispatched in parallel:
4. product-thinker PUSHED BACK on the feature:
"Core tension — sharing undermines personalization messaging. Ceeya's whole pitch is 'the same food means different things for different people.' If Sarah shares her C+ for a granola bar, Marcus might think it's bad — but for him it would be a B+. A shared personal verdict, stripped of context, becomes exactly the generic score Ceeya exists to replace."
This is the anti-echo-chamber design working. The agent didn't just say "great idea, let's build it." It evaluated whether the feature serves the product identity and pushed back with a specific argument.
5. Privacy risk flagged unprompted:
"Personal verdicts contain health condition data ('380mg sodium is elevated for your hypertension'). Sharing that publicly means publishing medical information."
6. Concrete recommendation with share card content rules:
- ✅ Product name, brand, verdict label, headline, standout positives/negatives, Ceeya branding
- ✅ Premium: personal grade LETTER only ("My Grade: B+") — no reasoning
- ✅ CTA: "Scan it yourself — ceeya.app"
- ❌ NEVER: personal matches, condition names, health lens data, target fulfillment
7. Timing recommendation: "Not now. You're in Private Beta Round 1. Share polish is post-launch (June/July). The existing ScanShareCard is adequate for beta."
8. Growth insight: "Add a PostHog screenshot detection event on the report card. If users are screenshotting and sharing manually, that's the signal to invest."
9. Asked before acting — 4 options presented:
- Audit only — verify existing share card doesn't leak health data
- Quick polish — upgrade ScanShareCard to use richVerdict + add screenshot analytics
- Plan for post-launch — write a plan file capturing the full spec for later
- Something else — if you have a different vision
Key demo moments to highlight: The agent discovered existing infrastructure before dispatching agents. product-thinker pushed back on a feature the user asked for. Privacy implications surfaced unprompted. The agent presented options and waited — it didn't auto-implement. All of this from a single prompt.
Fallback demo (shorter — triggers safety-auditor)
Triggers a critical domain override — safety-auditor dispatch regardless of scope. Takes ~2 minutes instead of ~5.
Check the safety gate code for any issues — are there edge cases where an allergen could be missed?
Step by step:
- SessionStart hook — handoff + priorities injected
- Knowledge loading — reads all knowledge files, especially
safety.mdrules - Model reads safety-related files — the PreToolUse (Read) hook fires: "This is safety-critical code. Consider dispatching safety-auditor."
- safety-auditor dispatch — critical domain override. Even though this is a "just check" request (arguably Tier 0), the safety override kicks in. The safety-auditor runs its structured audit: false-negative scan, missing-derivative check, code path completeness.
- Structured report — safety-auditor returns findings with severity ratings.
What makes a good demo prompt
The best demo prompts trigger visible architecture — hooks that fire, agents that get dispatched, skills that structure the workflow. Avoid prompts that just generate code silently.
- A Tier 0 task won't show the architecture — it just executes
- A Tier 2 task takes too long for a 5-minute talk
- A Tier 1 with domain-override agents is the sweet spot
02The Starting Point
How most people use Claude Code — and where it starts to break down.
The Defaults Work Great
Open your terminal, type claude, start chatting. Add a CLAUDE.md with project instructions. Maybe some .claude/rules/ files for auto-loaded conventions. For most projects, this is plenty.
Then You Build a Health App
Ceeya is a personalized nutrition scoring app. Users scan food products and get a verdict tailored to their health conditions, goals, allergies, and life stage. The core question: "Is this food good for ME, right NOW?"
Users make food decisions based on what Ceeya shows them. A wrong default is not a cosmetic bug — it's a lie that affects what someone eats.
What Breaks at Scale
- A single CLAUDE.md can't hold everything — safety rules, iOS conventions, backend patterns, shared type contracts, caching invariants, design system tokens
- Attention decay — rules at line 50 get followed, rules at line 800 get forgotten
- No review process — the AI marks its own homework
- No specialization — the same general-purpose model tries to be a safety auditor, a design system guardian, a security reviewer, and a product thinker simultaneously
- No memory — user corrections vanish at session end
The health-critical difference
// In a normal app, this is fine: const value = data?.score ?? 0 // In a health app, this is dangerous: // Zero means "measured zero." Null means "unknown." // A fabricated zero could tell someone a food is safe when we don't know. const value = data?.score ?? null // honest unknown
This distinction — null vs false vs 0 — runs through the entire codebase. Every fallback value is a potential health decision.
03The Prompt Conflict Problem
Your custom instructions compete with Claude's base behavior. Understanding the internal assembly pipeline is key to building reliable systems.
The Context Assembly Pipeline
From reading the Claude Code source (runAgent.ts, AgentTool.tsx, prompt.ts), here is exactly how the system prompt is assembled before the first user message:
getSystemPrompt() — Claude Code base prompt~3K tokens, invisible to userenhanceSystemPromptWithEnvDetails() — working dir, OS, shellabsolute path guidancegetSystemPrompt() — your promptceeya-dev.md — 716 linesbuildEffectiveSystemPrompt() — combines base + custommerges layers 1-3getUserContext() — CLAUDE.md hierarchyinjected as system-reminderTotal context before the first user message: ~30K+ tokens of instruction. Your custom agent prompt is layer 3 out of 7. It has to compete with everything above and below it.
The Conflicts
Your instructions sit in layers 3-5. Claude's base behavior (layer 1, in the weights) fights them constantly:
The omitClaudeMd optimization
Found in the Claude Code source: a frontmatter flag that skips injecting the CLAUDE.md hierarchy AND the session-start gitStatus for agents that don't need project-wide context.
- Built-in usage: Claude Code's own Explore and Plan agents set this flag
- Savings: up to ~40KB of context per dispatch for focused analysis agents
- Source comment: "Saves ~5-15 Gtok/week across 34M+ Explore spawns"
- Our usage: 13 of 18 specialist agents set
omitClaudeMd: true— they get relevant knowledge via the dispatch prompt instead
The 5 agents that keep CLAUDE.md: ceeya-dev (orchestrator), safety-auditor, standards-enforcer, design-system-guardian, test-strategist — these need full project rules.
The agent list cache-bust problem
From prompt.ts: the dynamic agent list (names + descriptions of all available agents) was originally embedded in the tool-use schema. This created a cascade problem:
- The agent list was 10.2% of fleet cache_creation tokens
- MCP connect, plugin reload, and permission changes mutated the list
- Any mutation = tool-schema cache bust = entire prompt re-cached
- Solution: moved agent list to an
agent_listing_deltaattachment message, making the tool description static
This is relevant because it shows how seemingly small additions to the prompt have fleet-wide cost implications. Every token you add to always-on context multiplies across every session.
The Discovery: criticalSystemReminder_EXPERIMENTAL
Found in Claude Code's source: a frontmatter field that gets re-injected at every user turn, fighting attention decay. A single line that persists across the entire conversation regardless of context window pressure.
Evolution: From Rules to Metacognition
Our first version was prescriptive — repeating the top 3 rules:
# Version 1 (prescriptive — less effective): criticalSystemReminder_EXPERIMENTAL: "null!=false!=zero — never fabricate defaults. ASK before acting on uncertain info. Invoke skills via Skill tool, not 'in spirit.'"
We learned that using it for rule repetition is a waste — the model already follows well-reinforced rules. Instead, use it for metacognitive introspection that fights momentum:
# Version 2 (metacognitive — current, much more powerful): criticalSystemReminder_EXPERIMENTAL: "PAUSE — Am I in momentum? Before continuing: (1) Should I be dispatching a specialist agent right now instead of doing this myself? (2) Am I about to skip a skill invocation? (3) Is my last fix a root-cause fix or a band-aid? (4) Did I just make a decision the user should have input on?"
Why metacognitive introspection works better
- It's metacognitive, not prescriptive — instead of "follow rule X," it asks "am I in a state where I'd break rule X?"
- "PAUSE" interrupts the generation flow — forces the model to stop and self-evaluate before continuing
- Catches ALL failure modes through self-reflection — each question maps to a top failure mode from our attention analysis
- Question 1: catches the orchestrator hoarding work instead of dispatching specialists
- Question 2: catches momentum-driven skill skipping (the #1 instruction violation)
- Question 3: catches symptom-fixing instead of root-cause investigation
- Question 4: catches autonomous decisions that should be user checkpoints
How buildEffectiveSystemPrompt() works: From the Claude Code source, this function assembles the final prompt by combining the base system prompt + your custom agent prompt + system context + user context. When you set "agent": "ceeya-dev" in settings.json, your agent prompt replaces Claude Code's default system prompt entirely — but CLAUDE.md still loads through message flow.
system-reminder messages are injected BETWEEN conversation turns, not in the system prompt itself. This means they get recency bias (the model pays more attention to recent messages) but they compete with your system prompt for attention. CLAUDE.md, skills, MCP instructions, and memory all arrive as system-reminder blocks.
The agent list cache-bust problem (from prompt.ts): the dynamic agent list was originally embedded in the tool-use schema. Any mutation (MCP connect, plugin reload, permission change) would bust the tool-schema cache, forcing the entire prompt to be re-cached. The fix: move the agent list to an agent_listing_delta attachment message, making the tool description static. This was 10.2% of fleet cache_creation tokens.
04The Agent Architecture
One orchestrator. Eighteen specialists. Each with its own system prompt, constraints, and role.
The Orchestrator: ceeya-dev
Set as the default agent via settings.json:
{ "agent": "ceeya-dev", "includeGitInstructions": false, "activeChanges": ".ceeya/active-changes.yaml", "hooks": { ... } }
Replaces Claude Code's default system prompt entirely. Contains a behavioral contract (anti-laziness, no scope anxiety, session continuity, proactiveness), a 7-step execution pipeline (Load Knowledge → Discover → Plan → Implement → Verify → Document → Handoff), and the 3-tier dispatch system.
The 7-step execution pipeline
- Load Knowledge — read all 8 knowledge files + handoff + guardian config before responding
- Discover — search the codebase broadly. Files move. Never hardcode paths from memory.
- Plan — scale to complexity. Tier 0: one sentence. Tier 1: plan file + domain agents. Tier 2: debate loop with adversarial review.
- Implement — build checkpoints every 5 files, self-interrogation during coding ("Am I fixing WHY this broke?"), parallel execution for independent subtasks
- Verify — mandatory skill invocation, root-cause audit, intent verification presented to user, Codex cross-model review
- Document — update knowledge base, product reference, marketing, plans, guardian config
- Handoff — overwrite handoff.md with a forward-looking prompt for the next session
The 18 Specialists — Overview
maxTurnsdisallowedToolsEdit, Write, NotebookEdit blocked. They analyze — they don't change.omitClaudeMdred-team
Role: Devil's advocate and idea stress-tester. Takes plans, proposals, and architectural decisions and systematically tries to break them. Exists because AI agents are agreeable by nature and don't challenge their own ideas hard enough.
Context required from dispatcher: Full plan text, user's original request (verbatim), codebase research (file paths + patterns), alternatives considered and why they were rejected.
Output: Structured critique with severity ratings: assumptions challenged, edge cases found, failure modes identified, complexity assessment, and a final ACCEPT/REJECT/CONDITIONAL recommendation.
Dispatched when: Tier 2 tasks (architecture changes, new systems), part of the Debate Loop alongside product-thinker and Codex.
product-thinker
Role: Evaluates features and UX decisions for product coherence. Thinks like a product person, not an engineer. Checks whether features align with Ceeya's identity: "Is this food good for ME, right NOW?" Not a tracker. Not a calorie counter. Verdicts, not numbers.
Context required: Full plan text, user's original request, whether the feature is free or premium.
Output: Product alignment assessment: does this fit Ceeya's identity, does the UX flow make sense, are there missing edge cases from a user perspective, what's the impact on the product story.
Dispatched when: New features, UX decisions, Tier 1/2 planning, Debate Loop reviews.
standards-enforcer
Role: Knowledge base compliance checker. Reads EVERY rule in EVERY knowledge file and checks changed code against ALL of them. Catches violations the main agent misses because it can't hold all rules in working memory during a long implementation.
Note: This is one of 5 agents that keeps CLAUDE.md (no omitClaudeMd). It needs full project rules to do its job.
Context required: Scope of changes (file paths or git diff range), which platform (iOS/backend/shared), audit trigger (pre-merge, post-refactor, targeted).
Output: Violation report: specific rule, specific line, specific fix. Grouped by knowledge file domain.
Dispatched when: Pre-merge reviews, post-refactor audits, Tier 1 implementation checkpoints.
codebase-analyzer
Role: Elite codebase forensics expert. Ingests, understands, and produces comprehensive strengths-and-weaknesses analysis of a codebase or subsection. Identifies code smells, architectural weaknesses, redundant patterns, tight coupling, dead code, and also recognizes well-designed patterns worth preserving. Does NOT refactor — produces the analytical foundation that makes refactoring safe.
Context required: Scope (directory path or module name), purpose (pre-refactor, dependency mapping, code quality check), known concerns.
Output: Structured analysis report: strengths, weaknesses, dependency map, refactoring recommendations with risk assessment.
Dispatched when: Before refactoring work, code quality assessments, dependency analysis.
codebase-health
Role: Structural health specialist — goes far beyond "find unused imports." Detects accretion debt (changes bolted onto existing systems instead of properly integrated), parallel types that should be unified, shim arguments indicating API drift, dead code, override objects that exist because someone didn't update the original. Unlike codebase-analyzer, this agent diagnoses AND treats — it can edit files.
Note: One of only 4 specialist agents that CAN edit files (no disallowedTools).
Context required: Scope (full audit / post-feature cleanup / targeted), list of changed files if post-feature, any intentional patterns to skip.
Output: Findings report + actual cleanup commits for dead code, duplicate logic, and orphaned test data.
Dispatched when: Post-feature cleanup, tech debt audits, "the codebase feels messy" investigations.
safety-auditor
Role: The most paranoid agent in the system. Audits all safety-related code with the assumption that a missed check could cause real harm. A false positive (unnecessary warning) is merely annoying. A false negative (missed allergen) could send someone to the hospital.
Identity: "You assume every change is guilty until proven safe. You check for the ways safety can silently degrade: a ?? false that silences a warning, a missing derivative that lets an allergen through, a code path that skips the safety gate entirely."
Note: Keeps CLAUDE.md — needs full safety rules. Has the safety-review skill for structured audit methodology.
Context required: Changed file paths AND diffs (not just names), which safety category (allergen / intolerance / life-stage / condition-nutrient / dietary / avoidance), whether direct safety code or adjacent.
Dispatched when: ANY change to safety gate logic — regardless of scope. Even a one-line change triggers this agent via critical domain override.
security-auditor
Role: Cybersecurity specialist for a health data app. Thinks like an attacker: what would you exploit? Where are the weakest links? Reviews code for exploitability, not correctness. Ceeya handles health data, auth tokens, and payment state — a breach has regulatory and trust implications.
Identity: "You are the adversary simulator. Your perspective is orthogonal to every other agent."
Context required: Changed file paths AND diffs, the security surface (auth / routes / data handling / dependencies / secrets).
Reads before auditing: .ceeya/product/free-vs-premium.md (verify auth enforcement matches product intent), .ceeya/knowledge/backend.md (verify auth architecture is current).
Dispatched when: ANY change to auth / token handling — critical domain override regardless of scope.
type-contract-auditor
Role: Cross-platform type synchronization auditor. Traces shared types from packages/shared through backend consumers to iOS model mirrors. Shared types are promises made to consumers — when a promise changes, every consumer must be updated. Finds divergences before they reach users.
Context required: Specific changed type names (not just file paths — traces by type name), nature of change (field addition / removal / rename / type change), whether iOS consumers have been updated yet.
Output: Field-by-field comparison table: shared type definition vs backend usage vs iOS model struct. Flags mismatches, optionality gaps, and missing fields.
Dispatched when: ANY shared type breaking change — critical domain override. Also dispatched during Tier 1 tasks that touch shared types.
tier-boundary-enforcer
Role: Audits free vs premium enforcement across the full stack — backend middleware, route guards, service logic, iOS paywall triggers, product documentation, and marketing claims. Ensures all layers agree on what's free and what's premium.
Note: One of the 4 agents that CAN edit — needs to fix tier mismatches when found. Has the tier-review skill for structured audit methodology.
Context required: What changed (route path, feature gate, service limit, or product doc), which layer affected, proactive vs reactive audit.
Dispatched when: ANY change to subscription / tier enforcement — critical domain override. Also on new route additions.
scoring-pipeline-validator
Role: Specialist for Ceeya's scoring pipeline — a 25-operation DAG that transforms a barcode scan into a personalized food evaluation. This is the most complex system in the codebase and the one where bugs directly produce wrong health information.
Context required: What changed (operation names and/or file paths), type of change (prompt edit, new operation, dependency reorder, caching change, type modification), whether DAG structure changed.
Output: DAG integrity report: dependency graph validation, cache version checks, L1/L2 boundary verification, stale-data risk assessment.
Dispatched when: ANY change to scoring / pipeline logic — critical domain override. Also dispatched for AI prompt modifications.
perf-analyst
Role: Performance and efficiency specialist. Reviews iOS views for expensive recomputations and main thread blockers, backend pipeline for unnecessary sequential execution, and SDK integrations for main thread impact. Thinks in milliseconds.
Context: Ceeya already had perf issues — PostHog session replay overhead, duplicate grain textures, root-level animation modifiers. This agent exists to prevent the next one.
Context required: Changed file paths AND diffs, domain (iOS UI / backend pipeline / backend API / infrastructure).
Dispatched when: Performance concerns reported, post-implementation reviews of new UI or pipeline changes.
ai-cost-optimizer
Role: AI operations economist. Every scan costs real money — Mercury 2 runs every scan, reached directly through Inception Labs (the primary), with OpenRouter as the overflow gateway to the same model. OpenAI models are reached only via OpenRouter (gpt-5.4-mini as OpenRouter's in-gateway scan fallback; gpt-5.4 for the review-time correction judge, off the scan path). Ensures maximum intelligence per dollar: prompt efficiency, caching strategy, model selection, token usage.
Context required: Changed operation names or files, type of change (prompt edit, model swap, caching change, new operation, full audit), specific cost concern.
Output: Cost analysis: tokens per operation, caching hit rates, model selection justification, per-scan cost estimates, optimization recommendations.
Dispatched when: AI prompt modifications, new AI operations added, cost review requests.
design-system-guardian
Role: Guardian of Ceeya's iOS design system. Prevents the #1 agent failure mode: creating duplicate components. Knows every component in DesignSystem/, every color token, every typography token, and enforces their consistent use.
Identity: "You are the design system's immune system. When new UI code enters the codebase, you check it against the established visual vocabulary."
Note: Keeps CLAUDE.md — needs the full iOS rules. Has swiftui-pro skill for deep SwiftUI analysis.
Context required: Files to audit (specific Swift paths), audit type (new UI review, compliance check, duplication detection), what was built.
Dispatched when: After new UI implementation, before design PRs, when new components are created in DesignSystem/.
sentry-investigator
Role: Error and crash triage specialist. Uses Sentry MCP tools to find, analyze, and explain production/staging errors. Correlates Sentry data with actual source code to identify root causes, not just symptoms.
Identity: "You are the error detective. You don't guess — you query Sentry, read the stack trace, find the code, and report with evidence."
Context required: Error symptom, platform (iOS / backend / both), time frame.
Dispatched when: Production crashes, post-deploy error spikes, user-reported crash investigations.
posthog-analyst
Role: Analytics and instrumentation specialist. Uses PostHog MCP tools to query real data, verify instrumentation, analyze user behavior, and answer questions with evidence — not guesses. Knows Ceeya's full event taxonomy and translates business questions into PostHog queries.
Context required: Specific analytics question, time range, event names if known.
Dispatched when: "How many users are...?", "Is this event firing?", funnel analysis, retention queries, feature usage checks.
context-scout
Role: Monorepo context gatherer. Traces features, types, and data flows across all three packages (shared, api, ios). Maps dependency chains and produces structured context reports that prevent the #1 cause of agent bugs: coding with incomplete context.
Identity: "You are the cartographer. Before anyone writes code, you map the terrain. You find ALL related code — not just the obvious files, but the tests, mock data, constants, configs, knowledge references, and documentation."
Context required: Feature/type/flow to trace, why context is needed, known starting points.
Dispatched when: Before cross-package changes, "how does X work end-to-end?", pre-implementation context gathering.
test-strategist
Role: Test coverage specialist. Ensures the right things are tested at the right level — not just that tests exist, but that they catch real problems. Focuses on critical paths where bugs cause real harm: safety gate, scoring pipeline, auth, subscriptions, data contracts.
Note: Keeps CLAUDE.md. One of the 4 agents that CAN edit — it writes test files.
Context required: Changed code (diffs or file contents), which critical path, if bug fix: what the bug was, current test file locations.
Dispatched when: After safety-related changes, bug fix regression coverage, pre-deployment test audit, "are we testing the right things?"
agent-architect
Role: Gatekeeper for agent infrastructure. Every agent creation, modification, and deletion goes through this agent. Maintains quality, prevents drift, ensures the agent team stays coherent and effective.
Note: CAN edit — it creates and modifies agent prompt files. The only agent that modifies other agents.
Context required: Action type (create / modify / delete), agent name, what to change and why, evidence of the problem (concrete examples of underperformance, overlap, or missing capability).
Dispatched when: "I want a new agent," agent underperformance observed, boundary overlap detected, ceeya-dev's self-analysis suggests an agent improvement.
Why read-only agents matter
The value of specialists is that they hold a different optimization function than the orchestrator. The orchestrator optimizes for "get this implemented." Specialists optimize for "find what's wrong with this implementation." If a specialist could edit files, it would try to fix what it finds instead of reporting it — losing the adversarial tension.
disallowedTools: Edit, Write, NotebookEdit makes this structural, not behavioral. The agent can't fix things even if it wants to.
The 4 agents that CAN edit: codebase-health (cleanup requires editing), tier-boundary-enforcer (fixes tier mismatches), test-strategist (writes test files), agent-architect (creates/modifies agent prompts).
How loadAgentsDir.ts parses agent markdown: It reads YAML frontmatter, validates required fields (name, whenToUse), and the markdown body becomes the system prompt via a closure. The closure pattern means custom agents store their prompt as a function — getSystemPrompt() — while built-in agents have dynamic prompts that receive toolUseContext.
How AgentTool.tsx call() works: Resolves the agent definition, assembles an independent tool pool (not inherited from the parent's restrictions), builds the system prompt from the closure, and spawns via runAgent(). This is why specialist agents can have different disallowedTools than the orchestrator — each gets its own tool set.
Memory auto-injection: When memory is enabled for an agent, Write, Edit, and Read tools are auto-injected into the agent's tool pool — you don't need to list them explicitly. The agent can save memory files even if those tools aren't in its normal configuration.
Complete agent reference table
| Agent | Lines | Turns | Read-Only | OmitClaudeMd | Skills |
|---|---|---|---|---|---|
| red-team | 145 | 10 | Yes | Yes | - |
| product-thinker | 105 | 10 | Yes | Yes | refero-design |
| standards-enforcer | 157 | 25 | Yes | No | - |
| codebase-analyzer | 193 | 20 | Yes | Yes | - |
| codebase-health | 120 | 20 | No | Yes | - |
| safety-auditor | 161 | 15 | Yes | No | safety-review |
| security-auditor | 186 | 15 | Yes | Yes | - |
| type-contract-auditor | 150 | 15 | Yes | Yes | - |
| tier-boundary-enforcer | 101 | 15 | No | No | tier-review |
| scoring-pipeline-validator | 119 | 15 | Yes | Yes | pipeline-review |
| perf-analyst | 165 | 15 | Yes | Yes | swiftui-pro |
| ai-cost-optimizer | 121 | 15 | Yes | Yes | - |
| design-system-guardian | 175 | 20 | Yes | No | swiftui-pro |
| sentry-investigator | 125 | 15 | Yes | Yes | sentry:getIssues, sentry:seer |
| posthog-analyst | 138 | 20 | Yes | Yes | posthog:query, posthog:insights, posthog:search |
| context-scout | 154 | 15 | Yes | Yes | - |
| test-strategist | 114 | 15 | No | No | - |
| agent-architect | 136 | 15 | No | Yes | - |
05The Dispatch System
Not every change needs a committee. Not every plan needs adversarial review. But every decision with blast radius needs external eyes.
3-Tier Calibration
Single-file fix, typo, <20 lines. No agents, no skills, no plan file. Test: "Could this be fully reviewed by reading one file?"
Multi-file changes, new features, schema updates. Brainstorm skill, plan file, domain agent checkpoints at pre/during/post implementation.
Plus: Codex (GPT-5.x) cross-model review after implementation.
New system, architecture change, anything that could go wrong in ways you haven't thought of.
The Debate Loop — Full Detail
The Debate Loop is the most important quality mechanism. It forces adversarial review before code is written — catching design flaws when they're cheapest to fix.
red-team + product-thinker review the plan in parallel. Simultaneously, Codex (GPT-5.x) runs a cross-model challenge. Three independent reviewers, two model families.
The orchestrator must address every finding with one of three responses: ACCEPT (incorporate the feedback), REBUT (explain why the finding is wrong), or INVESTIGATE (need more data before deciding).
User sees the refined plan + a "What was debated" summary showing every challenge and how it was resolved. User has full visibility and final authority.
Real example: the architecture overhaul debate
During one session, we ran the Debate Loop on an architecture overhaul proposal. red-team, product-thinker, and Codex (GPT-5.x) all reviewed it. Key findings:
red-teamfound 3 failure modes in the proposed migration strategy — data loss scenarios during the cutoverproduct-thinkerflagged that the new architecture would break the free tier's latency budget- Codex caught a race condition in the proposed caching layer that the Claude-family agents missed entirely
Result: 5 proposals from the original plan were killed based on combined findings. The final plan was significantly different (and better) than what went in.
The cross-model insight: why Codex (GPT-5.x)?
A different model family catches different bugs. Claude-family agents are the primary quality controls, but they share blind spots — same training, same tendencies.
- Codex is strong on: universal engineering — security patterns, race conditions, architecture anti-patterns, OWASP-style vulnerability detection
- Codex is weak on: Ceeya-specific patterns — DI conventions, shared types, safety gate ordering, knowledge base rules
- The combination is greater than either alone: Claude agents know the project deeply. Codex brings fresh eyes from a different perspective.
- Graceful degradation: if Codex is rate-limited or unavailable, continue without it. Never blocks the pipeline. The review degrades to dual-Claude, which is still valuable.
Critical Domain Overrides
These dispatch regardless of scope — even a one-line change to safety logic triggers the specialist:
| Change Area | Always Dispatches | Why Override Exists |
|---|---|---|
| Safety gate logic | safety-auditor | False negative = real harm. No change is "too small" to audit. |
| Auth / token handling | security-auditor | Health data breach has regulatory implications. |
| Scoring / pipeline logic | scoring-pipeline-validator | 25-op DAG — one wrong dependency = stale health data. |
| Shared type breaking changes | type-contract-auditor | iOS and backend must agree. Divergence = runtime crash. |
| Subscription / tier enforcement | tier-boundary-enforcer | Free users getting premium or premium users losing features. |
06Structural Enforcement — Hooks
Self-enforcement is unreliable. The model skips mandatory skills under momentum. Hooks enforce externally — they are shell scripts the model cannot override.
Key insight: 10 deterministic command hooks. Zero prompt hooks. Prompt hooks add a suggestion the model can ignore. Command hooks run real shell scripts that execute regardless of what the model wants. The hooks are configured in .claude/settings.json.
Hook 1: SessionStart — Full session initialization (dedicated script)
What it does: Runs .claude/hooks/session-start.sh — a proper shell script (not an inline command) that performs 4 functions:
- Resets edit counters — zeroes both Swift-specific and universal counters so they don't carry over from previous sessions
- Injects mandatory file read list — lists ALL 10 files (8 knowledge + handoff + guardian-config) with the instruction "MANDATORY BEFORE RESPONDING: Read ALL of these files now. Do not skip any." This enters the conversation as context the model received, not as a system prompt instruction it can deprioritize.
- Injects git state — current branch, number of unpushed commits from previous sessions (with warning), uncommitted files. The model knows immediately if there's unfinished work.
- Lists active plans — shows existing plan files so the model checks for an existing plan before creating a duplicate.
- Injects This Week's Focus — from
guardian-config.md, so every session starts aligned with current priorities.
Why a script file: The original version was a 200-character inline command with fragile JSON escaping. Extracting to a proper script means it's readable, testable (bash .claude/hooks/session-start.sh), and easy to extend.
Why this matters: The model was skipping knowledge files it didn't think were relevant. The SessionStart hook injects the file list as conversational context positioned right before the user's message, which gets high recency attention. The same instruction at line 289 of a 716-line system prompt was competing with 715 other lines for attention.
# .claude/hooks/session-start.sh (excerpt) # 1. Mandatory file reads CONTEXT+="MANDATORY BEFORE RESPONDING: Read ALL of these files now.\n" for f in .ceeya/knowledge/*.md .ceeya/handoff.md .ceeya/guardian-config.md; do [ -f "$f" ] && CONTEXT+="- $f\n" done # 2. Git state awareness UNPUSHED=$(git log origin/"$BRANCH".."$BRANCH" --oneline | wc -l) [ "$UNPUSHED" -gt 0 ] && CONTEXT+="⚠️ $UNPUSHED unpushed commits\n" # 3. Active changes ACTIVE_CHANGES=$(rg -n "^- id:|status: active|status: paused" .ceeya/active-changes.yaml | head) CONTEXT+="--- Active Changes ---\n$ACTIVE_CHANGES\n" # Output as Claude Code hook JSON printf '%s' "$CONTEXT" | jq -Rs '{hookSpecificOutput: {hookEventName:"SessionStart",additionalContext:.}}'
Hook 2: SubagentStart — Agent dispatch logging + priority injection
What it does: Logs the dispatch timestamp and agent name to .ceeya/_internal/dispatch-log.txt, then injects "This Week's Focus" into every subagent so specialists know current project priorities.
Why command hook: The dispatch log is for observability — tracking which agents get dispatched, how often, and when. The priority injection ensures every specialist knows what the team is focused on, even if the dispatcher forgets to mention it.
# Log dispatch + inject priorities
INPUT=$(cat)
AGENT=$(echo "$INPUT" | jq -r '.agentType // .agent_type // \
.agentName // .agent_name // "unknown"')
DATE=$(date -u +%Y-%m-%dT%H:%M:%S)
mkdir -p .ceeya/_internal
echo "$DATE | START | $AGENT" >> .ceeya/_internal/dispatch-log.txt
FOCUS=$(sed -n '/## This Week/,/^##/{/^##[^#]/!p;}' \
.ceeya/guardian-config.md 2>/dev/null | head -5 | tr '\n' ' ')
[ -n "$FOCUS" ] && printf '%s' "$FOCUS" | \
jq -Rs '{hookSpecificOutput:{hookEventName:"SubagentStart",
additionalContext:.}}' || true
Hook 3: SubagentStop — Agent completion logging
What it does: Logs the completion timestamp and agent name to the dispatch log. Paired with SubagentStart, this gives us dispatch duration tracking.
# Log completion
INPUT=$(cat)
AGENT=$(echo "$INPUT" | jq -r '.agentType // .agent_type // \
.agentName // .agent_name // "unknown"')
DATE=$(date -u +%Y-%m-%dT%H:%M:%S)
mkdir -p .ceeya/_internal
echo "$DATE | STOP | $AGENT" >> .ceeya/_internal/dispatch-log.txt
Hook 4: PreToolUse (Read) — Safety-critical file detection
What it does: When the model reads a file whose name matches safety, allergen, or intolerance, injects a context message suggesting the safety-auditor should be dispatched.
Why command hook: This is a soft nudge (additionalContext, not deny), but it's structural — the model always sees it. A prompt instruction to "remember to dispatch safety-auditor when you read safety files" would be forgotten by line 500.
Edge case: Excludes files in .claude/ directory to avoid triggering on the safety-auditor's own prompt file.
FILE=$(jq -r '.tool_input.file_path // empty')
[ -n "$FILE" ] && echo "$FILE" | \
grep -qiE '/(safety|allergen|intolerance)[^/]*\.' && \
! echo "$FILE" | grep -q '.claude/' && \
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse",
"additionalContext":"This is safety-critical code.
Consider dispatching safety-auditor for specialist audit."}}'
|| true
Hook 5: PreToolUse (Bash) — Git push blocker
What it does: Intercepts any git push command. Extracts the target branch. If the target is staging, prod, production, main, or master, returns a hard permissionDecision: "deny". The model cannot override this.
Why command hook: This is the most important hook. A prompt instruction saying "never push to staging" will eventually be ignored under pressure. A deny hook physically prevents it.
CMD=$(jq -r '.tool_input.command // empty') echo "$CMD" | grep -qE 'git\s+push' || exit 0 # Extract target branch — handles flags like -f, --force TARGET=$(echo "$CMD" | grep -oE \ 'git\s+push\s+(-[a-zA-Z]+\s+)*[^\s]+\s+([^\s]+)' \ | awk '{print $NF}') # Fallback: if no explicit target, check current branch [ -z "$TARGET" ] && TARGET=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) echo "$TARGET" | grep -qiE '^(staging|prod|production|main|master)$' \ && echo '{"hookSpecificOutput":{"permissionDecision":"deny", "permissionDecisionReason":"Blocked: pushing to protected branch '"$TARGET"'. Use a PR instead."}}'
The git push guard bug story
The original version of this hook checked HEAD (current branch) instead of the push target. This meant: if you're on dev and run git push origin staging, the hook would see the current branch is dev (not protected) and let it through. A push to staging from dev would pass right through.
The hook robustness analysis caught this. The fix: extract the actual target from the push command, then fall back to HEAD only if no explicit target is given.
Hook 6: PostToolUse (Edit|Write .ts) — Import extension lint
What it does: After any edit or write to a TypeScript file in packages/shared/src/ or apps/api/src/, scans for .ts import extensions. ES modules require .js extensions even for TypeScript files — using .ts causes runtime failures.
Why command hook: This is the most commonly violated convention. The model's training data is full of .ts imports. Without structural enforcement, it reverts to old habits every few sessions.
FILE=$(jq -r '.tool_input.file_path // empty') [ -n "$FILE" ] && echo "$FILE" | \ grep -q 'packages/shared/src/\|apps/api/src/' && \ grep -n "from ['\"].*\.ts['\"]" "$FILE" 2>/dev/null && \ echo 'WARNING: .ts import extension detected. Use .js extensions for ES module imports.' && exit 2 || exit 0
Exit code 2: Non-zero exit tells Claude Code the hook found a problem. The model sees the warning and fixes it before moving on.
Hook 7: PostToolUse (Edit|Write .swift) — iOS build checkpoint
What it does: Counts Swift file edits using /tmp/ceeya-swift-edits. At 5 edits, resets the counter and injects a message: "5+ iOS files modified — build checkpoint. Run a build, commit progress, and spot-check your last fix for root-cause vs symptom."
Why 5? After testing, 5 Swift edits without a build is the point where accumulated errors become expensive to debug. Earlier is wasteful, later risks compounding mistakes.
FILE=$(cat | jq -r '.tool_input.file_path // empty')
[ -n "$FILE" ] && echo "$FILE" | grep -qE '\.swift$' && \
echo "$FILE" | grep -q 'apps/ios/' && \
COUNT=$(cat /tmp/ceeya-swift-edits 2>/dev/null || echo 0) && \
COUNT=$((COUNT + 1)) && echo $COUNT > /tmp/ceeya-swift-edits && \
[ $COUNT -ge 5 ] && echo 0 > /tmp/ceeya-swift-edits && \
echo '{"hookSpecificOutput":{"additionalContext":
"5+ iOS files modified — build checkpoint.
Run a build, commit progress, and spot-check
your last fix for root-cause vs symptom."}}'
|| true
Hook 8: PostToolUse (Edit|Write iOS) — XcodeGen auto-sync
What it does: After any edit to a .swift file under apps/ios/Ceeya/, runs xcodegen generate --quiet asynchronously to keep the Xcode project in sync with the file system.
Why async: XcodeGen takes ~2 seconds. Running it synchronously would block every Swift edit. The async: true flag lets it run in the background while the model continues working.
Edge case: Uses --quiet to suppress output. Failures are silently ignored (; true) because XcodeGen failures are noisy but non-blocking — the Xcode project just stays slightly stale until the next edit.
cat | jq -r '.tool_input.file_path // empty' | \ grep -qE 'apps/ios/Ceeya/.*[.]swift$' && \ cd apps/ios/Ceeya && xcodegen generate --quiet 2>/dev/null ; true
Configuration: "statusMessage": "Syncing Xcode project...", "timeout": 15, "async": true
Hook 9: PostToolUse (every 5 edits) — Universal commit reminder
What it does: Counts ALL file edits (not just Swift) using /tmp/ceeya-all-edits. Every 5 edits, injects: "N files modified this session. Commit your progress now."
Why: The git workflow rule says "If you've modified 3+ files without committing — stop and commit NOW." But the model loses track during long implementations. This hook provides the external reminder at a slightly generous cadence (5, not 3) to avoid excessive interruptions.
COUNT=$(cat /tmp/ceeya-all-edits 2>/dev/null || echo 0)
COUNT=$((COUNT + 1))
echo $COUNT > /tmp/ceeya-all-edits
[ $((COUNT % 5)) -eq 0 ] && [ $COUNT -gt 0 ] && \
echo '{"hookSpecificOutput":{"additionalContext":
"'$COUNT' files modified this session.
Commit your progress now."}}'
|| true
Hook 10: PreCompact — Compaction safety net
What it does: Before Claude Code compacts the conversation (reducing context window), saves the current branch name and list of uncommitted files to .ceeya/_internal/compaction-log.txt, then injects this state as additional context so the model knows what it was working on.
Why this matters: Compaction erases working memory. Without this hook, the model loses track of what files it was editing and what branch it's on. The injection ensures continuity across compaction events.
DATE=$(date -u +%Y-%m-%dT%H:%M:%S)
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown')
CHANGED=$(git diff --name-only 2>/dev/null | head -10 | tr '\n' ', ')
mkdir -p .ceeya/_internal
echo "=== COMPACTION $DATE on $BRANCH === uncommitted: $CHANGED" \
>> .ceeya/_internal/compaction-log.txt
printf 'Pre-compaction state: branch=%s, uncommitted: %s' \
"$BRANCH" "$CHANGED" | \
jq -Rs '{hookSpecificOutput:{hookEventName:"PreCompact",
additionalContext:.}}'
Hook Architecture Summary
| Hook | Event | Type | Enforcement Level |
|---|---|---|---|
| SessionStart | Session opens | additionalContext | Context injection |
| SubagentStart | Agent dispatched | additionalContext | Logging + context |
| SubagentStop | Agent finishes | log only | Observability |
| PreToolUse (Read) | Reading safety files | additionalContext | Soft nudge |
| PreToolUse (Bash) | git push | permissionDecision: deny | Hard block |
| PostToolUse #1 | Edit/Write .ts | exit code 2 | Hard lint error |
| PostToolUse #2 | Edit/Write .swift | additionalContext | Build reminder |
| PostToolUse #3 | Edit/Write iOS .swift | async command | Auto-action |
| PostToolUse #4 | Every 5 edits | additionalContext | Commit reminder |
| PreCompact | Context compaction | additionalContext + log | State preservation |
The full hook event list (25+ events from source): SessionStart, SessionEnd, PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied, Stop, SubagentStart, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle, Notification, PreCompact, PostCompact, ConfigChange, FileChanged, CwdChanged, InstructionsLoaded, Elicitation, ElicitationResult, WorktreeCreate, WorktreeRemove, UserPromptSubmit, StopFailure. We use 6 of these — the rest are available for more advanced automation.
Hook handler types:
command— shell script (what we use). Deterministic, fast, can't be ignored.http— webhooks. For external integrations (Slack notifications, CI triggers).prompt— LLM evaluation. Unreliable — the model evaluates a prompt and decides what to do. We use zero of these.agent— spawns a subagent. Powerful but expensive. Could be used for automated code review on every edit.
PreToolUse decision precedence when multiple hooks disagree: deny > defer > ask > allow. A single deny hook overrides any number of allow hooks. This is why the git push blocker is absolute — nothing can override it.
The if field for narrow filtering: hooks support permission rule syntax for targeting specific tool invocations. For example, "if": "Bash(git *)" matches only Bash calls that start with git. This is how Hook 5 targets git push specifically without intercepting all Bash commands.
Hook deduplication: Identical hooks are auto-deduplicated by the runtime. All matching hooks for an event run in parallel, not sequentially. For subagents: Stop hooks auto-convert to SubagentStop — you don't need to register both.
07The Knowledge System
Four layers of information, separated by persistence and specificity. Rules are terse and always visible. Knowledge is comprehensive and loaded on demand.
The Separation Principle
Rules = "never do X" — always visible, even in the smallest context.
Knowledge = "here's how to do Y" — loaded on demand, comprehensive with examples.
Product = "here's what Ceeya does" — fed to product-thinker, red-team, and context-scout when they need domain context.
The Deduplication Story
Rules files used to have code examples that duplicated knowledge files. We stripped them to terse checklists — rules say NEVER DO X, knowledge shows HOW TO DO Y. The deduplication reduced rules from ~400 lines to 180 lines while making them more effective, because shorter rules get more attention weight.
Knowledge Files — Full Breakdown
All 8 knowledge files with line counts and content descriptions
| File | Lines | Contents |
|---|---|---|
| observability.md | 365 | PostHog event taxonomy (every event name, every property), Sentry configuration, instrumentation patterns, how to add new events, session replay config |
| ios.md | 290 | SwiftUI design system tokens (every color, every font), component catalog, navigation patterns, DesignSystem/ directory structure, view composition rules |
| cache-versioning.md | 202 | Auto-versioning system for AI outputs — how cache keys are computed, version manifest, L1/L2 cache boundary, how to bump versions when prompts change |
| conventions.md | 169 | Terminology table (what to call things in code vs UI), consumer language guidelines, naming conventions, import patterns, barrel export rules |
| backend.md | 154 | DI patterns, service conventions, error handling, route structure, middleware ordering, auth architecture, pg-boss v10 job patterns |
| migration-checklist.md | 56 | Database migration safety: pre-flight checks, backward compatibility requirements, rollback procedures, data preservation rules |
| ios-api-contract.md | 49 | iOS-backend API contract: response shapes, error format, pagination, auth header requirements |
| database.md | 20 | Schema conventions: naming, column types, nullable vs non-nullable, indexing strategy |
Total: 1,305 lines of domain knowledge loaded by the orchestrator at session start.
All 6 rules files with trigger patterns
Rules files are auto-loaded by Claude Code when a file matching the path pattern is read or edited. They are terse checklists, not comprehensive references.
| File | Lines | Triggered By | Key Rules |
|---|---|---|---|
| git-workflow.md | 63 | Any git operation | Commits are frequent, pushes are rare. Descriptive messages. PR format. Never push to staging/prod directly. |
| ios.md | 30 | *.swift files | Use DesignSystem/ tokens. Never raw SwiftUI styling. Check for duplicate components. Build after 5 edits. |
| backend.md | 26 | apps/api/* files | .js import extensions. DI injection. Error handling patterns. Route authorization. |
| safety.md | 24 | Safety-related files | null != false != 0. Conservative defaults. No fabricated safety data. Dispatch safety-auditor for any change. |
| shared-types.md | 23 | packages/shared/* files | Additive changes only when possible. Update all consumers. Type-contract-auditor for breaking changes. |
| database.md | 14 | Migration files | Backward compatible. Test rollback. No data loss. Follow migration-checklist.md. |
Total: 180 lines — deliberately sparse. Every word has to earn its place because these are always-on context.
Subagents with omitClaudeMd: true skip CLAUDE.md (saving context) but the orchestrator still feeds them relevant knowledge files in the dispatch prompt. The separation means you can control exactly what each agent sees.
How getUserContext() injects CLAUDE.md: It walks the directory hierarchy from the working directory up to the root, collecting every CLAUDE.md it finds. These are injected as system-reminder messages — not in the system prompt, but between conversation turns. This means CLAUDE.md content gets recency bias but competes with other system-reminder blocks.
The InstructionsLoaded hook event fires when CLAUDE.md or rules files load. This could be used for validation — for example, a hook that checks whether rules files have grown beyond a line threshold and warns about attention decay risk.
Rules files support paths: frontmatter for glob-matching. When a file matching the glob pattern is read or edited, the rules file auto-loads. Example: a rules file with paths: ["apps/ios/**/*.swift"] only loads when Swift files are touched — no wasted context on backend work.
Skills are loaded as initial messages (not system prompt). They're conversation context, positioned after the system prompt but before the first user message. This means they have strong attention weight at the start of a session but can decay in long conversations — which is why the criticalSystemReminder_EXPERIMENTAL metacognitive check includes "Am I about to skip a skill invocation?"
08Cross-Session Memory
User corrections become permanent behavioral changes. The system learns from its mistakes across sessions.
Two Memory Layers
.claude/agent-memory/ceeya-dev/. Project-specific lessons the agent learned. Auto-injected at session start.~/.claude/projects/.../memory/. User preferences and meta-lessons about agent behavior, tool performance, and workflow.Agent Memory — All 13 Files
Complete agent memory listing
| File | What It Captures |
|---|---|
| feedback_echo_chamber.md | Agent must serve intent, not literal words. Caught reflexively complying with a suboptimal request instead of proposing the better approach. |
| feedback_debate_depth.md | Exhaustive multi-round debates, maximum agent dispatch, never rush to consensus. |
| feedback_rebuild_over_patch.md | Prefer clean rebuilds over incremental patches; don't be conservative about scope. |
| feedback_conversational_thinking.md | Think out loud conversationally, not formatted reports; "yap away" during ideation. |
| feedback_comprehensive_fields.md | More fields not fewer; manage complexity through simple/advanced UX modes. |
| feedback_no_mid_updates.md | What appears on screen is FINAL; no placeholders, no progressive upgrading. |
| feedback_consumer_language.md | All user-facing language must be warm, actionable, personalized; never clinical/AI-sounding. |
| feedback_correction_downvote_only.md | Corrections are downvote-only; no positive signal; contest reasoning, AI reviews. |
| feedback_manual_baselines.md | No auto-apply for category baselines; manual review via script/admin view. |
| feedback_simulator_device.md | Always use iPhone 17 Pro Max simulator, never iPhone 16. |
| project_scoring_philosophy.md | Critical distinction: no stance on controversial ingredients, proactive on established science. |
| feedback_admin_mobile_first.md | Admin tooling mobile-first, maintainable, real internals not JSON dumps. |
| MEMORY.md | Index file — links to all other memory files with one-line summaries. |
The Echo Chamber Incident
One of the most important memory files. The incident: User said "do a smaller scale test." The agent immediately designed a different test prompt. The user pointed out that for proper controlled testing, a 5th repetition of the same prompt was the correct approach. The agent should have caught this and proposed it.
The lesson: "User said X → do X" is the default RLHF behavior. The agent needs to pause: "User said X → what's their GOAL → does X serve the goal → if not, propose Y."
The user's words: "If I make a mistake, or misjudgement, or say something that doesn't make sense, I don't want an echo chamber."
The Feedback Promotion Pattern
How corrections get promoted — the 4-layer escalation
Not all corrections stay as memory files. When a correction proves universal — it applies broadly, not just to one situation — it gets promoted:
- Memory file (initial capture) — agent reads it at session start. Cheapest to create, narrowest scope.
- Knowledge file (pattern proven) — becomes part of the reference all agents can access. Correction has been validated across multiple situations.
- Agent system prompt (architectural) — becomes a hard rule in the orchestrator or specialist. The correction is fundamental to how the agent should think.
- Hook (structural) — becomes a deterministic enforcement that can't be forgotten. The correction was so critical that behavioral enforcement isn't enough.
Real promotions: Several corrections were so important they got promoted from memory into the agent prompt itself. The MEMORY.md index file tracks which feedback has been incorporated into higher layers:
- "Consumer language" → promoted to
knowledge/conventions.mdterminology table - "Structured data over prose" → promoted to
knowledge/conventions.md+knowledge/backend.md - "User as final authority, constant checkpoints" → promoted to agent User Communication section
- "Calibrated dispatch (not all-or-nothing)" → promoted to agent 3-Tier dispatch system
- "Skills maximization" → promoted to agent Skills table (22 skills with triggers)
User Memory — All 8 Files
Complete user memory listing
| File | What It Captures |
|---|---|
| MEMORY.md | Master index — gotchas, patterns, all feedback files, tracking which corrections were promoted to higher layers. |
| patterns.md | Import conventions, barrel exports, service patterns, AI operations, pg-boss v10 patterns. |
| gotchas.md | Build order dependencies, duplicate export traps, similar-looking types, dead code markers. |
| feedback_tool_performance.md | Tool/skill overhead awareness; trim unused MCPs to reduce context bloat. |
| feedback_agent_speed.md | Don't delegate massive data files to agents; write directly or split small. |
| feedback_agent_architecture.md | All Opus models, comprehensive agent team, user transparency, self-improving system. |
| feedback_skill_enforcement.md | Actually INVOKE skills via Skill tool, don't just "follow in spirit." |
| feedback_ask_before_acting.md | Default is ASK, not act. Don't guess tool syntax or APIs — verify or ask user first. |
The Guardian
A scheduled agent that runs every morning:
- Reads
.ceeya/guardian-config.mdfor priorities, timeline, marketing claims - Reports on project health — stale plans, broken claims, missed deadlines
- The orchestrator maintains the config file so the Guardian always has current info
- Every session end: ceeya-dev updates "This Week's Focus" bullets so the Guardian knows what matters
- The SessionStart hook and SubagentStart hook both pull from the Guardian config — so priorities flow into every session and every agent dispatch
Three memory scopes from the Claude Code source:
user— stored at~/.claude/agent-memory/. Follows the user across all projects. Good for universal preferences ("always use iPhone 17 Pro Max simulator").project— stored at.claude/agent-memory/in the repo. Shared with the team via git. Good for project-specific lessons ("Ceeya's safety gate must run before scoring").local— stored at.claude/agent-memory-local/. Gitignored. Good for machine-specific or sensitive context.
MEMORY.md auto-injection: The first 200 lines / 25KB of MEMORY.md are auto-injected at session start. This is why MEMORY.md is an index file with one-line summaries linking to detail files — keep the index lean so the injection doesn't waste context on rarely-needed details.
The fire-and-forget mkdir pattern: The memory directory is created at agent spawn time in a sync callback. It can't be async because it's called from React render (Claude Code's UI layer). This means the directory creation blocks briefly — one reason to keep memory operations lightweight.
Snapshot system: Memory supports syncing across environments via snapshots. This means corrections learned on your laptop persist when you switch to a different machine or CI environment.
09How This Was Built
None of this was designed upfront. Every piece exists because something broke. The architecture is a scar tissue map of every failure mode we encountered.
It Started with the Problems
The developer was building Ceeya and kept hitting the same issues with the AI:
Fabricated defaults — the AI would show "0" instead of "unknown" for missing nutrition data. In a health app, a fabricated zero tells someone a food is safe when we don't know. This became the first line of CLAUDE.md: "Null means unknown. False means definitely not. Zero means measured zero. Never fabricate defaults."
Forgotten safety rules — mid-session, the AI would stop running allergen checks before scoring. It followed the rules at the start but lost them under attention decay. This led to the safety-auditor agent and the critical domain override system.
Raw colors instead of design tokens — every time the AI wrote SwiftUI, it would use raw Color.blue instead of .ceyaPrimary. Corrections worked once, then reverted next session. This led to the design-system-guardian agent and eventually persistent memory.
Symptom-fixing instead of root-cause fixing — when corrected, the AI would fix the immediate issue but make the same class of mistake elsewhere. This led to the metacognitive criticalSystemReminder_EXPERIMENTAL: "Is my last fix a root-cause fix or a band-aid?"
Implementing without asking — the AI would build entire features autonomously, then present finished work as "keeping you in the loop." This led to the echo chamber memory, the pushback instructions, and the user-checkpoint mandate.
Each problem became a rule. Each rule became a section of the agent prompt. Each section that got ignored became a structural enforcement — a hook, a disallowedTools constraint, a specialist agent with its own optimization function.
The Evolution Timeline
The architecture grew organically over months of daily use. Here's the actual sequence:
ceeya-dev.md as a 716-line behavioral contract that replaces Claude Code's default system prompt entirely.The Conversation Pattern
The building process is literally just talking to the AI. Every piece of the architecture traces back to a specific conversation:
safety-review skill with structured audit methodology — a checklist the agent follows every time, not just when it remembers.feedback_conversational_thinking memory file. The agent now thinks out loud conversationally during work instead of producing formatted reports after.feedback_echo_chamber memory AND the pushback instructions in the agent prompt. The agent now evaluates whether the user's request serves their actual goal before complying.criticalSystemReminder_EXPERIMENTAL, understanding the prompt assembly pipeline, and this entire architecture overhaul session.Why this matters for the audience
The point isn't the specific architecture. It's the process:
- You don't need to design the system upfront. Start with CLAUDE.md. When something breaks, add the fix at the right layer.
- Every layer exists for a reason. If you can't explain what failure mode a piece prevents, it probably shouldn't exist.
- The AI is your collaborator, not your executor. Tell it what's wrong. It will often propose the structural fix — a hook, an agent, a memory file — better than you would.
- Corrections are the most valuable input. Every time you correct the AI, that's a rule waiting to be written. The memory system captures these automatically.
- Read the source. Claude Code is open source. The features in the docs are a fraction of what's available.
criticalSystemReminder_EXPERIMENTAL,omitClaudeMd, hook event types, agent frontmatter fields — all found by reading the code.