diff --git a/.claude/skills/board/SKILL.md b/.claude/skills/board/SKILL.md
index d002b748..b26e35e9 100644
--- a/.claude/skills/board/SKILL.md
+++ b/.claude/skills/board/SKILL.md
@@ -30,7 +30,7 @@ Read-only. `BIN=scripts/pipeline` (relative to the engine root, every session's
Nothing runs before a refusal except `mp-task scope`, `mp-task project` and
`project list`.
1. `$BIN/mp-task board
` (with `all` when typed) — JSON lines with `key`,
- `status`, `title`, `dir`, `worktreePath`. The project's `checkoutPath` and
+ `status`, `title`, `parent`, `dir`, `worktreePath`. The project's `checkoutPath` and
`home` come from the `mp-task project
` output of step 0.
2. For each row, the files in `dir`, only those that exist: `derive.json` (plan
URL), `pr.md` (PR), `progress.md` (stage). For each task with a `derive.json`
@@ -40,7 +40,7 @@ Read-only. `BIN=scripts/pipeline` (relative to the engine root, every session's
`checkoutPath` is not `null`, the entry of `git -C worktree list`
whose path equals `--`; otherwise —. Run
`git worktree list` once per project.
-4. Print one table: key, title, status, plan (URL or —), open threads, PR,
+4. Print one table: key, title, parent (key or —), status, plan (URL or —), open threads, PR,
worktree, next. The next column, for any project: `/plan` for `new`; `/do` for
`planning` with no open threads; `/plan` again when threads are open;
«ручная проверка → /ship» for `review`; — for `merged`/`done`; the reason for
diff --git a/.claude/skills/do/SKILL.md b/.claude/skills/do/SKILL.md
index 5d11e9ec..68c59aa4 100644
--- a/.claude/skills/do/SKILL.md
+++ b/.claude/skills/do/SKILL.md
@@ -15,7 +15,8 @@ teammate.
`$BIN/mp-task scope`, then apply the «Session scope» rule of `docs/PIPELINE.md` to
`/do `: `global` — any key; `P` — continue only when the key has the code `P`;
-`P-N` — continue only for `/do P-N`; anything else (an argument that is not a key, a
+`P-N` — continue only when the key is `P-N` or its subtask by
+`$BIN/mp-task subtasks P-N`; anything else (an argument that is not a key, a
malformed session key, a non-zero exit) — print the refusal text from
`docs/PIPELINE.md` and stop. This step runs before «Preconditions»: no
`$BIN/mp-task show`, no `catch_up`, no `mp-worktree` before it.
diff --git a/.claude/skills/flow/SKILL.md b/.claude/skills/flow/SKILL.md
index f34530b1..a9e20201 100644
--- a/.claude/skills/flow/SKILL.md
+++ b/.claude/skills/flow/SKILL.md
@@ -23,12 +23,13 @@ reads.
`$BIN/mp-task scope`, then apply the «Session scope» rule of `docs/PIPELINE.md` to
`/flow`: `global` — continue when every key has the same code (keys of several
projects get the «mixes projects» text); `P` — continue when every key has the code
-`P`; `P-N` — refused; an argument that is not a key (other than the run name), a
-malformed session key or a non-zero exit — refused. A refusal prints its text from
+`P`; `P-N` — continue when every key is `P-N` or its subtask by
+`$BIN/mp-task subtasks P-N`; an argument that is not a key (other than the run
+name), a malformed session key or a non-zero exit — refused. A refusal prints its text from
`docs/PIPELINE.md` and stops. This step runs before «Preconditions», before
`git fetch`, before the flow branch is created or pushed and before the flow score
is written. The nested plan and do runs go in this same session and pass their
-own Step 0 with the same key.
+own Step 0 with the same key; the subtasks of `P-N` pass it by the same rule.
## Preconditions
@@ -41,14 +42,29 @@ own Step 0 with the same key.
- `$BIN/mp-task flow ` gives the project's `integration` and `prTarget`;
a non-zero exit → print its error and stop before any write.
+## Subtasks
+
+Before the flow score is written, run `$BIN/mp-task subtasks ` for every typed
+key. The run list: for each typed key, in the typed order, its descendants in the
+order of the `subtasks` output, then the key itself; a repeated key is dropped and
+its first occurrence stays. The status of each subtask decides by «Subtasks» of
+`docs/PIPELINE.md`: skipped, taken like a typed key, or one question — header
+`Подзадача`, options «Пропустить (рекомендую)» and «Включить», the reason from
+`$BIN/mp-task show ` in the question text. An included subtask gets
+`$BIN/mp-task status planning "в потоке : включена инженером"`.
+
## Order
-Before any write, compute the order of the keys: first the dependencies from the
-«Связи» section of each `source.md` (a key goes after the keys of this run it
-depends on); then the files each card names — tasks touching the same files go
-one after another, the one the others build on first; otherwise the order typed.
-Announce it in the chat: the keys in order, one line of reasoning per key. Then go
-on without waiting for a confirmation. A dependency outside the run that is not
+Before any write, compute the order of the keys. The base is the run list
+(subtasks before their parent). Then the dependencies from the «Связи» section of
+each `source.md` (a key goes after the keys of this run it depends on): a stable
+topological reordering that never puts a parent before its subtasks. Then the files
+each card names — tasks touching the same files go one after another, the one the
+others build on first — where those constraints allow it. A cycle between the tree
+and «Связи» is a question to the engineer. Announce it in the chat: the keys in
+order, a mark `подзадача ` on each subtask, one line of reasoning per key,
+then on a line of its own the skipped subtasks with their status. Then go on
+without waiting for a confirmation. A dependency outside the run that is not
`review`, `merged` or `done` is a question to the engineer before the start
(wait for it, or go on and record it as `/do --force` does).
@@ -63,7 +79,8 @@ on without waiting for a confirmation. A dependency outside the run that is not
(`git -C worktree add flow/` when missing). Merges
happen there; never in the base clone.
3. Flow score: `/flows/.md` — the branch with its base SHA, the
- worktree, the announced order with a checkbox per key, later the PR URL. Commit
+ worktree, the announced order expanded with subtasks with a checkbox per key,
+ the list of skipped subtasks, later the PR URL. Commit
with `$BIN/mp-home-commit ""` (it stages `flows` too).
## Reconnaissance up front
@@ -113,8 +130,8 @@ key is recorded in the flow score.
merged"`. Then shut the key's teammate down (`SendMessage` «shut down»;
`TaskStop` if it does not answer).
-Re-typing the same command resumes: keys already in `review` for this flow are
-skipped.
+Re-typing the same command resumes: it expands the subtasks again, and keys
+already in `review` for this flow are skipped.
## Questions
diff --git a/.claude/skills/plan/SKILL.md b/.claude/skills/plan/SKILL.md
index b18047f3..10eda36d 100644
--- a/.claude/skills/plan/SKILL.md
+++ b/.claude/skills/plan/SKILL.md
@@ -17,8 +17,8 @@ order typed.
`$BIN/mp-task scope`, then apply the «Session scope» rule of `docs/PIPELINE.md` to
`/plan` and every typed key: `global` — any key; `P` — continue only when every
-typed key has the code `P`; `P-N` — continue only when every typed key equals
-`P-N`; anything else (an argument that is not a key, a malformed session key, a
+typed key has the code `P`; `P-N` — continue only when every typed key is `P-N`
+or its subtask by `$BIN/mp-task subtasks P-N`; anything else (an argument that is not a key, a malformed session key, a
non-zero exit) — print the refusal text from `docs/PIPELINE.md` and stop. This step
runs before the status check in «Input», before `$BIN/mp-task show` and before any
derive call or subagent, so `/plan MP-41` in a TBA chat stops on the scope, not on
diff --git a/.claude/skills/ship/SKILL.md b/.claude/skills/ship/SKILL.md
index 2f6503d8..809a465a 100644
--- a/.claude/skills/ship/SKILL.md
+++ b/.claude/skills/ship/SKILL.md
@@ -15,7 +15,10 @@ one entered. `BIN=scripts/pipeline` (relative to the engine root, every session'
`$BIN/mp-task scope`, then apply the «Session scope» rule of `docs/PIPELINE.md` to
`/ship` and every typed argument: `global` — any keys and `flow/`; `P` —
keys with the code `P` and a flow of project `P`; `P-N` — continue only when every
-typed argument equals `P-N` (`flow/` in a task chat is refused); anything
+typed key is `P-N` or its subtask by `$BIN/mp-task subtasks P-N`, and for
+`flow/` when every key of the checkboxes of the flow score
+`/flows/.md` is `P-N` or its subtask (no file — the refusal text
+from `docs/PIPELINE.md`); anything
else (an argument that is not a key, a malformed session key, a non-zero exit) —
print the refusal text from `docs/PIPELINE.md` and stop. This step runs before `pr.md` is read, before
`git fetch`, merge, push, `gh`, any status change and any worktree removal.
diff --git a/.yoke/context.md b/.yoke/context.md
index cde8ff38..e05ce40a 100644
--- a/.yoke/context.md
+++ b/.yoke/context.md
@@ -21,16 +21,19 @@ The Session with the `global` Scope. Started from the engine root; administers a
The Session with a `project` Scope. Exactly one per Project. Works in the context of the whole Project, opens and manages its Task chats, receives their Events.
## Task chat
-The Session with a `task` Scope. Bound to one Task; works only in that Task's context, in its own worktree. Manages nothing.
+The Session with a `task` Scope. Bound to one Task; works only in that Task's context, in its own worktree. Manages nothing except the pipeline skills the engineer types in it, which may run its Task and its Subtasks (docs/PIPELINE.md «Session scope»).
## Task
The unit of work recorded in the database (formerly "request"). A Task belongs to a Project. A Task chat exists only for an existing Task.
+## Parent task / Subtask
+A Task may name one Parent task of the same Project (`parent`); Subtasks form a tree of any depth. Not the Parent of a Session: a Subtask's Task chat still has the Project chat as its Parent, and its Events and Milestones go there.
+
## Project / Organization
A registered Project belongs to one Organization. Organization is a grouping and a context source only; it has no Session of its own.
## Parent
-The structural owner of a Session: Task chat → Project chat → Main chat. Parenthood never changes and does not depend on who opened the Session.
+The structural owner of a Session: Task chat → Project chat → Main chat. Parenthood never changes and does not depend on who opened the Session. For Tasks see Parent task / Subtask.
## Event
A record created when a child Session's turn that a Parent message started stops, carrying the final message of that turn (`turn_end`), or stops mid-turn waiting for a human (`waiting`), and when a Task reaches a Milestone (`milestone`). Events flow only from a Task chat to its Project chat; the Main chat receives none. Parent messages are kept in the same table as kind `message` and go down, from a Parent to its child.
diff --git a/AGENTS.md b/AGENTS.md
index 4147650b..587ff44c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -202,7 +202,7 @@ logging or Herdr tab is a prerequisite. Follow the user's requested scope and se
the older record moves to `~.json`. A fork started by hand without
`MYPI_SCOPE` is not registered. `mypi sessions` (MCP `sessions`) shows the tree;
`mypi session forget ` drops a row on the user's request. Without
- `XDG_RUNTIME_DIR` or an initialized schema 12 database registration fails and the
+ `XDG_RUNTIME_DIR` or an initialized schema 13 database registration fails and the
warmup still runs. /open (MCP open_session, CLI mypi open) focuses a live
session's Herdr tab or opens a new tab in its project's Herdr workspace (label
org/project, created when missing; a task gets its tab there without starting
diff --git a/README.md b/README.md
index ad285fc1..b83ef88e 100644
--- a/README.md
+++ b/README.md
@@ -170,7 +170,7 @@ order: the row for this native id, the live record of the same process,
the older record moves to `~.json`. A fork started by hand without
`MYPI_SCOPE` is not registered. `mypi sessions` (MCP `sessions`) shows the tree;
`mypi session forget ` drops a row on the user's request. Without
-`XDG_RUNTIME_DIR` or an initialized schema 12 database registration fails and the
+`XDG_RUNTIME_DIR` or an initialized schema 13 database registration fails and the
warmup still runs.
`/open ` (the Claude skill `/open`, the Pi command `/open`, CLI
@@ -273,6 +273,9 @@ command that opens the database, including warmup, asks for it.
Upgrading to schema 12: run `mypi db init` once after updating (back up the
database first).
+Upgrading to schema 13: run `mypi db init` once after updating (back up the
+database first).
+
### Updating without overwriting customizations
The installed engine is not a development checkout; contribute through the
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 57476270..d283261f 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -67,7 +67,7 @@ organizations(id, slug)
projects(id, org_id, slug, code, checkout_path)
task_statuses(id, code, is_terminal)
tasks(id, project_id, number, title, status_id,
- context_dir, worktree_path, created_at, updated_at)
+ context_dir, worktree_path, parent_id, created_at, updated_at)
sessions(key, scope_type, project_id, task_id, agent, native_id, created_at)
events(id, from_key, to_key, kind, message, created_at, delivered_at)
```
diff --git a/docs/HTTP-API.md b/docs/HTTP-API.md
index 273af058..56cd0a39 100644
--- a/docs/HTTP-API.md
+++ b/docs/HTTP-API.md
@@ -97,7 +97,7 @@ A task key is `CODE-N` (`MP-12`), a project code is `CODE` (`MP`).
| `GET /v1/tasks?project&status&q&cursor&limit` | read | `task_page` | `{ items: task[], next }` |
| `GET /v1/tasks/{KEY}` | read | `task_show` | `task` |
| `POST /v1/tasks` | write | `task_create` | `task`, status 201 |
-| `PATCH /v1/tasks/{KEY}` | write | `task_status`, `task_title`, `task_progress`, `task_touch`, then `task_show` | `task` |
+| `PATCH /v1/tasks/{KEY}` | write | `task_status`, `task_title`, `task_parent`, `task_progress`, `task_touch`, then `task_show` | `task` |
| `GET /v1/events?to&from&kind&delivered&q&cursor&limit` | read | `event_list` | `{ items: event[], next }` |
| `GET /v1/journal?scope&from&to&type&q&cursor&limit` | read | `journal_page` | `{ items: journal[], next }` |
| `POST /v1/journal` | write | `journal_add` | `{ id }`, status 201 |
@@ -125,11 +125,13 @@ Bodies are JSON objects; every field is a string unless noted, unknown fields ar
refused:
- `POST /v1/tasks`: `project`, `title`, `status`, `slug`, `source` (the exact source
- text). No file sources, no `adoptSource`.
-- `PATCH /v1/tasks/{KEY}`: `status`, `title`, `reason`, `progress`, `touch` (only
- `true`). At least one of status, title, progress, touch; `reason` is required with
- status or title and refused without them; `touch` only alone. Parts apply in the
- order status, title, progress, touch; progress carries no artifacts.
+ text), optional `parent` (the key of a parent task of the same project). No file
+ sources, no `adoptSource`.
+- `PATCH /v1/tasks/{KEY}`: `status`, `title`, `parent` (a task key or `null` to
+ clear), `reason`, `progress`, `touch` (only `true`). At least one of status, title,
+ parent, progress, touch; `reason` is required with status, title or parent and
+ refused without them; `touch` only alone. Parts apply in the order status, title,
+ parent, progress, touch; progress carries no artifacts.
- `POST /v1/journal`: `scope`, `text` (a note). `scope` takes the same references
as the journal query (`global`, an org slug, `org/project`, `project:FX`,
`task:FX-1`).
@@ -139,7 +141,8 @@ Response shapes:
- `project`: `{ code, org, slug, identity, repository, checkoutPath }`
- `flow`: `{ project, code, source, integration, taskBranch, prTarget, mergeMethod, release }`
-- `task`: `{ key, project, number, title, status, isTerminal, contextDir, worktreePath, createdAt, updatedAt }`
+- `task`: `{ key, project, number, title, parent, status, isTerminal, contextDir, worktreePath, createdAt, updatedAt }`
+ (`parent` is the key of the parent task or `null`)
- `event`: `{ id, from, to, kind, text, createdAt, deliveredAt }`
- `journal`: `{ id, at, scope: { type, key? }, type, text, artifacts: [{ path, commit }] }`
(`commit` is `null` for historical path-only artifacts)
@@ -184,7 +187,7 @@ field or route needs `/v2`.
## Engine updates and migrations
-The server opens a database of schema 12 and keeps its code in memory. After an
+The server opens a database of schema 13 and keeps its code in memory. After an
engine update or a migration, restart it.
## Access log
diff --git a/docs/M1-CLI.md b/docs/M1-CLI.md
index e9b771f4..6b23673e 100644
--- a/docs/M1-CLI.md
+++ b/docs/M1-CLI.md
@@ -59,7 +59,7 @@ and projects without a clone exit 1 with a JSON error. Over 64 KiB the oldest fa
the farthest memory zone are hidden with a marker naming the MCP `memory` show call and,
in parentheses, `memory show -s …`. `--max-chars n` (1–21845) applies the same cut at n characters instead; the Claude Code hook uses 9500. `journal read` global remains global-only.
Task cards from task show and task list include worktreePath (null until a worktree is
-recorded).
+recorded) and parent (the key of the parent task, or null).
`project resolve` returns `{match:"project",identity,code,scope}` or `{match:"none"}`
or `{match:"ambiguous",projects}` using the registered checkout resolver; sibling
worktrees are not inferred.
@@ -78,11 +78,14 @@ the remaining folder or null.
```text
task create --project org/project --title "Title" --status research --slug short-slug --file /path/to/source
+task create --project org/project --title "Subtask" --status new --slug subtask --parent MP-1 --file /path/to/source
task list [--project org/project] [--status code]
task show MP-1
task worktree MP-1
task status MP-1 review --reason "ready for review"
task title MP-1 "Better title" --reason "clarification"
+task parent MP-2 MP-1 --reason "part of the epic"
+task parent MP-2 none --reason "split out"
task progress MP-1 "actual outcome" [--artifacts /path/to/artifacts.json]
status list
status add accepted --terminal
@@ -96,7 +99,7 @@ journal read --all --type status_changed
```
These are independent syntax examples, not a mandatory sequence. `task status` to `review`, `blocked`, `merged` or a terminal status also queues a `milestone` event for the project chat (inbox, warmup), unless the caller is the project chat itself (its registered key, else `MYPI_SCOPE`, as for `warmup`); milestones are never sent live. Tasks belong to a
-registered project; existing standalone REQ records remain readable.
+registered project; existing standalone REQ records remain readable. A parent task is a task of the same project, never the task itself and never one of its own subtasks (no cycles); `task parent` writes a `note` `Parent: → . ` to the task's journal.
`project flow MP` (or `org/project`) prints the project's branch flow from
`home/projects///flow.json`:
diff --git a/docs/MCP.md b/docs/MCP.md
index 3ee83840..404765e6 100644
--- a/docs/MCP.md
+++ b/docs/MCP.md
@@ -48,7 +48,7 @@ verification command. See the installed Pi MCP/security documentation for setup.
## Tool and transport contract
15 tools are available through standard tools/list: warmup, task_list, task_show, task_create, task_update, memory, note, journal, context_read, context_write, projects, sessions, open_session, send_message and inbox. Inspect live schemas for fields. The remote /mcp endpoint offers a narrower subset ([below](#remote-mcp-over-http)). Maintenance — db init, bootstrap, backup and its publication, restore, journal import, the status dictionary, context restore, project and organization removal, home migrate-tasks and task worktree — is CLI-only ([M1-CLI](M1-CLI.md)); over MCP these names are unknown tools.
-`memory` and `projects` take `action`, `note` takes `kind`, plus that action's fields; a field of another action is rejected. `task_update` runs its parts as the CLI's task status, title and progress commands (or touch alone) in that order within one serial slot; a failure after an earlier part saved returns `partial` with the saved and missing parts, a failure of the first part is a plain error. A status change to review, blocked, merged or a terminal status queues a milestone event for the project chat (inbox, warmup), unless the caller is the project chat itself (its registered key, else `MYPI_SCOPE`, as for `warmup`); milestones never start a turn.
+`memory` and `projects` take `action`, `note` takes `kind`, plus that action's fields; a field of another action is rejected. `task_update` runs its parts as the CLI's task status, title, parent and progress commands (or touch alone) in that order within one serial slot; `parent` (a task key of the same project, or `null` to clear) needs a reason like status and title and is refused for another project, the task itself or a cycle; a failure after an earlier part saved returns `partial` with the saved and missing parts, a failure of the first part is a plain error. A status change to review, blocked, merged or a terminal status queues a milestone event for the project chat (inbox, warmup), unless the caller is the project chat itself (its registered key, else `MYPI_SCOPE`, as for `warmup`); milestones never start a turn.
Arguments are strict, including nested fields. Context scopes: global/org/project;
task scope is supported for journal, not MEMORY; warmup takes a scope key string (below). Journal read also accepts
explicit `"all"`. A project scope key is its code; task_create.project is org/project
diff --git a/docs/PIPELINE.md b/docs/PIPELINE.md
index 19143981..78ace8ca 100644
--- a/docs/PIPELINE.md
+++ b/docs/PIPELINE.md
@@ -79,8 +79,9 @@ The rule:
`/ship flow/` take the keys of one project.
- `P` (any project): every typed key has the code `P`; a flow holds keys of `P`,
`/ship flow/` takes a flow of project `P`.
-- `P-N`: `/plan` and `/do` only when every typed key equals `P-N`; `/ship` only when
- every typed argument equals `P-N`; `/flow` and `/ship flow/` are refused.
+- `P-N`: `/plan`, `/do`, `/flow` and `/ship` take P-N and its subtasks — every task
+ whose chain of parents reaches P-N (`mp-task subtasks P-N`); `/ship flow/` —
+ when every key of the flow score `/flows/.md` is P-N or its subtask.
- A typed argument that is not a key `CODE-N` (other than `flow/` of `/ship`
and the run name of `/flow`), a malformed session key (`project` is `null` and
the key is not `global`) or a non-zero exit of `mp-task scope`: refused.
@@ -92,14 +93,17 @@ The rule:
A refusal prints its text and stops. Nothing runs before it except `mp-task scope`:
no `mp-task show/status/progress`, no `git fetch`, no `mp-worktree`, no
`mp-home-commit`, no derive calls, no subagents (`/board` also runs `mp-task project`
-and `project list`). The texts (``, the typed
+and `project list`). In a `P-N` session `mp-task subtasks P-N` may run too, and for
+`/ship flow/` also `mp-task project P` and reading the flow score. The texts (``, the typed
arguments and `` substituted):
- `/board` with another project's code: `/board is not allowed in session scope ; this chat shows the board of only. Use /board here or /open global for another project.`
- `/board` with a malformed key: `The board cannot tell this session's project (scope ). Open the main chat with /open global.`
- `/board` with an unknown code: `Unknown project code ; registered codes: .`
- a changing skill with another project's key: `/ is not allowed in session scope ; nothing was written. This chat runs tasks of only; run it in that task's or project's chat (/open ) or in the main chat (/open global).`
- (`` is the code of the typed key.) In a `P-N` chat append: ` This chat may run /plan P-N, /do P-N and /ship P-N only.`
+ (`` is the code of the typed key.) In a `P-N` chat append: ` This chat may run /plan, /do, /flow and /ship for P-N and its subtasks (|none) only.`
+- a non-zero exit of `mp-task subtasks`: `/ cannot read the subtasks of :