afk status --summary
afk status --blocked
afk take --dry-run --limit 5 --json --full
afk tasks --status doing --json
afk tasks --status failed --jsonRun those commands first when the queue looks surprising. They answer the usual questions: how much work exists, which dependencies are blocking todo work, what can be claimed, what is currently holding resources, and what failed recently.
| Symptom | First commands | Likely cause |
|---|---|---|
afk take prints no task |
afk take --dry-run --limit 0 --json --full, afk status --blocked, and afk tasks --status doing --json |
No ready work, unfinished dependencies, or active resource locks. |
afk status shows todo, but workers claim nothing |
afk status --blocked and afk tasks --status doing --json |
todo is not the same as ready. Dependencies or resource locks can block it. |
| A failed task still looks failed after retry | afk task <id> --json |
Retry must start with afk retry <id> --reason "..."; direct done is allowed but may not match the intended retry story. |
| Deleted work disappeared from lists | afk tasks --status deleted |
Default task lists hide deleted. History is still available. |
| You need a before/after queue comparison | afk snapshot --label before --output before.json |
Use snapshots instead of manually reconstructing old queue state. |
| Agents keep duplicating work | afk find <repo-or-topic> --json |
Discovery or add flow skipped duplicate checks. |
| A queue path seems wrong | afk --queue /path/to/tasks.sqlite status and printf '%s\n' "$AFK_QUEUE" |
The command is using the default queue, AFK_QUEUE, or a normalized .sqlite sibling path. |
AFK is a local SQLite task queue for coding agents. It stores durable task state, not process supervision. A worker or agent claims one ready task, does the work, and records the outcome.
The lifecycle is:
todo -> doing -> done | failed | deleted
Readiness is separate from status. A task can be todo but not ready when a
dependency is unfinished or another doing task owns the same non-empty
resource key.
afk add "fix the failing queue test"
afk tasks
afk task "$id"
afk find queue --json
afk take --dry-run --limit 5 --json --full
afk take --lease 30m --worker codex:1 --summary
afk set "$id" done --note "verified" --worker codex:1 --summary
afk set "$id" failed --note "missing credentials" --worker codex:1 --summary
afk set "$id" deleted --note "superseded"
afk snapshot --label after --task "$id"Use afk <command> --help for exact flags.
Use these replacements:
| Old command | Current command |
|---|---|
afk ls |
afk tasks |
afk show <id> or afk explain <id> |
afk task <id> |
afk pop |
afk take |
afk ready or afk run --dry-run |
afk take --dry-run |
afk done <id> |
afk set <id> done --note <evidence> |
afk fail <id> <reason> |
afk set <id> failed --note <reason> |
afk retry <id> |
Still supported. Prefer afk retry <id> --reason <reason> for a targeted retry. |
afk reset <id> |
afk set <id> doing --note "retrying" for a targeted retry, or afk set <id> todo --note <note> to return work to the queue. |
afk prune or afk rm |
afk set <id> deleted |
afk run |
An external loop that calls take, executes the task, then calls set. |
By default:
~/.claude/queue/tasks.sqlite
Override it per command:
afk --queue /tmp/tasks.sqlite statusOr through the environment:
AFK_QUEUE=/tmp/tasks.sqlite afk tasksNon-.sqlite paths are normalized to a sibling .sqlite database. For example,
/tmp/tasks.jsonl becomes /tmp/tasks.sqlite.
Inspect the command the agent ran, its environment, and the task metadata:
printf '%s\n' "$AFK_QUEUE"
afk status --summary
afk tasks --status all --jsonIf you suspect a different queue, run the same inspection with an explicit path:
afk --queue /path/to/tasks.sqlite status --summary
afk --queue /path/to/tasks.sqlite tasks --status all --jsonAFK stores data in SQLite. A non-.sqlite queue path is accepted for convenience
but normalized to a .sqlite sibling path.
Give the worker everything needed to execute without conversation context:
afk add \
--cwd /path/to/project/code/project \
--source task-discovery \
--tag discovery \
--resource repo:/path/to/project/code/project \
"Fix settings persistence. Evidence: /path/to/project/code/project/internal/settings/store.go:42 drops the save error. Scope: internal/settings only. Success: settings survive refresh. Verify with go test ./internal/settings. Reject-if: settings persistence moved out of this package."A strong task body includes:
- Evidence: current file, command output, queue record, failing behavior, or docs/source mismatch.
- Scope: exact package, command, document, or behavior to touch.
- Success: observable done state.
- Verify with: exact local command or deterministic check.
- Reject-if: condition that makes the task invalid or blocked.
--stage <value> is an optional free-form pipeline label (such as triage or
in-review) independent of status; it does not affect readiness.
Use dry-run:
afk add --dry-run --json "validate this task shape"Use --diagnose when you want detailed validation failures:
afk add --dry-run --diagnose "too vague"Dry-run validates shape and options. It does not prove the task is valuable or that the referenced files still contain the problem.
Common causes:
- The body is too vague to execute.
- Metadata is invalid, such as an unsupported priority.
--blocked-byreferences a missing task.- The dependency would create a cycle.
--forceand--diagnosewere used together.
Run:
afk add --dry-run --diagnose <your task body>Then make the task more specific instead of forcing it unless you are importing trusted, already-reviewed work.
Search before adding:
afk find "settings persistence" --json
afk find "repo:/path/to/project/code/project" --json
afk tasks --status todo --json
afk tasks --status doing --jsonDiscovery workflows should reject duplicates already visible in todo or
doing. Failed tasks can be retried or used as evidence, but they should not be
blindly duplicated.
An empty stdout is normal when no task can be claimed. This is deliberate so worker loops can test for an empty claim.
Diagnose readiness:
afk take --dry-run --limit 0 --json --full
afk status
afk tasks --status todo --json
afk tasks --status doing --jsonIf visible todo work is held back by a resource lock, an unsatisfied gate, or
a blocks relation pointing at unfinished work, afk take keeps stdout empty
and writes a short explanation to stderr.
todo means unfinished. Ready means claimable.
A todo task is not ready while:
- a
blocksrelation points at a task that is notdone - another
doingtask has the same non-empty resource key - a gate on the task is unsatisfied
store.Ready (SQL) is the single authority for this. A task is claimable only
when every blocks relation points to a done task, no other doing task
holds its resource key, and no gate is unsatisfied.
Inspect the task and active work:
afk task "$id"
afk status --blocked
afk tasks --status doing --json
afk take --dry-run --limit 0 --json --fullRead those commands this way:
afk status --blockedshowstodotasks blocked by unfinished dependencies.afk tasks --status doing --jsonshows active resource locks and claim timestamps.afk take --dry-run --limit 0 --json --fullshows the exact ready set a worker can claim.
afk take --dry-run --limit 0 --json --fullUse a positive --limit for a bounded preview:
afk take --dry-run --limit 5 --json --fullDry-run JSON previews truncate long task bodies unless you pass --full.
afk take --lease 30m --worker codex:1 --summary--summary includes the claimed task plus queue counts and
ready_remaining after the claim. With --dry-run, it returns the ready
preview plus queue counts without claiming work.
Use --envelope when you want the same top-level object style for both dry-run
and claimed output. Dry-runs return claimed:false, tasks, and queue;
claims return claimed:true, task, and queue.
Use something stable enough to identify the claimant in logs:
afk take --lease 30m --worker "$USER:$$"
afk take --lease 30m --worker "codex:docs"Worker ids are most useful when diagnosing abandoned doing tasks.
Add the prerequisite first, then add dependent work with --blocked-by:
first=$(afk add "fix the API contract")
afk add --blocked-by "$first" "update docs after the API contract lands"The dependent task remains todo until the prerequisite is done.
A dependency is a blocks relation. --blocked-by is the shorthand for it. You
can also create relations after the fact:
afk relate <task-id> <related-id> --type blocks|relates|duplicates|parentThe --type defaults to blocks. Only blocks gates readiness; relates,
duplicates, and parent are informational links that never block a task from
being claimed.
Use none:
afk add --blocked-by none "independent follow-up"This is useful for generated add commands that always include a blocked-by slot.
Tasks with the same non-empty resource key are serialized while one is doing.
This prevents two workers from editing the same repo or other shared resource at
the same time.
afk add --resource repo:/path/to/project/code/project "task one"
afk add --resource repo:/path/to/project/code/project "task two"After one task is claimed, the other stays todo but not ready until the active
task is finalized.
Use --resource none:
afk add --resource none "independent task"Use this only when the work truly does not conflict with other tasks in the same repository or shared resource.
A gate is a named precondition on a task. While a task has any unsatisfied gate, it stays out of the ready set.
afk gate add <id> <name>
afk gate satisfy <id> <name>afk gate add is idempotent. afk gate satisfy is one-way; satisfying an
unknown gate name errors. Use gates to hold work on an external condition such
as a review being approved or CI turning green.
A typical flow:
id=$(afk add "ship the release")
afk gate add "$id" ci-green
afk take --dry-run --limit 0 --json --full # does not surface the task
afk gate satisfy "$id" ci-green
afk take --dry-run --limit 0 --json --full # now surfaces the taskPrefer creating a corrected task and hiding the old one:
afk add --blocked-by "$right_id" "corrected dependent task"
afk set "$old_id" deleted --note "superseded by corrected dependency"Use failed instead of deleted when the old task records a real attempted
failure that should remain visible in failure reports.
afk set "$id" done --note "implemented and tested" --summaryUse --json when another tool or agent needs a structured confirmation:
afk set "$id" done --note "implemented and tested" --jsonUse --note-file - when the evidence contains quotes, &&, or multiple
lines:
printf '%s\n' "$evidence" | afk set "$id" done --note-file - --summaryafk set prints a small confirmation on success, such as the task id and new
status. That gives humans and agents a visible checkpoint while remaining easy
for scripts to ignore. Use --json when the caller needs a parseable result,
or --summary when the caller also needs queue counts in the receipt.
afk set "$id" failed --note "missing credentials" --summaryGood failure notes name the blocking condition and the smallest next action, not just "failed".
Use deleted for obsolete, duplicate, or superseded work that should disappear
from default lists:
afk set "$id" deleted --note "superseded by task 42"Use failed when a worker attempted the task and hit a real blocker or
execution failure:
afk set "$id" failed --note "test environment requires credentials" --summaryDeleted tasks remain inspectable through:
afk tasks --status deleted
afk task "$id"For a targeted retry, reopen the same task with a reason:
afk task "$id" --json
afk retry "$id" --reason "fixed the blocker"
# do the work
afk set "$id" done --note "verified" --summaryThe default manual retry moves the task to doing, clears stale task-level
error text, and opens a new attempt. Prior failed attempts remain in history.
To schedule the same failed task for later instead, use:
afk retry "$id" --disposition deferred --available-at 2026-07-18T13:00:00Z --reason "wait for maintenance window"Deferred retry returns the task to todo and does not open an attempt until a
worker claims it after available_at.
Retry is still modeled as a normal status transition. The command is narrow sugar for the common failed-task recovery path:
afk retry "$id" --reason "fixed the blocker"That is equivalent to:
afk set "$id" doing --note "retrying: fixed the blocker"If you do not want to retry the exact task now, return it to the queue:
afk set "$id" todo --note "ready for another worker"Inspect the task:
afk task "$id" --jsonDirect manual finalization is auditable: AFK records terminal attempts even when there was no open attempt. If the work was not actually done, set it back to an appropriate status with a clear note:
afk retry "$id" --reason "correcting accidental done"or:
afk set "$id" failed --note "accidental done; work still blocked" --summaryafk tasks --status doing --json
afk task "$id"Check the worker id, lease, started time, events, and attempts before changing state.
If the original worker is gone and the work should not remain active, close the attempt explicitly:
afk set "$id" failed --note "orphaned doing claim" --force --summaryThen either add a fresh task or retry the same one:
afk add "resume the abandoned work from task $id"or:
afk retry "$id" --reason "orphaned work"Use the retry form when the same task body is still the correct execution contract.
Use snapshots when a task or review asks for before/after queue evidence:
afk snapshot --label before --output before.json
afk snapshot --label after --task "$id" --output after.jsonSnapshots are read-only JSON. They include counts, ready tasks, todo tasks, doing tasks, and optional task details.
Manual comparisons are easy to lose once a task is claimed or completed. Snapshots create durable evidence at the moment you need it.
Use afk task <id> when you need one task's full record.
Use afk snapshot --task <id> when you need that task record plus queue context
in one JSON evidence artifact.
Use the built-in afk loop worker-driver (afk loop --command '...' --max-tasks N; see runner.md), or own the execution loop outside AFK:
task_json=$(afk take --lease 30m --worker "$USER:$$" --summary)
test -n "$task_json" || exit 0
id=$(printf '%s\n' "$task_json" | jq -r .task.id)
body=$(printf '%s\n' "$task_json" | jq -r .task.body)
if agent-command "$body"; then
afk set "$id" done --note "agent-command completed" --worker "$USER:$$" --summary
else
afk set "$id" failed --note "agent-command failed" --worker "$USER:$$" --summary
fiAFK owns durable queue state. Your wrapper owns process lifetime, logs, retries, tool permissions, and agent selection.
afk take intentionally leaves stdout empty when there is no claim. That makes
shell loops simple and avoids treating "no ready task" as a task payload.
Use JSON for automation:
afk take --summary
afk take --dry-run --json --full --envelope
afk tasks --status todo --json
afk task "$id" --json
afk set "$id" done --note "verified" --jsonPlain text is for humans and quick terminal checks.
It prints a read-only discovery workflow for an agent or human reviewer:
afk prompt --discoverIt does not add tasks. A discovery pass should classify the target, gather
current evidence, check the queue for duplicates, validate candidate task bodies
with afk add --dry-run, then ask one enqueue confirmation.
It should be:
- current: proven by live files, command output, queue history, or docs/source mismatch
- atomic: one behavior, package, command, doc, dataset, or media set
- bounded: roughly under one hour
- verifiable: exact local check included
- rejectable: clear
Reject-if:condition included
Avoid vague bodies like "improve docs", "continue cleanup", or "investigate the repo". Convert them into specific mini-specs or reject them.
For each candidate:
afk add --dry-run --json --cwd /path/to/repo --source task-discovery --tag discovery --resource repo:/path/to/repo "<task body>"Only enqueue candidates that pass dry-run validation and remain non-duplicates
after afk find and afk tasks --status todo/doing checks.
afk serveUse a custom address when needed:
afk serve --addr 127.0.0.1:8080The dashboard is a visibility and action layer over the same queue.
Check:
- The address is valid, for example
127.0.0.1:8080. - The port is not already in use.
- The configured queue path is writable.
- Your environment is allowed to open a browser, if browser opening is enabled.
Try a random local port:
afk serve --addr 127.0.0.1:0Commands default to human-friendly output unless JSON is the safer default for
worker payloads. For scripts, pass --json explicitly wherever the command
supports it.
afk status --summary
afk status --summary --jsonWithout --summary, afk status also includes todo and doing task lists.
Doing tasks include derived claim diagnostics. Text output appends claim age
and stale reason when available; JSON output adds a claim object:
{
"age_seconds": 1860,
"stale": true,
"reason": "lease_expired"
}stale and reason are omitted when the claim is still fresh. Add --blocked
when you need to see which unfinished dependencies are blocking todo tasks.
afk tasks --status all
afk tasks --status all --jsonUse --status deleted when you only want hidden tasks.
Avoid direct writes. Use AFK commands so events, attempts, dependencies, status migration, and resource locks stay coherent.
Read-only SQLite inspection is fine when debugging, but prefer:
afk task "$id" --json
afk snapshot --label debug --output debug.jsonNo. deleted hides the task from default listings while preserving inspection
through afk task <id> and afk tasks --status deleted.
Stored legacy statuses are migrated to current names:
pending->todoworking->doing
Use only todo, doing, done, failed, and deleted in new commands.
Check the id and queue path:
afk find "$id" --json
afk tasks --status all --json
afk --queue /expected/path/tasks.sqlite task "$id"Inspect the prerequisite id:
afk task "$blocked_by_id"Then add with a real id or use --blocked-by none for an independent task.
The dependency would make a loop. Add a new corrected task chain instead of trying to force the cycle:
first=$(afk add "true prerequisite")
afk add --blocked-by "$first" "dependent task"Then mark the bad task deleted or failed with a note.
AFK retries transient SQLite busy errors internally. If the error persists:
- check for a long-running process using the same queue
- retry the command after the active writer finishes
- avoid direct SQLite writes while AFK commands are running
- confirm the queue is on a local filesystem rather than a flaky sync mount
Use one of:
todo doing done failed deleted
Old command names like done or fail are not statuses by themselves. Use:
afk set "$id" done --note "verified"
afk set "$id" failed --note "reason"afk status --summary
afk take --dry-run --limit 5 --json --fulltask_json=$(afk take --lease 30m --worker "$USER:$$" --summary)
test -n "$task_json" || exit 0
id=$(printf '%s\n' "$task_json" | jq -r .task.id)
afk task "$id"
# do the work
afk set "$id" done --note "verified" --worker "$USER:$$" --summaryafk task "$id" --json
afk retry "$id" --reason "fixed blocker"
# do the work
afk set "$id" done --note "verified on retry" --summaryafk snapshot --label before --output before.json
afk set "$id" done --note "verified" --json
afk snapshot --label after --task "$id" --output after.jsonafk task "$duplicate_id"
afk set "$duplicate_id" deleted --note "duplicate of task $canonical_id"afk set "$id" todo --note "ready for another worker"Use this when no worker is actively retrying it now. Use retry when you are
starting the retry yourself.
- command-reference.md for exact command flags.
- getting-started.md for the shortest happy path.
- tasks.md for metadata and task history.
- scheduling.md for readiness, dependencies, and resource locks.
- workers.md for claim/finalize flows.
- runner.md for external worker-loop patterns.
- configuration.md for queue path behavior.
- task-discovery.md for discovery and enqueue validation.