All projects

Side project · macOS desktop app

Agents City

I run a lot of Claude Code agents in parallel. Agents City turns them into a living isometric city, so I can tell who's working, who's done, and who needs me without opening a single terminal.

Role

Solo: design, engineering, product

Platform

macOS (Electron) + browser mode

Year

2026

Status

Daily driver

All screens are recreations with invented data. The real app's UI is in Spanish.

The problem

Ten agents, ten terminals, zero overview.

My day job and side projects run on Claude Code: interactive sessions, background agents (claude --bg) and the subagents they spawn. On a busy day that's ten or more agents across five repos.

The bottleneck stopped being the agents and became me. An agent would sit blocked for twenty minutes waiting for a yes/no, while I was staring at another terminal. Finished work went unnoticed. I had no picture of what was happening where.

01Blocked agents waited unnoticed
02No idea which repo each agent was touching
03Switching terminals just to check status
04Dev servers left running on random ports
What it does

A map of everything my agents are doing.

01

Your folders become a city

Every folder in ~/dev becomes a city, sub-folders are districts and each repo is a building. Buildings grow with the project's history, light their windows when agents are inside and put up scaffolding while one is building something.

  • Agents are people: hard hat when working, “z” when idle, red bubble when they need you
  • Subagents are little robots that patrol the block, colored by type (Explore, Plan…)
  • A speech bubble shows the live tool call: 💻 Bash pnpm test, ✏️ Edit hero.tsx
  • City tabs, a ⌘K jump palette and a roster grouped by “needs you / working / waiting / done”
Close-up of a district. Height reflects how many sessions a repo has had; lit windows mean agents are inside.

02

Talk to any agent from the map

Click an agent to open its conversation, rendered as markdown with every tool call and running subagent. From the same panel I send follow-up orders, pick model, permission mode and effort, attach screenshots or queue work for when the turn ends.

  • Interrupt-and-send, queue, hand off to another agent, stop or remove
  • Rename an agent mid-turn: applied safely once the turn ends
  • Prompt history with ↑ / ↓, just like a shell
  • Dark mode with stars, because agents work late too
Night mode with an agent's conversation open: tool calls, a running Explore subagent and the order form.

03

A terminal and dev servers, per building

Click a building to launch a new agent there, or prefix the order with ! to run a shell command in that repo without going through any agent. Output can be shared with an agent in one click, which is perfect for “here's the error, fix it”.

  • Runs through the login shell, so aliases and .zshrc just work
  • Exit code and timing per command, last five kept
  • Running dev servers detected per repo and worktree, with a stop button
  • Start a dev server from the panel, on any branch or worktree
Building panel in ! terminal mode, with command history and the repo's running dev servers.

04

It taps you on the shoulder

The menu bar shows “❗ blocked · working” at a glance. Native notifications fire when an agent needs me, when a background agent finishes, or when a long turn ends while I'm in another app. Clicking one flies the camera to that agent.

  • Dock badge with the number of blocked agents
  • Per-city mute and per-type notification toggles
  • No noise: the first snapshot is a baseline and short turns are ignored
  • Opens at login and lives in the menu bar when the window is closed
Menu bar status, native notifications and the Dock badge.
Visual language

Four states you can read from across the room.

Working

Hard hat, walking, live tool bubble. The building's antenna blinks.

Needs you

Pulsing red “❗ Te necesita” bubble. Notification and Dock badge.

Waiting

Standing still with floating z's. Ready for the next order.

Done

Green “✅ Listo” bubble. Fades out after the session ends.

How it works

No database, no cloud. Just files, a watcher and a canvas.

Claude Code already writes everything to disk. Agents City watches those files, builds a model of the world in a Node process inside Electron, and streams changes to a Canvas 2D renderer over Server-Sent Events.

Sources on disk

~/.claude/sessions/*.json

~/.claude/projects/**/*.jsonl

~/.claude/jobs/*/state.json

~/dev folder tree

lsof (dev servers)

Node core (Electron main)

watcher

fs.watch + 150 ms debounce + 2 s poll

collector

incremental JSONL by byte offset

world model

cities → districts → buildings

actions

claude CLI via login shell

What you see

Canvas 2D city

SSE /api/events

Menu bar + Dock

tray rebuilt ≤ 1/s

Notifications

diffed against a baseline

Problems worth solving

01

fs.watch is unreliable on macOS

Events get dropped or duplicated. I combined a 150 ms debounced watcher with a 2 s polling fallback and only emit when the serialized snapshot actually changes.

02

Transcripts grow forever

Session logs are append-only JSONL that can reach many MB. The collector reads incrementally from the last byte offset and resets if the file shrinks.

03

A localhost API anyone could call

The server binds to 127.0.0.1 on a random port, injects a per-launch token into the page and checks Host and Origin, so other browser tabs can't drive your agents.

04

Notifications that don't cry wolf

The first snapshot only sets a baseline, turn-end alerts need 30 s+ and an unfocused window, and every city can be muted.

05

Apps launched from Finder have no shell

Agent commands run through $SHELL -lic so PATH, aliases and version managers behave exactly like in the terminal.

06

Two agents, one worktree

Resuming a background agent with new options can spawn a twin on the same worktree. The app detects twins and removes worktrees safely, keeping or committing dirty changes first.

0

runtime dependencies

~8.6k

lines of TypeScript and JS

19

agent actions exposed by the local API

150 ms

from a file change to the city

Built with

ElectronTypeScriptNode.jsCanvas 2DServer-Sent EventsesbuildClaude Code CLI

Want something like this for your team?

I build internal tools, AI agents and the dashboards that keep them in check.