MemtraceDOCS

memtrace rail & routeBeta

Add relevant graph context to ordinary code discovery, choose a Rail mode, and manage the hooks maintained by your workspace daemon.

Usage#

memtrace rail [status|report|doctor|enable [mode]|disable|help]Beta

Keep memtrace start running in your repository. Its bundled Node helper registers supported agent hooks automatically. With no mode configured, Rail usesnudge: relevant graph context accompanies ordinary discovery, and raw tools remain available. Use memtrace rail to inspect or change the behavior; a separate enable command is not required.

AGENT COVERAGE

Claude Code adds Nudge context through PostToolUse for Bash/Grep; OpenCode usestool.execute.after. Cursor supports shell routing and post-tool Shell/Grep context, with native search coverage determined by its hook surface. Codex local, Gemini CLI, and VS Code/Copilot use their supported pre-tool adapters and may require hook trust or reload. Windsurf can display routing output, but delivery into model context is not guaranteed. Hermes and Kiro use managed skills and MCP tools without automatic Rail delivery.

Managed stock skills adopt the contextual default on upgrade. Customized skills, explicit user or project workflows, and existing strict, off, or enrichment opt-outs are preserved. Standalone MCP supplies explicit tools and guidance; automatic context requires the workspace daemon.

Modes#

Rail has five modes, resolved with precedence --mode flag on route > MEMTRACE_RAIL env > persisted ~/.memtrace/rail.json > default nudge.

  • off — routing is a complete no-op; raw search always runs.
  • observe — the hook path never queries the daemon inline. A Memtrace-owned attempt is classified but the decision is always AllowRaw, so agent latency is unaffected. A separate, invisible shadow-measurement spool may record a content-free entry for later trust-dataset aggregation.
  • nudge (the default when no mode is configured) — makes one bounded daemon query per Memtrace-owned attempt. A relevant hit still lets the raw command run, but attaches Memtrace's result alongside it (InjectContext). Never blocks.
  • rail — enforcing. A relevant hit suppresses the raw search entirely and returns Memtrace's result instead (SuppressRawWithResult).
  • strict — same enforcing behavior as rail, the strictest mode currently shipped.
ENFORCING MODES STILL FAIL OPEN

rail and strict never dead-end an agent: any backend outage, timeout, or miss falls back to AllowRaw (or, on a miss, a narrowed raw search still proceeds). The retrieval backend used by the pure decision helper is a hardcoded fail-open placeholder — only the live route --hook path performs a real daemon query.

Subcommands#

status

