Skip to content

gordonbrander/busytown

Repository files navigation

busytown

A town full of busy little guys who do things.

busytown is a multi-agent coordination framework built around a shared SQLite event queue. Each agent is a separate Claude Code instance. Agents listen for events, react to them, and push new events, forming an asynchronous pipeline where no agent needs to know about any other agent, only about the events.

How it works

Everything is stored in a single SQLite database (events.db). Events are simple JSON objects:

{
  "id": 1,
  "type": "plan.request",
  "timestamp": "...",
  "worker_id": "user",
  "payload": { "prd_path": "prds/my-feature.md" }
}

The agent runner polls the queue and dispatches events to matching agents. Agents run in parallel, but each agent processes events serially, one at a time, in order. The agent:

  • Listens for specific event types (exact match, prefix glob like file.*, or wildcard *)
  • Reacts by reading files, writing code, producing artifacts
  • Pushes new events to notify other agents of what it did
  • Claims events when needed, so only one agent acts on a given event

Agents can also use the filesystem as a shared workspace for saving memories, plans, WIP, etc.

Architecture

┌──────────┐    push     ┌─────────────────┐    dispatch    ┌───────────┐
│  User /  │───────────▸ │  SQLite Event   │ ─────────────▸ │  Agent    │
│  Script  │             │  Queue          │                │  (Claude) │
└──────────┘             │                 │ ◂───────────── └───────────┘
                         │  events table   │    push new
                         │  worker_cursors │    events
                         │  claims         │
                         └─────────────────┘
                               ▲     │
                               │     ▼
                         ┌───────────┐
                         │  Agent    │
                         │  (Claude) │
                         └───────────┘

Getting started

Prerequisites: Deno and Claude Code installed.

Install

# Install the `busytown` command globally
deno task install

This creates a busytown command you can run from any directory.

1. Start the agent runner

# Run in foreground
busytown run

# Or start as a background daemon
busytown start

2. Push an event

busytown plan prds/my-feature.md

3. Watch it go

The runner picks up the event, dispatches it to matching agents, and each agent pushes new events that trigger the next stage. Every event is streamed to the terminal as ndjson.

Included agents

The agents/ directory ships with a plan-code-review loop:

Agent Listens for Does Pushes
plan plan.request, review.created Reads a PRD, explores the codebase, writes an implementation plan to plans/ plan.created
code plan.created Follows the plan to implement code changes code.review
review code.review Reviews the diff for correctness, writes a review to reviews/ review.created (verdict: approve or revise)

If the reviewer says "revise", the plan agent picks it up and the loop continues until approval.

Writing your own agent

An agent is a markdown file in agents/ with YAML frontmatter:

---
description: Summarizes new documents
listen:
  - "file.create"
allowed_tools:
  - "Read"
  - "Write"
---

You are a summarizer agent. When a new file is created, read it and write a
summary to `summaries/<filename>.md`.

After writing the summary, push an event:

    busytown events push --type summary.created --worker summarizer \
      --payload '{"path": "summaries/<filename>.md"}'

Each agent runs as a headless Claude Code subprocess (claude --print), sandboxed to only the tools you allow.

The agent's markdown body becomes its system prompt. The runner automatically injects context about the event queue CLI so agents know how to push events.

Fields:

  • type — Agent type (default claude)
  • description — What the agent does (included in its system prompt context)
  • listen — Event types to react to. Supports exact match (plan.created), prefix glob (file.*), or wildcard (*)
  • allowed_tools — Claude Code tools the agent can use (e.g. Read, Write, Edit, Grep, Glob, Bash(git:*)). Only applies to claude agents.
  • model — Claude model to use (e.g. haiku, sonnet, opus, or a full model ID). Optional; defaults to whatever model the user has configured globally.
  • effort — Thinking effort level: low, medium, or high. Optional; defaults to the CLI default.

Shell agents

