v2.145.1

Tools Reference

Complete reference for all 23 C3 MCP tools registered at v2.145.0 β€” parameters, actions, examples, and usage notes.

c3_read
read-only plan-mode safe
Map a file or directory, then read exact symbols or line ranges

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.

ParameterTypeDefaultDescription
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')
πŸ’‘
A directory map lists files most-recently-edited first, then by how much structure they hold. 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.
c3_filter
read-only plan-mode safe
Extract signal from terminal output and log files

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.

ParameterTypeDefaultDescription
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")
c3_edit
writes files logs to ledger
Atomic read-patch-write-log in one step

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.

ParameterTypeDefaultDescription
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 must be unique in the file. If it appears multiple times, c3_edit will fail. Expand the context in old_string to make it unique.
πŸ’‘
Unicode lookalikes are matched automatically. If 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.
c3_validate
read-only plan-mode safe
Syntax-check files using native parsers

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.

ParameterTypeDefaultDescription
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

Python (.py) JS (.js .mjs) TypeScript (.ts .tsx) HTML (.html) CSS (.css) JSON (.json) TOML (.toml) YAML (.yml .yaml) Bash (.sh)
c3_impact
read-only plan-mode safe
Blast-radius analysis β€” know what breaks before you change it

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.

ParameterTypeDefaultDescription
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

SAFE β€” 0 files LOW β€” 1-2 files MEDIUM β€” 3-6 files HIGH β€” 7+ files
πŸ’‘
Run 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.
c3_shell
execution ledger-aware
Structured shell execution β€” exit codes, auto-filter, git-ledger hooks

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.

ParameterTypeDefaultDescription
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

BLOCKED β€” rm -rf of /, a top-level system dir, $HOME/~; fork bombs; whole-drive wipes WARN β€” --force, --no-verify, reset --hard OK β€” everything else

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.