memtrace rail status is read-only: it prints a JSON object with the resolved mode, the MEMTRACE_RAW_SEARCH escape-hatch state, and a fixed advisory note that routing is search-backed (queries the running daemon's find_code), fails open on any backend failure, and requires memtrace start to be running for routing to take effect. It does not verify the daemon is actually running.

report

memtrace rail report reads the local rich telemetry JSONL (~/.memtrace/rail-telemetry.jsonl) and the content-free shadow telemetry JSONL (~/.memtrace/rail-shadow.jsonl), aggregates each into counters — coverage, precision proxy, hit/miss/unavailable counts, score/latency/query-shape histograms — and prints a combined {"local": ..., "shadow": ...} JSON report to stdout. Missing or unreadable files degrade silently to all-zero counters rather than erroring.

doctor

memtrace rail doctor prints a small fixed JSON health object: router: "ok", the resolved mode, and backend_port: "fail_open_placeholder".

DOCTOR IS A ROUND-1 STUB

It does not actually probe the daemon, hook files, or index — the "placeholder" label documents that the retrieval port is still the fail-open placeholder pending later rounds of the Rail build. Don't expect it to diagnose a broken installation the way memtrace doctor does.

enable [mode]

memtrace rail enable [off|observe|nudge|rail|strict] persists the given mode (default nudge if no argument) to ~/.memtrace/rail.json, then calls the bundled Node installer's install-rail subcommand to wire discovery hooks into every supported agent host: Claude, Cursor, Codex, Gemini, OpenCode, Windsurf, and VS Code Copilot. If the mode is nudge, it prints extra guidance that raw search still runs with Memtrace context injected alongside it.

Hook-wiring requires node on PATH and the bundled installer. If either is missing, enable still persists the mode — it prints a warning and tells you to run the installer manually rather than failing the command.

disable

memtrace rail disable persists mode off and best-effort removes the Rail hook entries from every supported host's config. Unlike enable, a failure here is silently swallowed rather than warned about.

help

memtrace rail help (also the fallback for no args, --help, or -h) prints the Rail usage banner to stderr and exits 0. An unrecognized subcommand instead prints an error to stderr and exits with status 2 — it does not fall back to help.

How a discovery call is routed#

Pre-tool routing uses the pipeline below; Nudge post-search enrichment uses the host's after-tool surface. Only attempts on a surface Memtrace actually owns (inside the indexed root, source, not generated/vendored) continue past classification — everything else allows the raw command through with zero backend contact.

Discovery call routingFLOW
noyesobserve / offnudge / rail / strictmiss or timeout — fail open
Grep/Glob/rg call
Host discovery hookMemtrace context
Surface owned?
Mode postureobserve/nudge/rail/strict
Query daemonfind_code, ≤500ms
Allow raw search
TRIGGERSUPPORTINGDECISIONSERVICESUCCESS

In observe mode the daemon is never queried inline — the pipeline stops at the posture check. In nudge/rail/strict, a relevant hit (score clearing a mode-aware floor) either attaches Memtrace's result alongside the raw search (nudge) or suppresses the raw search and returns Memtrace's result instead (rail/strict). Any connection error, non-2xx status, timeout, or bad JSON from the daemon is treated as unavailable and always falls open to allow.

memtrace route#

memtrace route --json '<ToolAttempt JSON>' | --json - | --hook [--mode off|observe|nudge|rail|strict] [--host claude-code|cursor|codex|gemini|open-code|windsurf|vscode|generic]

route is the internal hook entry point that does the actual per-attempt classification. It is invoked by a supported pre-tool routing hook — not typically run by hand. memtrace route ARGS is a thin alias that forwards into the same dispatcher as memtrace rail route ARGS, one token shorter.

Pass --json '<ToolAttempt>' for a one-shot manual classification: it prints a bare DecisionEnvelope JSON to stdout and exits 0. Use --json - or --stdin to read the attempt from stdin instead. Pre-tool routing hooks invoke the --hook form — stdin is a raw host PreToolUse payload (not a pre-built ToolAttempt), and output is reshaped into that host's own hook-output schema (for example Claude's hookSpecificOutput.permissionDecision).

SUB-MILLISECOND BY DESIGN

Both route and rail explicitly skip the normal Rayon thread-pool configuration (and its stderr startup banner) that every other subcommand runs, because the hook path must stay sub-millisecond on the no-op path and must never drive the indexing pipeline.

FlagDescription
--json '<ToolAttempt JSON>'Full ToolAttempt JSON given inline as a single argument.
--json -Read the ToolAttempt JSON from stdin instead of an inline argument.
--stdinEquivalent to --json -.
--hookTreats stdin as a raw host PreToolUse payload and prints host-shaped hook-output JSON instead of a bare DecisionEnvelope. This is the form used by pre-tool routing hooks.
--repo-id <ID>Explicit cwd-to-indexed-repo id override; wins over filesystem resolution.
--index-version <V>Opaque index-version value passed through as a cache key alongside --repo-id.
--host <name>One of claude-code, cursor, codex, gemini, open-code, windsurf, vscode, generic. Selects the hook-payload adapter for --hook mode; defaults to generic.
--mode <off|observe|nudge|rail|strict>One-shot override of the Rail mode for this single invocation, above the env var and persisted config.

