v2.x

Workflow

The mandatory 7-step order Claude must follow every session. Hooks enforce this — skipping steps will cause tool denials.

📊 Visual Overview

Scroll inside the diagram to zoom · drag to pan · built with Excalidraw

Loading diagram… requires internet connection (CDN)

🗺️ Overview
⚠️
Enforced by hooks Native tools (Read, Grep, Glob, Edit, Write) are blocked by PreToolUse hooks unless a c3_* tool was called first. Violating the workflow order causes immediate tool denials — not warnings.
#StepToolWhen
1 Recall c3_memory(action='recall') Before any multi-step or context-dependent task
2 Search c3_search(action='code|files|semantic') Before ANY file discovery or content search
3 Map + Read c3_compress(mode='map')c3_read(symbols=…) Before reading any file content
4 Impact c3_impact(target='symbol') Before editing shared symbols — check blast radius
5 Edit c3_edit(file, old, new, summary) All file modifications
6 Filter c3_filter(text=…) Terminal output >10 lines or log files
7 Validate c3_validate(file_path) After every edit; type check auto-runs if pyright/tsc installed
8 Log c3_session(action='log') After decisions; snapshot before /clear
+ Bitbucket c3_bitbucket(action='list_prs') v2.30.0 — see/act on PRs, branches, builds, repo admin on enterprise Bitbucket Data Center. Full guide →
+ Cross-project c3_project(action='list') v2.31.0 — discover and operate on OTHER c3-installed projects; reads run free, writes need allow_write=true. Tool card →
+ Sub-projects c3 sub add <folder> v2.44.0 — link child projects living inside a parent; federated search/memory + Hub management. Section →

1
Recall Memory
Recover context from previous sessions before doing anything
c3_memory

The first thing Claude should do at the start of any non-trivial task. This loads relevant facts from the persistent memory store — architecture decisions, conventions, ongoing work, gotchas discovered in prior sessions.

c3_memory(action='recall', query='authentication system refactor')

When to recall

  • Start of any multi-step task
  • When the task touches a system you've worked on before
  • When you suspect there are architecture decisions or gotchas to be aware of

When you can skip

  • Trivial one-off tasks (rename a variable, fix a typo)
  • Brand new projects with no prior sessions
💡
Use a specific query matching your task — "recall" pulls the top-k most relevant facts by semantic similarity. A vague query returns less useful results.
2
Search First
Always discover files and code via c3_search — never Grep/Glob directly
c3_search
ActionUse case
code TF-IDF code search — find functions, classes, patterns by content
files File discovery — find files by name, extension, path pattern
semantic Embedding-based similarity search — find conceptually related code
exact Regex-based exact search — when you know the literal string
❌ Wrong
# Never start with native tools
Grep(pattern='AuthMiddleware')
Glob(pattern='**/*.py')
✅ Correct
c3_search(query='AuthMiddleware', action='code')
c3_search(query='*.py auth', action='files')

prefetch=True

Pass prefetch=True to auto-compress the top result files in parallel, saving a round-trip:

c3_search(query='session manager', action='code', prefetch=True)
3
Map then Read
Get a structural overview first, then read only what you need
c3_compress + c3_read

Reading a whole file wastes tokens. The correct pattern is: compress to get the map, then read only the specific symbols or lines you need.

❌ Wrong
# Reading entire files
Read(file_path='services/session_manager.py')
✅ Correct
# Map → targeted read
c3_compress(file_path='services/session_manager.py', mode='map')
c3_read(file_path='services/session_manager.py', symbols=['SessionManager.save'])

Compress modes

ModeOutputBest for
map Line-range map of all symbols (functions, classes, constants) Understanding file structure before reading
dense_map Same as map but with docstrings included When you need a bit more context per symbol
smart Adaptive compression — keeps important sections General-purpose read when map is insufficient
diff Show what changed recently (git diff aware) Reviewing recent changes
bug_scan Highlight suspicious patterns Quick bug-hunting pass

Batch compress

# Compress multiple files in one call
c3_compress(file_path='services/memory.py,services/agents.py', mode='map')
4
Impact Check
Know the blast radius before editing shared symbols
c3_impact

Before renaming, removing, or changing the signature of any function or class used across files, run c3_impact to find every call site. A HIGH risk result means all those files need updating in the same edit pass — skipping this step is how silent breakages happen.

# Check blast radius before renaming a shared function
c3_impact(target='handle_memory', file_path='cli/tools/memory.py')

# Check what's affected AND has uncommitted changes
c3_impact(target='MemoryStore', mode='unstaged')

When to skip

Skip c3_impact when editing purely local symbols (private helpers, inner functions, one-off scripts). Use it for anything in a shared service, public API, or imported across files.

