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.

Arshan Claude Code Community April 2026

Scan to follow along

Open this page on your phone or save for later

QR Code for ceeya.app/talk

ceeya.app/talk

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.

Copy this prompt
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
SessionStart hook Handoff + git state injected brainstorming skill product-thinker context-scout Codex (GPT-5.x) writing-plans skill

What happens step by step:

  1. SessionStart hook — visible context injection: handoff from previous session + "This Week's Focus" priorities from guardian config
  2. Knowledge loading — ceeya-dev reads all 8 knowledge files (1,305 lines) before responding. 8+ Read tool calls fire in rapid succession.
  3. 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?
  4. 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.
  5. 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.
  6. 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.
  7. 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:

product-thinker
15 tool uses · 95.6k tokens · 1m 52s
Evaluated product coherence, privacy risk, free/premium alignment, timing
context-scout
23 tool uses · 42.3k tokens · 1m 44s
Mapped existing share infrastructure, shared types, missing pieces

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:

  1. Audit only — verify existing share card doesn't leak health data
  2. Quick polish — upgrade ScanShareCard to use richVerdict + add screenshot analytics
  3. Plan for post-launch — write a plan file capturing the full spec for later
  4. 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.

Quick demo — triggers safety-auditor
Check the safety gate code for any issues — are there edge cases where an allergen could be missed?
SessionStart hook PreToolUse (Read) safety detection safety-auditor (critical override)

Step by step:

  1. SessionStart hook — handoff + priorities injected
  2. Knowledge loading — reads all knowledge files, especially safety.md rules
  3. Model reads safety-related files — the PreToolUse (Read) hook fires: "This is safety-critical code. Consider dispatching safety-auditor."
  4. 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.
  5. 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.

19Features
3Packages (monorepo)
iOS + NodePlatforms
16Product Pages

What Breaks at Scale

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:

1getSystemPrompt() — Claude Code base prompt~3K tokens, invisible to user
2enhanceSystemPromptWithEnvDetails() — working dir, OS, shellabsolute path guidance
3Custom agent getSystemPrompt() — your promptceeya-dev.md — 716 lines
4buildEffectiveSystemPrompt() — combines base + custommerges layers 1-3
5getUserContext() — CLAUDE.md hierarchyinjected as system-reminder
6Skills catalog + MCP instructionsadditional system-reminder blocks
7Memory files + agent listingauto-injected attachments

Total 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:

RLHF Training
Helpfulness bias — comply with the request immediately
VS
Your Instructions
Pushback requirement — "push back when the literal request doesn't serve the underlying intent"
RLHF Training
Session closure instinct — wrap up, summarize, ask "anything else?"
VS
Your Instructions
Continuity mandate — "completing a task is a TRANSITION, not a terminus"
RLHF Training
Momentum — keep coding, skip the process overhead
VS
Your Instructions
Mandatory skills — "stop and invoke this skill FIRST"
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_delta attachment 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
Under the Hood — Prompt Assembly Internals

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

716Lines
22Pre-loaded Skills
7Execution Steps
13Memory Files

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
  1. Load Knowledge — read all 8 knowledge files + handoff + guardian config before responding
  2. Discover — search the codebase broadly. Files move. Never hardcode paths from memory.
  3. Plan — scale to complexity. Tier 0: one sentence. Tier 1: plan file + domain agents. Tier 2: debate loop with adversarial review.
  4. Implement — build checkpoints every 5 files, self-interrogation during coding ("Am I fixing WHY this broke?"), parallel execution for independent subtasks
  5. Verify — mandatory skill invocation, root-cause audit, intent verification presented to user, Codex cross-model review
  6. Document — update knowledge base, product reference, marketing, plans, guardian config
  7. Handoff — overwrite handoff.md with a forward-looking prompt for the next session

The 18 Specialists — Overview

