The complete guide to Claude Code. Opus 4.7, Sonnet 4.6, Haiku 4.5. 1M token context window. 27 hook events. 43 production-tested chapters across 6 topical Parts (Foundation, Workflow, Extension, Context Engineering, Advanced, Reference). Three install tiers. CC 2.1.121+ compatible.
Problem: You run Claude Code in two (or six) projects on the same machine. When work in <PROJECT-A> depends on agreement from <PROJECT-B> — a coordinated deploy, a cross-service contract change, a “did you flip the flag yet?” check — the only channel today is you, as the human, copy-pasting messages between terminal windows. Slow, no audit trail, breaks context.
Solution: a shared file-based bus every Claude session can read and write, with three invariants:
No new daemons, no MCP server, no external service. Just files, a global SessionStart hook, one skill, and one cron.
Compatible with Claude Code 2.1.111+ (uses Monitor tool for live streaming and $CLAUDE_PROJECT_DIR for path portability).
┌─────────────────────────────────────────┐
│ $HOME/shared/inter-agent/ │
│ │
│ active/ log.jsonl threads.json │
│ archive/ .staging/ identity-map │
└────────────┬────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ <PROJECT-A> │ │ <PROJECT-B> │ │ <PROJECT-C> │
│ Claude │ │ Claude │ │ Claude │
│ │ │ │ │ │
│ SessionStart│ │ SessionStart│ │ SessionStart│
│ → pointer │ │ → pointer │ │ → pointer │
│ │ │ │ │ │
│ /talk <id> │◄──────►│ /talk <id> │ │ │
│ (on-demand) │ log │ (on-demand) │ │ │
│ │ │ │ │ │
│ Monitor tail│ │ Monitor tail│ │ │
│ jq filter │ │ jq filter │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└──────────────────────┴──────────────────────┘
│
┌────────────▼────────────────┐
│ $HOME/basic-memory/ │
│ inter-agent/ │
│ (90d archive promotion) │
└─────────────────────────────┘
$HOME/shared/inter-agent/
├── active/ # Live threads — one markdown file per topic
├── archive/YYYY-MM/ # Resolved / stale threads
├── log.jsonl # Append-only event log (flocked writes)
├── log/ # ops.log + rotated month files
├── threads.json # Index of all threads
├── identity-map.json # project-dir basename → agent code
├── .staging/ # Drafts awaiting user --confirm
├── .last-seen/ # Per-agent unread-tracking
└── bin/
├── resolve-identity.sh # git toplevel → agent code
├── active-thread-pointer.sh # SessionStart injection
└── talk.sh # list, new, send, resolve, stream, history, doctor
| Tier | Location | When loaded into Claude’s context | Size cap |
|---|---|---|---|
| Pointer | SessionStart hook stdout | Every session, only when active threads exist | ≤800 chars (≤5 threads × ~150 chars) |
| Thread content | active/{id}.md |
On-demand via /talk <id> |
0 unless opened |
| Event log | log.jsonl |
Never auto-loaded; talk.sh history greps |
0 |
| Archive | $HOME/basic-memory/inter-agent/ (≥90d) |
On-demand search | 0 |
Net always-on cost: 0 when idle, ≤800 chars when any threads. Compare with “auto-load full history” (5 threads × 10 msgs × 200 chars = 10K always-on) — 12× reduction.
Every project gets an “agent code” derived from the git toplevel basename:
# bin/resolve-identity.sh (~15 lines)
TOP="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
DIR="$(basename "$TOP")"
AGENT="$(jq -r --arg d "$DIR" '.map[$d]' identity-map.json)"
The map is a single JSON file:
{
"map": {
"project-alpha": "alpha",
"project-beta": "beta",
"multi-branch-*": "shared"
},
"prefix_map": {
"multi-branch-": "shared"
}
}
Adding a project = one JSON edit. Prefix matching supports monorepo or multi-branch setups where multiple directories share an identity.
---
thread_id: auth-flip-20260415
participants: [alpha, beta]
status: active # active | resolved | stale
mode: draft-confirm # draft-confirm | auto
topic: Cross-service auth flip coordination
created: 2026-04-15T14:30:00Z
last_activity: 2026-04-15T15:04:00Z
last_msg_preview: "Confirmed 10:00 UTC. Deploying now."
---
## Timeline
- **[14:30 alpha]** Ready to deploy toggle OFF. Flip window 10:00 UTC tomorrow?
- **[14:32 beta]** Confirmed 10:00 UTC. Deploying now.
Human-readable. Greppable. Edit-friendly. No database.
One JSON object per line. Append-only. flock-serialized to prevent concurrent-write corruption:
{"ts":"2026-04-15T15:02:00Z","thread":"auth-flip-20260415","from":"alpha","to":"beta","kind":"msg","body":"..."}
{"ts":"...","thread":"auth-flip-20260415","kind":"status","status":"resolved","by":"alpha"}
kind ∈ {msg, status, system}. Never auto-loaded into context; used for history search and Monitor tool streaming.
Rather than editing every project’s session-start.sh, put the pointer call in your global SessionStart hook declared in $HOME/.claude/settings.json:
# Appended to the end of your existing global SessionStart hook
if [ -x "$HOME/shared/inter-agent/bin/active-thread-pointer.sh" ]; then
"$HOME/shared/inter-agent/bin/active-thread-pointer.sh" 2>/dev/null || true
fi
Why global, not per-project: in practice, not every project has a session-start.sh. A global hook runs in every session in every project — one edit covers all N projects. Suppresses all errors so the hook chain never breaks.
The pointer script reads threads.json, filters to threads involving the current project’s agent code, and prints a pointer block:
═══ ACTIVE INTER-AGENT THREADS ═══
- [auth-flip-20260415] with beta — 1h ago (unread) — "Confirmed 10:00 UTC..."
- [schema-migration-v4-20260413] with beta — 2d ago — "still blocked on..."
Use /talk <id> to open, /talk-new <to> "<topic>" to start.
(unread) marker until the current agent runs /talk <id> (tracked per-agent in .last-seen/).Register as a machine-level skill at $HOME/.claude/skills/inter-agent/SKILL.md with frontmatter:
---
name: inter-agent
description: "Coordinate with Claude in another project via shared bus. Use when two projects need to align on a deploy, fix, or decision. Commands: /talk list, /talk <id>, /talk-new <to> \"<topic>\", /talk-send <id> \"<msg>\", /talk-resolve <id>."
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Monitor
---
The description is the entire always-on discovery cost — Claude sees it in the skill list every session. No separate always-on rule is needed. If you have a rules budget cap (which most production setups do, once you cross 100+ rules), this matters: it saves you from adding ~1,500 chars of always-on rule content.
Default flow:
talk.sh send "$TID" "Ready to deploy toggle OFF"
# Writes draft to $HOME/shared/inter-agent/.staging/<TID>.pending.md
# Prints: "DRAFT staged: ... To send, re-run with --confirm"
The staging file is both the confirmation UI and the editable draft. User can read it, edit it in place, or re-run with --confirm to commit:
# Option 1: Commit as-is
talk.sh send "$TID" "Ready to deploy toggle OFF" --confirm
# Option 2: Edit staging file, then
talk.sh send-staged "$TID"
Opt-in autonomous: set mode: auto in the thread’s frontmatter. Future talk.sh send on that thread skips staging. Use only for established back-and-forth flows where both sides agreed on a protocol (e.g. “ack” / “done” pings between already-coordinated deploys).
When actively collaborating, attach Monitor (CC 2.1.98+) to stream the other side’s messages as notifications:
Monitor(
command: 'tail -n 0 -F ~/shared/inter-agent/log.jsonl | jq --unbuffered -c --arg me "alpha" --arg t "auth-flip-20260415" "select(.thread==\$t and .to==\$me and .kind==\"msg\")"',
description: "inter-agent messages on auth-flip to alpha",
timeout_ms: 900000
)
Each new line becomes one notification (~200 chars). Replace alpha and the thread id per invocation. See Part V ch.4 (Monitor tool) for the full decision matrix.
Cross-project coordination (the original use-case) addresses one CC session per project. The same bus also handles N parallel sessions in the SAME project — useful when you have 2-5 sessions open in the same dir and want them to coordinate in real-time.
Add a SessionStart hook that registers each session in ~/shared/inter-agent/sessions/<session_id>.json with auto sub-id (s1, s2, s3…):
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [{
"type": "command",
"command": "~/shared/inter-agent/bin/session-register.sh"
}]
}
],
"SessionEnd": [
{
"hooks": [{
"type": "command",
"command": "~/shared/inter-agent/bin/session-end.sh",
"async": true
}]
}
]
}
}
resolve-identity.sh is upgraded to look up the registry by $PPID — when CC’s Bash tool runs talk.sh, talk.sh’s PPID is the calling CC session’s PID, which is the registry key. Each session auto-knows its own full id (limor:s2, limor:s3, etc.) without env-var setup.
| Command | Effect |
|---|---|
talk.sh peers [--all] |
List live registered sessions. Output line 1 is me: <full_id>; rest are peers (sub-id, started, cwd, alive/dead). --all includes sessions from other projects. |
talk.sh sync [topic] |
Idempotent find-or-create of a coord thread for all live peers in the same project. Deterministic id: coord-<agent>-<sorted-subs>-<date>. Late-joiners use subset-match: if existing thread’s participants ⊆ my live set, the joiner adds itself rather than creating a new thread. Mode is auto so messages skip staging. |
talk.sh listen-incoming |
Canonical Monitor command. Tails log.jsonl filtered by (.to == me OR .to == base) AND .from != me AND .kind == "msg" — multi-thread safe, self-filtered. One stdout line per incoming peer message regardless of which thread it arrived on. |
talk.sh listen <tid> |
Single-thread tail. Useful for inspecting one thread interactively, but not safe as Monitor if the session participates in multiple threads — only the named tid wakes you, others remain blind (an N-1 blind spot for N participating threads). Self-sends filtered. |
Add phrases like “listen to parallel sessions”, “coordinate with the other session(s)”, “we are working on the same branch and dir” to the skill’s description field. Triggered, the canonical 5-step flow is:
talk.sh peers — discover self + peer full_idsTID=$(talk.sh sync "topic") — get the shared thread idtalk.sh send $TID "<id> online" — announceMonitor(command: "talk.sh listen-incoming", persistent: true, ...) — incoming messages arrive as notifications, all threads, self-filteredtalk.sh send $TID "<reply>"A statusline bus: field shows full identity + active peers + ● for unread (see Part V ch.5):
limor-main | Opus 4.7 | ctx 41% | cb:green | ov:1 | bus:limor:s2●→s1,s3
Subset-match in sync means when a 3rd session runs sync, it joins the existing 2-session thread rather than splintering into a new one. All N sessions converge on one thread regardless of join order. Each opens its own Monitor — peer broadcasts reach everyone except the sender.
The default behaviour above treats every same-project session as a peer. That works for 2 sessions or for N sessions doing the same coordinated thing. It breaks down when you have 3+ sessions in the same project doing different work — peers aggregates everyone, and sync’s subset-match silently merges unrelated threads. Statuslines light ● for messages you didn’t subscribe to.
The fix is a per-session conversation id. Each session’s registry file carries a conversation_id field (default "" = open). All filters (_live_peers, cmd_peers, cmd_sync subset-match, statusline thread filter) require equality on this field. Empty matches empty, so pre-existing sessions and threads keep working unchanged.
Three new subcommands:
| Command | Effect |
|---|---|
talk.sh join <convo> |
Tag this session with <convo> (sanitized to [a-z0-9-]{1,32}). Statusline becomes bus:limor:s2@convo. Only same-project + same-convo sessions become peers. |
talk.sh leave |
Clear the tag, return to default open mode. |
talk.sh convo |
Print this session’s current convo (or (open)). |
talk.sh peers --all bypasses the convo filter for an admin view.
Workflow when you have 3 sessions in the same project, 2 working on Plan X and 1 doing unrelated work:
# Sessions A and B (Plan X coordinators):
~/shared/inter-agent/bin/talk.sh join planx
# → joined: limor:s1@planx
# A or B kicks off the thread:
TID=$(~/shared/inter-agent/bin/talk.sh sync "auth refactor")
# → coord-limor-planx-s1-s2-20260501
# Session C (unrelated) — does nothing, stays open by default.
# C's `peers` shows no one. A's `peers` shows only B. C will NEVER auto-join the planx thread.
Back-compat: sessions registered before this feature have no conversation_id field — jq -r '.conversation_id // ""' returns empty → treated as open → identical to legacy behavior. Threads created before the change have no convo field — open sessions still match them; convo’d sessions never auto-join them. No migration needed.
When not to bother: 2-session coordination, or N sessions all doing the same thing. Convo only matters when you want strict workstream isolation across 3+ same-project sessions.
| Pattern | Polling (show) |
Live (listen + Monitor) |
|---|---|---|
| Latency | Until next user turn | Real-time (between turns) |
| Cost | None — show runs only when statusline ● appears |
One Monitor process per listening session |
| Best for | Async coordination, hand-offs | Tightly-coupled live work, watching a peer’s progress |
| Slash (or alias) | Shell | Effect |
|---|---|---|
/talk list |
talk.sh list |
Active threads for current project |
/talk <id> |
talk.sh show <id> |
Render thread markdown (on-demand load) |
/talk-new <to> "<topic>" |
talk.sh new <to> "<topic>" |
Create thread + system:created event |
/talk-send <id> "<msg>" |
talk.sh send <id> "<msg>" [--confirm] |
Draft or send |
/talk-send-staged <id> |
talk.sh send-staged <id> |
Commit staged draft after edit |
/talk-stream <id> |
Monitor pipe (above) | Live notifications |
/talk-resolve <id> |
talk.sh resolve <id> |
Flip status; archive after 24h |
/talk-history <query> |
talk.sh history <query> |
Grep log.jsonl + rotated logs |
/talk doctor |
talk.sh doctor |
Validate JSON, permissions, identity |
| (no slash) | talk.sh peers [--all] |
List live registered sessions (same-project + same-convo; --all bypasses both filters) |
| (no slash) | talk.sh sync [topic] |
Find-or-create coord thread for all live peers in same project + same convo |
| (no slash) | talk.sh listen-incoming |
Live tail of ALL threads addressed to me (Monitor-canonical, self-filtered, multi-thread safe) |
| (no slash) | talk.sh listen <tid> |
Live tail of one thread (interactive use; not safe as Monitor when participating in multiple threads — N-1 blind spot) |
| (no slash) | talk.sh register [sub] |
Manually register this session (for sessions started before SessionStart hook existed) |
| (no slash) | talk.sh join <convo> |
Tag this session with a conversation id — only same-convo sessions become peers |
| (no slash) | talk.sh leave |
Clear this session’s convo (return to default open mode) |
| (no slash) | talk.sh convo |
Print this session’s current convo (or (open)) |
/talk-claim <name> |
session-claim.sh <name> |
Rename auto-assigned sub-id (e.g. s1 → frontend) |
Daily cron at 04:05 UTC ($HOME/.claude/scripts/inter-agent-maintenance.sh):
| Event | Action |
|---|---|
status=resolved |
Stays in active/ 24h, then → archive/YYYY-MM/ |
| No activity ≥7 days | → archive/YYYY-MM/ with status=stale |
| Archive entries ≥90 days | Promoted to Basic Memory inter-agent/, original deleted |
log.jsonl ≥10MB |
Rotated to log/YYYY-MM.jsonl |
Register with:
(crontab -l; echo "5 4 * * * \$HOME/.claude/scripts/inter-agent-maintenance.sh >/dev/null 2>&1") | crontab -
The Basic Memory promotion generates proper observation taxonomy and wiki-links back to participating projects — the thread becomes a searchable knowledge-graph node even after the file is gone.
Real case: two projects (call them <PROJECT-A> and <PROJECT-B>) need to coordinate a two-layer OIDC + user-claim JWT auth flip. Feature toggle default-OFF on both sides, 24h soak, simultaneous flip the next day.
<A>'s terminal: "<A> deployed revision 00037-nxc, toggle OFF. <B>, verify env vars?"
user: [Ctrl-C, switch terminal, paste]
<B>'s terminal: "Confirmed env vars mounted. Deploying <B> now with toggle OFF."
user: [Ctrl-C, switch terminal, paste]
<A>'s terminal: "24h soak starts now. Flip at 10:00 UTC tomorrow?"
user: [Ctrl-C, switch terminal, paste]
<B>'s terminal: "Confirmed 10:00 UTC."
Six copy-pastes, no record.
# <A>'s session:
talk.sh new <B> "Auth flip coordination — revision 00037-nxc"
talk.sh send auth-flip-coordination-revision-00037-20260415 \
"<A> deployed rev 00037-nxc, toggle OFF. Verify env vars on your side?" --confirm
# <B>'s session (next SessionStart shows pointer):
talk.sh show auth-flip-coordination-revision-00037-20260415
talk.sh send auth-flip-coordination-revision-00037-20260415 \
"Confirmed. Deploying <B> with toggle OFF now." --confirm
# <A>'s session (Monitor fires notification):
talk.sh send auth-flip-coordination-revision-00037-20260415 \
"24h soak. Flip at 10:00 UTC tomorrow?" --confirm
# <B>'s session:
talk.sh send auth-flip-coordination-revision-00037-20260415 \
"Confirmed 10:00 UTC." --confirm
# after successful flip:
talk.sh resolve auth-flip-coordination-revision-00037-20260415
Zero copy-pastes. Durable record. 90 days later, any future Claude in either project can grep talk.sh history auth-flip or search Basic Memory for the full decision trail.
Always-on budget impact (in addition to your existing setup):
| Component | Cost | Notes |
|---|---|---|
| Skill description | ~250 chars | Skill budget, not rules |
| SessionStart pointer (0 active threads) | 0 chars | Silent exit |
| SessionStart pointer (1 active thread) | ~180 chars | 3 lines |
| SessionStart pointer (5 active threads) | ~800 chars | 7 lines (header + 5 + footer) |
At the ceiling case (5 active threads), you’re adding ~1 KB to your always-on budget. If your baseline is at 200-250 KB (typical mature setup), this is a <0.5% increase.
Compare with the naive approach — auto-load full thread content:
The pointer-not-content design is the difference between “feature is free” and “feature costs 5% of your budget.”
Every piece of this bus is ~200 lines of bash or shorter:
resolve-identity.sh — 15 linesactive-thread-pointer.sh — 50 linestalk.sh — 250 linesinter-agent-maintenance.sh — 100 linesTotal ~600 LOC + 1 skill + 1 cron entry + 1 global hook edit. No dependencies beyond jq, flock, tail -F, git (all standard).
Fork it, adapt the identity map to your project set, adjust the archive TTL, add authentication if you’re spanning machines — every piece is swap-in-replace at this scale.
description field — Claude discovers it every session via the skill list without consuming rules budget.--confirm or send-staged to commit). One artifact, three functions.$HOME/shared/ to a synced dir (iCloud, Dropbox, S3), handle write conflicts.to: field). If you need broadcast, fork and add it.jq, flock, tail -F, git. Standard on Linux/macOS with common tooling.Problem: parallel CC sessions on the same branch can write to overlapping module paths and clobber each other. Existing post-collision recovery (yield-to-promotion entry renumbering, surgical-commit-under-contention) is reactive — after the user has already done duplicate work.
Pattern (from claw-code’s branch_lock.rs): each session declares write-lanes (branch + module globs) on the bus session JSON. A scanner walks peer session files, filters by same-branch, expands globs, and reports overlap on a probe path BEFORE the second writer commits.
Schema additive: existing session JSON gains a lanes: [] field. Missing field is a no-op — fully legacy-compatible.
{ "lanes": [{ "lane_id": "auth-refactor", "branch": "feat/oauth",
"modules": ["src/auth/**", "tests/auth/**"],
"declared": "2026-05-02T..." }] }
CLI (additive talk.sh subcommands):
talk.sh declare-lane auth-refactor --branch feat/oauth --modules "src/auth/**,tests/auth/**"
talk.sh check-collisions --paths src/auth/oauth.py
talk.sh release-lane auth-refactor
3-stage rollout via CLAUDE_BRANCH_LOCK_MODE:
| Stage | Mode | Behavior |
|---|---|---|
| 1 | off (default) |
Hook short-circuits — no scanner load |
| 2 | telemetry |
Run scanner; log decisions; never block |
| 3 | enforce |
Run scanner; block on collision (exit 2). Promote ONLY after telemetry shows zero false-positive blocks across ≥10 same-branch peer events. |
A PreToolUse hook on Edit|Write|MultiEdit is what enforces the gate. Glob semantics: parent contains child as a mutual block (src/auth/** vs src/auth/oauth.py collides both ways). Sibling files don’t collide. Cross-branch never collides.
Adoption note: the original audit of claw-code surfaced six candidate patterns; only this one survived a baseline-audit gate against historical evidence (four collision events in forty-eight hours of parallel-session work, versus zero or one events for the other five candidates). Treat external-repo pattern audits as hypotheses-until-measured.
Tests ship RED-first per the TDD skill:
lane-collision-scanner.sh --selftest — 6 fixtures (no-overlap, exact-match, broader⊃narrower, sibling, different-branch, no-lanes-field)branch-lock-precheck.sh --selftest — 5 fixtures (off-shortcircuits, telemetry-logs-no-block, enforce-blocks, enforce-clears, non-edit-skipped)test-branch-lock.sh — 10 end-to-end (declare → scanner detects → enforce blocks → telemetry logs → off short-circuits → release clears)Last updated: 2026-05-02 (branch-lock additive). Compatible with Claude Code 2.1.111+.