README.md

June 3, 2026 · View on GitHub

clu

clu — agent-coordination issue tracker

🤖 SQLite-backed issue tracker for coordinating AI coding agents on a single machine.
Named after Tron's Codified Likeness Utility.

Build status Go report card Go reference Go 1.26+ SQLite (pure Go) MIT licensed No CGo No network

Documentation

Docs · Quickstart · Multi-agent · Bulk graphs · Workflows · Agent guide · Design


🤔 Why?

When you run more than one AI coding session against the same project, they need a shared, durable place to:

  • 🤝 pick up work without stepping on each other (atomic claim),
  • 📝 record what they tried, what worked, what didn't,
  • 🚦 gate risky steps behind human approval,
  • 🔓 surface what's unblocked vs. waiting on something else.

clu is that place. A small, fast, single-binary CLI backed by a local SQLite database. No daemon, no server, no account, no network.

✨ Highlights

📦 Single binary, pure GoSQLite via modernc.org/sqlite — no CGo, no system libs.
💾 Local-firstOne file: .clu/data.sqlite. Commit config.yaml, gitignore the DB.
Atomic claimUPDATE … RETURNING with subquery — racing agents get different issues.
🎯 Capability routingAgents declare capabilities in config.yaml; cap:foo labels flow to matching agents.
🕸️ Bulk graph instantiationclu batch turns one JSON doc into a whole validated graph (thousands of issues + deps) in one transaction — generate it with any script.
🌊 Cascading cancelclu cancel <id> walks the dep graph forward and cancels the whole tail.
🏁 Milestones & phasesmilestone issues auto-close when their dependencies do — self-completing umbrellas and automatic phase boundaries.
🧾 Audit logEvery write is recorded; clu history <id> and clu log show who did what, when.
🧠 Context inheritanceclu claim <id> --context prints the upstream chain (notes + comments) so an agent inherits what was done before.
🚀 Agent launcherclu agent start <name> spawns a configured agent (command + layered prompts) and heartbeats it.
📋 Workflow templatesYAML graphs of issues + deps with optional human-approval checkpoints.
🔒 Named locks + mailboxTTL'd clu lock for shared resources; clu ping/clu inbox for fire-and-forget inter-agent messages.
🖥️ Web UIclu web — a local dashboard: list, kanban, dependency graph, approvals.
🧾 JSON everywhereEvery command takes --json and emits exactly one JSON value to stdout.
🌿 Branchless sync (experimental)clu sync stores issues on a dedicated git ref (refs/clu/store) — branch-independent, no conflicts with code, syncs across clones. Details.
🔒 No networkNo telemetry, no cloud, no account. Sharing is opt-in over your own git remote.

🖥️ Web UI

clu web serves a local dashboard (default :5757) over the same SQLite file — no extra config, no separate API to run.

Kanban board grouped by status with assignee avatars and in-progress timers
Board — kanban by status, with avatars and how long each task has been in progress.
Issue list with priority, status and started-at timestamps
List — every issue with priority, status, and started_at ("started 3h ago").
Approvals view showing pending checkpoint cards
Approvals — pending checkpoints with suggested approvers and what they block.
Issue detail with checkpoint gate and dependency edges
Detail — status/priority/type controls, the checkpoint gate, and dependency edges.

📦 Install

# From a clone (installs the CLI + the web UI bundle):
make install

# Or, just the CLI:
go install github.com/arjia-labs/clu/cmd/clu@latest

make install runs go install and then clu web --install, which builds the web UI (pnpm install + pnpm build) and copies the output to ~/.local/share/clu/web so clu web works from any directory. Skipped silently if pnpm isn't on PATH — the CLI works without the UI.

Add $HOME/go/bin to your PATH. Verify with clu --help.

🚀 Quickstart

mkdir my-project && cd my-project
clu init                                       # 📂 creates .clu/ with DB + config
clu create -p 1 "fix the login redirect"             # → clu-a3f81b
clu create -d clu-a3f81b "add tests for the redirect"  # 🔗 wires the dep atomically

clu ready                                      # 🟢 what's unblocked?
clu claim --context                            # 🎯 take the next one + see its upstream context
clu close clu-a3f81b                           # ✅ done → unblocks the tests
clu ready                                      # 🟢 tests are now ready

That's the whole core loop. See demo.sh for an end-to-end exercise, or AGENTS.md for the agent-facing operational guide. From inside an agent session:

clu brief

prints the agent guide plus the project's declared agents and who's currently live — pipe it into your agent at session start. 🧠

🚦 Status semantics

statusmeaningdownstream effect
🟢 opennot yet startednormal
🟡 in_progressclaimed; an agent is workingnormal
closeddone successfullyunblocks dependents
cancelledabandoneddependents stay blocked (or cascade-cancel)

clu cancel <id> marks the target and all transitive descendants as cancelled — the cascade is the whole point of having a status distinct from closed. clu reopen <id> reverses either terminal state.