πŸ’‘
A [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.
c3_shell_job
execution v2.114.0
Background shell job β€” for work that outlives a tool call

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.

ParameterTypeDefaultDescription
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')
πŸ’‘
A job's state machine: 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.
c3_ci
execution v2.79.0
Run this repository's real .github/workflows locally, before pushing

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

ActionDescription
inspect, planJob DAG and runnability on this host; plan shows the run/skip decision for mode='required'
run, rerunExecute in needs order; job= selects one job or matrix cell; rerun retries only last run's failures
status, failures, logs, runsLast run's state; structured failures; raw logs; run history
historyPer-job fail rates; flags FLAKY jobs (passed and failed on identical inputs)
cacheA job whose inputs are unchanged since it last passed is reused (status cached); no_cache=true forces a re-run
publishPosts 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
doctorWhat'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.
c3_locks
coordination v2.65.0
Agent leases β€” who is already touching this file

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.

ParameterTypeDefaultDescription
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')
⚠️
A denied 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.
c3_override
coordination
Ask a human to allow ONE blocked call β€” never a policy change

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.

ParameterTypeDefaultDescription
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')
⚠️
Never retry the blocked call before a decision, never route around a hold via 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.
c3_session
session
Snapshot, restore, log, plus finding and marking past sessions

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.

ParameterTypeDefaultDescription
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

ActionRead-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')
c3_memory
memory
Persistent cross-session facts β€” add, recall, query, manage

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.

ParameterTypeDefaultDescription
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

ActionRead-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')
πŸ’‘
What to save in c3_memory vs Claude's auto-memory: C3 memory is the project's persistent knowledge store β€” architecture decisions, conventions, gotchas. Claude's ~/.claude auto-memory is for user preferences and cross-project behavior. Never conflate them.
c3_status
read-only plan-mode safe
Budget, health, notifications, session overview

Project and session status dashboard. Check token budget, server health, pending notifications, and active session summary.

ParameterTypeDefaultDescription
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
c3_delegate
AI
Hand bounded work to your own provider, one tier down

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.

ParameterTypeDefaultDescription
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')
ℹ️
For multi-step work in Claude Code, 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.
c3_agent
AI
Multi-step compound workflows

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.

ParameterTypeDefaultDescription
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

WorkflowWhat 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?')
c3_edits
ledger
Edit ledger β€” history, versions, audit trail

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.

ParameterTypeDefaultDescription
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

ActionDescription
logManually log an edit to the ledger (normally done automatically by c3_edit)
historyShow edit history (optionally filtered by file and/or branch)
versionsShow version history for a specific file
statsAggregate stats: most-edited files, change frequency
tagTag an edit entry with a label
verifyv2.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')
⚠️
A 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.
c3_task
pm v2.45.0
Durable per-project tasks, milestones, and decision notes β€” the project-management layer

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

ActionGroupDescription
addWriteCreate a task: title + optional description / priority p0-p3 / due_date (YYYY-MM-DD) / tags (CSV) / milestone / parent (subtask)
update, done, archiveWritetask_id + changed fields (incl. status); done stamps completion; parent='none' clears a subtask link
list, get, boardReadFiltered list (status/priority/tags/milestone/query), full detail, kanban columns + milestone progress
link, unlinkWritetask_id + link_type (file | commit | edit | session) + ref β€” ref='current' links this conversation
block, unblockWritetask_id + ref=blocking task id; cycle-safe. Completing the last open blocker auto-releases dependents to backlog
reportReadOverdue tasks, blocked chains + aging, ready-to-unblock, milestone health/at-risk, throughput
milestone_add / _update / _listBothMilestones with target dates; list shows progress %
milestone_complete, milestone_reopenWritev2.86.0 β€” close a shipped milestone (tasks KEEP their link, unlike archive); refuses while open tasks remain. _reopen undoes it
milestone_archiveWriteRemoval β€” detaches its tasks
time_add, time_update, time_deleteWriteManual time entries: minutes 1-1440 [+note/due_date/task_id]; update/delete by ref=entry id
time_list, time_summaryReadManual 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_listBothDated notes; kind='decision' for the decision log
historyReadAppend-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_artifacts
agent config v2.46.0
Version history, diff, and restore for the files that shape the agent itself

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

ActionGroupDescription
scanReadRefresh the inventory; captures out-of-band changes immediately (idempotent β€” unchanged files emit nothing)
listReadInventory with versions; filters: cls (instructions | settings | mcp | skill | agent | command | plugin), provider
historyReadChange events newest-first, with source attribution; artifact optional (all events when omitted)
showReadContent at a version (version=0 β†’ live file)
diffReadUnified diff version β†’ against (omit against to diff vs live)
restoreWriteWrite 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.
statusReadTracked 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.

c3_bitbucket
scm v2.30.0
Bitbucket Data Center / Server β€” see and act on PRs, branches, builds, repo admin

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

ActionGroupDescription
statusReadActive account, accounts list, defaults, server-version probe
whoamiReadCurrent authenticated user (PAT owner)
list_projects, list_repos, get_repoReadProject / repository discovery
list_prs, get_pr, get_pr_diff, get_pr_activities, get_pr_commitsReadPR browsing + diff + activity feed + commit list
create_pr, update_pr, comment_pr, approve_pr, unapprove_pr, needs_work_pr, decline_pr, merge_prWritePR 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_commentWriteEdit or remove a PR comment by comment_id β€” the comment's current version is fetched automatically
list_pr_tasks, create_pr_task, resolve_pr_taskTasksPR 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_branchBranchBranch listing and admin (uses the branch-utils API)
list_commits, list_activity, build_statusReadRecent commits, activity, build/CI status (defaults to default-branch HEAD)
repo_settings, update_repo_settings, list_webhooks, create_webhook, delete_webhook, list_permissionsAdminRepository 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.

c3_credentials
secrets v2.58.0
Credential vault β€” the agent uses secrets by name, never by value

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

ActionGroupDescription
listReadMerged registry (project shadows global) β€” names, scope, type, flags, usage. Never values
describeReadMetadata + storage + live fingerprint (first 8 hex of SHA-256, computed on demand, never persisted)
checkReadResolvability probe β€” does the stored value decode?
usageReadv2.88.0 β€” [name]: when/where/how often it was used (counts by surface, recent events for this project, other projects reduced to counts)
setWriteCreate/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
deleteWriteRemove value + registry entry (owning scope inferred when omitted)
revealGatedThe only value-returning action β€” refused unless the user enabled agent_readable on that entry, and permanently disabled for structured kinds
import_envWritev2.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.

c3_jira
issue tracking v2.56.0
Jira Cloud + Data Center β€” search, create, transition, comment through one tool

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

ActionGroupDescription
status, whoamiReadActive account, deployment, defaults, server probe / authenticated user
searchReadRaw JQL, paginated (helper-built JQL elsewhere is always quoted)
my_issuesReadOpen issues assigned to the token's user (statusCategory-aware)
get_issue, list_projects, list_transitionsReadIssue detail with comments, parent, and typed links / project discovery / legal transitions for an issue
get_create_metadata, search_users, list_link_typesReadField 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_worklogsReadAgile boards (project fallback applies) → sprints per board (sprint_state=active|future|closed); an issue's logged time
move_to_sprint, move_to_backlogWriteSprint assignment via the Agile API β€” issue takes one key or a comma-list; sprint_id comes from list_sprints
add_worklog, attach_fileWriteLog time (time_spent='2h 30m', optional body comment); upload one local file as an attachment (20MB local cap)
unlink_issues, delete_issueWriteRemove a typed link by link_id (shown on get_issue); permanently delete an issue β€” refuses one with subtasks unless delete_subtasks=true
create_issueWritePre-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_issueWriteEdit 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_issuesWriteTyped 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, assignWriteComment 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.

c3_project
multi-project v2.31.0
Run C3 against OTHER c3-installed projects β€” discover, read, and (guarded) write

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

ActionGroupDescription
listDiscoverRegistered projects (fast; registry only)
scanDiscoverRegistry + bounded filesystem scan for unregistered .c3 projects nearby
info, register, unregisterDiscoverProject details / add to / remove from the registry
search, read, compress, status, memory, impact, edits, validate, filterReadProxy 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_inspectReadDirect 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_cascadeWriteDesignate / 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)WriteMutate 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.