System Prompts
89-193 lines each, domain-focused. Not generic — each has its own optimization function.
maxTurns
10-25 turns per agent. Prevents runaway investigation loops.
disallowedTools
14 agents are read-only: Edit, Write, NotebookEdit blocked. They analyze — they don't change.
omitClaudeMd
13 agents skip CLAUDE.md injection. Saves ~40KB context per dispatch for focused analysis.
Review & Quality (5 agents)
red-team
145 lines model: opus maxTurns: 10 read-only omitClaudeMd

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
105 lines model: opus maxTurns: 10 read-only omitClaudeMd skill: refero-design

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
157 lines model: opus maxTurns: 25 read-only

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
193 lines model: opus maxTurns: 20 read-only omitClaudeMd

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
120 lines model: opus maxTurns: 20 omitClaudeMd

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 & Security (2 agents)
safety-auditor
161 lines model: opus maxTurns: 15 read-only skill: safety-review

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
186 lines model: opus maxTurns: 15 read-only omitClaudeMd

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.

Cross-Platform Contracts (2 agents)
type-contract-auditor
150 lines model: opus maxTurns: 15 read-only omitClaudeMd

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
101 lines model: opus maxTurns: 15 skill: tier-review

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.

Pipeline & Performance (3 agents)
scoring-pipeline-validator
119 lines model: opus maxTurns: 15 read-only omitClaudeMd skill: pipeline-review

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
165 lines model: opus maxTurns: 15 read-only omitClaudeMd skill: swiftui-pro

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
121 lines model: opus maxTurns: 15 read-only omitClaudeMd

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.

iOS (1 agent)
design-system-guardian
175 lines model: opus maxTurns: 20 read-only skill: swiftui-pro

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/.

Observability (2 agents)
sentry-investigator
125 lines model: opus maxTurns: 15 read-only omitClaudeMd skills: sentry:getIssues, sentry:seer

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
138 lines model: opus maxTurns: 20 read-only omitClaudeMd skills: posthog:query, posthog:insights, posthog:search

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 & Testing (2 agents)
context-scout
154 lines model: opus maxTurns: 15 read-only omitClaudeMd

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
114 lines model: opus maxTurns: 15

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?"

Meta (1 agent)
agent-architect
136 lines model: opus maxTurns: 15 omitClaudeMd

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).

Under the Hood — Agent Internals

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
AgentLinesTurnsRead-OnlyOmitClaudeMdSkills
red-team14510YesYes-
product-thinker10510YesYesrefero-design
standards-enforcer15725YesNo-
codebase-analyzer19320YesYes-
codebase-health12020NoYes-
safety-auditor16115YesNosafety-review
security-auditor18615YesYes-
type-contract-auditor15015YesYes-
tier-boundary-enforcer10115NoNotier-review
scoring-pipeline-validator11915YesYespipeline-review
perf-analyst16515YesYesswiftui-pro
ai-cost-optimizer12115YesYes-
design-system-guardian17520YesNoswiftui-pro
sentry-investigator12515YesYessentry:getIssues, sentry:seer
posthog-analyst13820YesYesposthog:query, posthog:insights, posthog:search
context-scout15415YesYes-
test-strategist11415NoNo-
agent-architect13615NoYes-

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

Tier 0 Just Execute

Single-file fix, typo, <20 lines. No agents, no skills, no plan file. Test: "Could this be fully reviewed by reading one file?"

Tier 1 Targeted Dispatch

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.

Tier 2 Full Debate Loop

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.

Phase A — Parallel Challenge

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.

Phase B — Respond

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).

Phase C — Present

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-team found 3 failure modes in the proposed migration strategy — data loss scenarios during the cutover
  • product-thinker flagged 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 AreaAlways DispatchesWhy Override Exists
Safety gate logicsafety-auditorFalse negative = real harm. No change is "too small" to audit.
Auth / token handlingsecurity-auditorHealth data breach has regulatory implications.
Scoring / pipeline logicscoring-pipeline-validator25-op DAG — one wrong dependency = stale health data.
Shared type breaking changestype-contract-auditoriOS and backend must agree. Divergence = runtime crash.
Subscription / tier enforcementtier-boundary-enforcerFree 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:

  1. Resets edit counters — zeroes both Swift-specific and universal counters so they don't carry over from previous sessions
  2. 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.
  3. 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.
  4. Lists active plans — shows existing plan files so the model checks for an existing plan before creating a duplicate.
  5. 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

