---
title: "Compass: Local Dashboard for Coding Agents"
description: "Case study: building Compass from the first commit, a local dashboard for coding agent sessions across ten harnesses. Read-only collectors on a standard-library Python server, a flag for the agent waiting on you, the ask_operator MCP tool, and an off switch for each kind of outbound traffic."
doc_version: 1
last_updated: 2026-11-12
canonical: https://variable.team/projects/compass
---

<!--
  Source of truth: app/projects/compass/page.tsx. Keep the h1 in sync.
  scripts/check-markdown-drift.ts verifies this in pre-commit.
-->

# Compass

- **Company:** [Spacedock](https://spacedock.md)
- **Industry:** Developer Tools / AI Agent Tooling
- **Dates:** Jul 2026 to Present
- **Project link:** <https://spacedock.md/products/compass/>
- **Source:** <https://github.com/spacedock-dev/compass>
- **Stack:** Python, SQLite, MCP, React, TypeScript, Vite, Tailwind CSS,
  shadcn/ui, Storybook, Vitest, mypy, GitHub 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.

## Sitemap

[Full site index](/sitemap.md)