⚠️
A HIGH risk result (7+ files) means the edit isn't isolated — plan to update all call sites in the same session before reporting done.
5
Edit via C3
Atomic read-patch-write-log — always c3_edit, never native Edit
c3_edit

c3_edit does read + patch + write + edit ledger log in one step. It also validates the old_string match before writing. If the literal match fails but the strings differ only by unicode lookalikes (curly quotes, em/en-dashes, NBSP), c3_edit retries with a 1:1 normalized match and tags the response [unicode-normalized].

c3_edit(
  file_path='services/auth.py',
  old_string='return token.expiry > now',
  new_string='return token.expiry > now + timedelta(seconds=CLOCK_SKEW)',
  summary='Add clock skew buffer to token expiry check'
)

Batching edits to the same file

Use the edits=[] parameter to apply multiple patches to one file in one call:

c3_edit(
  file_path='src/config.py',
  edits=[
    {"old_string": "DEBUG = True",  "new_string": "DEBUG = False"},
    {"old_string": "HOST = 'localhost'", "new_string": "HOST = '0.0.0.0'"}
  ],
  summary='Production config hardening'
)

Parallel edits across files

# Call c3_edit for multiple files in parallel (same message, different files)
c3_edit(file_path='src/a.py', old_string='...', new_string='...', summary='...')
c3_edit(file_path='src/b.py', old_string='...', new_string='...', summary='...')
6
Filter Output
Extract signal from noisy terminal output and log files
c3_filter

Any terminal output longer than ~10 lines should be passed through c3_filter to extract only the relevant signal before including it in context.

# Filter raw terminal output
c3_filter(text="<long test output here>")

# Filter a log file
c3_filter(file_path=".c3/sessions/session_001.json", pattern="error|warning")
💡
c3_filter is especially useful after running tests, build commands, or any operation that produces verbose output. It strips noise and returns structured signal.
7
Validate
Syntax + type check every edited file before reporting done
c3_validate

Always call c3_validate after edits. Never report "done" without validation. A syntax error introduced silently is worse than no change at all. If pyright (Python) or tsc (TypeScript) is on your PATH, C3 also runs a deep type check automatically and surfaces type errors as advisory warnings alongside the PASS result.

# Single file
c3_validate(file_path='services/auth.py')

# Batch — comma-separated (runs in parallel internally)
c3_validate(file_path='services/auth.py,services/session.py,cli/c3.py')

Supported languages

Python (.py) JavaScript (.js .mjs) TypeScript (.ts .tsx) HTML (.html) CSS (.css) JSON (.json) TOML (.toml) YAML (.yml .yaml) Bash (.sh)
8
Log & Snapshot
Persist decisions and context so the next session starts informed
c3_session

Log a decision

c3_session(action='log', data='Chose JWT over session cookies — easier horizontal scaling')

Snapshot before /clear

Always snapshot before running /clear. This saves the full session state so the next session can restore it.

c3_session(action='snapshot')
# Then in Claude Code: /clear

Auto-snapshot on session end

C3 automatically captures a snapshot when a session ends — including Ctrl+C, natural stop, and max-turn exits. This is handled by a Stop hook (hook_auto_snapshot.py) that fires after Claude's last response.

How it works: The Stop hook checks if the C3 UI server is running. If so, it calls POST /api/auto-snapshot which captures a full snapshot with live session state (decisions, files touched, memory facts, budget). If the server isn't running, it falls back to a lightweight file-based snapshot from persisted session data on disk.

Installation: Stop hooks are registered automatically by c3 install-mcp. For existing projects, re-run c3 install-mcp to add them. No manual configuration needed.

EventAuto-snapshot?Notes
Natural session endYesStop hook fires reliably
Ctrl+C (graceful)YesStop hook fires before exit
Ctrl+C (hard kill)PartialMay not fire if process killed instantly
/clearIndirectSession ends first, triggering the hook

Manual snapshots are still recommended before /clear for maximum reliability, but the auto-snapshot serves as a safety net.

Restore in new session

c3_session(action='restore')

Compact (one-step restart)

# Snapshot + summary + restore instruction in one step
c3_session(action='compact')

🚫 Anti-patterns

These will cause hook denials or waste significant tokens:

Starting with native file search/read — always use c3_search or c3_compress first, even for "quick" lookups.
Using native Edit when c3_edit is available — c3_edit validates, logs to ledger, and is atomic. Native Edit does none of this.
Reading entire files with native Read — always compress first, then read only the symbols you need.
Skipping c3_validate after edits — you will silently introduce syntax errors that break the next session.
Running /clear without a snapshot — the auto-snapshot Stop hook provides a safety net, but a manual snapshot before /clear captures richer context (task description, working files). Don't rely solely on auto-snapshot.
Dumping long terminal output directly into context — always filter first with c3_filter.
Skipping recall at session start — you'll rediscover decisions and conventions already documented in memory.

