afk goal compiles a free-text objective into an approved, dependency-ordered task chain in your queue. You describe the outcome; a configured setup agent turns it into a structured contract; you approve it; afk inserts the contract's tasks so a worker — or afk loop — can drain them. An optional audit agent independently verifies completion.
objective ──▶ setup agent ──▶ contract ──▶ you approve ──▶ task chain ──▶ (worker drains) ──▶ audit
text (LLM) (JSON) [yes/no] in queue
afk goal is fail-closed: it errors until you tell it how to call your agent. Configuration lives at ~/.config/afk/goal.yaml, written with defaults on first run:
setup_command: pi -p {{.Prompt}} # compiles the objective into a contract
audit_command: pi -p {{.Prompt}} # the independent completion auditor
# ... prompt templates, budget caps, timeouts ...{{.Prompt}}is replaced with the rendered prompt — it becomes a single argument even with spaces or newlines. Put your agent CLI here (pi -p {{.Prompt}},claude -p {{.Prompt}}, etc.).- Override per run with
--setup-command/--audit-command(handy for testing with a stub script). - Until
setup_commandis set,afk goalerrors;goal auditlikewise needsaudit_command.
--dry-run runs the setup agent and prints the compiled contract without queueing anything. Add --json for machine-readable output:
afk goal --dry-run --json "add CSV export to the report command"{"outcome":"report command supports CSV export","done_criteria":["`afk report --csv` emits valid CSV","existing JSON output unchanged"],"must_do":["add a --csv flag to the report command"],"avoid":["breaking the default JSON output"],"philosophy":"smallest change that satisfies the done criteria","tasks":["add --csv flag to the report command","implement the CSV encoder","add a unit test asserting valid CSV output"]}The contract's tasks array is the ordered work; each later task is queued blocked by the previous one.
Without --dry-run/--json, afk prints the contract, then prompts on stderr:
Approve contract? [yes/no]:
Only an explicit yes queues the tasks. On approval afk prints a receipt to stdout:
echo yes | afk goal "add CSV export to the report command"{"goal_id":"e74d05ef-0819-4dbe-990b-568936b6c369","tasks":3}Capturing the goal_id in scripts. The contract and receipt go to stdout; the
Approve contract?prompt goes to stderr. Redirect stderr away and read the last stdout line:goal_id=$(echo yes | afk goal "…" 2>/dev/null | tail -1 | jq -r .goal_id)
afk goal status <goalID> shows the durable goal record and a live count of its tasks:
afk goal status e74d05ef-0819-4dbe-990b-568936b6c369{"id":"e74d05ef-0819-4dbe-990b-568936b6c369","objective":"add CSV export to the report command","outcome":"report command supports CSV export","status":"active","created_at":"2026-06-03T20:31:38Z","group_id":"e74d05ef-0819-4dbe-990b-568936b6c369","task_counts":{"todo":3},"budget":{"max_tokens":0,"max_iterations":0,"max_duration_ns":0,"token_regex":"","tokens_used":0,"iterations_used":0,"epoch_started":"","reason":"","limited_at":""}}objective— your raw words, exactly as typed.outcome— the setup agent's one-line restatement (kept for reference).task_counts— member tasks bucketed by status.
The tasks are normal queue entries grouped by group_id and tagged source: goal:<goalID>. Only the first is ready; the rest stay blocked until it finishes:
afk take --dry-run --json --full{"id":"1f1c995d-462c-41fc-a9ba-5ac90e6d5589","created":"2026-06-03T20:31:38Z","status":"todo","body":"add --csv flag to the report command","cwd":"/Users/vampire/go/src/afk","source":"goal:e74d05ef-0819-4dbe-990b-568936b6c369","group_id":"e74d05ef-0819-4dbe-990b-568936b6c369"}Drain the chain with any worker — afk take / afk set by hand, or let afk loop run it end-to-end.
After a task is finished, afk goal audit <taskID> runs the independent auditor — a fresh agent invocation that inspects the real artifacts and does not trust the completion note:
afk goal audit 1f1c995d-462c-41fc-a9ba-5ac90e6d5589{"approved":true,"disapproved":false,"output":"…"}- The auditor judges against your raw objective, so it can catch a setup agent that misread the request.
<disapproved/>— or any audit error/timeout, since disapproval is the fail-safe default — re-queues the task totodoso the work is retried.
afk goal enforces per-goal limits (also settable in goal.yaml):
| Flag | Cap |
|---|---|
--max-iterations N |
stop after N agent iterations (0 = unlimited) |
--max-duration D |
wall-clock cap, e.g. 30m (0 = unlimited) |
--max-tokens N |
cumulative token budget; requires token_regex and fails closed when usage is unavailable (0 = unlimited) |
Limits and usage are stored in SQLite. Iterations and tokens remain cumulative
across restarts; duration begins at the first invocation in an epoch. A
nonzero token cap requires token_regex with exactly one decimal capture group.
If the bounded output tail contains no parseable usage, AFK fails closed with
token-usage-unavailable and marks remaining members budget-limited.
Resume after explicitly raising or changing at least one cap:
afk goal resume <goalID> --max-iterations 20Resume resets the duration epoch and atomically requeues every limited member; cumulative token and iteration usage is preserved.
The objective is HTML-escaped before it reaches the prompt (it is untrusted data) and is capped at 4000 characters.
| Symptom | Cause / fix |
|---|---|
no setup command configured |
Set setup_command in goal.yaml or pass --setup-command. |
goal audit errors immediately |
audit_command is empty — configure it or pass --audit-command. |
| Approval seems ignored | Only the exact word yes approves; anything else declines and writes nothing. |
| Can't find the goal_id | It's the receipt line on stdout; the prompt is on stderr. Use 2>/dev/null | tail -1. |
afk goal <objective>:
| Flag | Meaning |
|---|---|
--setup-command T |
agent command template for contract compilation |
--audit-command T |
agent command template for the auditor |
--dry-run |
print the contract and exit without queueing |
--json |
print the contract as JSON and skip approval (does not queue) |
--cwd PATH |
working directory recorded on queued tasks |
--max-tokens N / --max-iterations N / --max-duration D |
per-goal caps |
--token-regex REGEX |
exactly one decimal capture group for token usage |
--queue PATH |
queue DB path (or AFK_QUEUE) |
See also: afk loop · command reference · configuration.