Compass

Spacedock Developer Tools / AI Agent Tooling Jul 2026 to PresentProject LinkSource

PythonSQLiteMCPReactTypeScriptViteTailwind CSSshadcn/uiStorybookVitestmypyGitHub Actions

Every vendor shows only its own sessions

People run several coding agents at once on purpose. They spread work across subscriptions when one quota runs out, and they have a second model review what the first one wrote. Each vendor's overview lists only its own sessions, so the person running them becomes the orchestrator and keeps the status in their head. One person quoted in my launch post used to start 20 sessions in the morning, remember 15 to check at noon, and by the afternoon hold 10. Compass reads the session records that ten harnesses (the programs that run a coding agent and write its session to disk) already write and puts every session on one local dashboard: Claude Code, Codex, Pi, Gemini CLI, Antigravity CLI, GitHub Copilot CLI, OpenCode, Cursor CLI, Goose and Factory Droid. It installs as a native plugin for five of them, as standalone binaries for macOS and Linux, or through Homebrew.

I made the first commit on July 23, 2026. Compass ships under Spacedock, the agent framework Recce builds, and Recce is where I work. It launched as Cargento and took its current name at 1.0.0 on October 13, 2026. As of November 12, 2026, 808 of the 890 commits on main are mine.

Standard library only, and read-only on disk

The server imports nothing outside the Python standard library, so a plugin install has nothing to pip-install and Compass runs from any harness or from none. An AST scan of 140 runtime and hook files finds no third-party import. Compass started as one Python file, and by July 29 that server.py was 7,357 lines. Over about twenty pull requests in the next two days I moved the collectors, the HTTP service, notifications and lifecycle into a runtime package (cargento_runtime then, compass_runtime now). server.py is now a 7-line launcher. I hold the code to ruff with every rule selected and a short, documented ignore list, and to mypy in strict mode. The tests use stdlib unittest.

Compass only reads what a harness writes. It never changes it. The formats share almost nothing. Six harnesses write JSONL. OpenCode, Cursor and Goose keep SQLite databases, which Compass opens read-only, and Antigravity keeps a database per conversation with protobuf trajectory metadata plus CLI logs. Each collector shows only what its harness records. Codex pads its rollouts with large encrypted reasoning blobs, so the prompt is found by walking backward from the end of the file. Cursor stores hex-encoded JSON and records no turn boundaries, so Compass can find its sessions and tell when one is stopped waiting on you, but it can't estimate how long a turn will take. An Antigravity generation blob can run past 700 KB, so that collector uses sqlite3 blobopen to read the last 64 bytes without loading the row. The ten collectors come to 5,607 lines. I wrote most of them, and their behavior is tied to 59 recorded captures from specific harness versions.

The agent that has been waiting on you

The moment worth catching is a second agent that has been blocked on you for eleven minutes while you watched the first. On Claude Code, four independent paths can mark a row Needs input: a pending question in the transcript, the bundled PermissionRequest hook, the user-installed Notification hook, and the teams roster for a dispatched subagent that never started. Kent Huang worked out when a standing prompt outranks a session that looks busy. It took three attempts, and the design record he wrote keeps the two that were reverted. Kent also built the queue that lists blocked sessions longest-waiting first, and the code that classifies notification types. Only six of the ten harnesses can report a wait at all, so the board names the gap for the other four instead of letting a quiet row read as all clear.

Quiet doesn't always mean idle, either. Compass used to mark a row Working if its transcript had been written in the last 90 seconds. A headless Claude Code run waiting on a long tool call writes nothing. I measured it on Claude Code 2.1.322: a 150-second foreground wait read Idle from about 90 seconds in. Now an unanswered tool call keeps the row Working as "running Bash", and a background launch with no recorded completion reads "waiting on 1 background task". Both are capped at 900 seconds, the cap Pi already used.

An ask tool that answers with an index

The ask lane is the only path from the dashboard into a running session, and it opens only because the session asked. A stdio MCP server exposes one tool, ask_operator(question, options). The question becomes a card on the dashboard and the tool call blocks until someone clicks. The dashboard sends back an index. The server returns options[index] from the list it registered, after a range check, so a forged or confused answer can only pick one of the agent's own options. It's 962 lines of standard-library Python, with a reader thread, a dispatch loop and a worker per tools/call, so pings and cancellations still get answered while a question waits. Before building it I measured that Claude Code 2.1.239 and codex-cli 0.146.1 both hold a 20-second tool call. In two recorded trials of an ambiguous irreversible delete, the attended agent didn't call ask_operator. Framed as an unattended overnight batch, the same scenario produced an unprompted call. The value is on unattended runs.

Drift readings that say what they can't prove

Running many agents also means checking that each one is still doing what you asked. Compass's Intent and drift feature compares a session's recorded work (its messages, the checks it ran, the files it wrote) with a goal you type. Compass has no model of its own. It runs the Claude Code or Codex CLI already on the machine, and each departure it reports cites numbered entries. A reading made with Claude ends on a drift level, from None or low up to Extreme, or says too little is recorded yet to tell. Press Steer back on one and Compass drafts a correction for you to paste in, because it never writes into a session. A Codex reading lists its departures with no level and no correction. A project script only counts as a check if it's a known runner, and Compass reads it from the commit the session started on. A test script the session edited itself counts for nothing. Since 2.3.0 a reading covers the whole session instead of the newest hundred events. One 18-hour session with 282 subagents went from 84 entries and "Can't tell" to all 1,148 entries. The README states the limit: readings made with Claude haven't passed their accuracy check yet, so a level only describes the evidence Compass read.

An offline page and five off switches

Between October 7 and 12, before 1.0.0, I replaced the hand-written HTML, CSS and JavaScript page with a React and Vite app. Differential tests held the new page to the old one by running the legacy code beside it. React became the default through one constant on October 9, with --frontend legacy as the process-level rollback. The next day I recorded the legacy page's answers as 45 golden files, so the tests could keep replaying them, and then retired it. Before adopting shadcn/ui and Tailwind 4 I measured a naive install and wrote down ten things that broke or silently misbehaved. The shadcn CLI alone is 282 packages as a dev dependency, and shadcn add installs an unrelated npm package named cn. The built assets ship with Compass, fonts included, so opening the page fetches nothing.

The server listens on 127.0.0.1 unless you pass --host. The README's promise is that nothing leaves your machine that you can't switch off, and SECURITY.md lists every request that does leave it. There are five kinds: the quota reading, which sends your login token to your vendor and no session content (--no-usage); model calls (--no-observer-model); CI check reads through gh or glab (--no-ci-read); a release lookup at most once per UTC day (--no-update-check); and reach nudges, which post two counts (sessions that need you and finished sessions nobody has read) to an endpoint you set, and stay off until you set one (--no-reach). Model calls and the quota reading used to ask first. When I turned them on by default, I shipped that as 2.0.0, and the release notes say why.