HookEventTypeEnforcement Level
SessionStartSession opensadditionalContextContext injection
SubagentStartAgent dispatchedadditionalContextLogging + context
SubagentStopAgent finisheslog onlyObservability
PreToolUse (Read)Reading safety filesadditionalContextSoft nudge
PreToolUse (Bash)git pushpermissionDecision: denyHard block
PostToolUse #1Edit/Write .tsexit code 2Hard lint error
PostToolUse #2Edit/Write .swiftadditionalContextBuild reminder
PostToolUse #3Edit/Write iOS .swiftasync commandAuto-action
PostToolUse #4Every 5 editsadditionalContextCommit reminder
PreCompactContext compactionadditionalContext + logState preservation
Under the Hood — Hook Internals

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.

CLAUDE.md Injected into every context. Intentionally tiny — the health-critical invariant + file pointers. 10 lines
.claude/rules/ Auto-loaded by file path match. Terse checklists: "never do X." 6 domain files. 180 lines
.ceeya/knowledge/ Comprehensive reference with examples. "Here's how to do Y." 8 files, loaded on demand by the orchestrator at session start. 1,305 lines
.ceeya/product/ What Ceeya does — features, free vs premium, how scoring works. 24 files across features + reference. 24 files

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
FileLinesContents
observability.md365PostHog event taxonomy (every event name, every property), Sentry configuration, instrumentation patterns, how to add new events, session replay config
ios.md290SwiftUI design system tokens (every color, every font), component catalog, navigation patterns, DesignSystem/ directory structure, view composition rules
cache-versioning.md202Auto-versioning system for AI outputs — how cache keys are computed, version manifest, L1/L2 cache boundary, how to bump versions when prompts change
conventions.md169Terminology table (what to call things in code vs UI), consumer language guidelines, naming conventions, import patterns, barrel export rules
backend.md154DI patterns, service conventions, error handling, route structure, middleware ordering, auth architecture, pg-boss v10 job patterns
migration-checklist.md56Database migration safety: pre-flight checks, backward compatibility requirements, rollback procedures, data preservation rules
ios-api-contract.md49iOS-backend API contract: response shapes, error format, pagination, auth header requirements
database.md20Schema 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.

