Get the binary.
Kestrel is a single static binary. Prebuilt archives cover macOS (Apple Silicon and Intel), Linux (x86_64 and ARM64) and Windows (x64). Every install path verifies a SHA-256 checksum.
| Platform | Command |
|---|---|
| Homebrew | brew install aareevs/tap/kestrel |
| macOS / Linux | curl -fsSL …/scripts/install.sh | sh installs to ~/.local/bin |
| Windows | irm …/scripts/install.ps1 | iex installs to %LOCALAPPDATA%\Programs\kestrel and adds it to PATH |
| Go 1.26+ | go install github.com/Aareevs/Kestrel-CLI/cmd/kestrel@latest |
Or grab an archive and checksums.txt from the releases page. Then check your setup with kestrel doctor.
# Homebrew (macOS & Linux) $ brew install aareevs/tap/kestrel # curl, detects OS + arch, verifies SHA-256 $ curl -fsSL https://raw.githubusercontent.com/\ Aareevs/Kestrel-CLI/main/scripts/install.sh | sh # Windows PowerShell > irm https://raw.githubusercontent.com/Aareevs/\ Kestrel-CLI/main/scripts/install.ps1 | iex $ kestrel doctor ✓ platform darwin/arm64 ✓ data dir ~/.kestrel (0700) ✓ storage sqlite ok ✓ config none, discovery mode
Thirty seconds, four commands.
No config file needed. Run these from your project root.
kestrel inspectDiscover services, ports, health routes and dependencies. Reads files only.
kestrel watchSupervise the stack in dependency order with live, classified logs.
kestrel diagnoseSee what broke first, with ranked hypotheses and next steps.
kestrel verifyProve the fix holds across a 30s stability window.
kestrel run -- npm run dev captures, classifies and records it with zero config. Want a config? kestrel init proposes one from discovery.$ kestrel inspect ◆ services web (vite) · api (express) · db (postgres) ◆ ports 5173 · 4000 · 5432 ◆ health GET /health src/server.ts:41 ◆ depends web → api → db $ kestrel watch ✓ db ready 1.2s ✓ api ready 2.8s ✓ web ready 0.9s ✗ db exited 137 ✗ api ECONNREFUSED 127.0.0.1:5432 $ kestrel diagnose 1. K002 dependency-down 0.94 db exited first → api lost :5432 → web 502 $ kestrel verify PASS fix holds · 16 samples · 0 orphans
Every command, and its shortcut.
| Command | Short | What it does |
|---|---|---|
inspect | ins info | Detect technologies, service candidates, ports and health routes. Reads files only. |
watch | w start | Supervise configured services in dependency order with live status. |
run | r | Run any command under Kestrel with no config: capture, classify, probe, record. |
status | s stat | Liveness vs. readiness per service. |
diagnose | diag | Ranked, evidence-backed hypotheses for each incident. |
timeline | tl time | Last healthy state, first anomaly, symptoms, recovery. |
verify | v check | Probes across a stability window, with a before/after comparison. |
sessions | sess | List, show, export or delete recorded sessions. |
doctor | doc | Check platform, data directory, config and tools. |
mcp | Read-only MCP server over stdio, for AI agents. | |
init | Propose a kestrel.yaml. Asks first, never overwrites. | |
config | Show, validate or locate the config. Secrets are always masked. | |
cleanup | Apply the retention policy now. |
$ kestrel --help Usage: kestrel [command] inspect detect stack (reads files only) watch supervise with live status run wrap any command, zero config status liveness and readiness diagnose ranked root-cause hypotheses timeline incident timelines verify did the fix work? sessions list / show / export / delete doctor check this machine mcp read-only server for agents init propose kestrel.yaml config show / validate / locate cleanup apply retention now
Global flags.
These work on every command.
| Flag | Effect |
|---|---|
-j --json | JSON output, same as --format json. |
--project <dir> | Project directory. Default: nearest ancestor with a kestrel.yaml, else the current directory. |
--session <id> | Session to act on: full ID, unique prefix, or last (default). |
-v --verbose | Diagnostic logging to stderr. |
-q --quiet | Only essential output. |
--no-color | Disable ANSI styling. NO_COLOR is honored too. |
--allow-exec | Let verify run configured command checks without a prompt. |
$ kestrel diag -j { "session": "01JA9…", "incidents": [{ "id": 3, "hypotheses": [{ "rule": "K002-dependency-down", "confidence": 0.94, "claim": "hypothesis", "supporting": ["evt_8f21", "evt_8f24"], "next_step": "check db memory limit" }] }] }
Optional, and strict.
Kestrel works from discovery alone; a config just makes its picture sharper. It lives at the project root as kestrel.yaml or .kestrel.yaml (not both). Unknown keys are errors, every problem is reported at once with a line number, and kestrel config validate exits 2 when the file is invalid. A JSON schema ships in docs/kestrel.schema.json for editor support.
services[]
| Key | Notes |
|---|---|
name | Required, unique. Letters, digits, - _ ., up to 63 characters. |
dir | Working directory relative to the project root. |
command | What Kestrel runs, only under run, watch and verify. |
env | ${VAR} is resolved from your shell at start. Resolved values are never stored or printed. |
depends_on | Other services. Unknown names and cycles are errors. |
health | http, tcp or process, with interval 2s, timeout 1s, retries 3, startup_grace 10s. |
A service with only a probe, like a database you run yourself, is observed and never started.
Top level
| Key | Default | Notes |
|---|---|---|
storage.retention_days | 7 | Older ended sessions are removed. |
storage.max_total_mb | 500 | Oldest closed sessions go first. The active one never does. |
correlation.window | 10s | Used only as a weak correlation signal. |
redaction.extra_patterns | [] | Extra regexes to redact. |
verify.checks | [] | probe or command checks. |
version: 1 project: name: example-app storage: retention_days: 7 max_total_mb: 500 redaction: extra_patterns: ["acme_[a-z0-9]{32}"] services: - name: database health: { type: tcp, address: localhost:5432 } - name: api dir: ./backend command: go run . env: { DATABASE_URL: "${DATABASE_URL}" } depends_on: [database] health: { type: http, url: http://localhost:8080/health } - name: frontend dir: ./frontend command: npm run dev depends_on: [api] verify: checks: - { type: probe, service: api } - { type: command, run: go test ./... }
Six rules, all evidence.
Kestrel groups anomalies into incidents, finds the initiating failure using the dependency graph and port evidence (timing is only a weak signal), then runs these rules. Every hypothesis cites the event IDs that support or contradict it.
K001Port conflictTwo things want the same port. Both are named.K002Dependency downA dependency died and its dependents failed after it.K003Crash after startReached ready, then exited.K004Startup failureNever became ready. Here is the first fatal line.K005Dependency not runningStarted before what it needs was up.K006Missing moduleAn import or package that isn't installed.Claim levels
Each finding carries one: observed, correlated, hypothesis, verified or unresolved. A hypothesis is never presented as fact, and an incident nothing explains stays unresolved.
$ kestrel diagnose Incident #3 3 services · first signal 14:02:11.384 1. K002 dependency-down 0.94 hypothesis db exited 137 → api lost :5432 → web 502 supporting evt_8f21 evt_8f24 evt_8f2a contradicting — 2. K003 crash-after-start 0.21 next → raise db memory limit, then kestrel verify
"It went away" isn't "it's fixed".
kestrel verify samples the affected services across a stability window (default --window 30s, --interval 2s, so 16 samples). The window always runs to the end. It never starts, restarts or signals your services.
- passed every sample passed.
- flaky some passed, some failed. A fix that landed mid-window, or a relapse.
- failed no sample passed.
Checks are chosen from evidence: each affected service's health probe, a TCP check for every address the incident failed to reach, liveness when watch is still running, and your configured commands, but only with --allow-exec or a yes at the prompt.
kestrel watch is still going. It then also checks whether the incident's errors are still occurring during the window, which gives the strongest answer.$ kestrel verify Before K002 dependency-down · db exited 137 Window 30s · every 2s · 16 samples probe db :5432 ●●●●●●●●●●●●●●●● passed probe api /health ●●●●●●●●●●●●●●●● passed tcp 127.0.0.1:5432 ●●●●●●●●●●●●●●●● passed live web ●●●●●●●●●●●●●●●● passed During the window: no recurrence PASS fix holds
Give your agent the evidence.
kestrel mcp is a Model Context Protocol server over stdio. Agents ask for the diagnosis instead of grepping logs. It serves one project, and reads what run and watch recorded.
Claude Code
claude mcp add kestrel -- kestrel mcp --project /path/to/project
Tools
| Tool | Returns |
|---|---|
get_diagnosis | Ranked hypotheses with supporting and contradicting event IDs and a next step. |
get_failure_timeline | Incident timelines, the dependency graph, past verifications. |
get_recent_errors | Latest error and fatal events (default 50, max 500). |
get_session_summary | Final service state, counts by type, notable events. |
get_verification_results | Past verify runs and the latest report in full. |
get_service_status | Liveness and readiness per service. |
list_services | Services from the config, commands masked. |
inspect_project | Discovery findings with file:line and confidence. |
- Strictly read-only. No tool starts, stops, signals or executes anything.
- Logs are data. The agent is told that log text is untrusted and never instructions.
- Redacted twice. At record time and again on the way out, with today's patterns.
# Claude Code $ claude mcp add kestrel -- \ kestrel mcp --project /path/to/project # Cursor, VS Code, Claude Desktop… { "mcpServers": { "kestrel": { "command": "kestrel", "args": ["mcp", "--project", "/path/to/your/project"] } } }
Runs next to your secrets. Acts like it.
- Discovery only reads. It never runs scripts, package managers or build tools, never follows directory symlinks, and never opens
.env. - Redaction before storage. Private keys, JWTs, URL credentials, provider keys, auth headers and secret-named pairs are masked before anything is printed or written.
- Only its own processes. Signals go only to process groups (Windows Job Objects) Kestrel created. Ctrl-C leaves zero survivors.
- Hostile logs stay inert. Log text is never executed, and terminal escape sequences are stripped.
- Bounded under floods. A 50 MB/min log flood plateaus around 47 MiB of memory, with every drop accounted for.
- Exec needs consent.
verifycommands run only with--allow-execor an explicit yes.
# what your process printed DATABASE_URL=postgres://admin:hunter2@db:5432/app Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIi… STRIPE_KEY=sk_live_51Hx… # what Kestrel shows, stores and sends to agents DATABASE_URL=postgres://admin:[REDACTED:url-credential]@db:5432/app Authorization: Bearer [REDACTED:jwt] STRIPE_KEY=[REDACTED:provider-key]
The full references.
$ ls docs/ architecture.md packages + pipelines configuration.md kestrel.yaml reference diagnosis-rules.md K001 – K006 events.md event model mcp.md agent setup + tools security.md threat model verification.md stability window kestrel.schema.json editor schema