A relevant daemon hit is gated by score, not just result count: the top result must clear a mode-aware relevance floor (0.0 for nudge, since it never blocks anyway; 0.06 for rail/strict, calibrated against the RRF scorer's measured true-hit-versus-junk band) or it downgrades to a miss and the raw command proceeds untouched.

WINDSURF EXITS THE PROCESS DIRECTLY

In --hook mode, every other host prints one line of JSON and returns exit 0. Windsurf is the exception: it calls std::process::exit directly — exit 2 with a stderr message to block the raw command, or exit 0 to allow — matching Windsurf's own hook exit-code contract instead of JSON.

Environment variables#

VariableDefaultDescription
MEMTRACE_RAILnudgeOverrides the persisted mode for the current process (off|observe|nudge|rail|strict). Consulted by route/status/doctor mode resolution, not by enable/disable (those always write explicitly).
MEMTRACE_ENRICHon in nudgeSet to off to disable automatic post-search enrichment. Explicit disabled hooks and Off mode remain effective.
MEMTRACE_RAW_SEARCH—Escape hatch: set to allow (case-insensitive) to force raw search through even in enforcing modes. Also triggered by the literal token raw-search-ok anywhere in the tool input. Surfaced read-only in rail status as env_escape.
MEMTRACE_RAIL_CONFIG$HOME/.memtrace/rail.jsonOverrides the path to the persisted mode file that enable/disable write and status/route read.
MEMTRACE_RAIL_TELEMETRY$HOME/.memtrace/rail-telemetry.jsonlPath to the rich (query-containing) local telemetry JSONL. Rotates to <path>.1 past ~5MB.
MEMTRACE_RAIL_SHADOW_LOG$HOME/.memtrace/rail-shadow.jsonlPath to the content-free shadow telemetry JSONL, kept deliberately separate so the exportable file never carries query content.
MEMTRACE_RAIL_SHADOWonSet to off/0/false/disabled/no to disable observe-mode shadow measurement. Independent of MEMTRACE_TELEMETRY.
MEMTRACE_RAIL_SHADOW_SAMPLE1.0Sampling rate in [0,1] for observe-mode shadow measurement, to bound background daemon CPU.
MEMTRACE_RAIL_SHADOW_SPOOL$HOME/.memtrace/rail-shadow-spool.jsonlTransient spool the hook appends to in observe mode (zero agent latency); drained asynchronously by a daemon background loop every ~20s.
MEMTRACE_TELEMETRYonGlobal telemetry opt-out. When off, also disables Rail shadow measurement.
MEMTRACE_RAIL_SEARCH_URLworkspace daemon discoveryExplicitly selects the daemon base URL for this workspace. Without an override, routing uses the live workspace owner rather than guessing a port.
MEMTRACE_UI_PORT3030Selects the dashboard port when starting a daemon; hooks discover the owner serving their workspace.
MEMTRACE_RAIL_TIMEOUT_MS500Per-query timeout for the daemon retrieval call, clamped to [1, 5000] ms; invalid values fall back to 500ms.
MEMTRACE_RAIL_MIN_SCORE—Explicit relevance-score floor override; must parse as a float in [0,1]. Otherwise mode-aware defaults apply (0.0 nudge, 0.06 rail/strict).
MEMTRACE_INSTALLER_JS—Used by hook-wiring (enable/disable) to locate the bundled Node installer if the default relative-path lookup fails.

Gotchas#

REQUIRES MEMTRACE START RUNNING

Routing only takes effect while memtrace start is running, since hooks discover the live owner serving that workspace. Keep Node and the bundled installer available, and reload or trust hooks as required by your agent. An unavailable graph leaves ordinary tools usable.

TWO HOSTS HAVE NO RAIL HOOK SURFACE YET

Hermes ("pre_tool_call adapter not wired yet") and Kiro IDE ("hooks omit tool stdin, CLI-only for now") are reported by rail enable as skipped, not registered.

TELEMETRY FILES ARE DELIBERATELY SPLIT

rail-telemetry.jsonl can contain raw query text; rail-shadow.jsonl is content-free by construction so it's safe to aggregate/export as a cross-user trust dataset.

Examples#

terminal
$ memtrace rail status
# Shows resolved mode, MEMTRACE_RAW_SEARCH escape state, and a
# routing-requires-daemon note. Read-only.

$ memtrace rail enable nudge
# Persists mode=nudge and wires discovery hooks into every
# supported host. Requires node on PATH. Never blocks raw search,
# only injects Memtrace context alongside it.

$ memtrace rail enable rail
# Enforcing mode: Memtrace-owned hits suppress the raw search
# result entirely.

$ memtrace rail disable
# Persists mode=off and best-effort removes wired hooks from all hosts.

$ memtrace rail report
# Prints aggregated local + shadow telemetry counters as JSON.

$ memtrace route --json '{"host":"generic","cwd":"/repo","tool":"Bash","input":{"command":"rg foo"},"session_id":"s1"}'
# Manual one-shot classification; prints a DecisionEnvelope JSON.
# Manual classification uses the selected mode; ordinary tools remain
# available in the default nudge mode.

$ memtrace route --hook --host claude-code
# The form used by Claude Code pre-tool routing in Rail/Strict: stdin is
# Claude's raw hook payload, stdout is Claude's hookSpecificOutput
# JSON.

See also memtrace start, memtrace mcp, memtrace install, and Hooks.