stateDiagram-v2
    [*] --> open: create
    open --> in_progress: claim
    in_progress --> closed: close
    in_progress --> cancelled: cancel
    open --> cancelled: cancel
    closed --> open: reopen
    cancelled --> open: reopen
    closed --> [*]
    note right of closed: unblocks dependents
    note right of cancelled: cascade-cancels the tail

Two type-driven behaviours sit on top of the status loop: a checkpoint issue is a manual gate (stays checkpoint:pending until clu approve), and a milestone issue auto-closes when all its dependencies close — the self-completing umbrella behind clu batch --group and phase boundaries. Issues also carry a started_at (set on claim, distinct from updated) so clu show, the web list, and doctor's stuck-check know how long something's actually been in progress.

🤖 Multi-agent setup

Declare your agents in .clu/config.yaml:

id_prefix: clu-
agents:
  code-reviewer:
    description: "Reviews Go code for correctness and security"
    capabilities: [go-review, security-review]
  doc-writer:
    description: "Writes README + docs/ updates"
    capabilities: [docs]

Then each agent claims from its own lane:

clu claim --agent code-reviewer --wait --heartbeat

--heartbeat is opt-in; without it the claim loop doesn't advertise liveness. With it, clu agent ls shows who's online and when they were last seen.

Coordinators route work by either assigning directly (clu create -a doc-writer ...) or tagging capability (clu create --capability docs ...). Capability-tagged issues in the default lane flow to whichever agent advertises that capability.

🚀 Launching agents

Give an agent a launch spec in config.yaml and start it with one command:

agents:
  code-reviewer:
    capabilities: [go-review]
    command: claude                     # the executable to run
    prompts: [SOUL.md]                  # files under .clu/agents/code-reviewer/
    startup_prompt: "Check clu inbox -a code-reviewer, then claim ready work."
clu agent start code-reviewer          # exec the agent, heartbeating while it runs
clu agent start code-reviewer --print  # just show the assembled command

Any *.md in .clu/agents/_shared/ is prepended to every agent (a common AGENTS.md / AUTONOMY.md lives in one place, not copied per agent); the agent's own prompts layer on top. clu stays runtime-agnostic — command can be claude, codex, or anything.

🧠 Inheriting context

When an agent picks up a dependent task, --context walks the upstream chain and prints each prerequisite's description, notes, and comments — the story of what was done before:

clu claim <id> --context        # on claim
clu show <id> --context         # any time

👁️ Watching for work (the killer combo)

In Claude Code, point the Monitor tool at clu ready --watch -a <your-name> and you've got a push-style task feed: clu suppresses unchanged ticks, Monitor turns each new state into one notification. No polling loops, no while true, no diff against seen.

Monitor: clu ready --watch -a code-reviewer

See AGENTS.md for the full pattern.

🕸️ Bulk graphs (clu batch)

clu run is for hand-authored YAML. When you want to generate work — import a backlog, fan out a migration across modules, or build a thousand interdependent tasks — produce a JSON document with any tool and pipe it to clu batch. clu validates the whole graph (acyclic, every reference resolves, fields valid) and writes it in one transaction: a single bad entry aborts everything, so you never get a half-built graph.

generate-graph | clu batch --dry-run                 # validate + stats, write nothing
generate-graph | clu batch --group "Auth rollout"    # commit under a self-completing umbrella

The contract is just JSON — an array of issues that reference each other by local alias:

[
  {"alias": "design", "title": "Design auth", "priority": 1},
  {"alias": "impl",   "title": "Implement auth", "needs": ["design"], "capabilities": ["go"]},
  {"alias": "gate",   "title": "Approve release", "needs": ["impl"], "checkpoint": {"approvers": ["alice"]}},
  {"alias": "ship",   "title": "Ship", "needs": ["gate"]}
]

Run clu batch --docs for the full field reference. Highlights:

  • needs takes aliases or existing real issue IDs — so a generated subgraph can hang off the committed graph.

  • checkpoint makes an issue a manual approval gate (same as a clu run checkpoint).

  • key (e.g. "linear:ENG-123") makes re-running idempotent--on-existing skip (default) won't duplicate it; --on-existing update re-syncs its fields. Perfect for a recurring import:

    linear issue query --json | node examples/generators/linear-todo.js | clu batch --on-existing update
    

