Skip to content

Tasks

APX has a per-project TODO list backed by an append-only event log. Tasks are scoped to a project, addressable by short-id prefix, and never truly deleted — state transitions (done, drop) are recorded as events and the task persists forever.

Tasks live in ~/.apx/projects/<apxId>/tasks/YYYY-MM.jsonl, one file per month. State is the fold of the event stream: creating, completing, dropping, reopening, and patching all append events. Do not grep the JSONL directly for state — use apx task list or the API.

Terminal window
apx task add "<title>" \
[--project <name|id|path>] \
[--body <text>] \
[--tag <name>]... \
[--due <YYYY-MM-DD>] \
[--agent <slug>] \
[--source <label>]

--tag is repeatable. Examples:

Terminal window
apx task add "Review PR #42" --project myapp --agent reviewer --tag review
apx task add "Release notes" --project myapp --tag release --tag docs --due 2026-06-01
apx task add "Call client" --project myapp --due 2026-05-31 --tag urgent
Terminal window
apx task list \
[--state open|done|dropped|all] \ # default: open
[--status pending|running|in_review|blocked] \
[--tag <name>] \
[--agent <slug>] \
[--due-before <iso>] \
[--due-after <iso>] \
[--updated-since <iso>] \
[--limit <N>] \
[--all | --project <name|id|path>]
Terminal window
apx task list --project myapp # open tasks
apx task list --project myapp --state all
apx task list --project myapp --state done
apx task list --project myapp --tag urgent
apx task list --project myapp --due-before 2026-06-01
apx task list --project myapp --agent reviewer --limit 10
apx task list --project myapp --status blocked # stuck, in this project

--all folds every registered project into one list, newest first, with each row labelled by the project it came from. Every other filter still applies.

Terminal window
apx task list --all # everything open, everywhere
apx task list --all --status blocked # what is stuck, everywhere
apx task list --all --updated-since 2026-08-01T00:00:00Z # what moved since
apx task list --all --due-before 2026-09-01 --limit 20

--state is the storage lifecycle (open / done / dropped). --status is how an open task is progressing (pending / running / in_review / blocked). They are different questions: “what is blocked right now” is not “what is open”.

If a project’s task log cannot be read, that project is skipped and a warning names it — one unreadable file never blanks out the whole view.

apx
$ apx task list --project myapp
ID    STATE    DUE         TAGS                TITLE
t-1a  open     2026-06-16  checkout,urgent     Wire Stripe webhooks before merge
t-2b  open     2026-06-18  tests               Retry-guard the flaky cart-total test
t-3c  open     —           docs                Document the pairing flow for the web panel
t-4d  open     2026-06-20  infra               Backfill ingestion CLI flag
apx task list — open tasks with id, title, tags, due date, and agent
Terminal window
apx task show <id> [--project <name|id|path>]
apx task show abc [--project <name|id|path>] # prefix match (≥ 3 chars, must be unique)

Prints the full task as JSON, including all fields and current state.

Terminal window
apx task done <id> [--project P] [--by <name>] # mark completed
apx task drop <id> [--project P] # archive (no longer needed)
apx task reopen <id> [--project P] # flip back to open

done means “I completed this work.” drop means “this is no longer needed.” Metrics and reporting distinguish them — use the right one.

Terminal window
apx task patch <id> \
[--title <text>] \
[--body <text>] \
[--due <date>] \
[--agent <slug>] \
[--tag <name>]... \
[--project <name|id|path>]

--tag replaces the tag list when provided; passing no --tag flags leaves tags unchanged.

Terminal window
apx task patch t_abc123 --project myapp --title "New title"
apx task patch t_abc123 --project myapp --tag review --tag urgent # replaces tags
apx task patch t_abc123 --project myapp --due 2026-06-10

