Website · Dashboard surface · Security notes
Local workflow forensics for Claude Code. neo indexes text found on disk, transcripts, retained telemetry rows, hook-visible lifecycle events, and memory artifacts into a SQLite database, then exposes them through a dashboard and an MCP server running on the same machine.
- Reads local Claude Code artifacts directly from
~/.claude/and~/.neo/instead of relying on export surfaces that strip evidence - Separates measured, estimated, and inferred claims so row counts stay distinct from heuristics and anomaly labels
- Exposes a local operator surface through a browser dashboard, terminal commands, and an MCP server registered inside Claude Code
- Makes local overhead visible by surfacing reminder injections, sidechains, compaction churn, retained telemetry rows, and other traces the UI does not foreground
Recommended — pipx:
pipx install neo-harnesster
neoWith pip:
pip install neo-harnesster
neoDirectly from the repo:
git clone https://github.com/asuramaya/neo.git
cd neo
python3 neo.pyAny of the above runs setup + ingest + dashboard in one step and opens
http://127.0.0.1:7777.
Restart Claude Code after the first run so the installed hooks begin capturing events.
neo # setup + ingest + dashboard
neo --setup # install hooks + register MCP server
neo --ingest # ingest data only
neo --dashboard # dashboard only
neo --dashboard --no-open # serve without opening a browser
neo --port 8888 # custom portneo-tokens # data accounting in the terminal
neo-states diagram # state machine diagramLive event stream:
tail -f ~/.neo/harness_log.jsonlneo runs as a single shared daemon per host. That one process serves the
dashboard and the MCP protocol over HTTP at http://127.0.0.1:7777/mcp,
guarded by a per-host bearer token. neo --setup registers it in
~/.claude.json as an http MCP server (not a per-session stdio subprocess),
so every Claude Code session connects to the same URL — one neo process per
host, no per-session fan-out, no version skew. On the next session start,
Claude Code connects automatically and the dashboard opens locally.
Setup also installs a systemd --user service (neo.service) that keeps the
daemon running — auto-start on login, restart on crash. Where systemd --user
is unavailable, start it manually with neo-mcp --http. See
Daemon below.
The server exposes 12 tools:
| Tool | What it returns |
|---|---|
status |
setup health + the live process registry across sessions (roles, code versions, version_skew flag) |
summary |
top-level row counts across all tables |
data_accounting |
full measured/estimated accounting |
tokens_report |
compact accounting totals with the measured-vs-estimated basis |
query_reminders |
reminder rows with file + line provenance |
query_sessions |
session list across projects with agent / compaction counts |
query_agents |
subagent genealogy and messages |
query_probe_events |
raw hook events, filterable by type |
query_telemetry |
retained telemetry rows |
query_memory_files |
persistent memory files per project |
state_model |
inferred state-model labels |
correlations |
cross-signal patterns (hook timeline, totals by type) |
Tools that read hook events filter out neo's own MCP traffic by default
(include_self=false) so observer overhead does not contaminate the picture.
neo --setup writes a systemd --user unit at
~/.config/systemd/user/neo.service and enables + starts it. The unit runs the
shared daemon (neo-mcp --http), which owns the dashboard, periodic ingest, and
the HTTP /mcp endpoint. If multiple neo processes start, they elect a single
owner via a per-host lock; followers serve queries against the shared DB and
take over if the owner dies.
systemctl --user status neo # check the daemon
systemctl --user stop neo # stop it
systemctl --user disable neo # stop it auto-starting on login
neo-mcp --http # run the daemon manually (no systemd)neo labels its claims on purpose:
- measured — reminder rows, sessions, agents, tasks, memory files, telemetry rows, hook events; hidden-context share, data multiplier, and API call counts when transcripts carry API-reported token usage
- estimated — hidden-context share and data multiplier only when no token usage is recorded, where they fall back to on-disk transcript byte sizes
- inferred — state-model labels and anomaly interpretation from local timing + lifecycle patterns
For exact billable token numbers, use /usage inside Claude Code. neo does not
fabricate token totals.
| Source | Location | What |
|---|---|---|
| session transcripts | ~/.claude/projects/*.jsonl |
full conversations including system reminders |
| subagents and sidechains | ~/.claude/projects/*/subagents/ |
spawned transcripts and context copies |
| compaction events | ~/.neo/harness_log.jsonl |
PostCompact hook events from the probe |
| telemetry | ~/.claude/telemetry/ |
telemetry rows currently retained on disk |
| memory files | ~/.claude/projects/*/memory/ |
persistent context seeded by instances |
| tasks | ~/.claude/tasks/ |
task state across sessions |
| hook events | ~/.neo/harness_log.jsonl |
tool use, notifications, session lifecycle |
- server-side reasoning or model internals that never land on disk
- the contents of any secondary channel that is not persisted locally
- whether absent telemetry rows were uploaded, deleted, or never written locally
- system prompt assembly inside the compiled binary
- HTTPS request and response bodies without a proxy
The /export command in Claude Code strips system reminders. The raw JSONL
files in ~/.claude/projects/ retain them. neo reads the raw files.
That boundary is the whole reason the project exists.
src/neo/
app.py setup, ingest, threaded HTTP server, systemd daemon install
mcp_server.py HTTP MCP server (/mcp on the shared daemon); owner election + process registry
db.py SQLite ingest + query layer
tokens.py visible vs hidden channel accounting
states.py inferred state model + anomaly labels
harness_probe.py hook script copied into ~/.neo/ by setup
dashboard.html single-file local dashboard
neo.py repo-clone launcher shim
test.py smoke tests
All data lives in ~/.neo/neo.db. The dashboard binds to 127.0.0.1 only.
neo installs async hooks for 20 Claude Code event types:
PreToolUse PostToolUse PostToolUseFailure Notification SessionStart
SessionEnd Stop SubagentStart SubagentStop PreCompact
PostCompact UserPromptSubmit InstructionsLoaded PermissionRequest
PermissionDenied TaskCreated TaskCompleted FileChanged CwdChanged
ConfigChange
- dashboard and
/mcpendpoint bind to127.0.0.1only and validate localHostheaders - the
/mcpendpoint requires a per-host bearer token stored in~/.neo/ POST /api/ingestrequires a same-origin browser request~/.neo/is created with private permissions where the OS allows it- neo does not transmit your data anywhere
- hooks run async and do not block Claude Code operation
- setup installs a
systemd --userservice (neo.service) that auto-starts the local daemon on login and restarts it on crash; remove it withsystemctl --user disable --now neo(see Daemon) - no dependencies beyond Python stdlib
- Python 3.10+
- Claude Code installed (
~/.claude/settings.jsonmust exist)
If you used the project under its old harnesster name, the first run of neo
or python3 neo.py migrates ~/.harnesster/ to ~/.neo/ and renames
harnesster.db to neo.db. Hook commands in settings.json are rewritten
automatically.
The harnesster command remains as a forwarding shim.
MIT