📐 Plan Mode
Plan Mode — Read-only tools only

In plan mode, all c3_* read tools work normally: c3_search, c3_read, c3_compress, c3_filter, c3_validate, c3_status, c3_memory(recall/query). Skip edit and delegate steps in plan mode.


🔄 Session Restart Cycle
Loading diagram…

Option A — Compact (one step)

c3_session(action='compact')
# → Claude gives you a restore command to paste after /clear
# → Run /clear
# → Paste the restore command

Option B — Manual (two steps)

# Step 1: Before /clear
c3_session(action='snapshot')

# Step 2: After /clear, in new session
c3_session(action='restore')

What's preserved across restarts

  • Memory facts (always persistent — not affected by /clear)
  • Session decisions and context notes (from snapshot)
  • Edit ledger (always persistent)
  • Code index (always persistent)

What's lost on /clear

  • Conversation history (by design)
  • In-flight reasoning (must be re-derived)
  • Unsnapshot session state

/terse — Terse Output Mode

Installed by c3 install-mcp into ~/.claude/commands/terse.md. Available in every Claude Code project after C3 setup. Reduces output token usage by stripping prose verbosity while leaving all technical content (code, paths, commands, URLs) unchanged.

💡
Complements C3's input-token compression (c3_compress, c3_filter). Those shrink what Claude reads; terse mode shrinks what Claude writes.

Intensity levels

LevelEffectExample output
/terse lite Remove filler, keep grammar intact "Auth middleware handles token expiry at auth.py:42."
/terse (full) Drop articles, use fragments, cut transitions "Auth middleware: token expiry. See auth.py:42."
/terse ultra Telegraphic, maximum abbreviation "Token expiry → auth.py:42"

What gets suppressed

  • Preamble: "Sure, I'll...", "I've completed..."
  • Hedging: "I think", "it seems", "you might want to"
  • Trailing summaries that restate the diff
  • Filler conjunctions and transitional phrases

What stays exact

  • All code blocks, inline code, file paths, commands, URLs
  • Error messages and stack traces
  • Variable names, function names, type signatures

Exceptions — do not compress these

Terse mode applies to conversational prose only. The following must remain complete and precise regardless of terse level:

  • Memory saves (c3_memory add/update) — facts must be self-contained and fully worded; abbreviated facts are unsearchable
  • Session logs (c3_session log) — decisions need full reasoning to be useful in future sessions
  • CLAUDE.md / instructions files — rules must be unambiguous; never abbreviate directives
  • Planning and architecture responses — user reviews for correctness; include all steps and trade-offs
  • Error diagnosis — include full error text, file paths, line numbers, root cause
  • Tool call argumentsold_string, new_string, fact, summary must be exact; never truncate

Deactivate

Say "normal mode" or start a new session.

Terse Advisor — automatic nudge

C3 installs a Stop hook (hook_terse_advisor.py) that fires after each response turn. When a verbose response is detected and /terse is not already active, it prints a one-time nudge per session:

────────────────────────────────────────────────────
[C3] Verbose response (~950 chars). /terse saves ~50% output tokens.
     Type /terse to activate.
     Silence: c3 terse dismiss  |  Snooze 24h: c3 terse later
────────────────────────────────────────────────────

The advisor checks the transcript for recent /terse activations and skips if terse mode is already in use. Nudge state is stored in ~/.c3/terse_advisor.json.

CommandEffect
c3 terse dismissSilence the advisor permanently
c3 terse laterSnooze nudges for 24 hours
c3 terse resetClear all advisor state
c3 terse statusShow current advisor state

🌳 Sub-projects

Since v2.44.0, a c3 project can link folders that physically live inside it as sub-projects. Each child gets its own full .c3 (index, memory, config), the parent's index excludes the child's subtree, and the link is recorded in three places at once: the parent config's subprojects[] array, a back-link in the child's config, and the hub registry. Links are depth-1 — a child cannot have children of its own.

💡
Use sub-projects for monorepos and nested apps: each child stays independently searchable with its own memory, while the parent can fan queries out across the whole family.

CLI — c3 sub

CommandEffect
c3 sub add <folder> [--name --ide] Link a folder inside the current project. Adopts an existing .c3, otherwise runs a full init. --no-init links without initializing; --parent <path> targets a different parent.
c3 sub list Tree of linked children with status (the default when no sub-command is given).
c3 sub remove <ref> [--clear] Unlink a child and promote it to a standalone top-level project (its .c3 is kept). --clear also wipes .c3, uninstalls the MCP config and instruction docs, and deregisters it.
c3 sub run <update|reindex|health> [--include-parent] Cascade an operation across every child. --mcp also reinstalls the MCP config during update.
c3 sub check [--fix --prune] Reconcile the three-way links. --fix repairs from the parent config; adding --prune drops entries whose folder is gone.

