I run several AI agents at once: a handful of Claude Code sessions, plus a small "company" of agents on Paperclip that works on our online store. Giving them work is easy. The hard part is knowing, at a glance, which one is working, which one is done, and which one has been waiting for my answer for twenty minutes.
Logs don't answer that at a glance. So I drew an office.
Live demo, no install · GitHub · npx @caglarutkuguler/cubicle
What the office shows
Every agent is a character:
- Working: sits at its desk and types, with the current task or tool call above its head
- Needs you: raises a hand, and the monitor turns amber
- Done: says "✓ done" and walks to the lounge for a coffee
- Error: slumps at the desk, red screen
The header keeps a count of how many agents need you. That single number turned out to be the most useful thing on the screen.
Three design decisions
1. Zero dependencies
The server is a single Node file of under 200 lines, using only the standard library: it serves one HTML page and proxies a few requests. The page draws everything with fillRect on a 352×208 canvas. There are no image assets, no framework, and no build step.
This wasn't minimalism for its own sake. A tool that sits next to your agents all day should be something you can read in ten minutes and trust.
2. Read-only by design
Cubicle only ever makes GET requests. In Paperclip mode it forwards exactly three endpoints and refuses everything else:
const ALLOWED = [
/^\/api\/health$/,
/^\/api\/companies$/,
/^\/api\/companies\/[\w-]+\/(agents|issues)$/,
];
POST, PATCH and DELETE get a 405, any other path gets a 403. If you point it at an authenticated Paperclip, the API key is added on the server side only: it never reaches the browser, and the browser's own cookies are never forwarded upstream. There is deliberately no --token flag, because flags show up in the process list.
3. "Needs you" from real signals
For Claude Code, Cubicle uses the official hooks. A tiny script rewrites ~/.cubicle/claude-code.json on every event:
| Claude Code event | In the office |
|---|---|
PreToolUse, PostToolUse
|
Types at the desk; bubble shows e.g. Edit checkout.php
|
PermissionRequest |
Raises its hand |
Stop |
Goes to the lounge |
SessionEnd |
Leaves the office |
The hook always exits 0 and prints nothing, so it can never block a tool call or add tokens to the context. One thing I had to handle: Claude Code also sends a notification after a minute of idling ("waiting for your input"). Treating that as "needs you" made every idle session raise its hand, so those are ignored.
For Paperclip, the interesting signal was hiding in the issue data. When an agent asks the board a question, Paperclip records it in the issue's reviewAttention.paths with the board as the responder. Often the agent is already busy with another issue, so its own status says running. Cubicle keeps it typing, but its bubble flashes the waiting issue's ID every few seconds and the card links straight to it. The first time I turned this on, it found two questions I didn't know were waiting for me.
Try it
# Paperclip (default, expects it on :3100)
npx @caglarutkuguler/cubicle
# Claude Code
npx @caglarutkuguler/cubicle install-hooks
npx @caglarutkuguler/cubicle --source claude-code
# anything that can write a small JSON file
npx @caglarutkuguler/cubicle --source ./agents.json
Open http://127.0.0.1:3200. Add ?kiosk for a full-screen view on a TV or second monitor.
What's next
A few issues are open and labelled for first-time contributors: a new UI language, per-agent spend for Paperclip, an example adapter for Codex or Gemini CLI, and a replay mode to watch a whole day in 30 seconds. PRs welcome, as long as they keep the two rules: no dependencies, and read-only.
It's MIT licensed: github.com/caglarutkuguler/cubicle

Top comments (10)
The "always exits 0, never blocks" constraint is the right call, and worth calling out explicitly — it's a different contract from an enforcement hook (exit 2 to block, stderr as the reason Claude reads). Mixing the two is a real footgun: if your observability hook and your enforcement hook are the same script, a bug in the dashboard-updating logic can start silently blocking tool calls, or a tweak to the blocking logic can break the office display. Keeping them as separate hook entries, with a hard rule that the observability one is side-effect-only, means a crash in the visualization can't take down actual enforcement and vice versa. The reviewAttention.paths signal is a nice catch too — surfacing "waiting on you, but the agent already moved on to something else" is exactly the state that's invisible in a plain log stream.
Agreed, and that's why the hook only ever exits 0 and writes {}: if you add an enforcement hook, keep it as a separate entry so a bug in one can't touch the other. reviewAttention ended up being one of the most useful signals in the office.
Ignoring the one-minute idle notification is the detail that makes this actually usable. Hooking into PermissionRequest instead of generic idle state is the difference between a dashboard you can glance at and an alert firehose.
Appreciate the constraint of rendering with raw canvas fillRect instead of pulling in an entire frontend framework just to show four agent states. Keeping local harness observability lighter than the agents themselves saves a lot of homelab headaches.
Thanks! That filter is still there: only notifications that ask you for something (permission, a question) raise a hand. And the zero-dependency canvas has held up through 20+ releases, HD themes included.
The read-only rule is the right primitive. Two things worth hardening:
The hook script rewrites claude-code.json on every event from possibly several concurrent Claude Code sessions — a reader can catch a half-written file mid-write. Write to a temp file and atomic-rename instead, and key events by session id so two agents editing don't clobber each other's bubbles. Same fix also makes replay mode cheap later: one append-only JSONL line per event means replay is just reading yesterday's file.
GET-only covers the transport, but the rendered page is itself an egress surface: task names and tool-call text pulled from Paperclip or Claude Code can contain secrets (paths, env refs). Worth documenting what is safe to display — or hashing/redacting tool names in the bubble — since the dashboard will be on a shared TV in kiosk mode.
The reviewAttention catch is the best part: that state is genuinely invisible in a log stream.
Thanks, all three of these made it in. The hook now writes to a temp file and renames it, so readers never see half a file, with one character per session id (and per subagent). Replay mode (?replay) shipped in v0.10. For shared screens there's --redact, which strips task titles, commands and error text on the server, so a kiosk shows only ids, tool names and statuses. I wrote up what changed here: dev.to/megventure/what-your-commen...
Watching all three land in one release is the exact return I hoped for when writing them — and your details are better than mine: the temp-file-and-rename closes the half-file window I didn’t even see, and per-session-id scoping makes it audit-proof by construction. One question on --redact: does it run before the receipt is hashed? If the receipt binds the raw payload, a shared kiosk still leaks byte-lengths even with titles stripped — cheap fix if so: hash the redacted view instead, so kiosk and operator see the same shape. Reading the write-up now; comments that ship features deserve follow-ups that ship changelogs.
Sounds like a plan — let's compare notes when the p95 dashboard is live. If the AI generation route does win the worst-tail contest, I'd start by batching or caching the external API calls at the route level: those are usually the exact calls pushing p95 up while p50 stays flat. Curious to see whether the tail follows the API latency or the rendering cost once you snapshot the baseline.
We need to produce a comment as a regular developer, short, specific reaction or question about this video. No quotes, no labels, no hashtags, no markdown. Should start with lowercase letter. Should be about the video: maybe ask about how they set read-only, or why they made it read-only, or mention something about Claude Code sessions. Keep short, one or two sentences, maybe a fragment. Must not be promotional. Use casual voice. Avoid double hyphens. Must not use em-dash. Use straight quotes
Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more