This is the generation / instantiation split: any language emits the graph (loops, conditionals, computed fan-out — things a static template can't do); clu owns validation and atomic instantiation. See examples/generators/ for a zero-dependency JS helper (clu.js) with a phase() builder, plus runnable examples (feature rollout, release train, phased migration, Linear import).

📋 Workflows

Drop a YAML template into .clu/templates/:

name: release
vars:
  version: { required: true, pattern: '^\d+\.\d+\.\d+$' }
steps:
  - id: build
    title: "Build {{version}}"
  - id: test
    title: "Test {{version}}"
    needs: [build]
  - id: approve
    type: checkpoint                          # 🚦 human gate
    title: "Approve {{version}} for prod"
    wait: { approval: [alice, bob] }
    needs: [test]
  - id: deploy
    title: "Deploy {{version}}"
    needs: [approve]
clu run release -v version=1.2.3   # → parent + 4 children + deps in one shot

Agents drive it by claiming ready issues as each step closes; humans clear checkpoint gates via clu approve <id>. Failing a checkpoint cascade-cancels the rest of the run. See demo-workflow.sh for the full demo.

clu batch is the programmable superset of clu run — same checkpoints and grouping, but the graph comes from code instead of a YAML template.

🧾 Audit & history

Every write is recorded in an append-only event log:

clu history <id>                      # full timeline of one issue (who/what/when)
clu log --kind claimed --since 24h    # global stream, filterable by actor/kind/issue/since

The actor is the resolved --agent (or $USER); payloads record just the changed fields. The log is local — it's not part of clu export (which carries portable state, not history).

🖥️ Web UI

clu web        # serves a local dashboard (default :5757)

A read/write dashboard: filterable issue list, kanban board, dependency graph for a run, and an approvals queue for pending checkpoints. Backed by the same store via an in-process REST API (clu http exposes that API standalone).

🤝 Coordination primitives

Beyond the issue graph, for things that don't fit a ticket:

clu lock deploy --ttl 1h -- ./deploy.sh prod   # TTL'd named lock; auto-released, leak-proof
clu ping code-reviewer "PR #412 ready"          # fire-and-forget message (TTL'd, off the work log)
clu inbox -a code-reviewer                       # read your messages
clu worktree add feature-x --bootstrap           # git worktree + project-defined setup
clu sync push --remote origin                    # publish issues to a branch-independent git ref (experimental)

clu sync (experimental) keeps the tracker on a dedicated refs/clu/store ref — so any checkout sees the same issues, writes never collide with code commits, and a teammate's clone can clu sync pull --remote origin to get the backlog without the DB ever being committed. See the sync docs.

📁 Layout

cmd/clu/                 ⌨️  entrypoint
internal/cli/            🧩 one file per kong subcommand
internal/store/          💾 SQLite layer, split by domain
  ├── models.go            bun model types
  ├── migrations.go        manual migrations (PRAGMA user_version)
  ├── issues.go            create/get/close/reopen/cancel/update
  ├── claim.go             ready/claim atomic queries
  ├── deps.go              dependency edges + cycle detection
  ├── batch.go             validated bulk graph instantiation
  ├── milestone.go         auto-close cascade
  ├── events.go            append-only audit log
  ├── context.go           ancestor-context walk
  └── …                    labels, comments, kv, cron, agents, locks, mailbox, doctor
internal/workflow/       📋 YAML template loader + planner
internal/http/           🌐 REST API (backs the web UI / `clu http`)
internal/config/         ⚙️  config.yaml parsing
web/clu-web/             🖥️  TanStack Start web dashboard
examples/generators/     🕸️  codemode graph generators for `clu batch`
.clu/                    📂 per-project storage (DB, config, templates, agents/)

🧠 Design notes

flowchart TB
    subgraph clients["Clients"]
        cli["clu CLI<br/>(Kong)"]
        agents["AI agents<br/>(claim · comment · close)"]
        batch["generators<br/>(node script | clu batch)"]
    end
    subgraph core["clu binary"]
        cmds["internal/cli<br/>one file per command"]
        http["internal/http<br/>REST API"]
        store["internal/store<br/>Bun + sqlitedialect"]
        wf["internal/workflow<br/>YAML planner"]
    end
    web["web/clu-web<br/>TanStack dashboard"]
    db[(".clu/data.sqlite<br/>single file, pure-Go driver")]

    agents --> cli
    cli --> cmds
    batch --> cmds
    cmds --> store
    wf --> cmds
    http --> store
    web --> http
    store --> db
  • 🪪 One identity flag. -a / --agent is both the lane filter and the actor identity. No --as — single-user local tool, the user/agent distinction was deliberately collapsed.
  • 🗄️ Hand-rolled migrations via PRAGMA user_version. Append-only, never edit an applied migration.
  • 🛠️ Bun + sqlitedialect for queries. Raw SQL escape hatches in exactly two places: the atomic claim, and the cancel-cascade CTE.
  • 🎀 Kong for the CLI struct, with struct-tag commands and intermixed flags.

The rationale for each sticky decision lives in CLAUDE.md.

🙅 Not in scope

clu is deliberately small. It does not try to be:

  • 🔄 a live, server-backed sync layer with cell-level merge — for sharing across machines there's the experimental clu sync git ref (manual push/pull over your own remote), not a always-on sync server
  • 📊 a generic project-management tool — no sprints, milestones, OKRs
  • 🔗 a live bridge to GitHub / Linear / Jira. (You can still import from anything by piping a generated graph to clu batch; an idempotent key keeps re-runs clean — see examples/generators/linear-todo.js.)
  • 🤖 an agent runtime — you are the agent; clu just gives you somewhere to put the work

🤝 Contributing

PRs welcome. Before sending:

go build ./... && go test ./...
./demo.sh && ./demo-workflow.sh

See CLAUDE.md for code conventions (one file per kong command, sentinel errors per entity, JSON-clean output, etc.).

📜 License

MIT — see LICENSE.

Built for the era of many small agents working together. ⚡