FileLinesTriggered ByKey Rules
git-workflow.md63Any git operationCommits are frequent, pushes are rare. Descriptive messages. PR format. Never push to staging/prod directly.
ios.md30*.swift filesUse DesignSystem/ tokens. Never raw SwiftUI styling. Check for duplicate components. Build after 5 edits.
backend.md26apps/api/* files.js import extensions. DI injection. Error handling patterns. Route authorization.
safety.md24Safety-related filesnull != false != 0. Conservative defaults. No fabricated safety data. Dispatch safety-auditor for any change.
shared-types.md23packages/shared/* filesAdditive changes only when possible. Update all consumers. Type-contract-auditor for breaking changes.
database.md14Migration filesBackward 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.

Under the Hood — Knowledge Injection Internals

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

Agent Memory
13 files in .claude/agent-memory/ceeya-dev/. Project-specific lessons the agent learned. Auto-injected at session start.
User Memory
8 files in ~/.claude/projects/.../memory/. User preferences and meta-lessons about agent behavior, tool performance, and workflow.

Agent Memory — All 13 Files

Complete agent memory listing
FileWhat It Captures
feedback_echo_chamber.mdAgent must serve intent, not literal words. Caught reflexively complying with a suboptimal request instead of proposing the better approach.
feedback_debate_depth.mdExhaustive multi-round debates, maximum agent dispatch, never rush to consensus.
feedback_rebuild_over_patch.mdPrefer clean rebuilds over incremental patches; don't be conservative about scope.
feedback_conversational_thinking.mdThink out loud conversationally, not formatted reports; "yap away" during ideation.
feedback_comprehensive_fields.mdMore fields not fewer; manage complexity through simple/advanced UX modes.
feedback_no_mid_updates.mdWhat appears on screen is FINAL; no placeholders, no progressive upgrading.
feedback_consumer_language.mdAll user-facing language must be warm, actionable, personalized; never clinical/AI-sounding.
feedback_correction_downvote_only.mdCorrections are downvote-only; no positive signal; contest reasoning, AI reviews.
feedback_manual_baselines.mdNo auto-apply for category baselines; manual review via script/admin view.
feedback_simulator_device.mdAlways use iPhone 17 Pro Max simulator, never iPhone 16.
project_scoring_philosophy.mdCritical distinction: no stance on controversial ingredients, proactive on established science.
feedback_admin_mobile_first.mdAdmin tooling mobile-first, maintainable, real internals not JSON dumps.
MEMORY.mdIndex 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

User corrects agent → memory file saved → future sessions read it → same mistake avoided | Patterns promoted to agent system prompt or knowledge files when they prove universal (not one-off corrections)
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:

  1. Memory file (initial capture) — agent reads it at session start. Cheapest to create, narrowest scope.
  2. Knowledge file (pattern proven) — becomes part of the reference all agents can access. Correction has been validated across multiple situations.
  3. Agent system prompt (architectural) — becomes a hard rule in the orchestrator or specialist. The correction is fundamental to how the agent should think.
  4. 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.md terminology 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
FileWhat It Captures
MEMORY.mdMaster index — gotchas, patterns, all feedback files, tracking which corrections were promoted to higher layers.
patterns.mdImport conventions, barrel exports, service patterns, AI operations, pg-boss v10 patterns.
gotchas.mdBuild order dependencies, duplicate export traps, similar-looking types, dead code markers.
feedback_tool_performance.mdTool/skill overhead awareness; trim unused MCPs to reduce context bloat.
feedback_agent_speed.mdDon't delegate massive data files to agents; write directly or split small.
feedback_agent_architecture.mdAll Opus models, comprehensive agent team, user transparency, self-improving system.
feedback_skill_enforcement.mdActually INVOKE skills via Skill tool, don't just "follow in spirit."
feedback_ask_before_acting.mdDefault 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:

Under the Hood — Memory Internals

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:

1
Just CLAUDE.md — started with project instructions in a single file. Worked for a while. Simple rules, simple project.
2
Rules files — as the project grew, needed path-scoped rules that only load on relevant files. Six domain-specific checklists: safety, iOS, backend, shared types, database, git.
3
Knowledge base — rules said "never do X" but the model didn't know how to do Y instead. Built comprehensive reference docs with examples: 8 files, 1,305 lines.
4
Custom agent prompt — CLAUDE.md wasn't enough to change the model's behavior at a deep level. Created ceeya-dev.md as a 716-line behavioral contract that replaces Claude Code's default system prompt entirely.
5
Specialist agents — realized the main agent can't be expert at everything simultaneously. Created domain specialists: safety auditor, security auditor, design system guardian, type contract auditor, and more. Each with its own system prompt and constraints.
6
The dispatch system — needed calibration for when to dispatch. Not everything needs a committee. The 3-tier system emerged: Tier 0 (just do it), Tier 1 (targeted dispatch), Tier 2 (full debate loop).
7
Persistent memory — user corrections kept getting lost between sessions. Memory fixed this: corrections survive across sessions and get promoted to higher layers when they prove universal.
8
Hooks — self-enforcement was unreliable. The model would skip mandatory skills under momentum. External enforcement via deterministic shell hooks: 10 hooks, zero prompt hooks. Command hooks can't be ignored.
9
The Guardian — needed automated daily health checks when the developer isn't actively coding. A scheduled agent that reads project priorities and reports on stale plans, broken claims, missed deadlines.
10
Cross-model review — discovered that Codex (GPT-5.x) catches different things than Claude. Different training, different blind spots. Added it to the Tier 1/2 review pipeline for adversarial diversity.
11
This session — read Claude Code's actual source code, found undocumented features, ran 20+ subagents to analyze and improve the architecture itself. The system improved its own system.

The Conversation Pattern

The building process is literally just talking to the AI. Every piece of the architecture traces back to a specific conversation:

"The safety auditor keeps missing allergen derivatives"
Led to adding the safety-review skill with structured audit methodology — a checklist the agent follows every time, not just when it remembers.
"Stop summarizing what you did — I can read the diff"
Became the feedback_conversational_thinking memory file. The agent now thinks out loud conversationally during work instead of producing formatted reports after.
"If I make a mistake, I don't want an echo chamber"
Became the 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.
"Your prompt conflicts with Claude's own instructions — let's fix it"
Led to reading Claude Code's source code, discovering 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.