Skip to content
Back to skills

Apx Task

ASecurity

Per-project to-do list — a task, "una tarea", "tareas pendientes", "tareas abiertas", "anotá", "marcá como terminada" — with subtasks, comments and a board. Event-sourced, project-scoped, addressable by short id prefix. Load when user wants to note, remind, list, read, EDIT, reassign, complete, split or comment on a task. Triggers: 'add a task', 'remind me to…', 'what's pending', 'mark as done', 'open tasks', 'what does that task say', 'change the due date', 'cambiale la fecha', 'movelo al vi...

  • 12 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
ai-agentspythongoshellbashgitapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 7, 2026

npx -y skills add agentprojectcontext/apx --skill apx-task --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Apx Task?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Apx Task
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/agentprojectcontext-apx-task/badge)](https://www.skillsdirectory.com/skills/agentprojectcontext-apx-task)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: apx-task
description: Per-project to-do list — a task, "una tarea", "tareas pendientes", "tareas abiertas", "anotá", "marcá como terminada" — with subtasks, comments and a board. Event-sourced, project-scoped, addressable by short id prefix. Load when user wants to note, remind, list, read, EDIT, reassign, complete, split or comment on a task. Triggers: 'add a task', 'remind me to…', 'what's pending', 'mark as done', 'open tasks', 'what does that task say', 'change the due date', 'cambiale la fecha', 'movelo al viernes', 'reassign it', 'eso es de Ana', 'make it urgent', 'split this task', 'subtask', 'comment on the task', 'move it to QA'.
---

# apx-task

A `task` is a per-project TODO. Append-only JSONL event log per month at `~/.apx/projects/<apxId>/tasks/YYYY-MM.jsonl`. State is the fold of the event stream. Once created a task lives forever — `done` and `drop` record state transitions, don't delete events. `reopen` flips back to `open`.

## Concrete CLI calls

```bash
# Add
apx task add "Review the auth bug" --project acme
apx task add "Llamar al contador" --project casa --description "Antes del viernes, por el monotributo"
apx task add "Rotar las claves" --project acme --body "Rotate every API key in .env and open a PR" --agent ops
apx task add "Call the client" --project acme --due 2026-05-30 --tag urgent
apx task add "Demo for tester X" --project acme --agent reviewer --tag demo --tag external --source cli

# List (defaults to open)
apx task list --project acme
apx task list --all                          # every registered project, each row labelled
apx task list --all --status blocked         # what is stuck, everywhere
apx task list --all --updated-since 2026-08-01T00:00:00Z   # what moved
apx task list --project acme --status in_review
apx task list --project acme --state all
apx task list --project acme --state done
apx task list --project acme --tag urgent
apx task list --project acme --due-before 2026-06-01
apx task list --project acme --limit 5

# Inspect / mutate
apx task show t_abc123 --project acme
apx task show abc       --project acme    # prefix match (≥3 chars, unique)
apx task done    t_abc123 --project acme --by julian
apx task drop    t_abc123 --project acme               # archived (not "done")
apx task reopen  t_abc123 --project acme
apx task patch   t_abc123 --project acme --title "New title" --due 2026-06-10
apx task patch   t_abc123 --project acme --description "Lo que tengo que hacer yo"
apx task patch   t_abc123 --project acme --tag bug --tag blocker   # replaces tags
```

## ID format

`t_` + 6 base36 chars (~4B keyspace). Prefix matching works at ≥3 chars when the prefix is unique. If two tasks share a prefix you get null — use a longer one.

## Fields

| Field | When | Notes |
|---|---|---|
| `title` | always | One imperative line. Required. |
| `description` | optional | **What the OWNER has to do**, in their words. Markdown OK. This is what the panel, the phone and `apx task show` display. Fill it for anything a person will do. |
| `body` | optional | **The prompt an AGENT receives** if it runs the task. Only for tasks meant to be executed by an agent — leave it empty for a plain to-do. |
| `tags` | optional | Free-form. Used by `--tag` filter. |
| `due` | optional | ISO `YYYY-MM-DD`. Filter with `--due-before` / `--due-after`. |
| `agent` | optional | Who is responsible: an agent slug, or `owner`/`human` for the human owner. Used by `--agent` filter. |
| `priority` | optional | `low` \| `normal` (default) \| `high` \| `urgent`. |
| `reminder_frequency` | optional | `none` (default) \| `once` \| `daily` \| `weekly`. |
| `category` | optional | `general` (default) or `trip` — an errand with a place. Only a `trip` is considered by the mobility geofence. |
| `location` | optional | Where the errand is: `{ place, address, latitude, longitude, radius_m }`. Inert unless the category is `trip`. |
| `source` | auto/optional | Origin (cli, telegram, super-agent). |
| `state` | derived | Storage lifecycle: `open` after create, `done`/`dropped` after ops. |
| `status` | sub-status | Which BOARD COLUMN an **open** task sits in. Ships as `pending` (default) \| `running` \| `in_review` \| `blocked`, but the column catalog is configurable — call `GET /api/projects/:pid/tasks/columns` for what THIS project actually has. Orthogonal to `state`. Set via `POST …/tasks/:id/status` (the CLI `patch` does not set it). |
| `parent` | optional | Id of a parent task. **A subtask is a task with a parent** — same store, same verbs, so it can be assigned, moved and closed on its own. |
| `comments` | derived | The task's thread, oldest first. Present on `show`/`GET one`; list rows carry `comment_count` instead. |

## Subtasks — splitting a task that is really several

One row that bundles five requirements cannot be handed out, moved to QA, or
ticked off in pieces. Split it: keep the parent as the epic and add a subtask
per real unit of work.

```bash
apx task list --project acme --parent t_epic123   # its children
apx task list --project acme --parent ""          # only top-level tasks
```

With the tool: `create_task({ project, title, parent: "t_epic123" })`.

## Comments, and handing a task to an agent

```
POST /api/projects/:pid/tasks/:id/comments   { text }
```

**An @mention in a comment summons that agent.** It runs a REAL turn with its
own tools against the task, and its reply is posted back as another comment —
so "@qa probá el flujo de login" is a QA run, not a note. Agent→agent handovers
(`@dev` inside QA's reply) cascade, are mirrored onto the a2a ledger, and stop
at a ceiling of 4 replies per comment so a mention loop cannot run away.

The `comment_task` tool hands a task on the same way: an @mention in the comment
summons that agent, off your turn — do not wait for it; it replies on the task.
This is how work moves between agents (not `send_to_agent`, which is for a live
exchange while the owner is in the conversation). Two walls, because the thread
runs unattended: a task whose thread already has a cascade running starts no
second one (the running one reads your comment), and agents get at most 8
summoned turns per task per hour. The tool's result says which happened. The
owner's own comments are not capped.

## Board columns

One catalog for every project (`config.tasks.columns`), and each project shows
an ordered subset (the project config → `tasks.columns`). `done` is always the
last column and is not in the catalog — closing a task is `POST …/done`, not a
status. A column may carry `on_enter: { agent, instruction }`: a task landing
there is handed to that agent through the same comment path as an @mention.

```bash
GET  /api/tasks/columns                  # the global catalog
GET  /api/projects/:pid/tasks/columns    # what this project shows, + the catalog
```

## Agent tools

The whole lifecycle is tools. Nothing about a task needs a shell.

| Want to | Tool |
|---|---|
| note something down | `create_task` |
| see what is pending | `list_tasks` (omit `project` for every project, in ONE call) |
| read what a task actually says | **`get_task`** |
| change what it says | **`update_task`** |
| move it, close it, drop it, reopen it | `complete_task` |
| report what you found or did | `comment_task` |

`list_tasks` rows are deliberately compact — no `description`, no `body`, no
comments — so anything past the title is `get_task`. It returns the thread and
the subtasks with it, and finds the task without a `project` when you do not
have one.

`update_task` edits an existing task: `title`, `description`, `body`, `tags`,
`due`, `agent`, `priority`, `reminder_frequency`, `category`, `location`,
`parent`. Only the fields you pass change, and `""` clears one.

```json
{ "name": "create_task",
  "arguments": { "project": "acme", "title": "Close the auth bug", "description": "The 401 on refresh — reproduce it first", "due": "<tomorrow>", "tags": ["bug"] } }

{ "name": "get_task",    "arguments": { "task": "t_abc123" } }
{ "name": "update_task", "arguments": { "task": "t_abc123", "due": "2026-06-10", "agent": "ana", "priority": "urgent" } }
{ "name": "update_task", "arguments": { "task": "t_abc123", "due": "" } }
{ "name": "complete_task", "arguments": { "task": "t_abc123", "action": "status", "status": "in_review" } }
```

**`update_task` does not set `status` or close a task.** A board column is
`complete_task({ action: "status" })`, which validates against the column
catalog this install actually has; closing is `action: "done"` (or `"drop"`).
A raw patch would write a column id nothing checked, and the fold would read it
back as `pending` — a move reported as done that never happened.

**`description` vs `body`.** `description` is for the person; `body` is the prompt an agent receives. Most tasks the owner dictates are the first kind — write the description and leave `body` empty. Putting a prompt where the description belongs is what turns a to-do list into a queue of jobs its owner cannot read.

"What's pending in acme?" → `list_tasks({ project: "acme" })`. If user doesn't say which project, `list_projects` first and ask — never assume. If the channel has pinned project context (Telegram), use that.

Commitments are the sibling type and have the same shape: `record_commitment`,
`list_commitments`, `update_commitment`, `mark_commitment`. See `apx-commitment`.

**Three record types, one question each.** Getting this wrong fills the wrong list and the right one stays empty:

| Type | The question that picks it | Tool |
|---|---|---|
| task | Is there something still TO DO? | `create_task` |
| commitment | Did you promise it to a NAMED PERSON? | `record_commitment` |
| milestone | Did a phase of work just END? | `mark_milestone` |

A milestone is not a to-do — it already happened, and nobody is meant to act on it. It exists so a chat that ran forty turns can be followed afterwards: `mark_milestone({ title: "Reel analysed" })` when the phase ends, three to six times over an afternoon's work, not per tool call. Default state is `done`; use `failed` when it did not work — that is the one that tells the owner something was left half-finished, and it is the whole reason the type exists. Pass `track` to group the steps of one piece of work. Every call hands back the chat's still-open milestones, so a later turn can close one by id without a second lookup.

## Anti-examples

```bash
# DON'T add tasks without --project for real work.
apx task add "Stuff"            # falls back to first registered project (or default=0)

# DON'T use `done` when the task is no longer relevant. Use `drop`.
apx task done t_abc            # "I completed this work"
apx task drop t_abc            # "no longer needed; archive without completion"
# Reporting/metrics distinguish them.
```

## Endpoint surface

```
GET    /api/tasks                                cross-project; same filters, plus ?offset
GET    /api/projects/:pid/tasks                  ?state=open|done|dropped|all&status=pending|running|in_review|blocked&tag=X&agent=Y&due_before=ISO&due_after=ISO&updated_since=ISO&limit=N
POST   /api/projects/:pid/tasks                  { title, body?, tags?, due?, agent?, source?, meta? }
GET    /api/projects/:pid/tasks/:id
PATCH  /api/projects/:pid/tasks/:id              { patch: {...} }
POST   /api/projects/:pid/tasks/:id/done         { by? }
POST   /api/projects/:pid/tasks/:id/drop         { by? }
POST   /api/projects/:pid/tasks/:id/reopen
POST   /api/projects/:pid/tasks/:id/status       { status: pending|running|in_review|blocked }
GET    /api/projects/:pid/tasks-summary          → { open, done, dropped, overdue, total, status:{…} }
```

## Don't

- Don't use tasks for reminders that need to *fire* — that's a future routine kind (`task-due-notify`, not built). Tasks are a list, not a scheduler.
- Don't depend on `done` deleting the task. It doesn't. Event log stays.
- Don't grep `~/.apx/projects/<id>/tasks/*.jsonl` for state — use `list_tasks` / `get_task` (or `apx task list`). Fold logic isn't trivial (later events override fields).
- **Never edit a task by writing to that log.** A shelled `python`/`sed` against the JSONL skips every normalizer in the store, and an append-only log edited in place is a state nobody can reconstruct. `update_task` is the only correct way, and it covers every field.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…