observe-agents-do-things
v0.2.1
Published
Watch Claude Code and Codex agents work in real time: a read-only observer with a live dashboard. Run it with npx.
Maintainers
Readme
Keep using Claude Code and Codex exactly as you do now: the CLI, the IDE extensions or the desktop apps. oadt sits beside them, tails the transcripts they already write, and turns them into one clean live event stream. A visual frontend renders that stream. You can also build your own frontend on top of it in a few lines.
https://github.com/user-attachments/assets/128bed0d-3890-4ca1-8d75-0930b99a1472
A one-minute tour of Mission Control in demo mode (oadt --demo). Everything you see is simulated. No real transcripts are used in this repository. The video files are also in docs/media.
Every agent gets a little blob buddy that acts out what it is doing right now:

…or as a cosy isometric office
https://github.com/user-attachments/assets/6bf8c1ef-9c59-4247-a0a8-3441ba648cc5
Rooms, profile cards, the intercom, the front desk and the night shift.
…or as an imperial court
https://github.com/user-attachments/assets/b0317f4c-3d75-458b-953f-eb8dd4214956
Every session is a delegation in one great hall, and you are the empty throne. Ministers raise their tablet when they need you, memorials are stamped 准 and filed, and edicts glide down from the throne.
Visualizations are plugins over the same data. Three ship today: Mission Control (the default), a compact IDE-style dashboard (Dark, Light and Gruvbox themes) built around what needs you, the Agent Office, and The Court, a Tang–Song audience hall where subagents are ministers who raise their tablet when they need you. Pick one in Settings › Visualizations (the ⚙ button, or Ctrl+,), where each also has its own options page; Settings also holds the opt-in desktop notifications. Each one owns its entire view: layout, rendering, input and HUD. The office maps spawns to new hires walking in, permission prompts to raised hands, file work to papers flying to the filing cabinet, and finished turns to confetti. See docs/VISUALIZATIONS.md, including how to write your own.
Quick start
Requires Node.js 20.11 or newer. One command, nothing to install:
npx observe-agents-do-things # → http://127.0.0.1:4545Then start (or keep using) Claude Code or Codex anywhere on the machine. Nothing running? Try the simulator:
npx observe-agents-do-things --demonpm install -g observe-agents-do-things gives you the short oadt command. To run from source:
git clone https://github.com/Dri-water/observe-agents-do-things
cd observe-agents-do-things
npm install && npm run build
npm start # or: npm run demoVS Code
The Observe Agents extension puts the dashboard in the side bar, the bottom panel or an editor tab, and adds a status bar item that says when an agent needs you. It uses the observer on 127.0.0.1:4545 (the CLI or Docker) when one is running and is at least as new as the extension; otherwise it runs its own, shared by all your VS Code windows.
Install Observe Agents from the VS Code Marketplace, or search for "Observe Agents" in the Extensions view. Every release also has the .vsix attached: in Cursor, Windsurf or VSCodium, download it and run Extensions: Install from VSIX…. To build it yourself, run npm run build:vscode.
Docker
docker compose up -d --build # container 'observe-agents-do-things', restarts automaticallyThis mounts ~/.claude and ~/.codex read-only, publishes the UI on 127.0.0.1:4545 only, and keeps the last 12 hours of history (OADT_SINCE). Inside a container, change detection falls back to polling, because bind mounts don't forward file-change events. Updates still arrive within about a second.
Why
Agent visualisers tend to be the harness, or to install themselves into it. This project does neither:
- Zero setup in your agents. No hooks, no settings edits, no wrappers, no API keys. It only reads files.
- Use whatever app you like. Claude Code CLI, the VS Code and JetBrains extensions, the Claude desktop app, the Codex CLI and Codex Desktop all write transcripts that
oadtpicks up within a second. - Logic and pixels are separate. All detection, normalisation and state derivation lives in the core. Frontends get a small typed protocol, plus a pure function that folds events into state. The bundled UI is just one consumer, and the examples include a terminal dashboard, a notifier and a 60-line HTML page.
Features
- Swappable visualizations. Each visualization is a plugin that defines its whole view; the core only provides data. Three ship today: Mission Control (the default), Agent Office and The Court. Adding one is a single module plus one line to register it.
- Agents with a face. Every agent gets a blob buddy, drawn by blobatar, that types while it edits, peers around while it searches, hops with a "!" when it needs your approval and dozes off when idle. What it acts out comes from
sessionActivity/agentActivityin the protocol, so any frontend can use the same signal. - Subagents in both harnesses. Claude Code
Agent/Tasksubagents are linked through theirmeta.jsonsidecars. Codexspawn_agentthreads are rebuilt into one tree across separate rollout files. - Tells you when you're needed. Sessions move between working, waiting (a tool pending with no transcript activity, almost always a permission prompt) and idle. A shared attention model, in the protocol so every frontend agrees, flags approvals waiting, finished turns, failure streaks, nearly full context and long-running tools. Optional desktop notifications, a chime and a tab-title count work in every visualization.
- Many sessions at once. Every session across projects and harnesses side by side. Open one for the full picture: every tool call with its input and output, diffs, the conversation, the agent tree, files touched, tokens, context fill and reported cost.
- Huge transcripts are fine. A byte-level, UTF-8-safe tailer reads the head plus a bounded tail, so multi-hundred-MB rollouts load in milliseconds.
- Replay.
oadt replay <transcript.jsonl>replays any recorded session in compressed real time. - Bring your own agent.
POST /api/ingestaccepts protocol events, so your own agent can show up next to Claude and Codex. - Private by default. Binds to loopback and rejects foreign
Hostheaders (DNS rebinding) and foreign origins. Requires a token when exposed. Optional--redact content|strictmodes strip text before anything is served. - Tiny. No runtime dependencies in the core, server or client. The web UI, with both visualizations, is about 75 KB gzipped (JS and CSS), uses vanilla TypeScript and canvas, and makes no external network requests.
How it works
flowchart LR
subgraph Harnesses["Your agents (unchanged)"]
CC["Claude Code<br/>~/.claude/projects/**.jsonl"]
CX["Codex<br/>~/.codex/sessions/**/rollout-*.jsonl"]
YOU["Your own agent<br/>POST /api/ingest"]
end
subgraph Core["@oadt/core (logic)"]
T["Tailer<br/>byte-safe, huge-file aware"]
A["Adapters<br/>harness → protocol events"]
S["EventStore<br/>seq · redaction · replay log"]
H["Status heuristics<br/>waiting / idle / done"]
end
subgraph Proto["@oadt/protocol (contract)"]
P["ObserverEvent + applyEvent()<br/>→ WorldState"]
end
subgraph Server["@oadt/server"]
API["REST + Server-Sent Events"]
end
subgraph Frontends["Frontends (pixels)"]
W["Web app<br/>Mission Control · Agent Office · Court"]
TD["Terminal dashboard"]
N["Notifier"]
X["…yours"]
end
CC --> T --> A --> S
CX --> T
YOU --> API --> S
H --> S
S --> P
S --> API --> W & TD & N & X- Adapters understand each harness's on-disk format: record types, injected system content, split streaming records, subagent linkage, token accounting and exit codes. They emit a dozen kinds of normalised events.
- The store stamps each event with a sequence number, applies the privacy policy and folds it into a
WorldStateusingapplyEvent. That is a pure function exported by@oadt/protocol. - The server sends a snapshot of that world, then streams every event over SSE. Clients run the same
applyEvent, so every frontend sees identical derived state: agent trees, open tools, file heat and token totals. Reconnecting clients resume from their last sequence number.
Build your own frontend
The whole contract is in @oadt/protocol: 13 event kinds and one reducer.
import { connect, sessionList, openTools } from '@oadt/client'
const client = connect({ url: 'http://127.0.0.1:4545' })
client.onChange((world) => {
for (const s of sessionList(world)) {
console.log(s.status, s.meta.title, openTools(s).map((t) => t.title))
}
})Using React? @oadt/react wraps the same client in hooks:
import { ObserverProvider, useSessions, useOpenTools } from '@oadt/react'
function Sessions() {
const sessions = useSessions({ liveOnly: true })
return sessions.map((s) => <Session key={s.id} id={s.id} title={s.meta.title} />)
}
function Session({ id, title }: { id: string; title?: string }) {
const running = useOpenTools(id)
return <p>{title}: {running.map((t) => t.title).join(', ') || 'idle'}</p>
}
export const App = () => (
<ObserverProvider url="http://127.0.0.1:4545">
<Sessions />
</ObserverProvider>
)No build step? The raw stream is plain SSE:
const es = new EventSource('/api/stream')
es.addEventListener('event', (m) => {
const e = JSON.parse(m.data)
if (e.kind === 'tool.started') console.log(e.agentId, e.title)
})Serve any static frontend with oadt --ui ./my-frontend. See docs/FRONTENDS.md and the examples:
| Example | What it shows |
|---|---|
| minimal-feed | One HTML file, no build, no dependencies |
| react-dashboard | A React app built on @oadt/react hooks |
| terminal-dashboard | A live top for agents with @oadt/client |
| notify | A ping when an agent waits for you or finishes its turn; no UI at all |
| custom-agent | Make your own agent observable via /api/ingest |
CLI
oadt [serve] watch Claude Code + Codex and serve the UI (default)
oadt --demo simulated sessions, no real data
oadt replay <files…> replay transcripts as if live (--speed 10 --loop)
oadt tail [--json] print normalised events in the terminal (NDJSON with --json)
oadt sessions [--json] list recent sessions and exit
-p, --port <n> default 4545
--host <addr> default 127.0.0.1; a non-loopback host auto-generates a token
--token <secret> require this bearer token
--no-auth no token even on a non-loopback host (e.g. a container published on 127.0.0.1)
--since <dur> backfill window: 30m, 6h, 2d (default 6h)
--harness <list> claude-code,codex
--redact <mode> none | content | strict
--claude-dir <dir> default $CLAUDE_CONFIG_DIR or ~/.claude
--codex-home <dir> default $CODEX_HOME or ~/.codex
--ui <dir> serve a different frontend · --no-ui for API only
--cors <origin> allow a cross-origin frontend (repeatable)
--read-only disable POST /api/ingest
--open open the browseroadt tail --json | jq turns your agents into a structured log you can pipe anywhere.
API
| | |
|---|---|
| GET /api/stream | SSE: hello, then snapshot (WorldState), then events. Honours Last-Event-ID. ?session= filters. |
| GET /api/state | Full WorldState snapshot |
| GET /api/sessions | Session summaries |
| GET /api/sessions/:id | One session's full state |
| GET /api/sessions/:id/events | That session's event history (?after=seq) |
| GET /api/events?after= | Global event log, for polling clients |
| GET /api/info | Version, sources, privacy mode |
| POST /api/ingest | Push your own events (JSON object or array) |
Details in docs/API.md and docs/PROTOCOL.md.
What gets detected
| | Claude Code | Codex |
|---|---|---|
| Sessions & titles | ✓ custom titles, first prompt | ✓ session_index names, first prompt |
| Subagents | ✓ subagents/agent-*.jsonl + meta.json → spawning tool call | ✓ spawn_agent threads via session_meta.source.subagent |
| Tool calls | ✓ all tools, MCP (mcp__server__tool) | ✓ exec scripts (inner commands summarised), apply_patch files, MCP, web search |
| Success / failure | ✓ is_error, interrupts | ✓ exit codes, Script failed, aborts |
| Turns | ✓ prompt → end_turn / interrupt | ✓ task_started / task_complete / turn_aborted |
| Tokens | ✓ per message, de-duplicated across streamed records | ✓ cumulative token_count → deltas |
| Context fill | ✓ (window when known) | ✓ with model_context_window |
| Extras | cost, lines ±, PR links, permission mode | goals, model per turn |
| Waiting for approval | heuristic (tool pending with no activity) | heuristic |
New harnesses are a single adapter. See docs/ADAPTERS.md.
Privacy & security
Transcripts contain your code and your conversations, so oadt is careful with them:
- Read-only. It never writes to your agents' directories.
- Local by default. It listens on
127.0.0.1. With--host 0.0.0.0it requires a bearer token, generating one if you don't pass--token. Only--no-authturns that off, for setups like the Docker container that publish the port on loopback only. - No cross-site access. Requests with foreign
Hostheaders are rejected (DNS rebinding), and so are cross-origin requests from origins you haven't allowed with--cors./api/ingestrequiresapplication/json, which forces a CORS preflight. - Redaction.
--redact contentdrops prompts, replies, thinking, tool I/O, subagent tasks and goals but keeps the flow.--redact strictalso drops titles, paths and repository details. - No telemetry, no external requests. The UI uses system fonts and loads nothing from the internet.
Packages
| Package | Role | Runtime deps |
|---|---|---|
| @oadt/protocol | Event types, applyEvent projection, selectors | none |
| @oadt/core | Tailer, adapters, store, status heuristics, demo and replay sources | protocol |
| @oadt/server | HTTP + SSE API, the oadt CLI | core, protocol |
| @oadt/client | Browser/Node live client with resume | protocol |
| @oadt/react | React provider and hooks (useSessions, useSession, useAgentTree, useOpenTools, useEvents…) | client, react (peer) |
| apps/web | The bundled web app: a host plus the Mission Control, Agent Office and Court visualizations | client, blobatar |
Development
npm run dev # API in demo mode + Vite with hot reload on :5175
npm run dev -- --real # same, against your real transcripts
npm test # unit + end-to-end tests (node:test)
npm run build:cli # the standalone npm package in packages/cli (one bundled file + the UI)Credits
This project was inspired by agent-flow by Simon Patole (Apache-2.0), a VS Code extension that visualises Claude Code orchestration. Reading its source taught me several hard edge cases, including inline progress records, redacted thinking blocks, injected-prompt prefixes and timer-based permission detection. observe-agents-do-things is an independent implementation with a different goal: a standalone, harness-agnostic observer service that exposes a protocol any frontend can consume. No code was copied.
The buddies are drawn by blobatar by Alain (MIT), used as a dependency of the web app. The activity-driven poses, motion and props are this project's.
