A Git-native task ledger for humans and AI coding agents.
Quick start — the shortest path to a shared human-and-agent task ledger:
brew install aholbreich/tap/tl && tl init && tl agents --write-filesThen
tl readyto see what's open,tl claim <id>to take a task, andtl close <id>when it's done. Install options for every platform are below.
AI coding agents are a regular part of software teams now, and the hardest
problem in a repository is no longer writing code — it is keeping humans and
agents coordinated. Chat threads disappear. TODO.md files drift. GitHub
Issues live on a remote server and don't follow the code.
tl gives every repository a small, local task ledger that both humans and
agents read and update — no daemon, no database, no remote service. Its
differentiator from other Git-native trackers is agent-safe coordination:
explicit claims with time-limited leases, dependency-aware ready lists,
detectable stale work, and a recorded handoff trail — all in state you can
read, diff, and reason about with any tool.
- Agent-safe coordination: claims are explicit and lease-based, stale work is detectable, handoffs are recorded — agents don't silently step on each other
- Dependency-aware:
tl readyonly lists work whose blockers are done - Git-native: state lives in
.tl/— commit it, diff it, branch it, review it in any PR - Human-readable: tasks are plain Markdown with YAML frontmatter — read or edit any task in your editor
- Agent-readable: every read command supports
--json, every write can be attributed with--actor - Boring by design: no daemon, no database, no git hooks, no automatic push — you decide when to sync
For an honest feature-by-feature comparison with the nearest tools — Beads, Backlog.md — and with GitHub Issues, see How tl cli compares.
Contents: Quickstart · Setup for agent collaboration · Installation Options · Commands · How tl cli compares · Development · Further reading
One tl init per repository creates the ledger and nothing else:
tl init # create .tl/ (once per repo)
tl completion --install # TAB-complete task IDs (one-time)
tl create "Add login form validation" # add a task
tl create "Refactor auth errors" -t chore -p low --tag auth # with type, priority, tag
tl list # see everything
tl show <id> # full task detailTake a task from ready, work it, and close it:
tl ready # unclaimed, unblocked tasks
tl claim <id> # take a time-limited lease (re-run = heartbeat)
tl note <id> -m "Initial pass done." # record progress for the next person
tl close <id> # done and verifiedOnce the ledger exists, hand your agents the playbook in one step:
tl agents --write-files # merge the tl workflow into AGENTS.md, CLAUDE.md, …This injects a managed workflow block into the agent instruction files already
present in your repo (AGENTS.md, CLAUDE.md, .cursorrules, and friends),
so every agent that reads them also knows how to use tl. For constrained
context windows:
tl agents --compact # print the short version
tl agents --write-files --compact # write the short versionFrom then on, the agent loop is:
tl ready --json # what's claimable right now?
tl claim <id> --actor agent-a # take a lease (and say who you are)
tl show <id> # read the task in full
tl note <id> -m "Blocked on the API key; handing back." --actor agent-a
tl release <id> --actor agent-a # step away cleanly — or tl close when doneIdentity resolves in order: --actor flag > TL_ACTOR > ACTOR_NAME >
BEADS_ACTOR > agent auto-detection > hostname. Detection recognises Claude
Code, aider, Windsurf and pi from the environment variables those harnesses
export, and Codex from a .codex file. Cursor and GitHub Copilot are
deliberately not detected: their variables are set by plain VS Code or are
long-lived auth tokens, so a guess there would silently attribute one agent's
claims to another. Setting TL_ACTOR once per session is the easiest way to
stay attributed.
Latest releases are published to the GitHub Releases page as prebuilt archives for Linux and macOS (amd64 + arm64) and Windows (amd64 + arm64). Every release triggers an automatic update of the Homebrew tap, the RPM repository and the pacman repository.
brew install aholbreich/tap/tl # latest stable release
brew install --HEAD aholbreich/tap/tl # or: build from current mainIf you install multiple tools from the same tap, you can tap once:
brew tap aholbreich/tap
brew install tlAdd the Holbreich pacman repository, which is updated automatically on every release:
echo '
[holbreich]
SigLevel = Optional TrustAll
Server = https://aholbreich.github.io/pacman-repo/$arch' | sudo tee -a /etc/pacman.conf
sudo pacman -Sy
sudo pacman -S tl-binOn Omarchy:
omarchy pkg add tl-bintl-bin installs /usr/bin/tl, so the command stays tl. It is named -bin
because it packages the prebuilt release binary rather than compiling from
source. Thereafter pacman -Syu — or omarchy update — keeps it current
like any other package.
SigLevel = Optional TrustAll is required because these packages are not GPG
signed. Pacman will still verify the checksums in the repository database and
fetch over HTTPS, but it cannot verify who built a package. Add the
repository only if you are willing to trust its owner.
Without adding a repository
The PKGBUILD is in this repository, so it can be built directly. This needs no third-party repository and no AUR account, but nothing will upgrade the result — pacman has no source to compare it against.
curl -O https://raw.githubusercontent.com/aholbreich/tl/main/packaging/aur/PKGBUILD
makepkg -siAUR:
tl-binis prepared for the AUR but not yet published — account registration is unresolved. The pacman repository above is the supported path in the meantime. See pacman-repo.
curl -fsSL https://raw.githubusercontent.com/aholbreich/tl/main/install.sh | shInstall a specific version or target directory:
curl -fsSL https://raw.githubusercontent.com/aholbreich/tl/main/install.sh | sh -s -- --version 0.9.0
curl -fsSL https://raw.githubusercontent.com/aholbreich/tl/main/install.sh | sh -s -- --bin-dir "$HOME/.local/bin"Download the latest tl-windows-<arch>.zip from the
Releases page and unpack
tl.exe into a directory on your PATH.
git clone https://github.com/aholbreich/tl
cd tl
make install # installs `tl` to $HOME/binCross-platform release archives:
make dists # tl-linux-amd64.tar.gz, tl-darwin-arm64.tar.gz, …RPM (Fedora / Red Hat) — repo-based install
Add the Holbreich RPM repository:
# Documentation: https://aholbreich.github.io/rpm-repo/#installation-fedora-centos-redhat
echo '[Holbreich]
name=Holbreich Repository
baseurl=https://aholbreich.github.io/rpm-repo/
enabled=1
gpgcheck=0' | sudo tee /etc/yum.repos.d/holbreich.repoInstall tl:
sudo dnf install tl
tl --versionIf you run into issues with the RPM repository, see the rpm-repo project.
The whole surface at a glance:
# Set up
tl init # create the .tl/ ledger (once per repo)
tl completion --install # enable TAB completion for task IDs
# Define work
tl create "<title>" [-t type -p prio --tag x --ref r -d "..."] # add a task
tl refine <id> [-p prio -t title --edit] [--actor name] # edit an existing task
tl refine <id> [--add-ref r --remove-ref r] # attach/detach references
tl dep add <id> --on <id> # declare a dependency
tl dep remove <id> --on <id> # drop one
# Do the work
tl ready [--tag x] [--json] # unclaimed, unblocked tasks
tl claim <id> # take a time-limited lease (re-run = heartbeat)
tl note <id> -m "..." # record progress / handoff context
tl close <id> # done and verified
# When it doesn't just finish
tl block <id> -m "..." # external blocker; releases the claim
tl unblock <id> # blocker cleared; back to open
tl pending <id> --question "..." # need a human decision; releases the claim
tl resolve <id> --answer "..." [--actor name] # human answers; task reopens
tl cancel <id> -m "..." # won't be done
tl remove <id> -m "..." [--force] # delete a mistaken task file from the active ledger
tl release <id> # step away cleanly (leave a note first)
# Inspect
tl list [--all --status s --tag t --mine] [--type t --priority p] [--json]
tl list --dashboard [--tag t] > tasks.md # regeneratable Markdown overview
tl tree [<id>] [--all] [--json] # dependency graph as a forest
tl show <id> [--json] # full task detail
tl history [<id>] [--json] # event-by-event audit trail
tl stale # claims whose lease has expired
tl doctor [--json] [--fix] [--force] # scan ledger for integrity issues (optionally repair)
# Agents
tl agents [--compact] [--write-files [--dry-run] [--file path]] # print or install agent workflow guidetl list --dashboard writes a plain Markdown snapshot to stdout, grouped by
status in list order (pending human, blocked, in progress, open; then done and
cancelled with --all). Each task includes its ID, title, status, priority,
type, claimant, a one-line description capped at 240 characters, and references.
Missing types display and filter as task. References are displayed as text,
not interpreted as links. Notes are omitted to keep the overview compact.
Metadata occupies one line in status · priority · type · claimant order:
in_progress · high · task · 👤 aho
The 👤 icon identifies the claimant; - means unclaimed.
All list filters compose: --status, --claimed-by, --mine, --tag,
--type/-t, and --priority/-p (including l, m, h aliases).
--status done or --status cancelled includes that status without --all.
--json takes precedence over --dashboard and keeps the existing list JSON
format. Markdown never includes terminal colors or generation timestamps, and
listing does not modify the ledger.
tl list --dashboard --type feature --priority high > roadmap.md
tl list --dashboard --tag docs > docs-tasks.mdThere is no dedicated area or due-date field; use tags to scope areas of work. Watch mode and dependency-tree rendering are not part of the dashboard.
tl tree renders the graph the ledger already stores. A task's children are
the tasks it depends on, so a parent sits above the slices it waits for,
and a root is a task nothing else depends on.
$ tl tree
task-tvp Test AUR build and installation end-to-end open medium
└─ task-o9g Publish tl-bin initial import to AUR open high
└─ task-8xx Register AUR account and set up SSH access in_progress high
task-ahk Report feature-level spec tags open medium
└─ task-hwb Document the @implemented tag convention open low
Priority is coloured exactly as in tl list — red, yellow, blue for high,
medium and low — and closed rows are dimmed when --all reveals them.
tl tree carries the same spec column that list and ready do, so a
blocked slice and its specification are visible together.
With a task id, only that subtree is drawn. Closed tasks are hidden by
default and revealed by --all, matching tl list; naming a task explicitly
always renders it, whatever its status.
A shared dependency is legitimate, so it is drawn under every parent that
waits for it rather than being assigned to one. A dependency cycle is drawn
once and marked (cycle) instead of recursing, so the command always
terminates — diagnosing cycles is tl doctor's job, not this one's. Nothing
visible is ever dropped: a task no root reaches, which is what happens inside
a cycle, is rendered as its own root.
--json nests children so consumers do not re-derive the graph. Its nodes
carry id, title, status and priority rather than whole tasks — a shared
dependency appears at several positions, and repeating full bodies at each
would bloat the document rather than help.
tl tree --json | jq -r '.. | objects | select(.children == []) | .id' # leavesA reference whose path ends in .feature is treated as a spec reference.
That is the whole rule — no flag on tl create, no frontmatter field, no
configuration. tl show marks such a reference (spec).
tl list, tl ready and tl tree add a column showing each referenced spec,
whether the file is there, and how many scenarios it holds. A Scenario Outline counts once, however many Examples rows it has.
ID Status Title Spec
task-7fi open Add tl tree features/tree.feature (missing)
task-cys open Add tl agents --remove features/agents.feature (14)
task-wke open Add --type field -
The column is drawn only when some listed task actually carries a spec
reference, so a project that writes no Gherkin sees exactly the output it saw
before this existed and reads no files. Resolving every spec in a 500-task
ledger costs about 3 ms. A missing or unreadable spec renders as (missing)
or (unknown) rather than failing the listing — tl doctor is what
complains about dead references.
In --json, spec is always an array, never null, so consumers need no
presence check.
tl list --json | jq -r '.[] | select(.spec[]?.state == "missing") | .id'What this does not tell you. The link is to a file. A feature file
usually describes a capability while a task is a slice of one, so the column
says "this task points at a spec that exists", not "this task's behaviour is
specified" — and never that the work is done. Delivery state stays in the
task's own status, beside it. See
.decisions/0002-reading-referenced-files.md.
Exit codes: 0 success · 1 generic · 2 invalid args · 3 task not found · 4 task not ready · 5 already claimed · 7 lock failed
- Walkthrough:
docs/usage.md— tl by example, flow by flow - Behavioral spec:
features/(one.featurefile per command) - Per-command flags:
tl <cmd> --help
tl shares a category with Beads and
Backlog.md: Git-native task trackers for
humans and AI coding agents. The short version — tl is the files-only,
no-database option, and its one differentiator is agent-safe coordination
with readable, Git-native state: explicit claims, detectable stale work,
computable dependencies, recorded handoffs, everything inspectable by hand.
Feature-by-feature, including the honest "why tl and not Beads / Backlog.md /
GitHub Issues":
docs/comparison.md.
make build # version-stamped local binary
make test # all Go tests
make bdd # godog suite only
make dists # local cross-platform archives for manual testing
make release VERSION=x.y.z # validate, tag, and push; GitHub Actions publishes
make cleanCI runs gofmt, go vet, make build, make test on every PR and push to
main (see .github/workflows/ci.yaml).
make release VERSION=x.y.z only verifies that HEAD is clean, on main, and
already pushed to origin/main, then pushes the tag. The tag-triggered release
workflow builds all platform archives and publishes the GitHub Release.
docs/usage.md— tl by example, flow by flowdocs/tech-docs.md— some implementation detaildocs/PRD.md— design intent, non-goals, status enum