Tools Reference
Complete reference for all 23 C3 MCP tools registered at v2.145.0 β parameters, actions, examples, and usage notes.
All Tools
The primary discovery tool. Always use before Grep or Glob. code (the default) ranks candidates with SQLite FTS5/BM25 (services/lexical_index.py, stored in .c3/index/index.sqlite) and fuses them with the embedding index by reciprocal rank fusion (Ξ£ 1/(k + rank), k=60) once the dense backend is warm β action='lexical' returns the BM25 ranking alone, with no fusion. The tokenizer keeps identifiers whole and split with digits intact (sha256_digest indexes as sha256_digest sha256 digest; v2, S256 are tokens) and applies no stemming. A query that IS a symbol name puts its definition first. An oversized chunk comes back as a bounded window instead of being skipped.
| Parameter | Type | Default | Description |
|---|---|---|---|
| query | string | required | Search query β natural language or code pattern |
| action | string | "code" | code BM25 fused with dense by RRF Β· lexical the BM25 ranking alone Β· exact regex over every indexed file, definitions printed first Β· files by filename (exact, glob, or substring) Β· semantic embeddings alone, falls back to code when empty Β· transcript session history search |
| top_k | int | 3 | Max results. Honored up to 50 for files and exact; code, lexical and semantic cap at 10 |
| path | string | "" | Comma-separated globs or substrings against the relative path, e.g. 'src/**,*.go' |
| lang | string | "" | Comma-separated language names or bare extensions, e.g. 'python,go' |
| kind | string | "" | Comma-separated source/test/doc/config and/or chunk types function/class/method/heading |
| ignore_case | bool | false | Case-insensitive matching for action='exact' |
| scope | string | "" | '' current project Β· 'all' also searches linked sub-projects Β· '<name>' one sub-project |
| prefetch | bool | false | Auto-read the top results after the search returns |
| max_tokens | int | 1200 | Token budget for results |
Examples
# Find code by content (fused BM25 + dense)
c3_search(query='session snapshot restore', action='code')
# BM25 ranking alone, no fusion
c3_search(query='session snapshot restore', action='lexical')
# Find files by name/path
c3_search(query='hook files in cli/', action='files')
# Semantic similarity search
c3_search(query='code that handles authentication expiry', action='semantic')
# Exact regex match, case-insensitive, scoped to Python source
c3_search(query='class.*Manager', action='exact', ignore_case=True, lang='python', top_k=10)
# Filtered to tests under services/
c3_search(query='token bucket', path='services/**', kind='test')
[lexical], [dense], [lexical+dense], a symbol+ prefix for an exact-symbol match, and , reranked when the optional cross-encoder reordered the block (off by default; it lost on this project's own relevance suite; see c3 search-eval --rerank on). A zero-result response names the next action to try. c3_read on a hit is the next step β c3_compress left the MCP surface in 2.124.0.
Called with only file_path, this is the map tool: one line per symbol with its signature and [La-Lb] range, at roughly 10% of the file's tokens (services/file_map.py). Since 2.123.0 a directory path maps its files instead: one line per file with up to six of its top symbols, most-recently-edited first, capped at 400 files. Pass symbols or lines to read exact source. c3_compress left the MCP surface in 2.124.0 (six modes collapsed into one canonical map, since telemetry showed almost nothing used the other five); this tool is the only map entry point now, and it already accepts a comma-separated file_path for a batch. A file whose map would cost more tokens than its source (small files) is served whole instead, headed whole file β smaller than its map. A very large file (2 MB+) is not parsed at all. Read it with lines=[a,b]. Paths outside the project root are read too (tagged [c3-read:external]), for a sibling repo or an extra working directory.
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_path | string | required | Single path, comma-separated batch, or a directory (maps its files) |
| symbols | array | null | Symbol names as the map prints them: ["ClassName.method_name"] or ["function_name"] |
| lines | int | array | null | A single line, [start, end], or a list of ranges |
| include_docstrings | bool | true | Include each symbol's docstring when reading source |
Examples
# Map a file (no symbols/lines)
c3_read(file_path='cli/mcp_server.py')
# Map a directory β one line per file
c3_read(file_path='services/')
# Read specific symbols
c3_read(file_path='services/session_manager.py', symbols=['SessionManager.save', 'SessionManager.restore'])
# Read by line range (from the map's [La-Lb])
c3_read(file_path='cli/c3.py', lines=[660, 720])
# Map a batch of files in one call
c3_read(file_path='services/memory.py,services/agents.py')
lines=<int> on a directory sets its token budget (default 1500) instead of a line range, since a directory has no line numbers. See docs/file-map.md for the full grammar.
Filters noisy terminal output, log files, or JSONL data to return only relevant lines. Use whenever command output exceeds ~10 lines. Supports inline text or file path input.
| Parameter | Type | Default | Description |
|---|---|---|---|
| text | string | "" | Raw terminal output to filter (inline) |
| file_path | string | "" | Path to a log or data file to filter |
| pattern | string | "" | Regex pattern to filter by (e.g. "error|warning") |
| max_lines | int | 50 | Max lines to keep in the output |
| depth | string | "smart" | fast pattern-only Β· smart pattern + heuristics Β· deep heavier analysis |
| use_llm | bool | true | Allow a local-model summarization pass when the pattern/heuristic pass alone isn't enough |
Examples
# Filter inline terminal output
c3_filter(text="<pytest output with 200+ lines>")
# Filter a log file for errors
c3_filter(file_path=".c3/sessions/session_042.json", pattern="error|fail")
# Filter build output
c3_filter(text="<webpack build output>", pattern="warning|error")
The only way to edit files in C3. Validates the old_string match, applies the patch, writes the file, and logs the change to the edit ledger. Supports single edits and batched edits to the same file. Can be called in parallel for different files.
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_path | string | required | Relative path to the file to edit |
| old_string | string | "" | Exact text to find and replace (must be unique in the file) |
| new_string | string | "" | Replacement text |
| summary | string | "" | Human-readable description of the change (logged to ledger) |
| edits | array | null | List of {old_string, new_string} objects for batch edits to the same file |
Single edit
c3_edit(
file_path='services/auth.py',
old_string='return token.expiry > now',
new_string='return token.expiry > now + CLOCK_SKEW',
summary='Add clock skew buffer to token expiry'
)
Batch edits to same file
c3_edit(
file_path='core/config.py',
edits=[
{"old_string": "DEBUG = True", "new_string": "DEBUG = False"},
{"old_string": "LOG_LEVEL = 'DEBUG'", "new_string": "LOG_LEVEL = 'WARNING'"}
],
summary='Production config hardening'
)
old_string uses straight quotes but the file has curly quotes (or vice versa), or the two differ only by ASCII vs. unicode dashes (-/β/β) or non-breaking spaces, c3_edit retries with a normalized match and splices your new_string in at the matched offsets; unrelated lookalikes elsewhere are left intact. The response is tagged [unicode-normalized] when the fallback was used.
Runs the appropriate native syntax checker for each file type. Supports comma-separated batch validation. Always call after c3_edit and before reporting done. If pyright (Python) or tsc (TypeScript) is on PATH, also runs a deep type check and reports type errors as advisory warnings alongside the PASS result.
| Parameter | Type | Default | Description |
|---|---|---|---|
| file_path | string | required | Single path or comma-separated list of paths |
Examples
# Single file
c3_validate(file_path='services/memory.py')
# Batch (runs in parallel)
c3_validate(file_path='cli/c3.py,cli/mcp_server.py,services/agents.py')
Supported file types
Finds every file that references a symbol (function, class, variable) before you rename or remove it. Uses git grep for speed with a pure-Python fallback. Groups results by file, scores risk (SAFE / LOW / MEDIUM / HIGH), and in unstaged mode overlays which affected files have uncommitted changes.
| Parameter | Type | Default | Description |
|---|---|---|---|
| target | string | required | Symbol name, function, or class to analyse |
| file_path | string | "" | Source file to exclude from results (the definition site) |
| mode | string | "symbol" | symbol cross-project reference scan Β· unstaged overlay uncommitted-change files |
Examples
# Check blast radius before renaming a function
c3_impact(target='handle_memory', file_path='cli/tools/memory.py')
# Which affected files also have uncommitted changes?
c3_impact(target='MemoryStore', mode='unstaged')
# Safe to remove? (check before deleting)
c3_impact(target='_legacy_scan')
Risk levels
c3_impact before c3_edit whenever you're renaming, removing, or changing the signature of a shared symbol. A HIGH risk result means you need to update all call sites in the same edit pass.
Run a shell command through C3 with structured returns (exit_code, stdout, stderr, duration_ms), a hard timeout with tree-kill (taskkill /F /T on Windows), and automatic edit-ledger entries for git-mutating commands. Use for tests, git, builds, and one-shot scripts. The response never exceeds an 18 KiB budget (22 KiB ceiling; max_bytes may only lower it). Under budget the output is complete: ANSI/control sequences are stripped, \r progress rewrites collapse to their final state, and 3+ identical consecutive lines fold to one plus [x N]. Over budget, a recognised test/build runner (pytest, unittest, cargo, tsc, jest, vitest) keeps its failure blocks, error lines and totals first (each announced by [La-b: why]), then a bounded head/tail; everything dropped is kept in a spill store outside the project for 3 days and named by an output_id in the header. filter_output=False skips only the two collapses (escapes are still stripped, the cap is never lifted). Native Bash remains the fallback for interactive / TTY commands (gh pr create with an editor, ollama run). Work that outlives one tool call belongs in c3_shell_job β a c3_shell timeout is never converted into a job.
| Parameter | Type | Default | Description |
|---|---|---|---|
| cmd | string | "" | Shell command to execute (Git Bash on Windows when available; otherwise the platform default shell) |
| cwd | string | "" | Working directory. Defaults to the project root |
| timeout | int | 60 | Seconds before the process tree is killed |
| filter_output | bool | true | Apply ANSI-strip, \r collapse and repeated-line folding (the byte budget always applies regardless) |
| log | bool | true | Write shell_exec to the activity log and auto-log git-mutating commands to the edit ledger |
| env_creds | string | "" | Comma-separated vault entry names injected as env vars (see c3_credentials); {{cred:NAME}} inside cmd also expands server-side |
| max_bytes | int | 0 | Lower the response byte budget for this call (0 = default 18 KiB) |
| output_id | string | "" | Page back a prior response's spilled output ('o-β¦' from a clipped header) |
| output_action | string | "" | read (with lines='120-180') Β· search (with pattern=) Β· tail (with lines='80') Β· delete |
| stream | string | "stdout" | stdout or stderr, for paging a spilled output |
Examples
# Run tests β exit_code propagates, failure blocks kept first over budget
c3_shell(cmd='pytest tests/test_c3_shell.py -v')
# Git mutation β affected files auto-recorded in the edit ledger
c3_shell(cmd='git commit -m "wire c3_shell"')
# Build with a longer timeout
c3_shell(cmd='npm run build', timeout=300)
# Use a vault secret without it ever entering context
c3_shell(cmd='npm publish', env_creds='NPM_TOKEN')
# Page back the rest of a clipped response
c3_shell(output_id='o-3f9a1c2b4d5e', output_action='search', pattern='FAILED')
# Portable JSON formatting β Git Bash may not include jq
c3_shell(cmd='curl -s http://localhost:8000/health | python -m json.tool')
Safety classification
The blocklist is a best-effort guard against the most catastrophic commands β not a sandbox. c3_shell runs arbitrary commands by design; for an intentionally dangerous command, use native Bash with explicit approval.
[c3_shell:hint] line may follow a cat/grep/find/ls-shaped command, naming the equivalent c3_read/c3_search call β advisory only, the command still runs. An id from output_id resolves only for the same project and the same session, and only while the current Access Guard rules still allow the paths the original command touched. See docs/shell-output.md.
Full test suites, long builds, device/emulator commands: anything over the client's ~2 minute ceiling on c3_shell. start runs c3_shell's full pre-flight (blocklist, Access Guard cwd deny, advisory read scan, write scan with confirm holds, credential expansion) and then hands the command to a detached supervisor process that survives the MCP server, replying with a job_id='j-β¦' immediately; nothing here waits on the job, and a c3_shell timeout is never turned into one. Output lands in the same spill store as c3_shell and is always promoted there (a job's output is the deliverable), kept 3 days: page it with c3_shell(output_id=β¦, output_action=β¦). Jobs are per project AND per session β another project or session gets "not found". Not proxied through c3_project. Prefer c3_ci for this repository's own GitHub Actions workflows; jobs are for everything else.
| Parameter | Type | Default | Description |
|---|---|---|---|
| action | string | required | start Β· status Β· tail Β· cancel Β· list |
| cmd | string | "" | Command for start |
| timeout | int | 1800 | Seconds before the job's own timeout fires. Ceiling 21600 (6 h); a larger request runs at 6 h |
| job_id | string | "" | Required for status, tail, cancel |
| stream | string | "stdout" | stdout or stderr, for tail |
| lines | string | "" | tail line count, default 50 |
| env_creds | string | "" | Vault entry names injected as env vars β travel on the supervisor's stdin pipe only, never argv or disk |
Examples
# Kick off the full suite, get a job id back at once
c3_shell_job(action='start', cmd='pytest -q', timeout=1800)
# Check on it
c3_shell_job(action='status', job_id='j-3f9a1c2b4d5e')
# Watch the growing output while it runs
c3_shell_job(action='tail', job_id='j-3f9a1c2b4d5e', stream='stdout', lines='80')
# After it finishes, page the kept output by output_id from status
c3_shell(output_id='o-β¦', output_action='read', lines='1-200')
# All this project + session's jobs
c3_shell_job(action='list')
queued β running β done | failed | timeout | cancelled, or lost if the supervisor process disappears (its output is still promoted so nothing is lost). cancel kills the process tree only if the recorded pid still has the creation time recorded at spawn β a reused pid is never signalled. See docs/shell-jobs.md.
Reads .github/workflows/*.yml as the only source of truth; there is no second CI config. inspect shows the job DAG and what's runnable on this host; run executes in dependency order and SKIPS (never passes) a job whose dependency failed; rerun retries only what failed; failures returns structured {file, line, message} instead of raw logs. Only FULL_CI_PASS means every job ran here and passed β the only verdict that means safe to push. PARTIAL_PASS means something did not run (another OS, an action C3 can't execute, or a subset you selected) and is not a green light. Full action reference and the native/act engine choice live on the CI guide.
Actions
| Action | Description |
|---|---|
| inspect, plan | Job DAG and runnability on this host; plan shows the run/skip decision for mode='required' |
| run, rerun | Execute in needs order; job= selects one job or matrix cell; rerun retries only last run's failures |
| status, failures, logs, runs | Last run's state; structured failures; raw logs; run history |
| history | Per-job fail rates; flags FLAKY jobs (passed and failed on identical inputs) |
| cache | A job whose inputs are unchanged since it last passed is reused (status cached); no_cache=true forces a re-run |
| publish | Posts the last run as a GitHub commit status via your gh auth; refuses a dirty tree, an unpushed commit, and (unless forced) a PARTIAL result |
| doctor | What's available on this host: native vs. act+Docker |
Examples
# See the job DAG and what's runnable here
c3_ci(action='inspect')
# Run everything this change could have broken
c3_ci(action='run', mode='required')
# One job
c3_ci(action='run', job='test (ubuntu-latest, 3.12)')
# Fix loop β only the failures
c3_ci(action='rerun')
c3_ci(action='failures')
allow_host_mutation=true lets the native engine run steps that reconfigure this machine (pip install -g, apt, brew β¦) β it has no isolation, and running this repo's own CI natively once uninstalled C3. Prefer engine='act' when act + Docker are installed. Jobs that look like they publish or deploy are refused unless allow_side_effects=true; C3 never passes secrets to act.
Guards answer "may the agent touch this?"; locks answer "is someone already touching it?" Two sessions each run their own c3-mcp server, so two agents could interleave read β replace β write on the same file with no error on either side. c3_edit already takes a lease on the file it edits, carrying the intent from your edit summary; use acquire to claim a multi-file refactor up front. Acquisition is all-or-nothing over the path list, so two agents grabbing the same pair in opposite order cannot deadlock. TTL is the real release mechanism: a crashed agent's lease expires on its own (default 900s / 15min, locks.default_ttl_s in .c3/config.json) rather than wedging the repo; a lease you no longer need should still be released explicitly. Leases gate C3's own tool surfaces only β a raw shell redirect, a non-Claude agent, or a human in an editor is not covered. Set locks.enabled: false to opt out.
| Parameter | Type | Default | Description |
|---|---|---|---|
| action | string | "list" | list Β· acquire Β· release Β· renew Β· sweep |
| paths | string | "" | Comma-separated, project-relative. Required for acquire/renew; omit on release to release everything this session holds |
| intent | string | "" | Short note another agent sees when blocked, e.g. "refactor retry backoff" |
| ttl_s | int | 0 | Lease lifetime in seconds for acquire/renew (0 = the project's configured default) |
Examples
# Who holds what right now
c3_locks(action='list')
# Claim a multi-file refactor up front
c3_locks(action='acquire', paths='services/router.py,services/retry.py', intent='refactor retry backoff')
# Release when done
c3_locks(action='release', paths='services/router.py,services/retry.py')
# Drop expired leases (also happens automatically)
c3_locks(action='sweep')
acquire is a policy block, not a transient error. Do not route around it via native tools. Work elsewhere, or ask the holder (named in the response) to release. c3 locks force-release <path> exists to break a stuck lease, but it's human-only and audited β never call it from the agent surface.
When a call is refused with [c3-access:confirm] (a path the user set to "ask me first", or the builtin agent-config confirm tier), the refusal names a request C3 already filed, or the surface that files one. A yes from the user mints a single-use, session-bound, path-exact grant with a short TTL; the rule that blocked you stays in force. Most denials are not escalatable (credential vault, Tier-0, catastrophic shell commands) and are refused here without ever reaching the user. If a refusal did not invite you to ask, do not ask β mark the step blocked and tell the user instead. Full flow and the confirm-hold model live on the Access guide.
| Parameter | Type | Default | Description |
|---|---|---|---|
| action | string | "list" | request Β· status Β· wait Β· list Β· withdraw β there is no approve here; only the user decides |
| path | string | "" | The exact blocked path, for request |
| why | string | "" | One concrete sentence for the human, for request |
| request_id | string | "" | Id from the original refusal, for status/wait/withdraw |
| timeout_s | int | 60 | wait blocks this long; default 60, max 180. "Still pending" is not a denial β wait again or do unaffected work |
Examples
# A hold names a request id already filed β wait on it
c3_override(action='wait', request_id='req_a1b2c3', timeout_s=180)
# Nothing was filed β ask for this exact path yourself
c3_override(action='request', path='.env.production', why='need the DB host to reproduce the bug')
# What's still open
c3_override(action='list')
c3_shell or another tool, and never re-file β duplicates collapse into the pending request. Retry the SAME call once, on the SAME surface, only after an approval.
Manages the session lifecycle: log for decisions, snapshot before /clear, restore to recover after /clear, compact for a one-step restart. Since v2.143.0 it also covers the session catalog (one row per Claude Code conversation joining transcript, C3 sessions, decisions, snapshots and tasks), so you can find an old session, see what it was, and mark one a dead end. The full catalog, the Hub Sessions tab and resume flow are on the Sessions guide.
| Parameter | Type | Default | Description |
|---|---|---|---|
| action | string | required | See action table below |
| data | string | "" | Content for log/plan/note; a successor session id for stale; a search term for list |
| reasoning | string | "" | Rationale for log; required for stale; next steps for note |
| target | string | "" | A session id, an 8+ char prefix, or current β for note, stale, unstale; also filters list to stale/likely/all |
Actions
| Action | Read-only? | Description |
|---|---|---|
| start | β | Start a new session (auto-called on server start) |
| log | β | Record a decision or note to the session |
| plan | β | Record an ephemeral plan or approach (durable tracked TODOs belong in c3_task) |
| snapshot | β | Save full session state (call before /clear) |
| restore | β | Restore last session snapshot (call after /clear) |
| compact | β | One-step snapshot + summary + restore prompt |
| convo_log | β | Search session conversation history |
| note | β | v2.143.0 β leave what this session did and what's next, before stopping or /clear |
| stale | β | v2.143.0 β mark a session you superseded, finished or abandoned; reasoning required, data=successor id optional |
| unstale | β | v2.143.0 β undo a stale mark |
| list | β | v2.143.0 β titles and flags across past sessions (the Hub/Desk Sessions view); never another session's prompts |
Examples
# Log a decision
c3_session(action='log', data='Chose Redis over memcached β TTL support needed', reasoning='Session persistence required')
# Before /clear
c3_session(action='snapshot')
# Before stopping β what got done, what's next
c3_session(action='note', data='Wired c3_shell byte budget', reasoning='Still need output_id paging tests')
# This session superseded an earlier dead end
c3_session(action='stale', target='a1b2c3d4', reasoning='restarted after a bad approach', data='current')
# One-step restart
c3_session(action='compact')
Persistent key-fact store that survives /clear and session restarts. Use recall at session start to recover context. Facts are stored with salience scores and auto-decayed over time. Supports semantic recall, explicit query, and manual management.
| Parameter | Type | Default | Description |
|---|---|---|---|
| action | string | required | See action table below |
| fact | string | "" | Fact text for add and update |
| query | string | "" | Search query for recall and query |
| category | string | "general" | Fact category: general, architecture, convention, feedback |
| fact_id | string | "" | Fact ID(s) for update, delete, and fetch (comma-separated: "id1,id2") |
| top_k | int | 3 | Max facts to return |
| include_scores | bool | false | Add a per-fact salience score to recall results |
| scope | string | "" | recall only: '' config default Β· 'all' union linked sub-project facts Β· '<name>' one sub-project Β· 'project' this project only |
Actions
| Action | Read-only? | Description |
|---|---|---|
| recall | β | Semantic search, top-k relevant facts for a query |
| index | β | Compact list of ids + snippets for a large store β pair with fetch for the full text |
| fetch | β | Full text by fact_id (comma-separated for a batch) |
| query | β | Multi-source search: facts + sessions + files together |
| list | β | List all facts (category='' shows all, or filter) |
| add | β | Add a new persistent fact (empty category β general) |
| update | β | Update an existing fact by ID |
| delete | β | Delete a fact by ID |
| review | β | Review facts for staleness / deduplication |
| consolidate, consolidate_deep | β | Merge duplicate/redundant facts; _deep is the heavier pass |
| score | β | Show salience scores for all facts |
| trends | β | Show memory usage trends over time |
| graph | β | Show fact relationships graph |
| ground | β | Ground facts against current codebase (verify still accurate) |
| export | β | Export all facts as markdown |
| lifespan | β | Show fact age and decay information |
Examples
# Session start β recall relevant facts
c3_memory(action='recall', query='authentication token system', top_k=5)
# Large store: browse compact, then fetch just what you need
c3_memory(action='index')
c3_memory(action='fetch', fact_id='a1b2c3,d4e5f6')
# Add a new fact
c3_memory(action='add', fact='[architecture] JWT tokens use RS256 β public key in /etc/c3/jwt.pub', category='architecture')
# Save a convention
c3_memory(action='add', fact='[convention] All new API handlers must call audit_log() before returning', category='convention')
# Verify facts are still accurate
c3_memory(action='ground')
# List all architecture facts
c3_memory(action='list', category='architecture')
Project and session status dashboard. Check token budget, server health, pending notifications, and active session summary.
| Parameter | Type | Default | Description |
|---|---|---|---|
| view | string | "budget" | budget token budget Β· health memory/index/notifications Β· notifications actionable-only alerts Β· sessions session list Β· ghost_files 0KB / orphaned file check Β· access Access Guard rules (read-only) |
| detailed | bool | false | Include extended metrics |
Examples
c3_status(view='budget') # Token budget remaining
c3_status(view='health') # Memory / index / notification health
c3_status(view='notifications') # Pending alerts (budget warnings, etc.)
c3_status(view='access') # Current Access Guard rules
Sends a bounded task (summarize a diff or log, explain a function, triage a traceback, draft a docstring or test cases) to a smaller model from the provider you already run on. In Claude Code that is Haiku by default, through a tool-less claude -p that answers only from the context C3 packs for it. On C3's delegate eval, Haiku passed all 22 bounded cases at about a fifth of Opus's cost.
| Parameter | Type | Default | Description |
|---|---|---|---|
| task | string | required | The task or question to delegate |
| task_type | string | "ask" | ask Β· explain Β· summarize Β· review Β· diagnose Β· docstring Β· test Β· auto Β· ping (one live call to check auth) Β· available (CLI versions only) |
| context | string | "" | The text the delegate answers from |
| file_path | string | "" | Comma-separated files C3 reads under Access Guard and packs into the prompt (a file over 8k tokens goes as its map; a masked path is refused) |
| backend | string | "host" | host = your own provider (Claude Code β claude, Codex β codex, Grok Build β grok, Antigravity β gemini) Β· claude Β· codex Β· gemini Β· grok Β· ollama Β· auto |
| tier | string | "small" | small Β· medium Β· large Β· default. Claude: Haiku (thinking capped at 1024 tokens) / Sonnet / Opus; a scout defaults to Sonnet, which finds things in far fewer turns. Codex and Grok: reasoning effort low / medium / high on the account's model. Gemini: flash-lite / flash / pro. |
| model | string | "" | A model alias or id; overrides the tier |
| scout | bool | false | Let the delegate Read/Grep/Glob the project itself, for lookups when you cannot name the files. Claude runs --restricted with Access Guard's read denies; Codex uses its read-only sandbox. |
| write_paths | string | "" | Write mode (Claude only, defaults to Sonnet). Comma-separated project-relative globs the delegate may edit or create. You decide the change; it makes it with Read/Grep/Glob/Edit/Write, no commands. You get back the diff, and each change is in the edit ledger. Access Guard applies; confirm-held, locked, .git/ and .c3/ paths are refused. Review the diff and run the tests yourself. |
Examples
# Summarize a diff on Haiku (Claude Code host, default tier)
c3_delegate(task='Summarize this diff for a changelog in three bullets', context='<diff>', task_type='summarize')
# Explain a function on Sonnet
c3_delegate(task='What does resolve_host return inside a grok child?', file_path='core/host.py', task_type='explain', tier='medium')
# A lookup the delegate does itself
c3_delegate(task='Which functions call charge_card, and where?', scout=True)
# A change you have decided, made by Sonnet inside a write set
c3_delegate(task='Rename calc_total to order_total in its definition and every use; no alias', write_paths='inv/orders.py, inv/report.py, tests/test_orders.py')
c3 install-mcp also writes two subagents to .claude/agents: c3-scout (Haiku, read-only) and c3-worker (Sonnet). They run inside your session, so C3's hooks apply to them. Skip them with --no-agents. Any other Agent call that leaves model blank runs one tier below your session (Fable β Opus β Sonnet, never below Sonnet); set delegate.agent_downshift to "off" to keep the parent's model. When your own work looks delegable, a [c3:delegate-hint] line on the tool response names the call to make. c3 delegate-eval measures pass rate and cost per backend and tier on your machine.
Executes compound multi-step workflows using multiple C3 tools in sequence. Useful for common patterns like reviewing changes, preparing context, or running a preflight check.
| Parameter | Type | Default | Description |
|---|---|---|---|
| workflow | string | required | Workflow name β see workflow table below |
| scope | string | "" | File paths or focus area for the workflow |
| context | string | "" | Additional context to pass into the workflow |
Available workflows
| Workflow | What it does |
|---|---|
| available | List all available workflows with descriptions |
| review_changes | Map recently changed files + summarize what changed |
| prepare_context | Search + map files relevant to a scope/topic |
| investigate | Multi-step investigation: search β map β summarize β recommend |
| preflight | Pre-task check: recall memory + status + recent changes |
| validate_compress | Batch validate + map a set of files |
Examples
# Run a preflight before starting work
c3_agent(workflow='preflight', context='starting auth refactor')
# Prepare context for a specific area
c3_agent(workflow='prepare_context', scope='services/memory.py,services/agents.py')
# Review what changed recently
c3_agent(workflow='review_changes')
# Deep investigation
c3_agent(workflow='investigate', context='why does session restore fail after compact?')
Access the edit ledger β a persistent append-only log of all file changes made through c3_edit. Provides history, version diffing, stats, and tagging. The ledger is also enriched in the background by the EditLedgerEnricherAgent. Since v2.35.0, each entry is stamped with the git branch and HEAD it was made on, and history can be filtered by branch.
| Parameter | Type | Default | Description |
|---|---|---|---|
| action | string | required | See action table below |
| file | string | "" | Filter by file path |
| change_type | string | "modified" | Change type for log action |
| branch | string | "" | Filter history to edits stamped with this git branch v2.35.0 |
Actions
| Action | Description |
|---|---|
| log | Manually log an edit to the ledger (normally done automatically by c3_edit) |
| history | Show edit history (optionally filtered by file and/or branch) |
| versions | Show version history for a specific file |
| stats | Aggregate stats: most-edited files, change frequency |
| tag | Tag an edit entry with a label |
| verify | v2.75.0 β did an edit land? Pass the SAME file/old_string/new_string (or edits) a failed or timed-out c3_edit call took. Answers APPLIED (do not retry) / NOT_APPLIED (safe to retry) / INCONCLUSIVE (read the file) |
Examples
# Show recent edit history
c3_edits(action='history')
# Version history for a specific file
c3_edits(action='versions', file='services/memory.py')
# Ledger stats
c3_edits(action='stats')
# Edits made on a specific branch
c3_edits(action='history', branch='feature/x')
# c3_edit errored or timed out β did it actually write?
c3_edits(action='verify', file='services/router.py',
old_string='RETRY_LIMIT = 3', new_string='RETRY_LIMIT = 5')
c3_edit call can fail AFTER doing its work β the write landed but the response never arrived. Retrying blind can double-apply an edit whose new_string contains its old_string (appending a line, wrapping a call). Always route a failed or timed-out c3_edit through verify first.
Every C3 project carries a PM store at .c3/pm/pm.json: tasks with status (backlog / in_progress / blocked / done), priority (p0βp3), due dates, tags, and code links (files, commits, edit-ledger entries, sessions); milestones with computed progress; a time log; and a decision-note log separate from AI memory. The same store powers the Hub's Tasks tab and kanban board, the per-project UI's Tasks tab, and this tool. Task ids accept any unique prefix (β₯4 chars); milestones resolve by id or unique name. Read actions are plan-mode-safe. Ephemeral session plans stay in c3_session(action='plan').
Actions
| Action | Group | Description |
|---|---|---|
| add | Write | Create a task: title + optional description / priority p0-p3 / due_date (YYYY-MM-DD) / tags (CSV) / milestone / parent (subtask) |
| update, done, archive | Write | task_id + changed fields (incl. status); done stamps completion; parent='none' clears a subtask link |
| list, get, board | Read | Filtered list (status/priority/tags/milestone/query), full detail, kanban columns + milestone progress |
| link, unlink | Write | task_id + link_type (file | commit | edit | session) + ref β ref='current' links this conversation |
| block, unblock | Write | task_id + ref=blocking task id; cycle-safe. Completing the last open blocker auto-releases dependents to backlog |
| report | Read | Overdue tasks, blocked chains + aging, ready-to-unblock, milestone health/at-risk, throughput |
| milestone_add / _update / _list | Both | Milestones with target dates; list shows progress % |
| milestone_complete, milestone_reopen | Write | v2.86.0 β close a shipped milestone (tasks KEEP their link, unlike archive); refuses while open tasks remain. _reopen undoes it |
| milestone_archive | Write | Removal β detaches its tasks |
| time_add, time_update, time_delete | Write | Manual time entries: minutes 1-1440 [+note/due_date/task_id]; update/delete by ref=entry id |
| time_list, time_summary | Read | Manual entries + recent auto-tracked sessions; today/7d/30d totals (auto vs manual). Server startup + tool calls auto-ping .c3/time; idle gaps >15min close a session |
| note_add, note_list | Both | Dated notes; kind='decision' for the decision log |
| history | Read | Append-only event log ([+task_id] [+limit]), newest first β who/what/when, beforeβafter |
Examples
# Capture a task while coding
c3_task(action='add', title='Harden pm.json against concurrent writes',
priority='p1', tags='pm,backend')
# Tie it to the code it touches, then finish it
c3_task(action='link', task_id='a1b2c3d4', link_type='file', ref='services/task_store.py')
c3_task(action='done', task_id='a1b2')
# Milestones + the board
c3_task(action='milestone_add', name='v2.46', target_date='2026-08-01')
c3_task(action='board')
# Ship it β tasks keep their link, unlike archive
c3_task(action='milestone_complete', milestone='v2.46')
# Log time against a task
c3_task(action='time_add', minutes=45, task_id='a1b2', note='wrote the migration')
# Record a decision
c3_task(action='note_add', note='Kanban rank uses float sort keys', kind='decision')
Surfaces: the Hub shows a Tasks tab per project (drill-in panel), β N open-task chips on cards, and a global kanban board behind the Projects | Tasks switcher. Hub mutations audit pm_write events to the project's activity log. Disable with hybrid.pm.enabled=false.
C3 tracks every agent-affecting artifact across all IDEs it knows: instruction docs (CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules, .github/copilot-instructions.md), settings/hooks (.claude/settings*.json), MCP configs (.mcp.json, .codex/config.toml, .gemini/settings.json, β¦), and Claude Code extensions (.claude/ skills, agents, commands, plugins). Content-addressed snapshots land in .c3/agent_artifacts/; every change is a history event with attribution: c3_edit (this session), hook (native Edit/Write), scan (out-of-band β you edited it in an editor), install_mcp (C3 regenerating its own files), or restore. A background agent scans every ~2 min and warns only when settings or MCP configs change outside C3. Artifact refs accept an id (skill:browcontrol), unique prefix, or plain path (CLAUDE.md). Everything except restore is plan-mode-safe.
Actions
| Action | Group | Description |
|---|---|---|
| scan | Read | Refresh the inventory; captures out-of-band changes immediately (idempotent β unchanged files emit nothing) |
| list | Read | Inventory with versions; filters: cls (instructions | settings | mcp | skill | agent | command | plugin), provider |
| history | Read | Change events newest-first, with source attribution; artifact optional (all events when omitted) |
| show | Read | Content at a version (version=0 β live file) |
| diff | Read | Unified diff version β against (omit against to diff vs live) |
| restore | Write | Write a prior version's exact bytes back. Forward-only (new version + history event, never rewrites), cross-logged to the edit ledger, warns on settings/managed-block files. Resurrects deleted artifacts. |
| status | Read | Tracked counts by class, out-of-band changes, last scan, pending signals |
Examples
# What changed behind my back?
c3_artifacts(action='status')
c3_artifacts(action='history', limit=10)
# Someone (or something) touched the hooks β inspect and roll back
c3_artifacts(action='diff', artifact='.claude/settings.local.json', version=3)
c3_artifacts(action='restore', artifact='settings:.claude/settings.local.json', version=3)
# Track a skill's evolution
c3_artifacts(action='history', artifact='skill:browcontrol')
c3_artifacts(action='show', artifact='skill:browcontrol', version=2)
Surfaces: the Hub drill-in panel gets an Artifacts tab (class-grouped inventory, per-version timeline, diff viewer, two-step restore); REST at /api/artifacts* per project and /api/projects/artifacts* on the hub. Changes made through c3_edit and install-mcp self-attribute β only genuinely foreign edits show as scan. Disable with hybrid.agent_artifacts.enabled=false.
Connect to a self-hosted enterprise Bitbucket server and operate on it. Tokens live in the OS keyring (Windows Credential Manager / macOS Keychain / Linux Secret Service); only a non-secret index of accounts and the active-account pointer is written to .c3/config.json. Project key + repo slug fall back to the configured defaults.
Setup: c3 bitbucket login --url <URL> (interactive PAT prompt), then c3 bitbucket set-default --project PROJ --repo my-repo. See the Bitbucket integration guide for the full action reference, Hub UI tour, audit-trail integration, and troubleshooting.
Actions
| Action | Group | Description |
|---|---|---|
| status | Read | Active account, accounts list, defaults, server-version probe |
| whoami | Read | Current authenticated user (PAT owner) |
| list_projects, list_repos, get_repo | Read | Project / repository discovery |
| list_prs, get_pr, get_pr_diff, get_pr_activities, get_pr_commits | Read | PR browsing + diff + activity feed + commit list |
| create_pr, update_pr, comment_pr, approve_pr, unapprove_pr, needs_work_pr, decline_pr, merge_pr | Write | PR write actions. update_pr edits title/description/reviewers/to_branch (unchanged fields preserved); merge_pr, decline_pr, and update_pr auto-fetch the PR's current version; needs_work_pr marks "needs work" as the current user |
| update_pr_comment, delete_pr_comment | Write | Edit or remove a PR comment by comment_id β the comment's current version is fetched automatically |
| list_pr_tasks, create_pr_task, resolve_pr_task | Tasks | PR tasks (Bitbucket 7.2+ model: a task is a BLOCKER-severity comment). list_pr_tasks filters by state=OPEN|RESOLVED; resolve takes the task's comment_id |
| list_branches, create_branch, delete_branch | Branch | Branch listing and admin (uses the branch-utils API) |
| list_commits, list_activity, build_status | Read | Recent commits, activity, build/CI status (defaults to default-branch HEAD) |
| repo_settings, update_repo_settings, list_webhooks, create_webhook, delete_webhook, list_permissions | Admin | Repository administration |
Examples
# Connection probe + active account info
c3_bitbucket(action='status')
# List open PRs in the default repo
c3_bitbucket(action='list_prs', state='OPEN')
# Open a PR
c3_bitbucket(action='create_pr',
title='Add Bitbucket integration',
from_branch='feat/bitbucket',
to_branch='main',
reviewers='alice,bob')
# Approve and merge β version is fetched automatically
c3_bitbucket(action='approve_pr', pr_id=42)
c3_bitbucket(action='merge_pr', pr_id=42)
Audit trail: mutating actions (merge_pr, create_branch, delete_branch, webhook writes, etc.) are appended to the C3 edit ledger so the local audit log covers platform-side state changes too.
A user-managed secret store, global (~/.c3, visible in every project) or per-project (.c3, shadows the same-named global). Values live in the OS keyring (service c3-creds; values over ~1KB in a Fernet-encrypted .c3/secrets.enc whose random master key is itself a keyring entry), never in config files, and never in the model's context. Injection-first: the agent addresses secrets by name and C3 decodes them only at the subprocess boundary β c3_shell(env_creds='A,B') injects env vars, {{cred:NAME}} inside cmd expands server-side, and inject-flagged entries auto-inject into every shell run; echoed values are redacted back to [cred:NAME]. Resolution is realm-atomic: a project-registered name resolves in the project realm or not at all, so a hostile repo's committed config can never siphon your global values (tested).
Actions
| Action | Group | Description |
|---|---|---|
| list | Read | Merged registry (project shadows global) β names, scope, type, flags, usage. Never values |
| describe | Read | Metadata + storage + live fingerprint (first 8 hex of SHA-256, computed on demand, never persisted) |
| check | Read | Resolvability probe β does the stored value decode? |
| usage | Read | v2.88.0 β [name]: when/where/how often it was used (counts by surface, recent events for this project, other projects reduced to counts) |
| set | Write | Create/replace an entry (name, value [scope=project|global] [ctype=token|env|multiline|address|identity|card|login]). agent_readable may be set at creation only β raising it on an existing entry is user-only |
| delete | Write | Remove value + registry entry (owning scope inferred when omitted) |
| reveal | Gated | The only value-returning action β refused unless the user enabled agent_readable on that entry, and permanently disabled for structured kinds |
| import_env | Write | v2.93.0 β bulk-import a .env (file_path [only='A,B']). Never shows a value: names, lengths, fingerprints, per-row reasons only. Project-scope only, never overwrites, defaults to dry_run=true: preview, then re-run dry_run=false. A dry run also diffs (v2.94.0): a row already matching the vault reports unchanged |
Structured kinds
v2.87.0 added address/identity/card, v2.90.0 added login. These hold named fields (card: cardholder/number/expiry[/cvc/billing_zip]; login: site_id/canonical_target/username[/password/private_key/β¦]) and are inject-only: address a single field with env_creds='CARD.number' or {{cred:CARD.number}}; reveal is disabled and they never auto-inject. login (v2.118.0) covers servers and databases too, not just websites: canonical_target is scheme://host[:port] from an allowlist (https, ssh, sftp, postgres, mysql, redis, β¦). It is storage only β C3 opens no session and types nothing anywhere; have the user enter values through the Credentials UI or c3 creds set so they never enter the chat.
Examples
# What can I use? (names and metadata only)
c3_credentials(action='list')
c3_credentials(action='check', name='NPM_TOKEN')
# USE a secret without ever seeing it
c3_shell(cmd='npm publish', env_creds='NPM_TOKEN')
c3_shell(cmd='curl -H "Authorization: Bearer {{cred:API_KEY}}" https://api.example.com')
# Preview a .env import, then commit it
c3_credentials(action='import_env', file_path='.env.production')
c3_credentials(action='import_env', file_path='.env.production', dry_run=False)
Surfaces: the c3 creds CLI (set / get / list / rm / import .env, --global for the shared scope), a per-project dashboard Credentials tab, and a top-level Credentials view in the Hub (v2.59.0) managing the global vault and every registered project, with overriding shown both ways. Since v2.61.0 that view adds cross-project search (/ or Ctrl/β-K; project: scope: inject: agent: shadow: qualifiers, results grouped by credential name), a per-credential settings drawer (metadata, exposure switches, resolution check, write-only value replacement, usage & overrides, delete), and a right-click context menu; destructive and exposure-raising actions require typing the credential name. No HTTP route ever returns a stored value (write-only wire contract, endpoint-sweep tested), every mutation is ledger-logged by name, the vault is hard-excluded from the Oracle Discovery API, and cross-project (c3_project) shells run with credentials disabled.
See the credential vault guide for storage internals, the scope/override model, injection paths, exposure-flag semantics, the Hub UI tour, search qualifiers, and troubleshooting.
One tool for both Jira deployments: Cloud (REST v3, email + API token, ADF bodies handled transparently) and self-hosted Data Center / Server (REST v2, PAT Bearer, plain-text bodies) β normalized to a single DTO surface with an opaque pagination cursor. Transport is stdlib urllib (no new dependencies); reads get one bounded 429 retry honoring Retry-After, mutations are never auto-retried. Tokens live in the OS keyring keyed by (base_url, username); the jira config section resolves project β home wholesale from a single file, so a repository's committed config can never field-override a home account's URL or TLS settings (credential-redirect hardening). HTTPS-only.
Setup: c3 jira login --url https://yoursite.atlassian.net (Cloud inferred for *.atlassian.net; add --deployment data_center for self-hosted, --ca-bundle for enterprise certs, --global for a home config reusable across projects), then c3 jira set-default --project PROJ. Manage accounts with c3 jira status / use / logout.
Actions
| Action | Group | Description |
|---|---|---|
| status, whoami | Read | Active account, deployment, defaults, server probe / authenticated user |
| search | Read | Raw JQL, paginated (helper-built JQL elsewhere is always quoted) |
| my_issues | Read | Open issues assigned to the token's user (statusCategory-aware) |
| get_issue, list_projects, list_transitions | Read | Issue detail with comments, parent, and typed links / project discovery / legal transitions for an issue |
| get_create_metadata, search_users, list_link_types | Read | Field configuration per project/type (ids + names; the create screen may accept fewer β Jira over-reports, Epic Link is the classic case); user lookup for assignment; the server's issue-link type catalog |
| list_boards, list_sprints, list_worklogs | Read | Agile boards (project fallback applies) → sprints per board (sprint_state=active|future|closed); an issue's logged time |
| move_to_sprint, move_to_backlog | Write | Sprint assignment via the Agile API β issue takes one key or a comma-list; sprint_id comes from list_sprints |
| add_worklog, attach_file | Write | Log time (time_spent='2h 30m', optional body comment); upload one local file as an attachment (20MB local cap) |
| unlink_issues, delete_issue | Write | Remove a typed link by link_id (shown on get_issue); permanently delete an issue β refuses one with subtasks unless delete_subtasks=true |
| create_issue | Write | Pre-validates against create metadata and returns machine-readable missing required fields instead of guessing defaults. parent=EPIC-KEY files the new issue under an epic (deployment-mapped) |
| update_issue | Write | Edit an existing issue: summary, description, parent (epic/parent key; 'none' clears), or a fields JSON of field ids → values β the fallback when the create screen rejects a field ("not on the appropriate screen") |
| link_issues | Write | Typed link reading <issue> <link_type> <target> β accepts a type name or either directional phrasing (an inward match flips the pair); unknown types answer with the catalog |
| comment, transition, assign | Write | Comment on / move (accepts transition id or name) / assign an issue |
Examples
# Connection + account probe, then my open work
c3_jira(action='status')
c3_jira(action='my_issues')
# Raw JQL search
c3_jira(action='search', jql='project = PROJ AND status != Done ORDER BY updated DESC')
# Create β required fields come from metadata, not guesses
c3_jira(action='get_create_metadata', project='PROJ', issue_type='Task')
c3_jira(action='create_issue', project='PROJ', issue_type='Task',
summary='Fix login flow', description='Steps in thread')
# Field rejected on create ("not on the appropriate screen")? Set it post-create
c3_jira(action='update_issue', issue='PROJ-124', fields='{"customfield_10014": "PROJ-42"}')
# Put an issue under an epic β no customfield hunting (Cloud parent field
# vs Data Center Epic Link customfield is resolved for you)
c3_jira(action='update_issue', issue='PROJ-124', parent='PROJ-42')
# Typed links: reads "PROJ-1 blocks PROJ-2"
c3_jira(action='link_issues', issue='PROJ-1', link_type='blocks', target='PROJ-2')
# Sprint flow: board -> sprint -> assign
c3_jira(action='list_boards')
c3_jira(action='list_sprints', board_id=7, sprint_state='active')
c3_jira(action='move_to_sprint', issue='PROJ-1, PROJ-2', sprint_id=42)
# Time + evidence
c3_jira(action='add_worklog', issue='PROJ-1', time_spent='2h 30m', body='pairing')
c3_jira(action='attach_file', issue='PROJ-1', file_path='logs/build.log')
# Move it along β id or name both work
c3_jira(action='list_transitions', issue='PROJ-123')
c3_jira(action='transition', issue='PROJ-123', transition='In Progress')
c3_jira(action='comment', issue='PROJ-123', body='Deployed to staging')
Surfaces & audit: the per-project dashboard gets a Jira tab (My Work board grouped by statusCategory, JQL search, an issue drawer with transition buttons + comments, and an Activity view) backed by /api/jira/* routes. Issue keys like PROJ-123 are auto-linked from branch names and edit-ledger entries (acronym denylist, so UTF-8 / SHA-256 / CVE-2024 never match), and the issue drawer shows the local ledger activity for the open issue; that Activity view works with no account configured. Mutations (create_issue, update_issue, comment, transition, assign, link_issues, unlink_issues, move_to_sprint, move_to_backlog, add_worklog, attach_file, delete_issue) are edit-ledger-logged with identifiers only β bodies are never logged. Read actions are safe in plan mode.
Reach outside the current workspace. C3 keeps a global registry of installed projects (~/.c3/projects.json); c3_project lists/scans them and proxies the core C3 operations against any one β building and caching a full runtime per target project on demand. Name the target by registered name or absolute path (a .c3 directory is required).
Write safety: read ops run freely; mutating ops (edit, shell, and memory add/update/delete) are refused unless you pass allow_write=true, and every foreign mutation is recorded on the target project's activity log and edit ledger.
Actions
| Action | Group | Description |
|---|---|---|
| list | Discover | Registered projects (fast; registry only) |
| scan | Discover | Registry + bounded filesystem scan for unregistered .c3 projects nearby |
| info, register, unregister | Discover | Project details / add to / remove from the registry |
| search, read, compress, status, memory, impact, edits, validate, filter | Read | Proxy the matching c3_* tool against the target project (compress is the map, same as read with no symbols β it has no standalone top-level tool any more) |
| subprojects, sub_tree, sub_inspect | Read | Direct children; the whole hierarchy; a read-only report on any path (what is there, who already claims it, what it claims, which nested projects under it are unlinked) |
| sub_add, sub_link, sub_remove, sub_cascade | Write | Designate / link by path / unlink / cascade across the subtree β require allow_write=true (sub_cascade with mode='health' is a read) |
| edit, shell, memory(add/update/delete) | Write | Mutate the target project β require allow_write=true |
Sub-project hierarchy: project is the PARENT. A child need not live inside it: sub_link takes an absolute path anywhere on disk, including another drive, and hierarchies nest up to 8 levels. Nested children are excluded from the parent's index; externally linked ones were never in it. Linking is refused if it would make a project its own ancestor.
Examples
# Which projects have C3 installed?
c3_project(action='list')
c3_project(action='scan') # also finds unregistered .c3 dirs nearby
# Search / read inside another project
c3_project(action='search', project='SafeMirror', query='token bucket')
c3_project(action='read', project='SafeMirror', file_path='core/limiter.py', symbols='RateLimiter')
# Memory recall from another project's fact store
c3_project(action='memory', project='Sentinel', mem_action='recall', query='auth flow')
# Guarded write β the edit lands in the target project's ledger
c3_project(action='edit', project='Sentinel',
file_path='README.md',
old_string='v1', new_string='v2',
allow_write=true)
# What is at this path, and who already claims it? (read-only)
c3_project(action='sub_inspect', project='Code Context Control',
target=r'U:\1. Projects\Claude Code Companion (C3)\c3-mobile')
# Link it β a sibling on disk, not nested, so containment cannot express it
c3_project(action='sub_link', project='Code Context Control',
target=r'U:\1. Projects\Claude Code Companion (C3)\c3-mobile',
allow_write=true)
c3_project(action='sub_tree', project='Code Context Control') # every level
Sub-action params: search_action (code/files/semantic/β¦), mem_action (recall/index/fetch/add/β¦), and edits_action (history/versions/stats) pick the operation for those verbs; scan_roots (comma-separated) overrides where scan looks.