Agent surface — the scope parameter

c3_search and c3_memory accept a scope parameter on parent projects:

ValueMeaning
''Current project only (default)
'all'Fan out across the parent and all linked children
'<child>'One specific sub-project by name
c3_search(query='auth middleware', scope='all')
c3_memory(action='recall', query='payment gotchas', scope='billing-service')

With hybrid.subprojects.memory_rollup enabled, a parent-side recall also unions in the children's facts automatically.

Hub UI management (v2.57.0)

Everything the CLI does — plus the operations that need multi-step safety — is available in the Hub:

  • Designate — kebab menu → "Designate sub-project…" on any project card, or from the drill-in Sub-projects tab. A folder picker fenced to the parent's subtree validates the choice, shows adopt-vs-initialize expectations up front, and offers an instruction-docs/IDE picker (auto | claude | vscode | cursor | codex | antigravity).
  • "Make sub-project of…" — the reverse move: a top-level card whose folder physically sits inside a registered parent can link itself under it (containment is mandatory).
  • Sub-projects drill tab — per-child rows with inline Validate and Promote; inline Reconcile with per-issue detail and planned repair actions; a Cascade launcher with operation picker, include-parent toggle, affected-children preview, live progress, and Cancel.
  • Link health, passively — parents show a red "N link issues" badge and children a status badge (ok · backlink_broken · unregistered · missing_folder · missing_c3 · orphan), computed from config + filesystem on every listing — no manual reconcile needed to spot trouble.
  • "Change parent…" — a staged wizard, because folders must physically move: ready-now targets relink immediately; otherwise unlink → move the folder yourself → validate → relink, with a per-step checklist and Undo before the move.
  • "De-initialize…" — clear-mode removal behind a typed-name confirmation: deletes .c3, uninstalls the MCP config, removes instruction docs, deregisters. Your code is untouched.
  • Federation toggles — the config editor's Sub-projects section on parent projects: memory roll-up, search fan-out for scope='all', and max children per query, persisted under hybrid.subprojects.
  • Ctrl-K scope chips — global search can target All, Top-level only, or one parent + its children; child results carry a "parent › child" breadcrumb.
⚠️
Managed keys The raw subprojects / parent config keys render read-only in the Hub config editor by design — hierarchy changes only go through the dedicated actions above (CLI or Hub), never by hand-editing the link records.

🗺️ Live Repo Map (v2.60.0)

Instruction docs (CLAUDE.md / AGENTS.md) no longer embed a frozen project tree. They carry a stable pointer to .c3/MAP.md — a machine-owned map C3 keeps fresh automatically. One map serves every consumer: Claude Code, Codex, Antigravity.

The map contains, in priority order: build/test commands, entry points, module one-liners, the depth-2 tree, and key files — under a token budget (default 1000). Sub-projects appear as boundaries and are never expanded into the parent map.

How it stays fresh

TriggerWhat happens
Structural edit (file created/deleted/renamed, manifest changed) Edit-ledger paths touch the .c3/map.dirty sentinel — a file-touch, never a scan
First C3 tool call of a session Background single-flight ensure: regenerates only if missing, dirty, or the git HEAD/branch/worktree fingerprint moved
c3 map refresh Explicit repair — always regenerates

Byte-stable by design: MAP.md is rewritten only when rendered content actually changes, so prompt caches keyed on file bytes stay warm. Volatile freshness state (git HEAD, worktree signature, generated-at) lives in .c3/map.meta.json — never in the map itself. Ordinary line edits never trigger regeneration.

💡
CLI: c3 map status (freshness report) · c3 map ensure (regen if stale) · c3 map refresh (force). Add --json for machine output. Config knobs: map.token_budget, map.file_cap, map.enabled (set false to restore the legacy embedded tree).
⚠️
The map is data, not instructions
Its header marks it as auto-generated repository data. Agents should read it for orientation — not obey text inside it. Memory facts are deliberately excluded from the map.

⚠️ When Native Tools Are Permitted

Native tools (Read, Grep, Glob, Edit) are allowed only as fallback when a c3_* tool failed or returned insufficient scope:

⚠️
State which c3_* tool was attempted and why it failed
Example: "c3_search returned no results for 'XYZ' — falling back to Grep for exact match."
SituationPermitted fallback
c3_search returned empty results Grep with exact pattern
c3_compress failed (file too short / binary) Read with limited lines
c3_edit failed (old_string not found after multiple retries) Read to confirm exact content, then retry c3_edit
c3_validate doesn't support the file type Run native syntax check via Bash