A single-purpose web board for operating Claude Code background agents across many repositories — with GitHub issues as the backlog. Zero npm dependencies, no build step, self-deploying.
Agge is an operator console, not a project tracker. It serves one person running a fleet of coding agents, and it answers three questions at a glance: what needs me, what's running, what's queued — and each of those words in the header is a control.
Operating mode. The attention strip holds every judgment moment — an agent waiting on your call, a green PR ready to merge, a conflict being auto-resolved — above lanes that mirror live session state.
idea ──capture──▶ issue ──plan──▶ planned ──dispatch──▶ agent works ──▶ PR
▲ │ │
└────────────────── merge closes the issue ◀──── merge button ◀────────┘
- Capture an idea from the board (planning mode, or + new) → it becomes a GitHub issue. There is no local task store; gh is the source of truth, the board is a view + dispatcher.
- Dispatch an issue →
claude --bglaunches an agent named#N <title>, prompted to implement and open a PR withCloses #N. An optional per-task instruction note rides along. - WIP cap: each repo runs at most
CCB_MAX_ACTIVEagents at once (default 6, the cap Codex ships). Dispatch beyond that is queued — the held cards show in the lanes asqueuedand auto-launch as slots free (a merge, a stop, or an agent finishing). A queued issue clears its dependency gate again on the way out, so a blocker added while it waited still holds it — and it stays held until the issue sweep has actually read that repo, because an unread repo means unknown, not unblocked. This bounds the number of simultaneously-diverged branches, the dominant driver of merge conflicts (research). Conflict resolvers are exempt;CCB_MAX_ACTIVE=0disables the cap. - Watch: cards show live status from the daemon, a liveness line (● active — current tool / ○ quiet 14m) from the session transcript, and the agent's full thread (markdown-rendered) in a detail drawer.
- Attention strip ("NEEDS YOU"): agents waiting on an answer, PRs ready to merge (with CI state and diff stats), merge conflicts, blocked self-deploys — every judgment moment in one place, with an explicit calm state.
- Merge from the card: merges the PR, deletes the branch, reclaims the agent (
claude rm), and the issue auto-closes — the work item simply leaves the board. - Conflicts auto-resolve: a CONFLICTING PR — whether linked to an issue or opened by a free-form session — dispatches a resolver agent (the PR author's own session resumed as a fork when it still exists), once per PR.
- Open PRs at a glance: a third header view (
prs, beside operate/plan) lists every open PR across your repos — others' branches and orphaned PRs the board never triggered, not just its own work — each with CI state, diff size, author, branch, and a● on boardmark (a shortcut into the session) on the ones an agent is already tracking. The segment's count is the untracked PRs — your orphan radar. On-demand with a refresh button, so it spends no GitHub budget beyond one seed fetch (off the live snapshot and the issue sweep). - Self-deploy: when this repo's own
mainadvances, the board fast-forwards its checkout and restarts itself — merging an Agge PR is the deploy. Dirty/diverged checkouts block loudly.
The thread. Every card opens to the full conversation: the dispatch brief, tool calls, the agent's reasoning rendered as markdown, your own interventions as elevated blocks — and when the agent parks on a question, it sits pinned at the top with claude attach <id> one copy-click away. A structured AskUserQuestion is laid out in full there — every option with its description and any preview, the whole group if it asked several — so you can read the choices without attaching (a ❓ on the card flags it from the board).
Planning. The backlog is GitHub issues: capture a one-liner into a real issue, label it planned, dispatch it — without leaving the board. Blocked dependencies show up as exactly that.
Planning mode, light theme. Light and dark are separate designs, not inversions.
And for operators who'd rather it looked like 1996: the pop theme — same grammar, louder chrome.
- No status interpretation. Claude Code's session-status vocabulary is unstable (observed shifting in the wild). Lanes are created from the data: one lane per distinct status string, color from a stable hash of the string, no status→meaning map anywhere. The attention strip keys on field presence and GitHub enums only.
- gh is truth. Open issues are the backlog; labels
planned/starteddrive lanes; GitHub's issue↔PR link drives the merge button. The board persists only genuine user preferences (lane order, server-side). - Views, one screen. The header switch flips the board surface in place — Operating (default): attention strip + in-motion lanes; Planning: dense backlog/planned rows with capture; PRs: every open PR across the repos, grouped, as an orphan radar. The attention strip and repo filter stay put across all three; each segment carries its count.
- Quiet grammar. Two hues at rest — amber means needs your judgment, green means go — everything else neutral. Spacing over borders. Mono for identifiers only. Actions ghosted, never hidden. Light and dark are separate designs: dark elevates with lighter surfaces, light elevates with shadows on warm paper. A third opt-in skin, pop (90s Miami: ink-line enclosures, hard shadows, sunset wash), keeps the same grammar — its candy chrome is uniform, so color still carries meaning, never identity.
- Escape-first rendering. All markdown (issue bodies, agent prose) is HTML-escaped before any transform; only
https?:URLs become links. - Mobile is a first-class layout, not a shrink. Under
max-width:720pxthe board goes single-column, the mode switch drops to a full-width segmented control, and the repo filter becomes one swipeable row; the create/dispatch modal turns into a bottom sheet that rides above the on-screen keyboard (--kb, published from the VisualViewport inmain.js).viewport-fit=cover+env(safe-area-inset-*)keep every surface off the notch and home indicator, and form fields stay ≥16px on a coarse pointer so iOS never zoom-jumps on focus. Don't regress these when restyling the header, modal, or lanes.
node agent-board.js # http://localhost:4317
CCB_HOST=tailscale node agent-board.js # also bind the machine's Tailscale IP (never the public NIC)
node agent-board.js --root ~/src # repos root to auto-discover (default ~/code)Repos are auto-discovered: every git checkout under the root appears, including one filed under a grouping folder (<root>/clients/acme shows as clients/acme) — three levels down by default (CCB_ROOT_DEPTH), and a checkout's own contents are never searched. No list to maintain. Requires the claude CLI (the daemon does the real work) and an authenticated gh.
Runs durably as a systemd user service — see agent-board.service (install: copy to ~/.config/systemd/user/, daemon-reload, enable). Env knobs: CCB_PORT, CCB_HOST, CCB_REFRESH_MS, CCB_BACKSTOP_MS, CCB_GH_MS (GitHub-issue re-sweep — sized against GitHub's 5,000-points/hour GraphQL budget; see lib/config.js), CCB_GH_TRIES, CCB_DEPLOY_MS (self-deploy git-fetch poll), CCB_MAX_ACTIVE (per-repo cap on simultaneously-active agents — default 6; further dispatches queue and auto-start as slots free; 0 = unlimited), CCB_ROOT_DEPTH (how far below the root discovery looks — default 3, max 8); paths via --root/CCB_ROOT and repeatable --repo/CCB_REPOS for out-of-root extras.
Security model: every API route is host-guarded (exact Host allow-list — DNS-rebinding defense); actions are POST + same-site-gated (CSRF); all child processes are execFile argv, never shell; it never binds 0.0.0.0. Intended for localhost/tailnet use by one trusted operator — there is no auth layer.
agent-board.js entry: wiring + boot order
lib/
config.js env/argv/constants
sessions.js claude daemon data: live roster + jobs/<id>/state.json, normalize
timeline.js thread building: timeline turns + transcript tail, PR scan, liveness
github.js gh GraphQL cache: issues, labels, linked PRs, retry + stale-keep
snapshot.js the merge: sessions + issues → cards; change signature
resolver.js conflict auto-resolve dispatcher
deploy.js self-deploy (ff-only pull + restart on remote advance)
live.js SSE push: fs watchers, debounce, signature-gated broadcast
http.js routes, host/CSRF guards, static serving, listeners
prefs.js server-stored user preferences (lane order)
attachments.js operator-attached files: store, hand paths to the dispatched agent
public/ the browser app — plain script-tag globals, no bundler;
served per-request, so client changes apply on reload
The server pushes snapshots over SSE when content actually changes (file watchers + backstop poll); the client renders lanes/cards/strip from each snapshot and never touches the DOM when markup is unchanged.
Built incrementally by Claude Code working with its operator — including many of its own features, dispatched as issues from this very board, implemented by background agents, and merged (then self-deployed) from the cards they produced.
Elastic License 2.0 — free to use, copy, modify, and distribute, including at work and commercially. The carve-out: you may not provide Agge to third parties as a hosted or managed service (no selling it as a SaaS).
Copyright © 2026 sefr-sefr
