README
ΒΆ
βοΈ TLC
Task
[!WARNING] π§ Do Not Use β History Will Be Rewritten π§
This repo is undergoing major restructuring as we selectively open-source internal tools built at Idea Crafters LLC. Git history will be force-pushed and rewritten multiple times. Do not fork, clone, or depend on this repo in any capacity until we tag a stable release.
An open source toolkit that allows you to focus on product scenarios and predictable outcomes instead of vibe coding every piece from scratch.
βοΈ TLC
TLC is a high-performance, multi-agent task orchestration tool designed for developers and AI agents. It uses a human-friendly "Task Line Syntax" (TLS) to manage tasks in a flat text file while maintaining a synchronized SQLite database for advanced querying and state management.
π Key Features
- Hybrid Storage Model: Edit tasks directly in a human-friendly
todo.txt(Task Line Syntax) or use the synchronized SQLite database for high-performance querying. - Recipes: Versioned YAML templates that materialize into a track and its tasks the same way every time β ordered steps with dependencies, a kind per step (agent, exec, human), conditions, retries, gates and due dates, plus the variables a run binds.
- Recipe Execution: One executor runs the tasks in dependency batches, skipping on
when, retrying to a bound, holding a step at an eva gate and waiting on human approval β with every materialization recorded in a run ledger. - Multi-Agent Collaboration: Safe coordination between humans and AI agents using task claiming, responsibility transfer, delegation protocols, and time-bounded ownership leases (TTL).
- Deterministic Task Execution: Robust execution contract with stdout/stderr capture, error normalization, and configurable timeouts.
- Audit-Ready Logging: A canonical, reverse-chronological
CHANGELOGcapturing every state transition, collaboration action, and execution attempt. - Natural Language Interface: Type
tlc task "complete the auth task"instead of memorizing exact syntax. Local classifier handles common patterns instantly; LLM router (viahop.top/kit/llm) resolves complex prompts with confidence-gated execution and clarification. - Agent Context Dump:
tlc task prompt <id>renders enriched task context (deps, track info, audit log) in markdown or JSON optimized for LLM consumption. - Advanced Query Engine: Power-user filtering with intuitive shorthands (
@me,#tag), logical operators (AND/OR/NOT), and metadata-aware searching. - Extension System: Pluggable sync providers (GitHub Issues, Jira,
Linear) built on
kit/ext. Bidirectional integration while maintaining a strict boundary for internal agent tasks. - Event Bus: Decoupled state-change propagation via
kit/bus. Drives audit logging, stale-detection hooks, and track auto-transitions without tight coupling between subsystems. - Auto-Archiving: Automatically clean up your workspace by archiving completed tasks after a configurable duration (default: 7 days), keeping your active list focused and high-performance.
- Configurable Workflows: Define custom task statuses, state machine transitions, and per-tag workflow overrides via config. Semantic roles drive claim/unclaim/complete behavior.
- Workspace Model: Organize multiple projects into workspaces
and spaces. Query tasks across projects with
--workspaceand--spaceflags, or get a summary view with--summary. - Track Registry: First-class work streams that group related tasks with lifecycle management, computed health state, and phase progress. Auto-transition from pending to active on first task claim. Project health pulse with overcommit warnings.
- Modern TUI & CLI: A keyboard-driven Terminal User Interface built with Bubble Tea, featuring a Kanban board, dashboard, and a Runs view over the recipe run ledger.
- XDG Specification Compliance: Zero-config persistence following standard OS paths for data, logs, and configuration.
Architecture
TLC is built on hop.top/kit, a shared
foundation providing:
- kit/domain β Domain modeling primitives (entities, value objects,
repositories) used throughout
internal/core. - kit/bus β Event bus for decoupled state-change propagation. Powers audit logging, stale detection hooks, and track auto-transitions.
- kit/ext β Extension system replacing the legacy plugin architecture. Sync providers (GitHub, Jira, Linear) register as extensions.
- kit/llm β LLM provider abstraction for the natural language prompt router and config interactive mode.
- kit/tui β Shared TUI components (themes, layouts) used by the Bubble Tea interface.
π Installation
Quick Install (Build from source)
curl -fsSL https://raw.githubusercontent.com/hop-top/tlc/main/install.sh | bash
Build manually
# Clone the repository
git clone https://github.com/hop-top/tlc.git
cd tlc
# Build the binary
mkdir -p bin
go build -o bin/tlc cmd/tlc/main.go
Docker
Run TLC in a containerized environment with persistent storage:
# Build the image
docker build -t tlc-cli .
# Run interactively with TUI
docker run -it --rm \
-v tlc-data:/home/tlc/.local/share/tlc \
-v tlc-config:/home/tlc/.config/tlc \
tlc-cli tui
# Run one-off commands
docker run --rm \
-v tlc-data:/home/tlc/.local/share/tlc \
tlc-cli task list
# Or use docker-compose
docker-compose up -d
docker-compose exec tlc tlc tui
For more Docker usage examples, see docs/docker.md.
π§ͺ Testing
Run all tests
go test ./...
Generate Coverage Report
./scripts/coverage.sh
The HTML report will be available at coverage/all.html.
π¨βπ» Development
Quick Start
maketools # Install dev tools (golangci-lint, air)
makedev # Run all checks (format, lint, test)
makewatch # Auto-rebuild on changes
See docs/development-setup.md for comprehensive development workflow, watch modes, and editor integration.
Available Targets
| Command | Description |
|---|---|
makebuild |
Build binary to bin/tlc |
maketest |
Run tests with race detector |
makelint |
Run golangci-lint |
makefmt |
Format code with gofmt + goimports |
makewatch |
Watch and rebuild on changes |
makewatch-lint |
Watch and lint on changes |
makedev |
Run format + lint + test |
maketools |
Install development tools |
makecheck |
Run lint + test (CI-like) |
makevalidate-docs |
Validate doc command examples |
Watch Mode (Auto-detection)
Get instant feedback while coding:
makewatch-lint # Lint on file save
makewatch # Build on file save
makewatch-test # Test on file save
π Usage
Quick Start
Initialize a new project:
./bin/tlc init
AI Agent Integration
Configure AI agents to use TLC for task management:
For Claude Code users:
Add this to your global Claude configuration (~/.claude/CLAUDE.md):
## Task Management with TLC
Use TLC (Task Line CLI) for all task tracking instead of TodoWrite.
**Setup (run once per project):**
- Check if initialized: `ls .tlc/` or run `tlc init` if not found
- Optional GitHub sync: Run `tlc sync pull github` or `tlc sync push github` to auto-configure (syncs with GitHub Issues)
**Common commands:**
- Create: `tlc task create "Task title" --assigned-to @me --tag feature`
- Create blocked task: `tlc task create "Task title" --blocked-by T-0041 --blocked-by other-project/T-0007`
- List: `tlc task list --mine` (shows IN_PROGRESS first, then TODO by default)
- List stale: `tlc task list --stale` (only tasks past their stale threshold)
- Stale subcommand: `tlc task stale` (dedicated stale view; fires hooks with `--run-hooks`)
- List blocked: `tlc task list --blocked` (only tasks with a blocked reason)
- List by priority: `tlc task list --priority P0` or `--priority P0,P1`
- Group a listing: `tlc task list --group-by track` (one table per track, assignee, tag, status, priority, or project)
- Cap rows per group: `tlc task list --group-by track --group-limit 5`
- List blocked by: `tlc task list --blocked-by T-0041` (tasks that depend on T-0041)
- Update: `tlc task update T-0042 --status IN_PROGRESS`
- Update blockers: `tlc task update T-0042 --add-blocked-by T-0009 --remove-blocked-by T-0003`
- Mark blocked: `tlc task update T-0042 --blocked "waiting on T-0009"`
- Clear blocked: `tlc task update T-0042 --unblock`
- Set stale timeout: `tlc task update T-0042 --timeout 2h`
- Create with timeout: `tlc task create "Task title" --timeout 4h`
- Create with due date: `tlc task create "Task title" --due tomorrow`
- Create with reminder: `tlc task create "Ship v2" --due "in 3d" --remind-every 1h`
- Suppress auto-remind: `tlc task create "Review" --due friday --no-auto-remind`
- Update due date: `tlc task update T-0042 --due "2025-05-01"`
- Clear due date: `tlc task update T-0042 --due -`
- View reminders: `tlc task remind` (upcoming + overdue)
- Check overdue: `tlc task remind --check` (exits 1 if overdue)
- Complete: `tlc task complete T-0042`
- Assign: `tlc task assign T-0042 codex`
- Claim: `tlc task claim T-0042`
- View logs: `tlc log T-0042`
- Track create: `tlc track create "Title" --type feature`
- Track list: `tlc track list --status active`
- Track show: `tlc track show <id>`
- Track summary: `tlc track summary`
- Link task to track: `tlc task create "Title" --track <track-id>`
**Tool definition:** Run `tlc schema --format mcp` for MCP tool schema.
For other AI platforms:
Add to your project's AGENTS.md file:
## Task Management
This project uses TLC for task tracking.
Setup: `tlc init`
Tool definition: `tlc schema` (or `--format openai` for OpenAI platforms)
Natural language prompts:
Use freeform text instead of exact CLI syntax:
# Classifier handles common patterns instantly (confidence 1.0)
tlc task "complete T-42"
tlc task "list my tasks"
tlc task "show T-42"
# Complex prompts route to LLM (requires LLM provider config)
tlc task "create 3 tasks for auth: login, logout, refresh"
# Flags
tlc task "mark the auth task done" --dry-run # preview only
tlc task "delete T-42" --execute # skip confirmation
tlc task "list blocked tasks" --json # JSON output
Configure the LLM provider:
export TLC_PROMPT_LLM="ollama://llama3.2" # or anthropic://, openai://
Cross-domain resolution (no LLM): Common track, recipe, and project queries resolve
deterministically without LLM. Examples: "list active tracks", "list recipes",
"count active tracks" β track list --status active. Typo-tolerant:
"list trakcs" resolves to track list (fuzzy match, confidence 0.8).
Agent context dump:
Get enriched task context for LLM consumption:
tlc task prompt T-0042 # markdown (default)
tlc task prompt T-0042 --json # structured JSON
Output includes: task fields, blocked-by/blocking summaries, track details with phase, and recent audit log entries.
Managing Tasks
List active tasks (IN_PROGRESS first, then TODO):
./bin/tlc task list
List archived tasks:
./bin/tlc task list --archived
Claim a task for work:
./bin/tlc task claim T-0042
Release a claimed task:
./bin/tlc task unclaim T-0042
Complete a task:
./bin/tlc task complete T-0042
Assign or unassign a task:
./bin/tlc task assign T-0042 codex
./bin/tlc task unassign T-0042 --note "reason"
Create a new task:
./bin/tlc task create "Design API schema" --assigned-to engineer-1 --tag infra
Update a task status:
./bin/tlc task update T-0042 --status DONE
Stale Task Detection
Tasks are stale when not updated within their timeout. Use tlc task stale to list them:
# List stale tasks (uses per-task timeout; falls back to task.stale.default_timeout config)
tlc task stale
# List stale tasks and fire configured hooks (sets StaleFiredAt; re-fires even if set)
tlc task stale --run-hooks
Configure project-wide defaults and hooks in .tlc.yaml:
task:
stale:
default_timeout: 6h # applied to tasks with no per-task timeout
hooks:
- command: 'echo "stale: {{.ID}} ({{.Title}})" >> /tmp/stale.log'
Per-task timeout via CLI:
tlc task update T-0042 --timeout 2h
tlc task create "Design API schema" --timeout 4h
Due Dates & Reminders
Set deadlines and reminders on tasks:
# Due date (flexible parsing: "tomorrow", "in 3d", "+2w", "friday", ISO 8601)
tlc task create "Ship v2" --due "in 3 days"
tlc task update T-0042 --due friday
tlc task update T-0042 --due - # clear due date
# One-shot reminder
tlc task create "Review PR" --remind-at "2025-05-01T14:00:00Z"
# Recurring reminder
tlc task create "Check CI" --remind-every 1h
# Auto-remind: 12h before due (on by default)
tlc task create "Release" --due "2025-05-01" --no-auto-remind # suppress
# View upcoming + overdue
tlc task remind
# Scriptable overdue check (exit 1 if any overdue)
tlc task remind --check
Due dates appear in task list (Due column) and task show (overdue
indicator). Overdue tasks are prefixed with ! in list output.
Auto-Scheduling by Priority
Configure automatic due dates and reminders per priority level in
.tlc/config.yaml:
task:
scheduling:
by_priority:
P0:
due: 24h
remind_every: 2h
P1:
due: 72h
remind_every: 12h
P2:
due: 168h
remind_every: 24h
age_nudges:
- status: IN_PROGRESS
threshold: 48h
action: remind
- status: TODO
threshold: 168h
action: remind
Priority defaults apply at task creation β if --due is not
explicitly set and the task has a priority, the configured duration
is added to created_at. Explicit --due always takes precedence.
Age nudges trigger in tlc task remind for tasks without
explicit due dates. Surfaced as "AGE NUDGES" section.
Workspaces
Organize projects into workspaces for cross-project task management:
# List workspaces and their projects
tlc workspace list
# Query tasks across a workspace
tlc task list --workspace myws
# Filter to a specific space
tlc task list --workspace myws --space labs
# Summary view (grouped counts by status)
tlc task list --summary
Grouping listings
Split a listing into one titled table per group with --group-by:
tlc task list --group-by track
Adoption rollout
ID Title Status Assigned Due Stale Blocked
T-0004 Ship rollout toggle To Do - - - -
T-0001 Wire adoption metrics To Do ann - - -
T-0002 Draft rollout plan To Do bob - - -
T-0003 Measure activation funnel To Do ann - - -
Billing migration
ID Title Status Assigned Due Stale Blocked
T-0005 Port billing schema To Do bob - - -
(none)
ID Title Status Assigned Due Stale Blocked
T-0006 Untracked chore To Do - - - -
Group by track, assignee, tag, status, priority, or project.
Headings show names, not internal IDs: a track renders its title, an assignee its resolved aps profile. A track that has since been deleted falls back to its raw ID rather than an unnamed section.
Tasks with no value for the dimension collect into a (none)
section, always last β no track, no assignee, no tags, no priority.
Named groups lead; the leftovers trail.
Ordering is by group name ascending, except status and priority,
which follow your configured lifecycle order (so TODO precedes
IN_PROGRESS rather than sorting alphabetically). The order is stable
across runs.
Tags duplicate rows
Tag membership is many-to-many, so a task tagged [api, urgent] appears
under both groups. When that makes the rendered row count exceed the
number of distinct tasks, a footer says so:
tlc task list --group-by tag
api
ID Title Status Assigned Due Stale Blocked
T-0005 Port billing schema To Do bob - - -
T-0001 Wire adoption metrics To Do ann - - -
T-0002 Draft rollout plan To Do bob - - -
docs
ID Title Status Assigned Due Stale Blocked
T-0003 Measure activation funnel To Do ann - - -
urgent
ID Title Status Assigned Due Stale Blocked
T-0002 Draft rollout plan To Do bob - - -
(none)
ID Title Status Assigned Due Stale Blocked
T-0004 Ship rollout toggle To Do - - - -
T-0006 Untracked chore To Do - - - -
7 rows, 6 distinct tasks
The footer is omitted when the two counts agree.
Capping rows per group
--limit caps the match set fetched before grouping, so one large
group can crowd out the rest. --group-limit caps the rows shown
within each group, keeping every group represented:
tlc task list --group-by track --group-limit 2
Adoption rollout (2 of 4)
ID Title Status Assigned Due Stale Blocked
T-0004 Ship rollout toggle To Do - - - -
T-0001 Wire adoption metrics To Do ann - - -
Billing migration
ID Title Status Assigned Due Stale Blocked
T-0005 Port billing schema To Do bob - - -
(none)
ID Title Status Assigned Due Stale Blocked
T-0006 Untracked chore To Do - - - -
A truncated group announces it as (shown of total). A group that fits
under the cap is not annotated. --group-limit needs --group-by; on
its own it is rejected rather than silently ignored.
JSON output nests
With --group-by, -f json (and -f yaml) wrap the results in groups:
tlc task list --group-by track -f json
{
"groups": [
{
"name": "Adoption rollout",
"tasks": [
{
"id": "task_01m29y23gcfkvvwvn1fypmqwvy",
"seq": 4,
"title": "Ship rollout toggle",
"status": "TODO",
"assigned_to": null,
"track_id": "track_01m29y22cvecktj19v6edcfpdb"
}
]
}
]
}
(Task objects are shown abbreviated; each carries the same fields as an
ungrouped listing. A group with no tasks serializes "tasks": [], never
null.)
Group names match the table headings, and group order matches the
rendered order. Without --group-by the payload stays the flat array
existing consumers iterate, so adding the flag is opt-in.
--group-limit does not apply to structured output β it caps what a
screen shows, and a truncated list inside a payload carries no marker of
its truncation. Passing both prints a note on stderr and emits every
task.
Not combinable with aggregates
--group-by lists rows; --summary and -f counters collapse the match
set into counts. Combining them is rejected:
$ tlc task list --group-by track --summary
--group-by cannot be combined with --summary: --group-by lists rows in one table per track, --summary counts the whole match set; pick one
Tracks
Organize related tasks into tracks β cohesive work streams with their own lifecycle, progress, and health state:
# Create a feature track
tlc track create "Browser rendering" --type feature
# Link tasks to the track
tlc task create "Parse HTML" --track browser-rendering --tag phase:1
tlc task create "Render DOM" --track browser-rendering --tag phase:1
tlc task create "CSS engine" --track browser-rendering --tag phase:2
# View track progress with phase breakdown
tlc track show browser-rendering
# Output:
# Track: browser-rendering
# Progress: 1/3 tasks (33%)
# Phase 1 β 1/2
# Phase 2 β 0/1
# List tracks with state and progress
tlc track list --status active
# Output: ID, Title, Type, Status, State, Progress, Assignee
# Project health pulse
tlc track summary
# Shows: status counts, overcommit warning, active track table
# Link a plan with automatic task extraction
tlc track update browser-rendering \
--add-plan docs/plans/rendering.md
# A plan's frontmatter may express blocked-by as:
# - 0 # intra-track 0-based index into this plan
# - "T-0001" # an existing concrete task ID
# - "other-track#2" # cross-track ref: task #2 in "other-track"'s plan
# Cross-track refs require the target track to already be ingested;
# resolved refs are rewritten back into plan.md on disk.
# Lifecycle commands
tlc track update browser-rendering --status completed
tlc track archive browser-rendering
Track statuses: pending -> active -> completed/abandoned
-> archived. A pending track auto-transitions to active when a
linked task is claimed.
Configure health thresholds in .tlc/config.yaml:
tracks:
stale_threshold: 48h
health:
max_active: 3
min_progress_to_start: 50
Recipes
Recipes are versioned YAML templates that create tracks and tasks the same
way every time: an ordered list of steps with dependencies, kinds (agent,
exec, human), retries, gates and due dates, plus the variables a run binds.
recipe: code-review
version: 1.2.0
description: Review a pull request
vars:
pr: {description: PR number, required: true}
depth: {default: standard}
steps:
- id: lint
kind: exec
exec: {argv: [golangci-lint, run], timeout: 5m}
- id: review
title: "Review {{pr}} ({{depth}})"
description: "Lint said: {{results.lint.stdout}}"
depends_on: [lint]
when: "results.lint.exit_code == 0"
retry: {max_attempts: 3, backoff: 30s}
- id: sign-off
kind: human
human: {assignee: "@lead", timeout: 48h, on_timeout: reject}
depends_on: [review]
- Templates:
{{var}},{{subject.title}},{{run.iteration}}and{{steps.<id>.<field>}}are rendered when tasks are created;{{results.<id>.<field>}}andwhen:are evaluated at dispatch from upstream task results. Due dates compose:due: "{{steps.planning.due}} + 5d"ordue: {after: planning, offset: 5d}. - Composition:
include: security-scan@1.0withwith: {target: "{{pr}}"}splices another recipe in as<id>/<step>;repeat: 3withuntil: "results.review.verdict == 'approved'"unrolls a block into bounded iterations. - Search path:
recipe.dirfrom.tlc/config.yaml, then.tlc/recipes/, then~/.config/tlc/recipes/. Reference a recipe by name,name@versionor file path.
tlc recipe list # every recipe in the search path
tlc recipe show code-review # header, vars and expanded steps
tlc recipe show code-review -f json # the expanded document
tlc recipe validate ./my-recipe.yaml # exit 1 with the first problem
Testing a recipe β tlc recipe test <recipe> [run] materializes the
recipe into a throwaway store and executes it inside a sandbox: a clone of
the working tree, shim binaries ahead of the real ones on PATH, and a home
of its own. Replay serves every tool call from a recorded cassette, so a run
is deterministic and offline; --record reaches the real binaries and writes
the cassettes. Runs live in fixtures/<recipe>/<run>/ beside the recipe,
with record/ cassettes per step id, optional contracts/ for eva
assertions, and a test.yaml declaring expected_exit and vars. A missing
cassette exits 2.
Creating from a recipe β four verbs share one pipeline: locate, expand, bind vars, select steps, render, materialize. Each pass is recorded as a run (which recipe, version, vars, subject, selection) so a later pass can tell what already exists.
# A new track: title from the argument or the recipe's track.title,
# type from --type or track.type, plan.md from track.plan.
tlc track create --recipe release --var version=1.4.0
tlc track create "Review 42" --recipe code-review --var pr=42,depth=deep
# Tasks only: into a track, for a subject, or trackless.
tlc task create --recipe code-review --var pr=42 --track review-42
tlc task create --recipe fix-bug --for T-0042 # into T-0042's track
tlc task create --recipe fix-bug --for auth # into the auth track
tlc task create --recipe code-review --var pr=42 # trackless
# Create for a task, then run the created tasks.
tlc task execute T-0042 --recipe fix-bug --var branch=main
# Bring a track up to date with its recipe, then run it.
tlc track execute review-42 --recipe code-review
tlc track execute review-42 --recipe code-review --recreate
- Vars:
--var key=value[,key=value](repeatable). A required var without a value is prompted for on a terminal; otherwise the command fails naming every missing var and the--varfix.--inferis reserved and errors as not implemented yet. - Selection:
--task 1-5,7,--task lint(repeatable) keeps a subset of the expanded steps; ordinals are the onesrecipe showprints. A dependency on an unselected step is dropped and warned about, or pulled in with its closure by--with-deps. The selection and the dropped edges are recorded on the run. - Subjects:
--for <task-or-track>binds{{subject.id}},{{subject.title}},{{subject.description}},{{subject.track}},{{subject.tags}}and{{subject.project}}.requires: {subject: task}makes a task subject mandatory,tracka track one. A task subject is blocked on the run's leaves (steps nothing else in the run depends on), andtrack execute/task executecomplete it once they are done. - Assignment:
--assignruns the assignment engine overrecipe.assignees_dirfor every step that names no assignee. - Reconcile:
track execute <track> --recipe <r>reads the track's run ledger for that recipe, creates only the steps not materialized yet, and warns when the recipe's version or content changed since the latest run. A step whose task was deleted is reported and left alone unless--recreate. Vars are the latest run's, overridden by--var. - Dry run:
--dry-runon any of the four prints what would be created (and, ontrack execute, the batch plan) without writing. - Output:
--format jsonrenders the run id, created and skipped steps with their task ids, warnings, and the track.
Capturing and inspecting recipes
A track that already works can be captured as a recipe: one step per task
in dependency order, ids from recipe provenance or the task titles,
depends_on from the blocked-by edges inside the track. --var name=value
lifts every whole-word occurrence of value back into {{name}} and
declares the var; the vars of the track's latest recipe run are lifted the
same way. A markdown procedure reachable by URL is converted by the model
behind LLM_API_KEY and kept verbatim as track.plan.
tlc recipe import release-flow # writes ./release-flow.yaml
tlc recipe import release-flow --var pr=1234 # "Review PR 1234" β "Review PR {{pr}}"
tlc recipe import release-flow --install # into .tlc/recipes/release-flow.yaml
tlc recipe import release-flow -o - --name release --version 1.0.0
tlc recipe import https://github.com/org/repo/blob/main/docs/release.md
Every materialization is recorded in a run ledger. recipe runs lists it;
recipe diff walks a recipe's steps against the runs on a track and reports
each step as materialized, missing (never created), deleted (task since
removed) or extra (created by the run but no longer in the recipe), noting
version and content drift between the file and the run.
tlc recipe runs # newest first, with task counts
tlc recipe runs code-review --track release-flow # one recipe, one track
tlc recipe runs --all-projects
tlc recipe diff release-flow # against the latest run's recipe
tlc recipe diff release-flow --recipe ./code-review.yaml
Assignees
Assignees are specialized executor profiles that the assignment engine matches against a task's requirements:
List available assignees:
./bin/tlc assignee list
View assignee details:
./bin/tlc assignee show assignee:code-analyst:1.0
See docs/recipes.md for how --assign picks one.
Agent Execution
TLC supports delegating tasks and tracks to registered AI agents:
Run agents directly:
tlc agent run --agent code-analyst --task T-0042
tlc agent run --agent code-analyst --track browser-rendering
Execute from task/track context:
tlc task execute T-0042 --agent code-analyst
tlc track execute browser-rendering --agent code-analyst
Agent protocol: every run receives its context file path in
TLC_CONTEXT_PATH and must write its results file to TLC_RESULTS_PATH
(container: /workspace/.tlc/context.json and results.json; local: a
per-run .tlc/runs/<run-id>/ directory under the repo root), then print
a JSON status line on stdout. tlc track execute --concurrency N runs
container agents in parallel, one pod each; local agents share the
working tree and run one at a time.
Monitor and manage agent runs:
tlc agent status <job-id> # check async job status
tlc agent cancel <job-id> # cancel queued job
tlc agent list # list agent run audit records
tlc agent watch # live tail of agent output
Agent registry (.tlc/agents.yaml):
agents:
code-analyst:
image: ghcr.io/org/code-analyst:latest
binary: ./bin/analyst
env:
TIMEOUT: "300"
default_timeout: 300
engineer:
image: ghcr.io/org/engineer:latest
binary: ./bin/engineer
env:
TIMEOUT: "600"
default_timeout: 600
Modes:
- Container mode is the default (uses
imagefrom agents.yaml) --localβ opt-in: run agent in-process usingbinarypath
Exec-kind tasks under tlc track execute:
- Host by default: the argv runs in the working directory with
exec.cwd,env,timeoutandstdout_maxall enforced. --with-podpassed explicitly (--with-podor--with-pod=<image>) runs each exec task in a fresh pod from that image, else the--agent's image: the working tree is copied to/workspace,exec.envis set at pod creation andexec.cwdresolves under/workspace.- What the pod path cannot cap:
exec.timeoutends the wait and kills the local pod client, but the protocol cannot signal the remote command (it dies with the pod, destroyed right after);exec.stdout_maxis applied after the transfer, so the whole output is still buffered on the host before it is cut; an emptyenvvalue cannot unset a variable the image provides; files the command writes stay in the pod.
Interactive TUI
Launch the interactive terminal interface:
./bin/tlc tui
Keybindings:
j/k: Navigate tasksenter: View task details (including markdown description)n: Create new task (interactive form)c/u: Claim / Unclaim tasks: Cycle task status/: Search/Filter taskst: Open theme pickerv: Cycle views (Dashboard -> Kanban -> Runs)r: Refresh dataq: Quit
π¨ Theme Customization
TLC features a powerful theme switcher that allows you to customize your terminal experience.
- Built-in Themes: Start with the high-contrast default theme.
- 250+ Community Themes: Pull themes directly from the popular
mbadolato/iTerm2-Color-Schemesrepository. - Lazy Loading: The picker fetches theme names instantly and downloads color data only when you highlight a theme, ensuring a smooth experience even with hundreds of options.
Inside the Theme Picker:
j/k: Scroll through themesR(Shift+R): Fetch/Refresh the full list of 250+ remote themesenter: Apply and save the selected themeesc: Cancel and return to dashboard
Local Configuration
You can use a local configuration file to override global settings:
./bin/tlc --config tlc-local.yaml task list
Multi-Clone Workflows
TLC supports working with the same repository in multiple clones (different machines, worktrees, or branches). It automatically detects your project from git remote and provides flexible options for managing tasks across clones.
Automatic Project Detection:
When you run any TLC command in a clone without explicit configuration:
cd ~/repo-clone
tlc task list
# TLC detects project from git remote and tasks work correctly
Choosing Between Shared or Isolated Tasks:
When running tlc init in a clone, you can choose how tasks are managed:
# Share tasks across all clones (default)
tlc init --duplicate-id-strategy share
# Isolate tasks for this clone
tlc init --duplicate-id-strategy unique
# Ask interactively
tlc init --duplicate-id-strategy prompt
Common Multi-Clone Scenarios:
-
Feature Branch Worktrees: Isolated tasks per branch
cd ~/main-repo tlc init --duplicate-id-strategy unique git worktree add ../feature-X feature-X cd ../feature-X tlc init --duplicate-id-strategy unique # Isolated tasks -
Multiple Machines: Shared tasks across devices
# Machine A (work laptop) tlc init --duplicate-id-strategy share # Machine B (home computer) tlc init --duplicate-id-strategy share # Same tasks -
CI/CD Environments: Auto-detect without config files
export TLC_PROJECT_FALLBACK_MODE=detected tlc task list # Works in CI without .tlc directory
For detailed multi-clone setup guides, see:
- Project Detection - How TLC detects projects automatically
- Multi-Clone Setup - Strategies for shared vs isolated tasks
Configuration Reference:
Add to your .tlc/config.yaml:
project:
id: "user/my-repo" # Auto-detected from git remote
fallback_mode: "auto" # auto, detected, or prompt
duplicate_id_strategy: "share" # share, unique, or prompt
π Task Line Syntax (TLS)
Tasks are stored in a canonical single-line format:
[status] <ID> <Title> @assignee #tag prio:<P> domain:<D> ref:<R>
Example:
[~] T-0002 Fix token refresh race @engineer-3 #auth #bug ref:docs/rfc/012.md
π Identifiers
Tasks and tracks each carry a durable internal ID and a short display alias. You type the alias; TLC stores the durable ID.
| Entity | Type to refer to it | Also accepted | Durable identity |
|---|---|---|---|
| Task | T-0042 |
42, task_01h455vb4pex5vsknk084sn02q |
task_<typeid> |
| Track | L-0002 |
slug, track_01h455vbqkfsn02nk084ksn02q |
track_<typeid> |
Aliases are numbered per project and stay fixed once assigned β L-0002
keeps pointing at the same track for the life of that track. They are
unique inside a project, so the same alias may exist in another project.
Alias input is case-insensitive: l-0002 resolves like L-0002.
The track_<typeid> form is the durable identity. It never changes and is
unique across every project, so use it when a reference must survive being
copied between projects.
Track slugs
A track also has a slug β the readable name you see in prose and on
disk. The slug is the directory name under .tlc/tracks/:
.tlc/tracks/browser-rendering/
metadata.json
plan.md
That is why tracks keep a slug alongside L-NNNN: tracks/L-0002/ would
tell you nothing in a file listing or a diff.
Both forms address the same track, so use whichever reads better:
tlc track show L-0002 # by alias
tlc track show browser-rendering # by slug
tlc track list shows both, so you can copy either one:
tlc track list
# ID Slug Title Type Status State Progress Assignee
# L-0002 render-v2 Browser rendering two feature pending unlinked 0/0 -
# L-0001 browser-rendering Browser rendering feature active unlinked 0/0 -
On a narrow terminal the Slug column is the first to be dropped, and a long
slug is clipped with an ellipsis to keep the other columns on screen. That
is display only β tlc track show and -f json always carry the full
slug, so a script never reads a clipped value.
tlc track create derives the slug from the title, or takes one via
--id:
tlc track create "Browser rendering" --type feature
# slug: browser-rendering
tlc track create "Browser rendering" --type feature --id render-v2
# slug: render-v2
Slugs are lowercase alphanumerics and hyphens, starting and ending with an alphanumeric.
Slug length
New slugs are capped at 24 characters by default. A derived slug is cut at a word boundary, so it stays readable:
tlc track create "Config driven label templates and workflow wiring" --type feature
# slug: config-driven-label
Change the cap with tracks.slug_max_len:
# .tlc/config.yaml
tracks:
slug_max_len: 32 # default: 24
The cap applies only to slugs being created. Tracks created before you set it β or under a longer cap β keep their names and keep resolving; TLC never renames a track directory. Only a new slug over the cap is rejected:
tlc track create "Legacy" --type feature --id config-driven-label-templates-and-workflow-wiring
# error: track slug "config-driven-label-templates-and-workflow-wiring" too long (max 24 chars)
βοΈ Configuration
TLC follows a hierarchical configuration:
- Environment variables (
TLC_*) - Local config (
.tlc/config.yamlor custom--config) - User config (
<os user config dir>/tlc/config.yaml) - System config (
/etc/tlc/config.yaml) - Built-in defaults
Write behavior:
tlc config setwrites to the active local or explicit--configfile.- If only system config is loaded, or no config file exists yet, TLC writes the new value to the
OS user config path instead of
/etc/tlc/config.yaml.
Defaults:
- Todo file:
$XDG_DATA_HOME/tlc/todo.txt - Database:
$XDG_DATA_HOME/tlc/db.sqlite - Logs:
$XDG_DATA_HOME/tlc/tlc.log - Archive threshold: 168h (7 days)
Custom Paths
All directory paths are configurable. When omitted, defaults match the original hardcoded values so existing projects work unchanged.
# .tlc/config.yaml
tracks:
dir: docs/tracks # default: "tracks" (relative to .tlc/)
slug_max_len: 24 # default: 24 (new slugs only)
recipe:
dir: recipes # default: none; then .tlc/recipes, ~/.config/tlc/recipes
assignees_dir: team # default: "examples/assignees"
task:
todo_file: TODO.md # default: "todo.txt"
projection_dir: work-items # default: "tasks"
storage:
db_path: data/store.db # default: "db.sqlite"
inbox:
dir: incoming # default: "inbox"
Paths must be relative (resolved from project root).
This is useful for mono-repo layouts where .tlc/ sits at the
repo root but artifacts live in a shared directory:
# mono-repo example: .tlc/config.yaml at repo root
tracks:
dir: packages/project-a/tracks
recipe:
dir: packages/project-a/recipes
π Documentation
New to TLC? Start with Getting Started Guide for a progressive learning path from basic tasks to advanced workflows.
Detailed specifications can be found in the docs/ directory:
Features & Guides:
- Recipes & Assignees - Recipe templates, execution and capability-based assignment
- Development Setup - Development workflow and watch modes
- Editor Setup - IDE/editor integration
- Docker Usage - Container deployment
Plans:
Specifications:
- Task CRUD Spec
- Task Line Syntax Spec
- Recipe Spec
- Task Log Spec
- Sync Architecture
- TUI Spec
- Config Spec
- Config Discovery
- Project Detection
Contributing
Contributions are welcome! Check the open issues for things to work on, or open a pull request with your improvements. All PRs are appreciated -- whether it is a bug fix, new feature, or documentation update.
Feedback
Found a bug or have a feature request? Open an issue on the GitHub Issues page. For questions and general discussion, use GitHub Discussions.
License
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
genfixtures-vtodo
command
Command genfixtures-vtodo regenerates the .ics test fixtures under tests/fixtures/vtodo.
|
Command genfixtures-vtodo regenerates the .ics test fixtures under tests/fixtures/vtodo. |
|
tlc
command
Package main provides the TLC CLI entry point.
|
Package main provides the TLC CLI entry point. |
|
internal
|
|
|
cli
Package cli: `tlc agent register|show|registered` subcommands.
|
Package cli: `tlc agent register|show|registered` subcommands. |
|
displaytime
Package displaytime is the single tlc path for rendering absolute time values to user-facing output.
|
Package displaytime is the single tlc path for rendering absolute time values to user-facing output. |
|
events
Package events defines topic constants and payload types for the tlc event bus.
|
Package events defines topic constants and payload types for the tlc event bus. |
|
extensions
Package extensions provides the tlc-specific ext.Manager bootstrap layer.
|
Package extensions provides the tlc-specific ext.Manager bootstrap layer. |
|
flowtest
Package flowtest provides deterministic e2e testing for tlc recipes: a hermetic sandbox, cassette-backed tool shims, and the named runs that drive `tlc recipe test`.
|
Package flowtest provides deterministic e2e testing for tlc recipes: a hermetic sandbox, cassette-backed tool shims, and the named runs that drive `tlc recipe test`. |
|
flowtest/shims/shimlib
Package shimlib provides the shared logic for LLM shim binaries used by the flowtest sandbox.
|
Package shimlib provides the shared logic for LLM shim binaries used by the flowtest sandbox. |
|
flowtest/shims/shims
Package shimlib provides the shared logic for LLM shim binaries used by the flowtest sandbox.
|
Package shimlib provides the shared logic for LLM shim binaries used by the flowtest sandbox. |
|
rpc
Package rpc provides a lightweight JSON-RPC 2.0 client for communicating with external binary plugins over stdio.
|
Package rpc provides a lightweight JSON-RPC 2.0 client for communicating with external binary plugins over stdio. |
|
uriutil
Package uriutil provides shared URI helpers that both internal/core and internal/uri need.
|
Package uriutil provides shared URI helpers that both internal/core and internal/uri need. |
|
vtodo
Package vtodo encodes and decodes tlc Task/Track/LogEntry models to and from RFC 5545 (iCalendar) + RFC 9253 (RELATED-TO RELTYPE) wire format.
|
Package vtodo encodes and decodes tlc Task/Track/LogEntry models to and from RFC 5545 (iCalendar) + RFC 9253 (RELATED-TO RELTYPE) wire format. |
|
plugins
|
|
|
github-sync
command
|
|
|
jira-sync
command
|
|
|
linear-sync
command
|
|
|
vtodo-sync
command
|