Task IDs have the form t_ + 6 base36 characters (32-bit entropy, ~4 billion keyspace). You can address a task by any prefix of ≥ 3 characters as long as it uniquely identifies one task. If two tasks share a prefix, the command returns an error — use a longer prefix.

Terminal window
apx task done t_abc123 --project myapp # full id
apx task done abc --project myapp # prefix, resolves if unique
FieldWhenNotes
titleRequiredShort imperative line.
bodyOptionalLonger notes. Markdown accepted.
tagsOptionalFree-form strings. Filterable with --tag.
dueOptionalISO date YYYY-MM-DD. Filterable with --due-before.
agentOptionalAgent slug responsible for the task.
sourceAuto / optionalOrigin: cli, telegram, super-agent, …
categoryOptionalWhat KIND of task: general (default) or trip. Closed set — see below.
locationOptionalWhere a trip errand is: place, address, latitude/longitude, radius_m.
stateDerivedopen → done or dropped. Reopenable.

A tag is a free-form label you invent. A category is a closed set the system itself acts on, which is the difference that matters: trip means “an errand, at a place”, and the daemon can route on that without asking a model to read the title and guess.

That guess is exactly what a category replaces. While a trip is running, APX matches your position against open tasks and reminds you once when you pass somewhere useful. For a task with no location it has to search OpenStreetMap for places that might satisfy the errand; for a trip task that already carries its own coordinates it does none of that — no geocoding, no place search, and no model call. Same reminder, no network and no tokens.

Terminal window
apx task add "Buy ibuprofen" --category trip --place "Pharmacy on Main" --at "-41.13,-71.31"

Without coordinates the errand still works, but it becomes a choice: the daemon searches for places that satisfy it and keeps every one of them, pointing at the nearest as you drive. “Comprar pan lactal en el súper” matches both bakeries and supermarkets, so it is watching several shops until one is settled — by there being only one, or by you answering “voy”. See Android · the trip plan.

--radius <m> sets how close counts as “there” for that one errand; without it the mobility default applies. An empty --place on apx task patch clears the location. Tags are untouched by any of this — a task can have both.

The web task list draws a small mark for a category that has one; general draws nothing, because an icon on every row carries no information.

The whole lifecycle is tools, registered in the super-agent’s core tool set and available on every channel: create_task, list_tasks, get_task, update_task, complete_task and comment_task. When you say “remind me to close the auth bug in myapp”, the model calls create_task with the right project, title, and optional fields. When you ask “what’s pending in myapp?”, it calls list_tasks.

list_tasks rows are compact on purpose — no description, no comments — so anything past the title is get_task, which returns the full record plus its thread and subtasks. Editing is update_task: pass only the fields that change, and "" to clear one. Board columns and closing stay on complete_task (action: "status" | "done" | "drop" | "reopen"), which validates the column against the catalog this install has.

Nothing about a task needs a shell. A task edited by writing to the JSONL event log skips every normalizer in the store and leaves a state the fold cannot reconstruct.

Example tool call the model emits:

{
"name": "create_task",
"arguments": {
"project": "myapp",
"title": "Close auth bug",
"due": "2026-06-01",
"tags": ["bug"]
}
}

If the project is ambiguous (user didn’t say which one), the model calls list_projects first and asks — it never assumes. In a Telegram channel pinned to a project, the model uses that project as the default context.

Work for another agent goes into a task, not a message: assign it (create_task with that agent), or comment on the existing task mentioning them — @qa ready for QA. A mention in a comment summons the agent: it takes the task up in its own turn and replies on the same thread. Every agent is told this is how work moves; talking to a peer directly is for when you are in a live conversation and need that answer now.

A thread can run with nobody watching, so it has walls: one comment starts at most four replies (cascade included), a task that already has agents working on its thread does not get a second run, and agents handing a task back and forth get at most eight turns on it per hour. Your own comments are not capped.

  • Routines — schedule the super-agent to create or report on tasks.
  • Super-agent — the tool loop that can create and query tasks conversationally.