ait is a small, local-first issue tracker built primarily for coding agents. Heavily inspired by Steve Yegge's beads project. Just pruned down to the essentials I need.
It is intended to help an agent turn a plan into structured work, track dependencies, preserve notes between sessions, and quickly answer a practical question: what should I do next?
Repository: https://github.com/ohnotnow/agent-issue-tracker
Sibling projects:
- web-ait — web UI for monitoring agent progress
- ant — agent-first note/ADR/"why" tracker to compliment ait's "what"
This project is working - but use at your own risk. It's been used on real projects, but no guarantees are made about API stability or compatibility. However, of note - it's effectively feature complete. The project has a fairly tight scope - and all expected features are implemented. If you need something 'fancier' - then look for another tool.
Schema changes are managed through a forward-only migration system, so existing databases are upgraded automatically on startup.
The tool is optimized for agent workflow first:
- create initiatives, epics, and tasks
- model dependencies
- store progress notes
- claim issues to coordinate between multiple agents
- resume work after session loss or conversation compaction
- surface unblocked work via
ready, ordered by priority - allow delegation of work to sub-agents
Human-friendly output is intentionally secondary for now. JSON is the default interface.
There are three issue types, forming a natural hierarchy:
initiative— the strategic "why". Captures the vision, goals, and key decisions behind a group of related epics. Useful as a reference point when an agent needs to make a judgement call during implementation. Initiatives are always top-level (no parent).epic— a container for related tasks. Can be top-level or a child of an initiative.task(default) — a unit of work. Always a child of an epic (or another task for subtasks).
ait create --title "Auth overhaul" --type initiative --priority P0
ait create --title "OAuth Epic" --type epic --parent <initiative-id>
ait create --title "Login page" --parent <epic-id>
ait create --title "Login page" --description @spec.md # description from fileThe hierarchy is reflected in the ID structure: proj-abc (initiative) -> proj-abc.1 (epic) -> proj-abc.1.1 (task).
The tool is not a replacement for a real issue tracker. The workflow is envisioned as 'developer has a plan/issues/feature - gets the coding agent to plan them out (or does it themselves), then the actual coding agent manages the sub-epics/issues for that work alone.
It's not designed to handle cross-team shared issues, work, projects. The internal database lives in .ait/, which is created by an explicit ait init — every other command refuses with an uninitialised error (exit code 1) until then, so a stray ait list in the wrong directory never leaves a database behind. In a git repository, init also adds .ait/ to your .gitignore; outside one, its JSON output carries a note saying the .gitignore step was skipped.
initconfigcreateshowlist(--type,--status,--priority,--parent,--all,--long,--human,--tree)statussearchupdate(--title,--description,--status,--priority)close(--cascade,--note,--reason— alias for--note)reopencancelclaimunclaimready(--type,--long)flush(--dry-run,--summary)log(--last,--since,--search,--long)log purge(--keep,--before,--full)dep adddep removedep listdep treenote addnote listversionself-update(--check,--yes)export(--output)completion(bash,zsh)
Every subcommand supports --help (or -h) for command-specific usage, flags, and examples:
ait list --help
ait dep add --help
ait create -hTab completion is available for bash and zsh. It completes subcommands, flags, flag values (status, type, priority), and issue IDs.
Bash — add to ~/.bashrc or ~/.bash_profile:
eval "$(ait completion bash)"Zsh — add to ~/.zshrc:
eval "$(ait completion zsh)"Restart your shell or source the file, then use Tab to complete:
ait <Tab> # subcommands
ait list --<Tab> # flags
ait show <Tab> # issue IDs
ait list --status <Tab> # status valuesThe search command matches against issue titles and descriptions, case-insensitively:
ait search "auth" # matches "Auth Flow", "AUTH_TOKEN", etc.By default, queries (list, ready) and mutations (create, update, close, cancel, reopen, claim, unclaim) return a slim view with only the fields an agent typically needs: id, title, status, type, and priority. This keeps token usage low when chaining commands or running them in batches.
Pass --long to get the full issue record including description, parent_id, claimed_by, timestamps, and closed_at.
The relation mutations dep add, dep remove, and note add return a slim ack ({ok: true, ...ids}) by default. Pass --long to get the full blocker list (dep add/dep remove) or the full Note record (note add).
list also includes a hidden_count field in its JSON response when the default filter is active, telling you how many closed/cancelled issues are being filtered out — handy when an empty-looking response would otherwise be confusing. The field is omitted when --all or an explicit --status is passed.
For human-friendly output, two display modes are available:
--human— compact tabular view with initiatives, epics, and children grouped, child IDs shown as short suffixes (.1,.2)--tree— parent-child hierarchy using tree connectors (├──,└──), full IDs on every line
These are mutually exclusive and can be combined with the usual filters (--type, --status, --priority).
ait list # slim JSON (5 fields per issue) + hidden_count
ait list --long # full JSON record
ait list --all # include closed/cancelled, no hidden_count
ait list --human # compact tabular view
ait list --tree # tree hierarchy view
ait list --human --priority P1 # filtered tabular view
ait ready --type task # slim, tasks only (excludes epics)
ait ready --long # full record, all types
ait create --title X # slim ref back
ait create --title X --long # full Issue backWhen multiple agents share one tracker, claiming prevents duplicate work:
ait claim <id> <agent-name> # mark an issue as being worked on
ait unclaim <id> # release the claimIf an issue is already claimed by another agent, claim returns a conflict error with the current holder's name. Claims are visible in show output via claimed_by and claimed_at fields.
The --description flag on create and update supports reading from a file using the @file convention (familiar from tools like curl). This avoids shell escaping problems with long descriptions containing quotes, backticks, or newlines:
ait create --title "Auth overhaul" --description @design-doc.md
ait update <id> --description @updated-spec.txtIf the value starts with @, the remainder is treated as a file path and the contents are used as the description. Without the @ prefix, the value is used as a literal string as before.
The close command accepts an optional --note flag that attaches a closing note to the issue before closing it, leaving a breadcrumb for why something was closed:
ait close <id> --note "Superseded by new approach"This is equivalent to running ait note add <id> "Closed: ..." followed by ait close <id>, but in a single command. It can be combined with --cascade.
--reason is accepted as an alias for --note for backwards compatibility.
By default, close only affects the specified issue. To close an initiative or epic and all of its descendants in one operation:
ait close <id> --cascadeThis recursively closes all open or in-progress children and grandchildren. Issues that are already closed or cancelled are skipped. The command returns the list of newly closed issues.
The flush command permanently deletes all closed and cancelled issues to keep the database lean. Since ait tracks ephemeral work, there is no need to keep completed issues around indefinitely.
ait flush # delete all terminal issues
ait flush --dry-run # preview what would be deletedFlush only removes root-level issues whose entire descendant tree is also closed or cancelled. If a closed initiative or epic still has open or in-progress children, it is skipped and reported in the skipped list. Notes and dependencies belonging to flushed issues are removed automatically via cascade delete.
The --summary flag lets you attach an editorial note to the flush, giving future sessions a quick description of what was accomplished:
ait flush --summary "Fixed pg case-sensitivity in searches, added API docs"Before deleting, flush records every flushed issue into a history log stored in the same SQLite database. This gives agents a way to look back at what was done in previous sessions without keeping the issues themselves around.
ait log # summary view: date, summary, root items, item count
ait log --long # full detail: all items with parent IDs and close reasons
ait log --last 5 # most recent 5 flush events
ait log --since 2026-04-01 # flushes since a date
ait log --search "migration" # find items by title or close reason
ait log --search "auth" --long # search with full detailBy default, log returns a slim view: each flush entry shows its date, summary, total item count, and only root-level items (initiatives, standalone epics and tasks). This follows the same slim/long pattern as list and ready. Use --long to see all items including children and their close reasons.
The --search flag filters items by title or close reason (case-insensitive). Flush entries with no matching items are excluded. This is useful when a user mentions past work and you need to find the relevant history.
Over time the history log can grow large. The log purge subcommand compacts it:
ait log purge --keep 20 # compact all but the last 20 entries
ait log purge --before 2026-01-01 # compact entries older than a date
ait log purge --keep 10 --full # fully delete old entriesBy default, purge compacts — it removes the per-issue item records but keeps the summary rows (date, summary text). This preserves the timeline of what happened while reclaiming storage. Use --full to delete entries entirely.
The export command produces a self-contained Markdown briefing for an issue and all its descendants. This supports a lightweight delegation workflow for handing work to sub-agents that don't have access to the ait binary, or don't need to know about it at all.
ait export <id> # print Markdown to stdout
ait export <id> --output briefing.md # write to fileFor an epic or initiative, the output includes the title, ID, priority, description, a checklist of children ordered by priority, dependencies, notes, and a summary with counts. The resulting file is also useful as a human-readable report of current state.
The delegation workflow is straightforward:
- Export an epic as a Markdown briefing
- Delegate the file to a sub-agent — in a worktree, background process, or remote context
- The sub-agent works through the checklist — no tracker needed
- The supervisor agent reconciles the results back into the tracker by closing completed tasks
This keeps the contract in plain Markdown, so it works across context boundaries, and doesn't couple the receiving agent to any tooling. For the full workflow with worked examples, see claude/skills/ait/DELEGATION.md.
The ready command surfaces unblocked issues ordered by priority (P0 first, then P1, P2, etc.), with creation order as a tiebreaker within the same priority level. This means the most urgent actionable work appears first.
Use config to check the current project settings without inspecting the database directly:
ait configReturns the current prefix and schema_version as JSON.
When adding a dependency with dep add, the tool performs a transitive reachability check. If the new dependency would create a cycle (e.g. A depends on B, B depends on C, and you try to make C depend on A), the command is rejected with a validation error.
Run ait init once per project to create the database — until then, every other command returns an uninitialised error pointing you at it. Use --prefix <value> to set the project prefix used for public issue IDs (it defaults to the project directory name).
Errors are emitted as a JSON {"error": {"code", "message"}} envelope on stderr with a non-zero exit code — stdout only ever carries data. This matches ant, so scripts and agents can treat the two tools identically.
Examples:
ait init --prefix aitait init --prefix deliveries
If no prefix has been set yet, the tool will infer one automatically the first time you use it by normalizing the current project directory basename.
The prefix is stored in local project configuration inside the SQLite database. Running init --prefix ... later will update the stored prefix and re-key existing public issue IDs to match.
Public issue IDs are hierarchical:
- root issue:
<prefix>-<sqid> - first child:
<prefix>-<sqid>.1 - first grandchild:
<prefix>-<sqid>.1.1
This makes parent-child structure visible directly in the identifier while keeping the root segment compact and readable.
By default, the database is stored at .ait/ait.db in the current git repository root. You can override this with the --db flag:
ait --db /path/to/other.db list
ait --db /path/to/other.db create --title "Task in another DB"This is useful for git worktrees (pointing back to the main repo's database), keeping separate databases for different subsystems, or using :memory: for testing.
The database schema is managed through a forward-only migration system. Each migration is numbered and runs in its own transaction. On startup, the tool checks the current schema version and applies any pending migrations automatically.
This means you can update the ait binary and your existing database will be upgraded transparently — no manual steps required. The current schema version is visible via ait config.
The tool uses SQLite and creates a local database at .ait/ait.db in the current git repository root (or the current directory if no git root is found). The first time it creates that directory inside a git repository, it adds .ait/ to the project's .gitignore so the database isn't committed. It only does this for the default location, and it never touches an entry you've already added or removed yourself.
That database stores:
- issues, dependencies, and notes
- project-level configuration such as the current public ID prefix
This keeps issue state close to the codebase it belongs to and makes it easy to inspect or back up.
The claude/ directory contains pre-written skills and agents that teach an agent how to use ait effectively:
claude/skills/ait/SKILL.md— core command reference, workflow patterns, and best practicesclaude/skills/ait/DELEGATION.md— guide for delegating work to sub-agents via Markdown exportclaude/agents/plan-to-ait.md— agent that converts plan-mode plans into structured ait epics and issuesclaude/commands/hello-ait.md— example session-start command that reads the README and checks for outstanding work. This is a boilerplate starting point — edit it to suit your own conversational style.
To install, copy the skill and agent directories into your agents configuration directory (eg, ~/.claude/skills/, ~/.claude/agents/
You might also want to allow the agent to run ait and use the ait skill without needing permission. For claude code for example, in ~/.claude/settings.json :
{
"permissions": {
"allow": [
"Bash(ait *)",
"Skill(ait)"
]
}
}Released binaries can update themselves in place:
ait self-update # interactive: shows release notes, prompts y/N
ait self-update --yes # skip the prompt
ait self-update --check # report whether an update is available; exit onlyExit codes for --check mirror the composer outdated style — 0 if you are
already on the latest, 1 if a newer release is available, 2 if the lookup
failed (e.g. offline). The command verifies the downloaded binary against the
release's SHA256SUMS before swapping it into place atomically.
If ait was installed via Homebrew or go install, self-update declines and
points you at the right tool. Dev builds (Version == "dev") are also a no-op
— rebuild from source instead.
To run the test suite:
GOCACHE=$(pwd)/.gocache go test ./...To build the binary:
GOCACHE=$(pwd)/.gocache go build -o ait ./cmd/aitRelease builds inject the version and repository URL at compile time via ldflags. For development builds, the version defaults to dev and the update check is skipped.
If you fork this repository and want the update check to point at your own releases, either update the RepoURL default in internal/ait/version.go or pass it via ldflags:
go build -ldflags "-X github.com/ohnotnow/agent-issue-tracker/internal/ait.Version=v0.1.0 -X github.com/ohnotnow/agent-issue-tracker/internal/ait.RepoURL=https://github.com/youruser/yourfork" -o ait ./cmd/aitIf you'd like a web UI to monitor the progress of your agent - there is a sibling project at ohnotnow/web-ait
There is a technical overview document that explains the internals of the tool.