gptodo

v0.1.0 Task management and work queue generation utilities for gptme agents packages/gptodo View on GitHub

gptodo

Task management and work queue generation utilities for gptme agents.

Features

Installation

Standalone (Recommended)

Install as a CLI tool:

# Using uv (recommended)
uv tool install git+https://github.com/gptme/gptme-contrib#subdirectory=packages/gptodo

# Using pipx
pipx install git+https://github.com/gptme/gptme-contrib#subdirectory=packages/gptodo

From gptme-contrib workspace

# Install with workspace
uv sync

# Or install package directly
uv pip install -e packages/gptodo

Usage

Task Management CLI

# View task status
gptodo status
gptodo status --compact

# Show specific task
gptodo show <task-id>

# Edit task metadata
gptodo edit <task-id> --set state active
gptodo edit <task-id> --set priority high
gptodo edit <task-id> --add tag feature

# Validate tasks
gptodo validate

# List tasks with filters
gptodo list --priority high
gptodo list --state active

# Auto-expire long-quiet tasks (default: 90 days since `created`)
gptodo expire --dry-run          # preview
gptodo expire                    # apply
gptodo expire --days 60          # tighter window
gptodo expire --state backlog    # only reap backlog

Machine-Readable Output (--json)

Scripts should parse --json rather than grep the rendered human output (the emoji/formatting are not a stable contract).

# Detect whether any task is active (used by autonomous-run gates)
gptodo status --json | jq -e 'any(.tasks[]; .state == "active")'

# Per-state counts
gptodo status --json | jq '.summary.by_state'

Single-type shape (default):

{
  "type": "tasks",
  "tasks": [ { "id": "...", "state": "active", "priority": "high", ... } ],
  "summary": {
    "total": 12,
    "by_state": { "active": 1, "backlog": 11 },
    "issues": 0,
    "untracked": 0
  }
}

With --all, results are grouped under types keyed by directory type, each holding the same {type, tasks, summary} shape. gptodo list, ready, and next also support --json (and --jsonl).

Generate Work Queue

# Basic usage (current directory as workspace)
gptodo generate-queue

# Specify workspace path
gptodo generate-queue --workspace ~/my-agent

# Specify GitHub username for assignee filtering
gptodo generate-queue --github-username YourUsername

Task Locking (Multi-Agent)

# Acquire lock on a task
gptodo lock acquire <task-id>

# Release lock
gptodo lock release <task-id>

# Check lock status
gptodo lock status <task-id>

Output

Generates state/queue-generated.md with:

Task Sources

  1. Local Task Files (tasks/*.md):

    • Filter: priority=high/urgent AND state=new/active
    • Uses frontmatter metadata
  2. GitHub Issues:

    • Filter: label=priority:high/urgent AND state=open
    • Boosts score if assigned to configured username

Configuration

Via command-line arguments or environment variables:

Task Format

Task files should use frontmatter metadata:

---
state: active      # backlog, todo, active, ready_for_review, waiting, someday, done, cancelled, expired
priority: high     # low, medium, high
task_type: project # project (multi-step) or action (single-step)
assigned_to: bob   # agent name
tags: [ai, dev]    # categorization tags
---
# Task Title

Task description...

## Subtasks
- [ ] First subtask
- [x] Completed subtask

State Semantics

The nine canonical states and what they mean — not just what they're called. The autonomous loop drifts when "active" gets used as an opaque "recently touched" tag; enforcing the semantics is the point of the gptodo transitions table and the --force gate on gptodo edit --set state.

State Meaning In next/ready?
backlog Queued, not yet triaged. Default for newly-created tasks. Yes
todo Triaged and ready to start; unclaimed; nothing is blocking work. Yes
active A human or agent is working on it right now. Should be paired with assigned_to and assigned_at. No (already owned)
waiting Blocked on an external event (a date, a reply, an approval, a gate firing). Should carry wait: and/or waiting_for: explaining what it's waiting for. No
ready_for_review Work done, awaiting operator sign-off before done. Should reference a commit or PR in the body. No
someday Parked idea; may or may not ever be picked up. Explicitly excluded from next/ready (GTD someday/maybe). No
done Terminal. Work merged / criterion met. No (terminal)
cancelled Terminal. Will not be picked up; rationale in body. No (terminal)
expired Soft-terminal. Auto-applied by gptodo expire when a backlog/todo/someday task has sat quiet longer than the expire window (default 90d since created). Revive to backlog/todo without --force. No

Legacy deprecated aliases (still accepted with a warning): newbacklog, pausedbacklog.

Common confusions to avoid

Legal transitions

stateDiagram-v2
    [*] --> backlog
    backlog --> todo
    backlog --> someday
    backlog --> cancelled
    todo --> active
    todo --> backlog
    todo --> someday
    todo --> cancelled
    active --> ready_for_review
    active --> waiting
    active --> someday
    active --> done
    active --> cancelled
    ready_for_review --> active : review fails
    ready_for_review --> done
    ready_for_review --> cancelled
    waiting --> active : blocker resolved
    waiting --> someday
    waiting --> cancelled
    someday --> backlog : revived
    someday --> todo : revived
    someday --> cancelled
    backlog --> expired : auto-reap
    todo --> expired : auto-reap
    someday --> expired : auto-reap
    expired --> backlog : revived
    expired --> todo : revived
    expired --> cancelled
    done --> [*]
    cancelled --> [*]
    expired --> [*]

Auto-expire

The queue grows without bound if long-quiet tasks never get closed. gptodo expire walks the tree, finds tasks in eligible states (backlog/todo/someday by default) whose created date is older than --days N (default 90, env GPTODO_EXPIRE_DAYS), and transitions them to expired. expired_from and expired_at are stamped so revival is a one-liner:

gptodo expire --dry-run       # preview what would be reaped
gptodo expire                 # apply
gptodo expire --days 60       # tighter window
gptodo expire --state backlog # only reap backlog
gptodo expire --json          # machine-readable output for cron/CI

# Revive an expired task (no --force needed — expired is soft-terminal)
gptodo edit <task> --set state backlog

Auto-expire deliberately skips:

Age is measured from created, not modified, because a task that only gets touched by lint/reformat still hasn't been worked — using mtime would let queue drift hide behind incidental edits.

gptodo transitions prints the machine-readable table. gptodo edit --set state X enforces legality and refuses illegal transitions unless you pass --force (e.g. reopening a done task, or dropping active back to todo without finishing / handing off). The escape hatch exists — the check is a nudge, not a wall — but every --force should be a conscious act, not a habit.

Frontmatter Schema — Known vs. Hallucinated Fields

The set of supported frontmatter fields lives in KNOWN_FRONTMATTER_FIELDS (see src/gptodo/utils.py). gptodo lint scans task files for anything outside that set and emits a warning.

Do not add ad-hoc fields. Autonomous LLM sessions repeatedly invent plausible-sounding fields under pressure — modified, last_modified, updated_at, last_completed — most of which duplicate information you can already get for free.

The canonical anti-example is modified: (proposed by an autonomous loop in 2026-07-01 as a "solution" to queue-health monitoring). It was rejected as an anti-design-goal: it's a high-churn field that would have to be wired into every edit path, and the answer it purports to provide is already available via:

python -c "import os; print(os.path.getmtime('tasks/foo.md'))"   # file mtime
git log -1 --format=%ai tasks/foo.md                             # last commit

Both are free. Adding a stored modified field creates a churn hazard (every edit forgets to update it, every test needs to inject it, every diff carries noise) with no net information gain.

The gptodo lint command surfaces these to keep the schema clean:

gptodo lint                        # scan all tasks
gptodo lint tasks/foo.md           # single file
gptodo lint --json                 # machine-readable
gptodo lint --strict               # non-zero exit if warnings found (CI)

Deprecated / anti-goal warnings suggest the correct alternative in the message body. Unknown-field warnings ask you to either add the field to KNOWN_FRONTMATTER_FIELDS (deliberate PR + test) or remove it. Warnings never reject a task — a fresh loop must still be able to write whatever frontmatter it decides on; the linter's job is to nudge toward the schema, not gate loop output.

GitHub Integration

Requires GitHub CLI (gh) installed and authenticated:

gh auth login

Priority labels:

Development

Running Tests

cd packages/gptodo
make test

Type Checking

cd packages/gptodo
make typecheck

Migration from tasks

If you were using scripts/tasks.py, the wrapper script will continue to work but will show a deprecation warning. To migrate:

  1. Install gptodo directly: uv tool install git+...
  2. Replace ./scripts/tasks.py calls with gptodo
  3. All commands remain the same

Integration

This package is designed to work with:

For full autonomous agent setup, see gptme-agent-template.