Every task should trace up to the mission. Goals sit above tasks: a tree of
G-n nodes, each with a title and a why. A worker that picks up a task
gets the why with the what — pt show, MCP, and pt context all carry the
chain.
Borrowed from Paperclip's "goal ancestry". pTask already had tasks, a
free-text project column, parent_uuid subtasks, and task_links. Nothing
sat above a project.
| Piece | Where | Role |
|---|---|---|
| Goal | goals |
id uuid, unique seq for G-n, title, why, nullable parent_id, status active / achieved / abandoned |
| Direct link | tasks.goal_id |
Optional FK to goals.id |
| Inheritance | tasks.parent_uuid |
A subtask with no goal_id inherits its parent task's effective goal |
Human ids are G-<n>, minted from pt_counters.goal_id the same way tasks
mint PT-<n> and approvals mint AP-<n>.
Status defaults to active. pt goal done G-n sets achieved;
pt goal abandon G-n sets abandoned. pt goal ls lists active goals;
--all includes the rest.
There are no per-project default goals. project is free text (a tag, not an
entity) and too fragile to key a default on. Linking is always explicit.
effective_goal(task) resolves, in order:
- direct — the task's own
goal_id - parent — walk
parent_uuiduntil a task with agoal_idis found - none
The returned chain is leaf → root (the linked goal first, then its
ancestors toward the mission). pt show --json and MCP put that chain on
goal_chain ([{id, title, why}, ...]) plus goal_source
(direct | parent | none).
pt goal show G-n uses the same walk for chain (ancestors, nearest first,
excluding self). tasks are those whose effective goal is this goal.
rollup.{open,done} counts tasks whose effective goal is this goal or any
descendant. Open means not done / dismissed.
pt goal orphans lists open tasks with no effective goal.
pt goal set-parent G-n G-m refuses self and any cycle (the new parent is
already a descendant of the node being moved).
A corrupted database can still contain a cycle — there is no trigger that
prevents one, because pt show must survive it. Every parent-pointer walk
(task parent_uuid and goal parent_id) stops on a repeated node and caps
depth at 16. A cycle is cut, not repeated; pt show always terminates.
Tree listing (pt goal ls) is a pre-order walk: parent before children,
siblings by seq. Depth is the real tree depth, even when inactive parents
are filtered out of the default listing.
pt goal add TITLE [--why W] [--parent G-n]
pt goal ls [--all]
pt goal show G-n
pt goal link PT-n G-n
pt goal unlink PT-n
pt goal set-parent G-n G-m
pt goal done G-n
pt goal abandon G-n
pt goal orphans
All honour the global --json. Goal JSON:
{
"id": "G-1",
"uuid": "...",
"title": "...",
"why": "...",
"parent": "G-n or null",
"status": "active",
"created_at": "...",
"updated_at": "..."
}ls items add depth. show adds chain, children, tasks, and
rollup.
pt show PT-n in JSON is the task object plus goal_chain and
goal_source. Human pt show prints a short Why block when a chain exists.
Mutations are journaled with the actor: goal.created, goal.updated,
task.goal_linked, task.goal_unlinked.
pt context PT-n prints a Markdown brief for a worker mission:
- task title (and description, when set)
- a Why section listing the goal chain root → leaf, each with its why
- Blockers: open
depends_onprerequisites with their PT ids and titles
When the task has no effective goal the Why section is omitted; the command still succeeds. Use this as the mission preamble when dispatching a worker: the what and the why in one page, plus anything that must finish first.
pt context PT-42
task_show, task_next, and task_claim each carry goal_chain and
goal_source per task. New tools:
| Tool | Arguments |
|---|---|
goal_list |
optional all |
goal_show |
id (G-n) |
goal_link |
{task, goal} |