Unified code intelligence daemon for AI agents. Pre-computes semantic indexes across six domains — code symbols, git history, database schemas, HTTP traffic, a cross-domain graph, and a global episodic memory — and exposes them as millisecond-latency local queries. One binary, one daemon, one MCP server. Replaces scry + tome + flume + lore.
Status: Unified binary shipped. TypeScript/JavaScript, Go, PHP/Laravel, Python indexing. Git intelligence (blame, history, cochange, hotspots, contributors). Database schema introspection (MySQL, PostgreSQL). HTTP reverse proxy capture. Unified cross-domain graph with community detection. 27 MCP tools across 6 domains. See
docs/SPEC.mdfor the original PRD anddocs/DECISIONS.mdfor architectural decisions.
Explicitly retain a correction or blocker with its run ID and evidence using
scry friction record event.json. Retrieve it with friction get <event-id>,
list a run with friction list --run-id <run-id>, and review recurring signatures
with friction review. Events survive without memory extraction; reviews return
cited proposals. See the journal contract and examples.
Quick setup for code intelligence (optional AI memory setup follows below):
curl -fsSL https://raw.githubusercontent.com/jeffdhooton/scry/main/scripts/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
scry setup # MCP server + skill + PreToolUse hook for Claude Code
scry install # provision and verify the npm SCIP indexers
cd ~/path/to/your/repo && scry init --all
scry doctor # green/yellow/red checklist; exit 1 if anything failedEverything below is the long version. If you're an agent doing this on someone's behalf, jump to Setting scry up as an AI agent.
The installed Scry daemon is a Go binary; it does not bundle an AI model. Code, git, schema and HTTP queries do not require an AI API key. Language indexing uses external tools, and turning transcripts into memories requires an accessible generative AI model. Optional Jev assessment uses a second model service. Install the dependencies for the domains you actually use.
| You want | You need | Notes |
|---|---|---|
| Go code indexing | the project’s Go toolchain and dependencies | scip-go auto-downloads to ~/.scry/bin/ (pinned, SHA256-verified) |
| PHP / Laravel indexing | php 8.1+ on PATH |
scip-php is embedded in the binary, extracted on first use |
| TypeScript / JS indexing | Node.js + npm on PATH, project dependencies installed |
scry install typescript installs and verifies the global npm package |
| Python indexing | python3 (3.10–3.13) + npm on PATH |
scry install python; Node >=16; see the Python gotcha below |
| Git intelligence | git on PATH |
already there if it's a repo |
| Schema introspection | a reachable MySQL/PostgreSQL DSN | read from --dsn or a .env file |
| Claude Code integration | the claude CLI on PATH |
scry setup shells out to claude mcp add |
| Memory extraction | a generative model with a Messages-API-compatible endpoint, model access/credit, and its API key | Optional; the store-owning daemon needs the key and outbound HTTPS. Default configured in code: DeepSeek deepseek-v4-flash. |
| Jev shadow assessment | a working extraction pipeline plus TypeSafe access to jev-1.13.0 and TYPESAFE_API_KEY |
Separately optional and off by default; Jev assesses proposed facts, it does not extract them. |
| Local memory storage and retrieval | writable ~/.scry/ and sufficient disk space |
Badger and local retrieval/embedding code are compiled in; no separate database server, vector service, GPU, or local model download is required. Existing memories remain queryable without provider keys. |
| Shared memory on another machine | a reachable Unix socket, usually through an SSH tunnel | Put model configuration and credentials on the machine owning the store. |
| Unattended transcript ingestion | readable supported transcript files and a sweep scheduler | Configure launchd/cron separately; installing the binary does not schedule sweeps. |
Python gotcha: scip-python 0.6.6's bundled Pyright only recognizes Python 3.10–3.13. If your default python3 is 3.14+ (common on bleeding-edge Homebrew), scry automatically shims scip-python to the first compatible interpreter on PATH (python3.13, python3.12, python3.11, then python3.10). If none exists, install one.
One-liner (darwin / linux, amd64 / arm64):
curl -fsSL https://raw.githubusercontent.com/jeffdhooton/scry/main/scripts/install.sh | shDrops the binary at ~/.local/bin/scry after verifying its SHA256 against the release manifest. Customize with INSTALL_DIR=/usr/local/bin or pin a version with SCRY_VERSION=v0.1.0. Make sure the install dir is on your PATH:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc # or ~/.bashrcFrom source (this checkout declares Go 1.26.2 in go.mod; use that toolchain or a compatible newer one):
go install github.com/jeffdhooton/scry/cmd/scry@latestStaying current: scry upgrade downloads the latest release and replaces the running binary in place; scry upgrade --check just prints what's available; scry upgrade --version v0.2.0 pins a specific tag. After upgrading, re-run scry setup so the registered MCP binary path and the installed skill match the new build. See docs/RELEASING.md if you're publishing a version.
scry setupIdempotent — safe to re-run after every upgrade. It writes exactly three things and cleans up a fourth:
| Artifact | Path | What it does |
|---|---|---|
| MCP server registration | ~/.claude.json (via claude mcp add --scope user --transport stdio scry -- <bin> mcp) |
Makes all 27 scry_* tools available in every Claude Code session |
| Routing skill | ~/.claude/skills/scry/SKILL.md |
Teaches Claude when to pick scry over Grep / git / DB clients |
| PreToolUse hook | ~/.claude/settings.json under hooks.PreToolUse |
Intercepts Grep/Glob and nudges toward scry_refs/scry_defs (see PreToolUse hooks) |
| Legacy cleanup | — | Removes stale tome, flume, lore MCP registrations and any old mcpServers.scry key wrongly left in settings.json |
Useful flags: --dry-run (print the plan, write nothing), --force (overwrite a customized SKILL.md), --scry-binary /abs/path (register a specific binary instead of os.Executable()).
settings.json is backed up before any change. Restart Claude Code afterwards so the new MCP server and skill are picked up.
Not using Claude Code? See Global agent config files for Codex, Cursor, Gemini CLI, and Claude Desktop — scry setup only handles Claude Code today, the rest is a two-line config paste.
The daemon auto-spawns on the first call; you never have to start it manually.
cd ~/path/to/your/repo
scry init --all # code + git + schema, auto-detecting what applies| Command | Indexes | Cost |
|---|---|---|
scry init |
code symbols only (TS/JS, Go, PHP, Python — auto-detected) | ~10s per 100k LOC |
scry init --git |
blame, commits, cochange, hotspots, contributors | seconds; --depth N bounds commit count (default 500, 0 = all) |
scry init --schema |
database tables, columns, FKs, enums | needs --dsn mysql://… / postgres://…, or --detect-env to read a .env |
scry init --all |
everything detected, including auto-DSN discovery | the usual choice |
After the first index, a per-repo fsnotify watcher keeps it fresh (300ms debounce, atomic swap — queries keep serving during a rebuild). Indexes live at ~/.scry/repos/<hash>/. Check what's indexed with scry status.
The memory domain is a global (not per-repo) episodic graph built from your past Claude Code, Codex, Kimi Code, and OpenCode transcripts and loom runs: entities (projects, machines, people, decisions) with time-stamped facts. It's what makes "set this up on hermes" resolvable in a fresh session.
It is opt-in and dormant by default. With no API key in the daemon's environment, extraction is dormant: writes still queue (scry memory queue shows them) and are extracted once a key is configured and the daemon restarted — nothing is billed until then. The sweep and scry memory ingest never talk to a model themselves; they distill on the client and hand episodes to the daemon that owns the store, so only that daemon needs a key.
To turn it on, export a key where the daemon runs:
export SCRY_MEMORY_API_KEY='YOUR_DEEPSEEK_API_KEY' # placeholder; see secure persistent setup below| Env var | Default | Meaning |
|---|---|---|
SCRY_MEMORY_API_KEY |
— | Extraction API key. Falls back to DEEPSEEK_API_KEY. |
SCRY_MEMORY_MODEL |
deepseek-v4-flash |
Model id. Required if you set a custom base URL. |
SCRY_MEMORY_BASE_URL |
https://api.deepseek.com/anthropic |
Any Messages-API-compatible endpoint. |
SCRY_MEMORY_UI_ADDR |
127.0.0.1:7279 |
Live memory UI address; off disables it. Loopback is forced. |
SCRY_MEMORY_SOCKET |
local daemon | Unix socket for a shared memory daemon. Memory CLI verbs and the memory MCP profile use it; other domains stay local. |
TYPESAFE_API_KEY |
— | Separate TypeSafe credential, used only by Jev assessment; never substitutes for the extraction key. |
Model chain (~/.scry/config.yaml). A cheap model sometimes returns nothing usable — an empty reply, or an entity type it invented — and with a single model that episode is dead-lettered on the spot. memory.models is an ordered fallback chain: the first entry handles every episode, each later one only runs when the previous failed. When present it replaces SCRY_MEMORY_MODEL / SCRY_MEMORY_BASE_URL entirely (the daemon logs that it's ignoring them). Keys stay in the environment: api_key_env names the variable, defaulting to SCRY_MEMORY_API_KEY / DEEPSEEK_API_KEY.
# ~/.scry/config.yaml
memory:
models:
- model: deepseek-v4-flash # primary — cheap
- model: deepseek-v4-pro # fallback when flash returns nothing usable
# - model: claude-haiku-4-5 # cross-provider fallback: explicit opt-in only
# base_url: https://api.anthropic.com
# api_key_env: ANTHROPIC_API_KEYscry memory status lists the live chain under models; each fallback is logged in ~/.scry/scryd.log as extraction on <a> failed (…) — falling back to <b>. Restart the daemon after editing the file. A malformed file makes extraction dormant (loudly, in the log) rather than crashing the daemon.
What Scry looks for. With no memory.models entries, it reads SCRY_MEMORY_API_KEY, then DEEPSEEK_API_KEY if the first is empty. It uses SCRY_MEMORY_MODEL and SCRY_MEMORY_BASE_URL, defaulting to deepseek-v4-flash at https://api.deepseek.com/anthropic. An explicit base URL requires an explicit model ID; the endpoint must support the Messages API, not merely OpenAI-style chat completions.
With a configured chain, every entry requires model; an omitted base_url still means DeepSeek. An explicit api_key_env reads only that named variable, without falling back to another provider’s key. Without api_key_env, the usual SCRY_MEMORY_API_KEY → DEEPSEEK_API_KEY lookup applies. A missing primary key makes extraction dormant; a missing fallback key or invalid chain also prevents the worker from starting. All configured models need usable accounts and access, not just nonempty key strings.
ANTHROPIC_API_KEY alone never activates extraction. To use Anthropic, explicitly configure its endpoint and model, with api_key_env: ANTHROPIC_API_KEY, or supply the Anthropic key as SCRY_MEMORY_API_KEY together with the environment model/base URL overrides. Jev reads only TYPESAFE_API_KEY. It calls https://api.typesafe.ai/v1/systemone; its model pin is independent of the extraction chain. The TypeSafe HTTP reference documents Bearer authentication.
Scry reads process environment variables at startup. It does not automatically load the repository’s .env, ~/.scry/.env, shell profiles, a credentials file, or a password manager. Reading .env for schema DSN discovery is a different feature. Never place key values in ~/.scry/config.yaml; api_key_env is a variable name, not a secret.
For a new installation, one convenient convention is a private environment file on the store machine:
mkdir -p "$HOME/.config/scry"
chmod 700 "$HOME/.config/scry"
touch "$HOME/.config/scry/credentials.env"
chmod 600 "$HOME/.config/scry/credentials.env"
# Edit this file privately; do not paste real keys into a command history or chat.
${EDITOR:-vi} "$HOME/.config/scry/credentials.env"Its contents can be:
SCRY_MEMORY_API_KEY='YOUR_DEEPSEEK_API_KEY'
# Include only if enabling Jev assessment:
TYPESAFE_API_KEY='YOUR_TYPESAFE_API_KEY'
# Add any variables explicitly named by memory.models[].api_key_env.Replace placeholders with real credentials. That file path is a launcher convention, not an auto-discovered Scry path. Start the daemon with the file explicitly loaded:
set -a
. "$HOME/.config/scry/credentials.env"
set +a
scry start --foregroundUse this in a terminal for a foreground daemon, or have your supervisor execute the equivalent. If a daemon already runs, update its launcher and restart that service; a second scry start does not update its environment.
- macOS launchd: use a wrapper or
ProgramArgumentsthat runs/bin/sh -c 'set -a; . /Users/YOU/.config/scry/credentials.env; set +a; exec /Users/YOU/.local/bin/scry start --foreground'. Use real absolute paths. Exporting a key in an unrelated terminal does not change launchd’s daemon environment. Reload the job if its plist changed, then restart it. - Linux systemd: set
EnvironmentFile=/home/YOU/.config/scry/credentials.envandExecStart=/home/YOU/.local/bin/scry start --foregroundin the service; reload the unit after edits and restart it. Use simpleNAME=valueentries, not shell scripts, withEnvironmentFile. - Existing installations: preserve their credential-loading convention instead of creating a competing one. This workspace’s Mini launcher explicitly sources
/Users/jclaw/.hermes/.env; extraction keys andTYPESAFE_API_KEYbelong there. The laptop’s shell environment does not configure the Mini. See memory operations for service labels, binary paths and rollback.
Client-side exception: memory backfill loads the calling machine’s model config and environment and makes paid extraction calls there. It needs its own matching keys even when storing results remotely. Batching is supported only for Anthropic; other endpoints use serial extraction. Ordinary queued sweep/ingest work uses the store daemon’s extractor.
After any model, config or credential change, restart the store-owning daemon and verify its status. A boolean key_available: true confirms Jev saw a nonempty key; only a successful request confirms provider acceptance. Do not print credentials to diagnose either case.
scry memory sweep --dry-run # what would be queued, without touching the daemon
scry memory sweep # scan transcript roots, queue new episodes at the daemon
scry memory backfill --since 2026-01-01 # one-time historical pass (extracts client-side)
scry memory status # counts, chain, queue depth, last ingest/sweep/extraction
scry memory queue # what is waiting for extraction, and why
scry memory recall "why did we switch off deepseek" # ranked facts, under 24 KB
scry memory browse # searchable HTML graph, opened in your browser
scry memory backup # Badger backup into ~/.scry/backups on the daemon's machine
scry memory migrate # dry-run the resolver rules over the whole store; --apply after a backup
scry memory bench --file docs/memory-bench/probes.json # is the answering fact in the top N?
scry memory repair-repos # re-attach repository refs from the transcripts (no model calls)Jev is an additional assessor after extraction. Each candidate fact becomes a separate background job; one request asks whether the fact is supported, durable, and established/planned/hypothetical/denied/unclear. Source text and eligible history go to TypeSafe after redaction. Results are stored separately in ~/.scry/memory-assess/ and never decide memory admission, deletion or recall ranking.
Once extraction works and sending this evidence is authorized, merge this block into the store daemon’s existing memory configuration (do not replace models or create a second memory: key):
memory:
assessment:
mode: shadow # absent configuration defaults to off
model: jev-1.13.0
target_input_tokens: 20000
concurrency: 1
timeout: 15sSupply TYPESAFE_API_KEY to that daemon and restart it. The 20k target is approximate and may underfill. For a small trial, also configure all three trial fields: starts_at and expires_at as explicit RFC3339 timestamps, plus max_requests (for example 100). Choose new dates; do not reuse the historical September 20 trial window. The cap counts lifetime dispatch reservations in this sidecar, including failed/uncertain attempts; restart and retention do not reset it. A trial excludes old or undated episodes and earlier context. See the complete bounded-trial configuration.
scry memory status # extraction models, dormancy, worker and queue
scry memory assess status --json # effective config, key boolean, block, counts, errors
scry memory assess list --limit 20 --json
scry memory assess show ASSESSMENT_ID --jsonmissing_credentials requires fixing the daemon environment and restarting; then explicitly resume if still blocked. Provider 401/403/429 suspends dispatch; fix access/cooldown before scry memory assess resume. There are no automatic Jev retries. trial_expired and trial_request_limit are intentional stops that resume cannot bypass. Setting assessment mode off and restarting stops calls while preserving records. Inspecting --include-context explicitly exposes locally retained redacted evidence.
For an isolated experiment, scry memory assess --file docs/memory-assess/synthetic.json previews locally; --live sends the selected fixture to TypeSafe. This file mode does not contact the daemon. The evaluation guide also covers the real builder/worker synthetic harness. A fixture run uses the calling shell’s key; it does not prove that a background daemon has its key.
Recall ranks facts (BM25 over fact text, entity names and episode summaries, boosted toward entities the query names) and returns twenty by default with a hard 24 KB cap, so an MCP host never truncates it. scry memory orient shows what happened in the current repository first, ranked by whether a fact came from a session that ran there. scry doctor has a Memory section that fails when the chain is dormant, the queue worker is down, or nothing has been queued for six hours.
While the daemon runs it also serves a live, always-fresh version of that UI at http://127.0.0.1:7279.
Scheduling the sweep — the sweep is the source of truth (hooks are only a latency optimization), so run it on a timer. There's no installer for this yet; on macOS drop a launchd plist at ~/Library/LaunchAgents/com.user.scry-memory-sweep.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.user.scry-memory-sweep</string>
<key>ProgramArguments</key>
<array>
<string>/Users/YOU/.local/bin/scry</string>
<string>memory</string>
<string>sweep</string>
</array>
<key>StartInterval</key><integer>1800</integer>
<key>StandardErrorPath</key><string>/Users/YOU/.scry/logs/memory-sweep.log</string>
</dict>
</plist>launchctl load ~/Library/LaunchAgents/com.user.scry-memory-sweep.plistThe sweep needs no API key: extraction happens in the daemon. On Linux, a crontab line does the same job: */30 * * * * /home/you/.local/bin/scry memory sweep >/dev/null 2>&1. See docs/MEMORY_SPEC.md for the original design and docs/MEMORY_OPS.md for how it is deployed across two machines.
Shared store on another machine. Set memory.socket in ~/.scry/config.yaml (or SCRY_MEMORY_SOCKET) to the Unix socket of the daemon that owns the store — an SSH forward works — and every memory client on this machine, including scry doctor, uses it.
scry doctor # human checklist
scry doctor --json # machine-readable report
scry doctor --fix # auto-remediate the fixable checksscry doctor is read-only and checks: binary location, ~/.scry writability, NOFILE rlimit, daemon state (running / stale / clean), every indexer (php, scip-typescript, scip-go, embedded scip-php, python3, scip-python), Claude Code integration (claude CLI, MCP registration, skill, PreToolUse hook, global CLAUDE.md rule), stale tome/flume/lore servers, and the current repo's index state. Every failing check prints its own remediation line.
Exit code is 1 if any check FAILED, 0 otherwise. Warnings are advisory and don't affect the exit code — "scip-typescript not installed" only matters if you index TypeScript.
--fix creates ~/.scry, clears stale daemon files, refreshes missing Claude integration, and provisions missing npm SCIP indexers. It verifies each installed binary and restarts a daemon whose environment predates the install. This may use the network and take several minutes. Installing PHP and running scry init are still reported, never executed.
Then confirm end to end:
claude mcp get scry # expect: Status: ✓ Connected
scry status # expect: your repo, with per-domain index state
scry refs SomeSymbolYouKnow # expect: occurrences with file:line:col| Symptom | Cause / fix |
|---|---|
scry: command not found |
Install dir isn't on PATH. export PATH="$HOME/.local/bin:$PATH". |
claude mcp get scry says "Failed to connect" |
Registered binary path is stale (moved or upgraded). Re-run scry setup. |
| Claude never calls scry tools | Skill and hook missing or Claude Code wasn't restarted. scry doctor, then restart. Add the global config block. |
| Empty results for a symbol you can see | Vendor/external symbol, name collision, or repo not indexed. Try the qualified form (Class::method), check scry status, fall back to Grep. |
"not indexed yet" RPC error |
The watcher is mid-reindex. Retry once — the swap takes ~12ms. |
| TypeScript repo indexes zero symbols | Run scry install typescript, then scry daemon restart if the diagnostic says the daemon predates the install. |
| Memory commands print "dormant" | Check the store daemon’s model configuration and key lookup above. A key in your interactive shell does not prove the daemon has it; restart the correct service after fixing its environment. |
| Jev is off, blocked or produces no jobs | Check scry memory assess status --json, then extraction health. Jev needs extracted candidates, its own TYPESAFE_API_KEY, valid shadow config, and an unexhausted trial window/cap. |
| Stale daemon after a crash | scry stop then any command re-spawns it, or scry doctor --fix. |
| Everything is weird | scry stop && rm -rf ~/.scry/repos && scry init --all. Indexes are disposable. |
If you previously used tome, flume, and lore as separate binaries, scry setup automatically removes their MCP registrations. You can also do it manually:
claude mcp remove tome
claude mcp remove flume
claude mcp remove lore
scry setup # re-registers the unified scry MCP serverThe old binaries can be deleted — all functionality is now in scry.
If you are an AI coding agent installing scry on a user's machine, this section is the whole contract. It is non-interactive, idempotent, and verifiable.
First identify the requested domains and which machine owns memory. The sequence below installs code intelligence only. It does not provision AI accounts, configure extraction/Jev keys, install a persistent daemon service, create a shared-memory tunnel, or schedule sweeps. Complete the relevant memory setup and credential loading steps when those features are requested.
Run this, in order:
# 1. Install (skip if `scry version` already works)
curl -fsSL https://raw.githubusercontent.com/jeffdhooton/scry/main/scripts/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
# 2. Register with Claude Code (idempotent; also removes legacy tome/flume/lore)
scry setup
# 3. Provision npm indexers (explicit global install; requires npm on PATH)
scry install
# 4. Index the repo you're working in
cd "$(git rev-parse --show-toplevel)" && scry init --all
# 5. Verify — exits 1 if any check FAILED
scry doctor --jsonThen confirm the tools actually answer, rather than assuming step 4 was enough:
scry status --pretty # repo listed? which domains?
scry defs <a symbol you saw in the code> # non-empty result set?Rules for doing this well:
- Parse
scry doctor --json, don't eyeball the pretty output. Each check carries anid,status(pass/warn/fail), and aremedystring. Act onfail; reportwarnto the user only when it's relevant to their stack (a missingscip-pythonis noise in a Go repo). - Try
scry doctor --fixbefore asking the user for anything. It resolves missing~/.scry, stale daemons, missing MCP registration/skill, and missing npm SCIP indexers. It may use the network and take several minutes. Re-runscry doctor --jsonafterward and only surface what's still failing. - Never install language indexers silently.
scry installand a user-requestedscry doctor --fixexplicitly authorize the global npm installs. Outside those commands, propose the exact command instead of running it unasked.scip-goandscip-phpuse the existing auto-download / embedded paths. - Honor the requested scope. Memory extraction and Jev involve paid provider calls and source uploads. If the user has requested or already authorized them, complete their model/key/service setup and verification; do not repeatedly request the same approval. A code-indexing request alone does not authorize memory ingestion, historical backfill, or Jev. Preserve existing trial limits.
- Tell the user to restart Claude Code. MCP servers and skills are loaded at session start; nothing you did in step 2 is visible in the current session.
- If the host isn't Claude Code,
scry setuphas nothing to register — do the per-host MCP config instead (Codex, Cursor, Gemini CLI, Claude Desktop). Always write an absolute binary path;~isn't expanded in those config files. - Don't hand-edit
~/.claude.json.scry setupshells out toclaude mcp add, which is the supported path. Editingsettings.jsonfor MCP config is a known dead end — Claude Code doesn't read MCP servers from there. scry init --allis minutes, not seconds, on a large repo. Run it in the background or warn the user before blocking on it.- Indexes are disposable. If anything is inconsistent,
rm -rf ~/.scry/reposand re-init. There is no user data in there.
What "done" looks like: the requested domain works end to end. For code intelligence, confirm the host MCP connection, indexed repo and a real symbol query; interpret intentionally dormant memory diagnostics separately. For memory, verify dormant: false, the intended models, worker_running: true, a working sweep/tunnel, and a successfully extracted authorized episode. For Jev, verify configuration.mode: shadow, key_available: true, the intended limits, and an inspectable completed assessment or a diagnosed typed failure. Report an exhausted/expired trial as stopped, not as a missing installation. scry setup alone proves none of the AI setup.
Finally, add the routing block from Global agent config files to the user's global agent config file — without it, the tools exist but the agent won't reach for them.
# Index a repo. The daemon auto-spawns on first call.
cd ~/path/to/your/repo
scry init # code symbols (TS, Go, PHP, Python)
scry init --git # git history (blame, cochange, hotspots)
scry init --all # everything: code + git + schema (auto-detects DSN)
# Code intelligence
scry refs processOrder # every reference
scry defs processOrder # every definition
scry callers processOrder # call sites with containing function
scry callees processOrder # outgoing calls
scry impls Repository # implementors of an interface
scry tests processOrder # test coverage status
# Git intelligence
scry blame src/handler.go # structured blame
scry history src/handler.go # recent commits
scry cochange src/handler.go # co-changed files
scry hotspots # most churned files
scry contributors # main authors
scry intent src/handler.go --line 42 # why was this line written?
# Schema (requires --schema or --all during init)
scry describe users # table structure
scry relations orders # foreign keys
scry schema-search email # find tables/columns
scry enums # enum types and values
# HTTP capture
scry proxy start --port 8089 --target localhost:8000
# Point your app at localhost:8089 instead of :8000
scry requests --path /api # list captured traffic
scry request <id> # full request/response
scry proxy stop
# Cross-domain graph
scry graph build # build from all indexed domains
scry graph report # architectural summary: god nodes, communities
scry graph query UserService # find nodes by name
scry graph path --from UserService --to "users table" # shortest path
# Global memory (opt-in — see setup step 5)
scry memory recall hermes # what do I know about this entity?
scry memory remember "book-system deploys to hermes-mini"
scry memory orient # orientation blurb for the current directory
scry memory browse # searchable HTML graph
scry memory status # counts, cursors, dormancy
# Infrastructure
scry status # what repos and domains are indexed?
scry start # explicit start (auto-spawned otherwise)
scry stop # graceful shutdown
scry setup # install skill + MCP server
scry doctor # health checkOutput is JSON by default — this tool's primary user is an AI agent. Pass --pretty for human reading.
| Feature | Status |
|---|---|
| Code languages | TypeScript, JavaScript, Go, PHP (Laravel-aware), Python |
| Git intelligence | Blame, history, cochange, hotspots, contributors, intent |
| Schema | MySQL and PostgreSQL introspection (tables, columns, FKs, enums) |
| HTTP capture | Reverse proxy with request/response recording (30-min TTL) |
| Unified graph | Cross-domain nodes and edges, Louvain community detection, BFS path finding |
| Memory | Global episodic graph from past sessions: entities, time-stamped facts, provenance (opt-in) |
| Daemon | Auto-spawned, Unix socket at ~/.scry/scryd.sock |
| JSON-RPC 2.0 | Newline-delimited over Unix socket; methods across 6 domains |
| MCP server | 27 tools: 7 code + 6 git + 4 schema + 3 HTTP + 3 graph + 4 memory |
| Watch loop | fsnotify per indexed repo, 300ms debounce, atomic reindex swap |
| Index store | BadgerDB per domain per repo at ~/.scry/repos/<hash>/ |
| Auto-download | scip-go (pinned, SHA256-verified). scip-php embedded in binary. |
| Call graph | Built at index time from SCIP enclosing_range. Full on TS, partial on Go. |
| Implementations | Built at index time from SCIP Relationships.is_implementation |
| Laravel support | Non-PSR-4 walker, facade resolver (31 facades), view + config string-refs |
| Test coverage | Auto-detects cover.out, Istanbul JSON, Clover XML, Python coverage.json |
| Claude Code integration | Skill routing + 27 MCP tools. scry setup handles everything. |
Real-world numbers (measured against ~/herd/advocates, 400 TS files / 55k LOC):
| Metric | Target | Actual |
|---|---|---|
| Daemon cold spawn | <500ms | ~17ms |
scry refs <symbol> end-to-end (warm) |
<10ms p50 | 6-7ms |
| Cold index build, 100k-LOC TS repo | <60s | 9.9s |
| Query unavailability during reindex | (was ~3-15s) | 12ms swap |
All tools use the scry_ prefix. Registered as a single MCP server via scry setup.
| Domain | Tools |
|---|---|
| Code | scry_refs, scry_defs, scry_callers, scry_callees, scry_impls, scry_tests, scry_status |
| Git | scry_blame, scry_history, scry_cochange, scry_hotspots, scry_contributors, scry_intent |
| Schema | scry_describe, scry_relations, scry_schema_search, scry_enums |
| HTTP | scry_requests, scry_request, scry_http_status |
| Graph | scry_graph_query, scry_graph_path, scry_graph_report |
| Memory | scry_recall, scry_remember, scry_episodes, scry_memory_path |
scry mcp --profile memory advertises and accepts only the four memory tools.
This is intended for a shared memory authority launched over SSH from another
machine. scry mcp --profile local exposes every domain except memory, keeping
code, git, schema, HTTP, graph, and room queries on the caller's machine.
Point SCRY_MEMORY_SOCKET at an SSH StreamLocalForward of the authority's
~/.scry/scryd.sock to route memory CLI commands and the memory MCP profile
there without making the daemon listen on the network.
The memory tools only return data once the memory domain is enabled; until then they answer from an empty graph.
scry works with Claude Desktop (the chat app) as a standard MCP server. Add it to your Claude Desktop config:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"scry": {
"command": "/path/to/scry",
"args": ["mcp"]
}
}
}Replace /path/to/scry with the actual binary path — typically ~/.local/bin/scry (install script) or the output of which scry (Go install). On macOS you must use the full expanded path, not ~.
After saving, restart Claude Desktop. You'll see the 27 scry_* tools available in the toolbox icon. Before using them, index your repo from a terminal:
cd ~/path/to/your/repo
scry init --allWhat you get: All 27 MCP tools across the six domains — code intelligence, git history, schema introspection, HTTP traffic, graph queries, and global memory. Claude Desktop doesn't support PreToolUse hooks or skills, so you'll need to explicitly ask Claude to use scry tools (e.g. "use scry_refs to find where processOrder is called"). Once Claude sees the tool results, it quickly learns to prefer them.
Limitations vs Claude Code: No PreToolUse hooks (Claude Desktop doesn't support hooks), no routing skill, no automatic nudging, and no ~/.claude/CLAUDE.md auto-loading. The tools themselves work identically.
Pro tip: To get Claude Desktop to prefer scry tools without being asked each time, paste the routing table from the Global agent config files section into your first message or use it as a project prompt. This teaches Claude when to reach for scry_graph_report vs scry_refs vs Grep — the same routing that Claude Code gets automatically from ~/.claude/CLAUDE.md.
scry integrates with Claude Code (CLI, desktop app, VS Code, and JetBrains extensions) at four levels: MCP tools, a routing skill, PreToolUse hooks, and your global CLAUDE.md. scry setup handles the first three; the routing block in CLAUDE.md is a paste. The hooks and the routing block are what make Claude prefer scry over raw Grep/git without you having to ask.
scry setup- Registers the MCP server — runs
claude mcp add --scope user --transport stdio scry -- <binary> mcp, making all 27scry_*tools available in every Claude Code session. - Installs the routing skill — writes
~/.claude/skills/scry/SKILL.md, a detailed routing table that teaches Claude when to reach for scry vs Grep vs Read. Covers all six domains with example queries. - Installs the
pre-searchPreToolUse hook — into~/.claude/settings.json, backing the file up first and replacing any stale scry hook entry pointing at an old binary path. - Cleans up legacy tools — removes old
tome,flume,loreMCP registrations if present.
Verify with:
claude mcp get scry # should show Status: ✓ Connected
scry doctor # full health checkThe MCP tools and skill give Claude the ability to use scry, but Claude will still sometimes reach for Grep or git log out of habit. PreToolUse hooks intercept those calls and nudge Claude toward scry equivalents — or tell you when a repo isn't indexed yet.
scry setup installs the pre-search hook (Grep|Glob) for you. The pre-git hook (Bash) is opt-in — add it by hand. Both live in ~/.claude/settings.json under the hooks.PreToolUse array:
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"command": "/path/to/scry hook pre-search",
"statusMessage": "Checking scry index...",
"type": "command"
}
],
"matcher": "Grep|Glob"
},
{
"hooks": [
{
"command": "/path/to/scry hook pre-git",
"statusMessage": "Checking scry index...",
"type": "command"
}
],
"matcher": "Bash"
}
]
}
}Replace /path/to/scry with the actual binary path (e.g. $HOME/.local/bin/scry or the output of which scry).
What each hook does:
| Hook | Fires on | Behavior |
|---|---|---|
pre-search |
Every Grep or Glob call |
If the pattern looks like a symbol name (not a regex/glob) and the repo is indexed, nudges Claude to use scry_refs/scry_defs instead. If a graph is available, also mentions scry_graph_report, scry_graph_query, and scry_graph_path. |
pre-git |
Every Bash call |
If the command is git blame, git log, git shortlog, or git diff --stat and git history is indexed, nudges Claude toward scry_blame, scry_history, scry_contributors, scry_hotspots, or scry_cochange. |
Unindexed repo behavior: Both hooks detect when the current repo has no scry index and return a message suggesting scry init --all. Claude sees this in its context and will relay the suggestion to you. No silent failures.
MCP call logging writes to ~/.scry/logs/mcp-calls.jsonl. Every scry MCP tool invocation is recorded with timestamp, tool name, repo, result count, and latency:
# See what tools Claude is actually calling
cat ~/.scry/logs/mcp-calls.jsonl | jq .
# Count tool usage by name
cat ~/.scry/logs/mcp-calls.jsonl | jq -r .tool | sort | uniq -c | sort -rn
# Check if graph tools are being used
grep graph ~/.scry/logs/mcp-calls.jsonl | jq .If you see zero graph entries after working in an indexed repo, Claude may not be reaching for the graph tools. The pre-search hook's graph nudge should help, but you can also explicitly ask Claude to "show me the graph report" or "what connects X to Y" to prime the behavior.
The hooks intercept Grep and git calls, but Claude also needs to know when to reach for scry proactively — especially for graph, memory, and architecture questions where it would otherwise just read files. Paste the routing block from Global agent config files into ~/.claude/CLAUDE.md, or let scry doctor --fix write the file for you if you don't have one yet (it never edits an existing CLAUDE.md).
That routing table is what makes Claude reach for scry_graph_report when you ask "what's the architecture?" instead of ls-ing directories.
scry setup # MCP server + skill + PreToolUse hook
scry doctor # verify prereqs
scry init --all # index current repo
# Confirm hooks landed in ~/.claude/settings.json (see above)
# Add the routing block to ~/.claude/CLAUDE.md (see below)
# Restart Claude Code
# Verify: work in Claude Code, check ~/.scry/logs/mcp-calls.jsonlMCP registration gives an agent the ability to call scry. The routing block below is what makes it actually reach for scry instead of Grep, git log, or a DB client. Install both.
Paste this into whichever global instruction file your agent reads (paths in the next table). It's the same content scry setup installs as a Claude Code skill, condensed to fit a global config file.
## scry — use FIRST for code intelligence
scry is a local code intelligence daemon with pre-computed indexes. It answers in <10ms what
Grep/git/file reading takes 30+ seconds to assemble. Always check scry before reaching for
Grep, git commands, or reading files to understand code structure.
### When to use which scry tool
**Starting work in a repo or answering "what is this codebase?":**
→ scry_graph_report — shows architecture: god nodes (highest coupling), communities (feature
clusters), cross-domain connections. Start here for any orientation or onboarding question.
Do NOT read docs or ls directories first.
**"Where is X used/called/defined?":**
→ scry_refs or scry_defs — every reference or definition with file:line:col. Use instead of
Grep for any symbol/identifier lookup. Accepts Mail::to, auth->user, client.Connect forms.
**"What calls X?" / "What does X call?":**
→ scry_callers / scry_callees
**"What implements this interface?" / "Is this function tested?":**
→ scry_impls / scry_tests
**"Who wrote this?" / "What changed recently?" / "Why was this written?":**
→ scry_blame, scry_history, scry_intent — use instead of git blame/git log.
**"What files change together?" / "What are the hotspots?" / "Who maintains this?":**
→ scry_cochange, scry_hotspots, scry_contributors
**"How does X connect to Y?" / "What are the main modules?":**
→ scry_graph_path, scry_graph_query — across code, git, schema, and HTTP domains.
**"What tables/columns/FKs exist?":**
→ scry_describe, scry_relations, scry_schema_search, scry_enums — use instead of DB clients.
**"What HTTP requests happened?":**
→ scry_requests, scry_request
**Unknown referent (a project, service, machine, person, or decision not defined in this
conversation or repo):**
→ scry_recall FIRST — global memory of past sessions across all projects ("set this up on
hermes" → recall "hermes"). scry_episodes traces when it was discussed; scry_memory_path
shows how two things relate.
**Durable facts and decisions (deploys, choices made, lasting preferences):**
→ scry_remember — store it, don't just say it.
### When to fall back to Grep/Read
- String searches in comments, docstrings, error messages
- TODO/FIXME hunting
- Regex pattern matching over file content
- The repo is not indexed (scry_status to check)
- scry returned empty results for a known symbolDrop the memory lines if you didn't enable the memory domain, and the schema/HTTP lines if you never index those.
| Host | Global instruction file | Project-level file |
|---|---|---|
| Claude Code | ~/.claude/CLAUDE.md |
./CLAUDE.md |
| Codex CLI | ~/.codex/AGENTS.md |
./AGENTS.md |
| Cursor | Settings → Rules → User Rules | .cursor/rules/scry.mdc |
| Gemini CLI | ~/.gemini/GEMINI.md |
./GEMINI.md |
| Claude Desktop | no global file — paste into project instructions or your first message | — |
| Windsurf / others | check the host's docs; most now read AGENTS.md |
./AGENTS.md |
For a repo-scoped variant, put a shorter version in the project file naming the specific symbols and tables that matter in that codebase.
scry setup only registers with Claude Code today. scry mcp is a standard MCP stdio server, so every other host is a two-line config entry. Use an absolute path — ~ is not expanded in most of these files. Get it with which scry.
Codex CLI — either codex mcp add scry -- /abs/path/to/scry mcp, or in ~/.codex/config.toml:
[mcp_servers.scry]
command = "/abs/path/to/scry"
args = ["mcp"]Cursor — ~/.cursor/mcp.json (global) or .cursor/mcp.json (project):
{
"mcpServers": {
"scry": { "command": "/abs/path/to/scry", "args": ["mcp"] }
}
}Gemini CLI — ~/.gemini/settings.json, same mcpServers shape as Cursor.
Claude Desktop — see Claude Desktop setup above.
Anything else — the generic form is: command /abs/path/to/scry, args ["mcp"], transport stdio, no env vars required. The daemon auto-spawns on the first tool call.
Config paths are what the vendors document as of this writing; if a host has moved its config, its own docs win. Whatever the host, the check is the same: the tool list should show 27 scry_* tools, and a call should return results within milliseconds.
- npm is required to provision
scip-typescriptandscip-python. Runscry install; when npm is absent, scry prints the prerequisite and exact manual command instead of silently skipping the indexer. - Vue Single File Components are not indexed. scip-typescript only walks
.ts/.tsxfiles. - Symbol kind always reports
UnspecifiedKind. scip-typescript v0.4.0 doesn't populateSymbolInformation.Kind. <200msincremental update is unreachable. SCIP indexers are project-wide. Realistic: ~600ms small, ~10s large.scip-goenclosing_rangeis partial. Call graph coverage on Go is best-effort.- Graph
queriesedge (function -> table) is not yet implemented. Currently the graph connects code, git, schema, and HTTP domains via structural edges (calls, implements, changed_with, fk). - Schema requires explicit init.
scry init --schemaorscry init --allwith a DSN or.envfile. - Incidental languages are skipped, not indexed. A language is indexed only if it has a root-level marker file (
composer.json,go.mod,package.json,pyproject.toml, …) or holds ≥10% of source files. A Laravel app with a handful of stray.pyscripts will not runscip-python, and will not be reported as degraded for it.scry doctorlists skipped languages per repo. scry statusreports per-indexer outcomes. Each repo entry carries anindexersarray recordingok/missing/failed/skippedper language, with the remedy for a missing indexer.- HTTP proxy is explicit.
scry proxy startmust be run manually; the daemon doesn't auto-start the proxy.
~/.scry/
scryd.sock # one socket, one daemon
scryd.pid
bin/ # auto-downloaded indexers (scip-go, extracted scip-php)
logs/mcp-calls.jsonl # every MCP tool invocation
memory/ # global episodic memory graph (not per-repo)
repos/<hash>/
code/index.db # SCIP symbols, refs, call graph
git/index.db # blame, commits, cochange, hotspots
schema/index.db # database tables, FKs, enums
http/ # captured request/response pairs
graph/index.db # unified cross-domain graph
manifest.json # per-repo metadata across all domains
┌────────────────────────────────────────────────────────────────┐
│ scry CLI │
│ refs | defs | blame | describe | requests | graph query ... │
└───────────────────────────┬────────────────────────────────────┘
│ JSON-RPC 2.0 / Unix socket
▼
┌────────────────────────────────────────────────────────────────┐
│ scry daemon │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ JSON-RPC dispatcher (rpc.Server) │ │
│ │ code.* git.* schema.* http.* graph.* ping │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Code │ │ Git │ │ Schema │ │ HTTP │ │
│ │ Registry │ │ Registry │ │ Registry │ │ Proxy │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ │
│ │ Graph │ │
│ │ Registry │ │
│ └──────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Index Builders: scip-ts, scip-go, scip-php, scip-py │ │
│ │ Git indexer, Schema introspector, Graph builder │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
scry/
├── cmd/scry/ # cobra CLI; one binary
│ ├── main.go # root command, subcommand wiring
│ ├── init.go # scry init (code, --git, --schema, --all)
│ ├── refs.go # refs / defs
│ ├── graph.go # callers / callees / impls
│ ├── tests.go # test coverage query
│ ├── blame.go # git blame/history/cochange/hotspots/contributors/intent
│ ├── schema.go # describe/relations/schema-search/enums
│ ├── proxy.go # proxy start/stop, requests, request
│ ├── graphcmd.go # graph build/query/path/report
│ ├── status.go # daemon status
│ └── ... # start, stop, setup, doctor, upgrade, mcp
├── internal/
│ ├── rpc/ # JSON-RPC 2.0 server + client
│ ├── daemon/ # daemon lifecycle, registries, methods
│ │ ├── daemon.go # Run, signals, PID file, socket
│ │ ├── registry.go # code store registry
│ │ ├── git_registry.go # git store registry
│ │ ├── schema_registry.go # schema store registry
│ │ ├── graph_methods.go # graph registry + RPC handlers
│ │ ├── methods.go # code RPC handlers
│ │ ├── git_methods.go # git RPC handlers
│ │ ├── schema_methods.go # schema RPC handlers
│ │ ├── http_methods.go # HTTP proxy RPC handlers
│ │ └── watch.go # fsnotify watcher
│ ├── store/ # code BadgerDB store
│ ├── git/ # git indexer + store
│ ├── schema/ # schema introspector + store
│ ├── http/ # HTTP proxy + request store
│ ├── graph/ # graph builder + query + store
│ ├── mcp/ # MCP stdio server (27 tools)
│ ├── sources/ # language indexers
│ │ ├── scip/ # SCIP protobuf parser
│ │ ├── typescript/ # scip-typescript
│ │ ├── golang/ # scip-go
│ │ ├── php/ # embedded scip-php + Laravel post-processors
│ │ ├── python/ # scip-python
│ │ └── coverage/ # coverage file parsers
│ ├── index/ # code build pipeline
│ ├── query/ # code query engine
│ └── install/ # indexer auto-download
└── docs/
├── SPEC.md # original PRD
├── DECISIONS.md # architectural decisions
├── UNIFICATION_SPEC.md # unification design doc
└── PHP_CALIBRATION.md # PHP/Laravel feasibility report
Four separate tools (scry, tome, flume, lore) shared 90% of their infrastructure: cobra CLI, BadgerDB storage, JSON-RPC 2.0, MCP stdio server, daemon lifecycle. Running four daemons, four sockets, and four MCP servers for one project was wasteful. The unified binary eliminates routing decisions for Claude Code — one MCP server, one tool namespace.
Built by Jeff Hooton · GitHub
Optional snapshot-bound reviews combine changed code, indexed callers, and recalled decisions while your coding agent works. Results remain provisional, carry freshness and usage, and are shared across CLI/MCP clients. See configuration, operation, and acceptance testing.