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.
Storage
Section titled “Storage”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.
apx task add
Section titled “apx task add”apx task add "<title>" \ [--project <name|id|path>] \ [--body <text>] \ [--tag <name>]... \ [--due <YYYY-MM-DD>] \ [--agent <slug>] \ [--source <label>]--tag is repeatable. Examples:
apx task add "Review PR #42" --project myapp --agent reviewer --tag reviewapx task add "Release notes" --project myapp --tag release --tag docs --due 2026-06-01apx task add "Call client" --project myapp --due 2026-05-31 --tag urgentapx task list
Section titled “apx task list”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>]apx task list --project myapp # open tasksapx task list --project myapp --state allapx task list --project myapp --state doneapx task list --project myapp --tag urgentapx task list --project myapp --due-before 2026-06-01apx task list --project myapp --agent reviewer --limit 10apx task list --project myapp --status blocked # stuck, in this projectAcross every project
Section titled “Across every 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.
apx task list --all # everything open, everywhereapx task list --all --status blocked # what is stuck, everywhereapx task list --all --updated-since 2026-08-01T00:00:00Z # what moved sinceapx 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 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 show
Section titled “apx task show”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.
State transitions
Section titled “State transitions”apx task done <id> [--project P] [--by <name>] # mark completedapx task drop <id> [--project P] # archive (no longer needed)apx task reopen <id> [--project P] # flip back to opendone means “I completed this work.” drop means “this is no longer needed.” Metrics and
reporting distinguish them — use the right one.
apx task patch
Section titled “apx task patch”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.
apx task patch t_abc123 --project myapp --title "New title"apx task patch t_abc123 --project myapp --tag review --tag urgent # replaces tagsapx task patch t_abc123 --project myapp --due 2026-06-10ID format and prefix addressing
Section titled “ID format and prefix addressing”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.
apx task done t_abc123 --project myapp # full idapx task done abc --project myapp # prefix, resolves if uniqueTask fields
Section titled “Task fields”| Field | When | Notes |
|---|---|---|
title | Required | Short imperative line. |
body | Optional | Longer notes. Markdown accepted. |
tags | Optional | Free-form strings. Filterable with --tag. |
due | Optional | ISO date YYYY-MM-DD. Filterable with --due-before. |
agent | Optional | Agent slug responsible for the task. |
source | Auto / optional | Origin: cli, telegram, super-agent, … |
category | Optional | What KIND of task: general (default) or trip. Closed set — see below. |
location | Optional | Where a trip errand is: place, address, latitude/longitude, radius_m. |
state | Derived | open → done or dropped. Reopenable. |
Categories and places
Section titled “Categories and places”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.
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.
How the super-agent feeds tasks
Section titled “How the super-agent feeds tasks”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.
Handing a task to another agent
Section titled “Handing a task to another agent”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.