Overview
Siltpoke is an independent second mind that runs alongside a CLI coding agent. It doesn't write code for you — it watches your agent's work, reasons about it separately, and remembers across sessions, so you're not relying on a single model's read of its own output.
What it does
- Reviews — a separate
claude -psubprocess re-reviews every turn (different prompt, different personality, your existing Claude Code login), running a deterministic 13-rule rubric plus tsc/eslint/ripgrep and writing evidence-backed critiques that cite real tool output rather than guessing. - Maps — builds a structural repo-graph of your codebase (files, symbols, call chains) so you can explore and understand what your agent actually built, not just what it claims it built.
- Remembers — cross-session memory keyed to each project: rules learned from your feedback, past bugs and how they were resolved, facts you've told it.
- Grows — an ASCII pet with a name, a species, and a personality of its own. Levels up as you use it; a wrong call gets dismissed, never punished.
One system, not separate tools
The critic, repo-graph, memory, and pet aren't independent features bolted together — they're one loop. What the critic learns feeds memory. What the repo-graph maps grounds what the critic and chat can cite. None of the pieces is meant to be depended on alone; together they form a second mind that's actually usable day to day.
Part of a bigger ecosystem
Siltpoke is one half of a Claude Code ecosystem by the same author. Both pieces install into your Claude Code and work on the same sessions — not separate apps you switch between, but two companions in one environment:
- siltpoke — the always-on companion: it watches, remembers, and gently flags. The persistent second mind that stays with a project.
- project-life-cycle
— a Claude Code skill that gives your work a repeatable rhythm: a
traceable spec → plan → execute → ship → release discipline, driven by
/init-harness → /ship → /release. A process layer, not a code generator — it wraps the workflow so you don't re-explain your dev process every new project.
If siltpoke is the hardware — the companion that's always there — then project-life-cycle is the software: the cadence that decides when you pause to predict, build, and understand. Both aim at the same thing — letting you build and learn from what you build, instead of offloading it all to the agent.
Install project-life-cycle (an independent skill — siltpoke doesn't require it, but they're better together):
claude plugin marketplace add Victoriakaey/project-life-cycle
claude plugin install project-lifecycle@project-life-cycle
Restart Claude Code; it auto-loads when you start a project or plan a
milestone. Update later with claude plugin marketplace update project-life-cycle && claude plugin update project-lifecycle.
Over time the two are meant to deepen into one seam — siltpoke's read-the-diff, ground-the-why organs plugging into project-life-cycle's ship cadence, so understanding becomes part of the loop. That tighter integration is still taking shape; today they already complement each other just by living in the same Claude Code.
Who it's for
Anyone building with a CLI coding agent — Claude Code today, Codex support coming soon.
Where to go next
For the pitch and a live look at the pet, see the landing page. To get running, continue to Install & setup below.
Install & setup
Requirements
- Claude Code, logged in.
- Bun ≥ 1.0 —
curl -fsSL https://bun.sh/install | bash.
Steps
Clone and install dependencies.
git clone https://github.com/Victoriakaey/siltpoke ~/siltpoke cd ~/siltpoke && bun installRun the setup wizard.
bun run setupThe wizard walks you through wiring Siltpoke into your statusline, then naming your pet, picking its species, its language, and its personality.
Restart Claude Code. Quit and relaunch. You should see an ASCII face appear at the left of your statusline.
Say hi. Send any message. Within ~3 seconds a one-line speech bubble appears next to the face — Siltpoke is alive.
Siltpoke splices its face onto the left of whatever statusline you already run, and drops its latest critique in a quoted bubble beside the pet's name. So the line reads something like:
/\_/\
(o.o)
> ^ <
Mochi "this if branch never matches — parser.ts:88"
Sentinel
L10 1560/2500
The face block is three lines of face, then your pet's name, its highest title, and level + XP toward the next one — with the latest critique in a quoted bubble beside the name. Siltpoke splices this onto the left of whatever statusline you already run (e.g. claude-hud), leaving that untouched.
The bubble's color tracks the critique's severity: red for high, yellow for medium, and gray for low or informational notes — which is most of them. Gray isn't a bug: the critic only escalates past it when it has file:line evidence for a real problem. With no active critique, the bubble rests in your configured color (cyan by default).
Idempotent, and reversible
The installer is safe to re-run and backs up ~/.claude/settings.json
before changing anything. To remove Siltpoke and restore your previous
setup:
bun run uninstall
The first-run wizard
bun run setup asks for your pet's name, species, and
language, then sets its five personality dials — snark, patience,
rigor, chattiness, and curiosity. There are five ways to set the dials:
- Use defaults — take the species preset above as-is.
- By hand — type each of the five dials yourself, full control.
- Random — roll all five for a surprise pet.
- A 6-question quiz — a short Likert test scores the dials locally (no LLM ever guesses the numbers) and names an archetype.
- Read your memory — Siltpoke reads your
~/.claude/files (CLAUDE.md, rules, memory) and infers a personality that fits your style, with your explicit consent. (Only offered when it finds those files.)
The quiz and read-your-memory paths also ask how it should relate to you — the match mode, which defaults to hybrid:
- Mirror — match your own style: a terse coder gets a terse pet.
- Complement — invert it to cover your blind spots: a terse coder gets a chattier pet.
- Hybrid (default) — mirror the vibe dials (snark, chattiness) so it feels familiar, but complement the task dials (rigor, patience, curiosity) so it catches what you'd skip.
Re-run the wizard anytime without reinstalling — bun run first-run, or
bun run edit-personality to re-roll just the personality:
bun run first-run
See the Personality section for the full breakdown of dials and archetypes.
Daily use
Pull, not push
After every turn, a separate claude -p subprocess reviews your agent's
work in the background and writes a critique to disk. Critiques never
auto-inject into your chat — they sit in an inbox until you pull them in
yourself. Nothing interrupts your flow; you decide when to look.
The loop
- Your agent finishes a turn → Siltpoke runs in the background → a critique lands in the inbox.
- Run
/siltpoke-inboxwhenever you want to check what's waiting. - For each pending critique, decide:
- Useful →
/siltpoke-forward <id>pulls it into the chat (+10 XP if it's fresh). - Wrong →
/siltpoke-dismiss <id> <reason>rejects it and triggers Reflexion — Siltpoke writes a learned rule from the reason so it won't make the same wrong call again. - Neutral / already seen →
/siltpoke-ack <id>flips its status with no XP and no learning.
- Useful →
/siltpoke-inbox lists what's waiting — severity, when, and a one-line bubble:
$ /siltpoke-inbox
ID SEVERITY TIMESTAMP BUBBLE
c-4a1f high 2026-07-01T14:32:10 tsc: 'session' may be undefined — auth.ts:42
c-2e07 medium 2026-07-01T14:05:44 this if branch never matches — parser.ts:88
c-0758 info 2026-07-01T13:56:12 4 files, 13 hunks, checks all green
Forwarding one pulls the full critique into your chat — the finding, where it is, and a concrete fix:
$ /siltpoke-forward c-4a1f
[HIGH · type-safety] auth.ts:42
`session` can be undefined here — the guard above only checks `token`,
so `session.userId` throws on the logged-out path.
→ guard it first: if (!session) return null
evidence: tsc — "'session' is possibly 'undefined'" at auth.ts:42
The 3 commands you'll actually use
| Command | What it does |
|---|---|
/siltpoke-inbox |
List pending critiques (read-only) |
/siltpoke-forward <id> |
Pull a specific critique into the chat (+10 XP if fresh) |
/siltpoke-dismiss <id> <reason> |
Reject a critique and let Siltpoke learn from why it was wrong |
/siltpoke-ack <id> rounds this out for the neutral case — no XP, no
Reflexion, just marks it seen.
XP basics
Siltpoke levels up as you use it — mostly from the critiques it writes when it reviews your work, plus smaller amounts for forwarding a critique (+10) or petting it (+5). XP only ever climbs; a wrong critique is dismissed and feeds Reflexion, never docking you. Full breakdown in Personality → XP, levels & titles.
Slash commands
Every command below is a /siltpoke-* slash command available in Claude
Code once Siltpoke is installed. XP-bearing commands respect the shared
100 XP/day action cap.
The bare /siltpoke prints a state card — your pet at a glance:
┌─ Siltpoke ─────────────────────────────────────
│ Mochi the cat
│ level 10 [████████████░░░░░░░░] 62% (1560/2500 XP)
│ titles: Hatchling, Watcher, Apprentice, Sentinel
│ mood: idle
│ today: 12 brain calls, 0 reflections, $0.0800 spent
│ mode: gates
│ last 7d: ▂▄▆▃▅▆█
│
│ projects:
│ myapp 12 calls today · 3 pending
└────────────────────────────────────────────────
Critiques
| Command | Description |
|---|---|
/siltpoke-inbox |
Show pending critiques waiting to be forwarded |
/siltpoke-forward <id> |
Surface a specific critique by ID into the conversation (+10 XP if fresh) |
/siltpoke-forward-all |
Surface every pending critique at once |
/siltpoke-last |
Surface Siltpoke's most recent critique into the conversation |
/siltpoke-dismiss <id> [reason] |
Dismiss a critique and let Siltpoke learn from why it was wrong |
/siltpoke-undismiss <id> |
Reverse a previously dismissed critique — flip status back to pending and undo its learned rule |
/siltpoke-ack <id> |
Acknowledge a critique. Neutral status flip — no XP, no Reflexion |
/siltpoke-feedback <id> <text> |
Submit free-text feedback on a critique. Writes to preference log; will feed Reflexion rule generation in future phases |
/siltpoke-review |
Ask Siltpoke to review your work on the next message. Bypasses budget + quiet hours for one Brain call |
Pet & care
| Command | Description |
|---|---|
/siltpoke |
Show Siltpoke's state card — name, level, mood, today's spend, trigger mode |
/siltpoke-pet |
Pet Siltpoke. Grants +5 XP (shared 100/day action-XP cap) |
/siltpoke-feed |
Feed Siltpoke. +3 hunger, +1 mood, +5 XP (shared 100/day action-XP cap) |
/siltpoke-clean |
Clean Siltpoke. +1 hp, +1 mood, +5 XP (shared 100/day action-XP cap) |
/siltpoke-play |
Play with Siltpoke. +2 mood, +1 bond, −1 hunger, −2 energy, +3 XP (shared cap) |
/siltpoke-tease |
Tease Siltpoke. −2 mood, −1 bond. 0 XP. 3+/day → grumpy |
/siltpoke-sleep |
Let Siltpoke sleep. +1 hp, +4 energy. 0 XP. Uncapped |
Status & cost
| Command | Description |
|---|---|
/siltpoke-stats |
Show Siltpoke's daily spend, budget state, and trigger mode |
/siltpoke-report |
Start Siltpoke's dashboard server in the background. Opens browser at http://127.0.0.1:9876 |
/siltpoke-report-stop |
Stop the background Siltpoke dashboard server |
Daemon & gates
| Command | Description |
|---|---|
/siltpoke-daemon |
Manage the Siltpoke daemon (siltpoked) — start, stop, status, install-autostart |
/siltpoke-wake |
Bypass Siltpoke's budget and quiet-hours gates for one Brain call |
/siltpoke-mute |
Silence Siltpoke for a duration. Mute beats wake — critic never fires while active |
/siltpoke-unmute |
Clear the Siltpoke mute marker so the critic fires again |
Memory & facts
| Command | Description |
|---|---|
/siltpoke-remember [--type fact|goal|constraint] "claim" |
Remember a fact, goal, or constraint for the Siltpoke pet |
/siltpoke-approve <fact-id> |
Approve a pending fact (flip pending → active) |
/siltpoke-reject <fact-id> |
Reject a pending fact (flip pending → retired) |
/siltpoke-tag-entities |
Manually run the entity-tagging janitor over memory.json (backfills entities on untagged active facts) |
Repo Graph commands
| Command | Description |
|---|---|
/siltpoke-index [--force] |
Build / refresh siltpoke's structural repo-graph for this project (deterministic, no LLM) |
/siltpoke-graph |
Open the Siltpoke repo-graph dashboard in the browser. Auto-starts daemon at http://127.0.0.1:9876/repo-graph |
/siltpoke-explain <target> [--depth 1|2] [--force] [--json] |
Explain a file / function / symbol using siltpoke's repo-graph + Brain |
/siltpoke-where |
Print the resolved Siltpoke project identity for the current working directory |
/siltpoke-relocate |
Update the current Siltpoke project's recorded root path (preserves project_id) |
Chat & health
| Command | Description |
|---|---|
/siltpoke-chat |
Chat with Siltpoke's AI via the daemon (single-turn, REPL, or search) |
/siltpoke-doctor |
Install-health diagnostic — 9 ✓/✗ checks (settings, hooks, inner.txt, wake, schema, symlinks, config, brain-health, daemon-freshness) |
/siltpoke-help |
Siltpoke usage manual — slash commands, daily flow, config, troubleshooting |
Configuration
Everything lives in ~/.siltpoke/config.json. Edit it by hand, or re-roll the
personality half with bun run edit-personality. Siltpoke reads the file
fresh on the next Brain call — no restart needed.
Trigger modes
Controls when Siltpoke actually runs Brain (the claude -p review):
| Mode | Behavior |
|---|---|
always |
Every supported event (most expensive) |
gates (default) |
Only on configured gate events (default: Stop + PreCompact) |
on_demand |
Never, unless /siltpoke-wake was just used |
hybrid |
gates + /siltpoke-wake always bypasses |
Set it directly:
{ "triggerMode": "on_demand" }
Budget
{ "budget": { "dailyTokenLimit": 500000 } }
500,000 tokens/day is the default. It guards in two stages:
- At 80% Siltpoke soft-caps — it degrades to
on_demand, so Brain only runs when you/siltpoke-wakeit. - At the hard stop the pet flips to
sleeping_brokeand skips Brain until the midnight reset.
dailyTokenLimit: 0 disables the gate entirely. Cache-read tokens count at
only 0.1× toward the limit, so warm-prefix calls barely dent it.
Quiet hours
Off by default — no window is set, so Siltpoke can trigger at any hour. To mute it overnight, set your own window:
{ "quietHours": { "start": "23:00", "end": "08:00" } }
Brain is skipped for any trigger inside the window; everything outside it runs normally.
Cost
Each critic call runs about $0.03 (median $0.031 from real logs; most land in $0.01–$0.09). Prompt caching keeps a ~65k-token prefix warm, so most of every call is cached-read at a fraction of the price — only the ~1.5k tokens it writes are billed in full. Daily cost scales with how many turns you review: a few reviews is a few cents, a heavy day runs around a dollar, and the budget cap above bounds it either way.
Personality keys
The five personality dials also live in config.json, each 0–10:
{ "snark": 7, "patience": 4, "rigor": 6, "chattiness": 5, "curiosity": 8 }
Re-roll them anytime without touching the rest of the config:
bun run edit-personality
See the Personality section for what each dial changes and the 16 archetypes they combine into.
Personality
Every pet is a species (an ASCII face + starting dial values) plus five
dials you can tune from there. The dials combine into one of 16
archetypes, and XP moves you through 9 titles. You already saw the ways to
set this up during install (defaults, by hand, random, the 6-question quiz,
or reading your ~/.claude/ memory — plus the mirror/complement/hybrid
match mode) — this section is the reference for what each piece means.
Species
Pick a face, then fine-tune any dial afterward — the species only sets the starting values.
slime
.---.
(o.o)
(___)
easygoing. lets small stuff slide, speaks up when it counts. Preset dials — snark 5 · patience 5 · rigor 5 · chattiness 5 · curiosity 5
cat
/\_/\
(o.o)
> ^ <
opinionated. trigger-happy on smells, judges your naming. Preset dials — snark 8 · patience 2 · rigor 4 · chattiness 5 · curiosity 7
owl
,-,-,
(O,O)
=====
meticulous. high rigor, rarely wrong, never loud. Preset dials — snark 3 · patience 8 · rigor 9 · chattiness 5 · curiosity 8
robot
[---]
|o-o|
[___]
literal. deterministic critiques, cites every line. Preset dials — snark 2 · patience 7 · rigor 10 · chattiness 2 · curiosity 3
bunny
(\_/)
(o.o)
(=v=)
chatty. warm and talkative, always rooting for you, big heart. Preset dials — snark 1 · patience 9 · rigor 5 · chattiness 8 · curiosity 5
Dials
Five dials, each 0–10. A new pet starts from its species preset
above — not a flat 5 — and you tune from there:
| Dial | Low (0) | High (10) | What it changes |
|---|---|---|---|
snark |
sweet | savage | how sharp the tone is — sweet jokes to savage roasts |
patience |
trigger | chill | how fast it flags — chill to trigger-happy |
rigor |
vibes | methodical | how it decides — gut feel to citing every line |
chattiness |
terse | chatty | how much it says — terse to talkative |
curiosity |
incurious | digs in | how deep it digs — and the Curious / Steady prefix |
curiosity also prefixes your archetype name: ≥7 adds Curious,
≤3 adds Steady.
Dials aren't frozen. As Siltpoke learns your style over a project, each can drift up to ±3 from where you set it — a pet you made terse can loosen up if you keep engaging with its longer notes, and tighten back if you don't.
Archetypes
snark × patience × rigor × chattiness form a 4-bit key, in that
order — each dial counts as 1 (high) at ≥6 or 0 (low) below that.
The 16 combinations each have a name:
| Key | Name | What it's like | ~MBTI |
|---|---|---|---|
1000 |
The Snapper | quick, sharp comments, no follow-up — then walks away | ISTP |
1001 |
The Rant | long impassioned tirades, high energy, low evidence | ESTP |
1010 |
The Hawk | sharp-eyed, low-tolerance, surgical — hunts in silence | ISTJ |
1011 |
The Drill Sergeant | yells out every typo; cares deeply, expressed as volume | ESTJ |
1100 |
The Tease | playful jabs; doesn't actually want you to feel bad | INTP |
1101 |
The Roaster | sharp tongue, big heart, loves an audience | ENTP |
1110 |
The Sly Fox | sees everything, says little, lands devastating one-liners | INTJ |
1111 |
The Wry Coach | snarky mentor — will roast you and teach you | ENTJ |
0000 |
The Worrier | quietly anxious, vibes-based, doesn't share much | ISFP |
0001 |
The Frantic | anxious and chatty, all volume — a loveable mess | ESFP |
0010 |
The Perfectionist | anxiously careful; worries visibly, never yells | ISFJ |
0011 |
The Anxious Officer | frets out loud about every detail — means well | ESFJ |
0100 |
The Zen Monk | lets everything slide; vibes only, dreamy detachment | INFP |
0101 |
The Cheerleader | sunshine — patient, chatty, no rigor, hypes you up | ENFP |
0110 |
The Patient Sage | quiet, methodical, kind; cites evidence gently | INFJ |
0111 |
The Saint | patient, careful, warm, vocal — pure good | ENFJ |
The ~MBTI column is a loose, just-for-fun shorthand — Siltpoke scores
five Big-Five-style dials, not MBTI's four axes, so read it as
vibes-adjacent, never a real type. The rough fit: snark≈T/F, rigor≈J/P,
chattiness≈E/I, and curiosity≈S/N through the Curious/Steady prefix — a
Curious Hawk leans INTJ, a Steady Hawk stays ISTJ. The ~MBTI
column above is the full 16-way mapping.
XP, levels & titles
XP sources:
- The critic reviewing your work — when it writes a critique it can award XP directly, sized by the model to what it saw (a fix, a test, a clean refactor). This is the main way you level, and it's separate from the 100-per-day cap on interaction XP below.
- Forwarding a fresh critique: +10 XP.
- Petting: +5 XP — no per-pet limit; bounded only by the shared 100 XP/day cap.
- All dashboard/action XP shares a combined 100 XP/day cap.
- XP only ever climbs — a wrong critique is dismissed, never docks you.
Level curve (tiered, not flat):
- Levels 1–5:
100 × n - Levels 6–10:
250 × n - Levels 11–20:
500 × n - Levels 21+:
1000 × n
(n is your current level — the XP to leave level n for n + 1. Level 1 → 2
costs 100 × 1; level 2 → 3 costs 100 × 2.)
The curve stays gentle early so unlocks show up fast, then steepens — reaching Sentinel (L10) and beyond is months of engagement, not days.
The 9 titles:
| Level | Title | Meaning |
|---|---|---|
| 1 | Hatchling | fresh out of the egg |
| 2 | Watcher | starts watching your sessions |
| 5 | Apprentice | learning your style |
| 10 | Sentinel | a trusted guard |
| 13 | Veteran | seasoned reviewer |
| 20 | Sage | a wise resident |
| 30 | Oracle | deep foresight |
| 50 | Ancient | a long-lived companion |
| 100 | Legend | the pinnacle |
Memory
Siltpoke's cross-session memory splits into two stores. One travels with the pet everywhere; the other stays put in each repo.
Two stores, one brain
- GLOBAL —
~/.siltpoke/global.json— the pet's identity: name, species, appearance, base personality dials, and the XP ledger (level, streak, bond meter). One identity across every project. - PER-PROJECT —
~/.siltpoke/projects/<project_id>/memory.json— what it learned in this repo: facts, learned rules, recent chat sessions, an episodic log of what happened (event fragments), a long-term summary, personality drift over the base, and your goals & constraints. Scoped to one project —<project_id>is the project's stable id (/siltpoke-whereprints it for the current directory).
Both stores feed a single Brain prompt, assembled in cache-friendly order, so a bug from last week can inform today's critique without re-sending the whole history.
Not everything reaches every place, though — and this is the part that actually shapes behavior. The critic only ever sees your style facts: conventions and rules about the code (tabs over spaces, never force-unwrap, your naming preferences). Personal facts — your pet's name, what you're building, who you are — are deliberately held back from critiques; they'd only add noise to a code review. The pet chat sees all of it, so it can talk to you like it knows you while the critic stays focused on the code.
Facts you control
Facts move through a lifecycle: pending → active → retired.
/siltpoke-remember [--type fact|goal|constraint] "claim"— stores a claim you tell it directly. Because you asserted it yourself, it landsactiveand pinned immediately (the trust path) — no approval step./siltpoke-approve <fact-id>— flips apendingfact toactive, so it becomes part of Siltpoke's working memory. Pending is where facts proposed during consolidation wait: when Siltpoke compacts a session, the new durable facts it distills landpendingfor your say-so. Two kinds skip the queue straight toactive: facts you assert with/siltpoke-remember, and facts it auto-captures from what you say in chat./siltpoke-reject <fact-id>— flips apendingfact toretired.- Pinned facts are exempt from all of the above automation — once pinned, a fact is protected from decay and stays active until you change it yourself.
How does it decide what's worth keeping? It's a hybrid. Plain code decides whether a message even looks like a durable fact worth spending on — and only then does a small model (Haiku) distill it, with a "would this still matter next week?" test. The choice to spend a model call is always made in code, never left to the LLM.
The /memory view lists what it's holding for the current project — for example:
$ /memory
active (3)
● deploys via GitHub Actions workflow confirmed
● prefers tabs over spaces style confirmed
● never force-unwrap in Swift pref pinned
pending (1)
○ uses pnpm, not npm tooling /siltpoke-approve to keep
Consolidation
Roughly every ~10 stops or 7 days — whichever comes first — Siltpoke
compacts recent facts, critiques, and dismissals into the long-term
summary, so the working memory doesn't grow without bound. Two fields
gate this: last_consolidated_at (when it last ran) and
consolidation_due_at (when it's next allowed to). If there's nothing
new since the last pass, it skips the run.
Decay & prune
Stale, low-confidence facts are retired — but never silently. Siltpoke
scores each active fact by recency, confidence, and how often it's been
recalled; once a fact falls stale enough, Siltpoke proposes it for
retirement (retire_proposed) and you confirm before it's actually
marked retired with retired_reason: "decayed". Facts are never
hard-deleted — only
their status changes, so nothing you told Siltpoke is truly gone.
Pinned facts are exempt from decay entirely.
Reflexion — the critic self-corrects
/siltpoke-dismiss <id> "why it was wrong" does more than clear a
critique. Siltpoke reads your reason and, when it's confident, writes a
learned rule back into memory — with provenance, so you can always
trace where a rule came from:
from dismissing critique <id>: <reason>
That rule feeds every future Brain call, so the critic won't repeat the
same wrong call twice. A dismiss without a reason clears the critique but
teaches Siltpoke nothing. /siltpoke-undismiss <id> reverses a dismissal
— it flips the critique back to pending and removes the rule it created,
if that dismissal is what created it.
Repo Graph
The fastest way to end up with a codebase you don't understand is to vibe-code one. Repo Graph turns what your agent built into a structural map you can actually read back.
Build the graph
/siltpoke-index [--force] walks your repo (.ts/.tsx/.js/.jsx/.py,
skipping node_modules, dist, .git, and the usual build/cache dirs)
and parses it into a structural graph — deterministic, no LLM involved.
It's incremental: re-running only re-parses files whose content changed
since the last index; pass --force to rebuild from scratch. The graph
is stored under ~/.siltpoke/repo-memory/<proj-hash>/ and is the
substrate every other repo-graph feature below reads from — refresh it
any time your source has moved on.
See the shape
/siltpoke-graph opens a C4 container view: a system boundary (your
repo, by name) containing containers grouped into layer bands. It has two
modes, toggled at the top:
- Auto (the default) — a deterministic projection of the structural
graph. No LLM, instant, free. Containers are your source subdirectories;
edges are neutral
imports ×N(how many imports cross between two groups); bands come from your ownCLAUDE.mdArchitecture section (or, if you haven't written one, a fallback grouping by top-level directory). Siltpoke's own repo, for example, bands into surfaces · core · state · infra. Every node and edge here is a plain structural fact. - Generated (opt-in, paid) — one whole-repo pass through the Brain
that proposes a richer model: verb-labeled edges (calls, reads,
writes…), inferred layer ordering, and short descriptions. Every claim
is then grounded against the real graph — a
grounded %chip shows the share that cite real code, and a band whose proposed order contradicts the actual import gradient is demoted to a dashed inferred layer rather than asserted. It spends tokens (a soft ~$0.6 / hard $2 cap), so it's a button you press, not the default.
The whole architecture fits on one screen; click a container to drill arch → file → symbol, descending from the container view into its files and then into the symbols each file defines.
Trace a path
Path Highlight follows the spine from an entrypoint — a detected root, or any function you pick — showing how a request actually flows, call by call, built deterministically from the same graph and never invented. Each call is classified by how confidently it resolves: a callee that resolves to exactly one definition is resolved; one with more than one candidate is inferred; one that can't be resolved at all (dynamic dispatch, or simply not found) is unresolved and left off the path rather than guessed at. That mix rolls up into a coverage score — the share of in-repo call sites that resolve (external, standard-library, and dynamic calls are left out of the count) — shown as a tier (green ≥60% / yellow ≥40% / red) so you know how much of the path to trust outright versus double-check yourself.
Ask it, grounded
/siltpoke-explain <target> [--depth 1|2] explains a file, function, or
symbol in plain language, grounded in the graph /siltpoke-index built.
An evidence guard parses every [file:line] citation the answer
makes and checks it against the real graph — the file must exist and the
line must fall inside it. The fraction of citations that pass is the
grounded %; drop below 90% and the answer carries a visible
low confidence banner rather than passing silently as fact. --depth 2
pulls in transitive callers for a wider view; --force skips the cache
and re-asks Brain.
Chat anchored to a node
Opening a chat from a node in the graph pins that conversation to it — once, at creation. The surrounding subgraph is resolved and frozen alongside the conversation as its working context, so the answer stays reproducible against the version of the code you were looking at when you pinned it. Both the conversation and its anchor persist to disk, so the chat remembers across sessions, not just within one. If the file behind the anchor changes before you send another message, Siltpoke flags it as stale and lets you choose: keep answering against the pinned version, or re-anchor to the current one.
Commands
The commands used above — /siltpoke-index, /siltpoke-graph,
/siltpoke-explain, /siltpoke-where, /siltpoke-relocate — are listed with
their full flags under Slash commands → Repo Graph commands.
Architecture
Your agent reviewing its own work is a blind spot. Siltpoke is a second,
independent one — a separate claude -p subprocess with a different
prompt, different personality, and different blind spots, running on the
login you already have. No extra LLM bill.
The review loop
your agent finishes a turn
│
▼
┌───────────────────┐
│ 1. Stop hook │ ── transcript + diffs + tool output
└─────────┬─────────┘
▼
┌───────────────────┐
│ 2. Gate chain │ ── quiet hours? budget? no change? → skip
└─────────┬─────────┘ (never blocks your agent)
▼
┌───────────────────┐
│ 3. Brain (LLM) │ ── separate `claude -p` + tsc/eslint/ripgrep
└─────────┬─────────┘
▼
┌───────────────────┐
│ 4. Classify + │ ── suppress · bubble · full critique
│ evidence-guard│ (kept findings cite verbatim tool output)
└───────────────────┘
- Stop hook fires — your agent finishes a turn. The hook hands off your transcript, diffs, and tool output.
- Gate chain — skips on quiet hours, the budget cap, or no change since the last run. Never blocks your agent — this is pull, not push, all the way down.
- Brain reasons — a separate
claude -psubprocess reviews the work withtsc/eslint/ripgrepevidence. It runs on your existing Claude Code login, so what it costs is tokens, not a separate bill. - Classify, then guard — the tool output is sorted into three outcomes: nothing usable → suppressed; clean but with changes → a quiet one-line bubble; a real signal → a full critique. Before any critique is kept, an evidence guard checks that every cited snippet is a verbatim substring of the real tool output — anything it can't ground is dropped, never shown. Critiques are written to disk, never auto-injected into your chat — you pull them in yourself (see Daily use).
Pull, not push. Every critique lands on disk. Nothing enters your chat until you ask for it — so the review can never derail your agent mid-task.
What it reviews
Two layers do the work. First a deterministic rubric — 13 rules run over the diff, most via a tree-sitter AST, no LLM involved:
- Size & shape — god-file (>500 lines), god-function (>50 lines or high complexity), deep nesting (>3), long parameter lists (>4), and behavior-selecting boolean flag params.
- Smells — magic numbers, commented-out code, comments that just narrate
the next line, defensive over-reach (a
try/catchthat silently swallows), and sprawling abstractions (an interface with one impl and one caller). - Coverage & consistency — a test-gap check (source changed >30 lines with no matching test) plus repo-aware rules that flag code diverging from your own repo's conventions.
Alongside the rubric it runs four CLI tools — tsc, eslint,
ripgrep, and git-diff — and hands all of it to the second layer: the
Brain (claude -p), which writes the actual prose critique. The rubric
hits and tool output are its evidence; the Brain decides what's worth saying
and how, in your pet's voice.
Before the Brain writes anything, a quick intent classifier tags the change — bugfix, refactor, feature, chore, or exploration — mostly by regex over your message, commit, and the files you touched, with the Brain filling in the rest. That label sets the critique's framing, so an exploratory spike isn't held to the same bar as a bugfix.
Your personality dials shape that voice — the tone, and how readily it speaks up — but they never touch the hard gates or the rubric thresholds. A patient pet sounds calmer; it doesn't quietly raise the bar on what counts as a real problem.
Inspecting a critique
A critique is never a black box. Every inbox entry expands into an audit
card — which rubric rules fired, the Brain's reasoning, and the raw tool
evidence behind each claim — and gets a shareable permalink at /critique/:id
for anything you want to revisit or pass on.
Every action you take on one — forward, dismiss, ack, or a /siltpoke-feedback
note — is appended to a local preference log: the running signal trail
behind Siltpoke's learning, kept entirely on your machine.
And for the Brain's own runs, siltpoked emits a trace per Brain turn to a
local store, browsable as a waterfall at /traces. The daemon prunes it on a
schedule — 30-day retention, 500 MB cap — so it stays a debugging aid, not
a disk leak.
Cost
Brain rides your existing Claude Code login, so there's no separate LLM bill
— just tokens, kept cheap by prompt caching and bounded by a hard daily cap
(budget.dailyTokenLimit) that can never run away. For the actual per-call
and per-day figures, see Configuration → Cost.
The daemon
siltpoked is a long-lived Hono service listening on
127.0.0.1:9876. It's the Stop hook receiver and the process the
critic, dashboard, and chat all run in — one background service instead
of a fresh process per event. Manage it with /siltpoke-daemon (start,
start --detach, stop, status, install-autostart). It also
auto-spawns lazily from the Stop hook if it isn't already running, so
install-autostart is only needed if you want zero cold-start before the
first Stop event of a session.
Troubleshooting
/siltpoke-doctor
The install-health diagnostic. It prints a 9-row ✓/✗ checklist and
exits 0 if every check passes or 1 if any fails. Each failing row
already includes the path and the actionable fix, so read it back
verbatim rather than guessing. --json gives machine-readable output for
piping into another tool; --quiet prints a one-line summary instead of
the full table.
$ /siltpoke-doctor
siltpoke-doctor — checking install health
✓ ~/.claude/settings.json valid
✓ Stop hook registered (http + command pair)
✓ ~/.siltpoke/inner.txt readable
✓ ~/.siltpoke/wake.json healthy (absent OK)
✓ ~/.siltpoke/global.json schema v3 current
✓ slash command symlinks intact (35/35)
✓ ~/.siltpoke/config.json valid
✓ last Brain call: ok @ 2026-07-01T14:32
✓ daemon running latest code
All 9 checks passed. Install healthy.
A failing row looks like this, with the fix inline:
✗ slash command symlinks intact (34/35)
siltpoke-tag-entities.md (missing). Run `bun src/cli/install.ts` to re-link.
Common fixes
How to clear each failing doctor row — most echo the doctor's own message; a couple (schema, config) add a suggested next step:
| Problem | Fix |
|---|---|
settings.json missing |
re-run the installer (bun src/cli/install.ts) |
| Stop hook not paired | re-run the installer — it's idempotent, safe to run again |
global.json schema mismatch |
possibly a stalled migration — check docs/handoff/ for the most recent schema-related phase |
| Symlinks broken | the repo moved since install — re-run the installer from the new location |
config.json missing fields |
re-run bun src/cli/first-run.ts (the personality wizard) |
Common symptoms
- No face in the statusline — check that
~/.claude/settings.json'sstatusLinepoints at the Siltpoke wrapper, then restart Claude Code. - Brain never runs — run
/siltpoke-statsto see today's spend and trigger mode, thentail -f ~/.siltpoke/brain-calls.jsonland look forskippedreasons:quiet_hours,budget_hard_stop,no_change,wrong_event_mode,on_demand_no_bypass.
Want a review right now?
/siltpoke-wake is a one-shot bypass of the budget and quiet-hours
gates — use it when you want Brain to run on demand instead of waiting
for the next gated event.