For lightweight tasks that don't need an LLM — formatting, linting, running scripts — use type: shell instead. The text body becomes a shell script (run via sh -c) with Mustache-style template placeholders. The body can be a single command or a full multi-line script — variables, conditionals, loops, pipes, and heredocs all work:

---
type: shell
listen:
  - "file.create"
---

path={{event.payload.path}} echo "Processing $path"
deno fmt "$path" busytown events push --type file.formatted --worker formatter

Template syntax:

  • {{key}} — Resolves the value and shell-escapes it (single-quote wrapping). Safe by default.
  • {{{key}}} — Resolves the value and inserts it raw (no escaping). Use when you need unquoted paths or command fragments.
  • Dot paths like {{event.payload.path}} walk nested objects.
  • Missing keys resolve to an empty string.

The template context contains the full event object as event, so any field from the event (id, type, timestamp, worker_id, payload) is available.

Shell agent stdout and stderr are logged to logs/<agent-id>.log, the same as Claude agents.

CLI

After installing (deno task install), use busytown from any directory:

busytown <command> [options]

Event queue commands

# Push an event
busytown events push --type my.event --worker my-script --payload '{"key":"value"}'

# Query events
busytown events list --type plan.* --limit 10 --tail

# Watch for new events (streams ndjson)
busytown events watch --worker my-watcher

# Check a worker's cursor position
busytown events cursor --worker my-agent

# Claim an event
busytown events claim --event 42 --worker my-agent

# Check who claimed an event
busytown events check-claim --event 42

Agent runner commands

busytown <command> [options]

# Commands
run                    # Run poll loop in foreground
start                  # Start as background daemon
stop                   # Stop the daemon
restart                # Restart the daemon
status                 # Check if daemon is running
plan <prd-file>        # Push a plan.request event

# Options
--agents-dir <path>    # Agent definitions directory (default: agents/)
--db <path>            # Database path (default: events.db)
--poll <ms>            # Poll interval in ms (default: 1000)
--agent-cwd <path>     # Working directory for agents (default: .)
--watch <paths>        # Paths to watch for FS changes (default: .)
--exclude <patterns>   # Glob patterns to exclude from watching

Dashboard

busytown includes a terminal dashboard for monitoring agent activity:

busytown dashboard

The dashboard tracks agent status, file events, active claims, and events. It also surfaces tool permission requests from agents.

Keyboard shortcuts:

Key Action
j/k Scroll the event stream up/down
y/n Allow / deny a permission request
s Toggle system events on/off
Tab Switch focus between panels
q Quit

Options:

busytown dashboard --db events.db --poll 500
  • --db <path> — Database path (default: events.db)
  • --poll <ms> — Poll interval in ms (default: 500)

File system watcher

The runner watches directories for file system changes and pushes file.create, file.modify, file.delete, and file.rename events. By default it watches the current directory (.).

# Watch specific directories
busytown run --watch src --watch docs

# Exclude patterns (glob syntax)
busytown run --exclude '**/dist/**' --exclude '**/build/**'

Common paths (.git, node_modules, .DS_Store, *.pid, *.log, events.db*) are excluded automatically.

This lets you build agents that react to file changes — useful for auto-indexing, summarization, or triggering builds.

Key concepts

  • Cursor-based delivery — Each worker maintains its own cursor in the queue. The cursor advances immediately when an event is read, before it is processed, giving at-most-once delivery. Workers only see events newer than their cursor.
  • First-claim-wins — When multiple agents listen for the same event type, claimEvent() ensures only one agent processes a given event.
  • Namespace wildcards — Event types use dot-separated namespaces. Agents can listen for file.* to catch file.create, file.modify, etc.
  • Agents are just markdown — Agent definitions are markdown files. The body is the system prompt, the frontmatter is config. Easy to version, review, and iterate on.
  • No agent coupling — Agents don't know about each other. They only know about events. You can add, remove, or swap agents without changing anything else.

License

MIT

About

Multi-agent factories coordinated over a SQLite queue

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors