From 04db738a351e25f6ae92e3c679cffa57887f9368 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Mon, 12 Oct 2026 14:38:24 -0500 Subject: [PATCH] plans: PM task flow brief, task reads, requirement ids, role checkout (#1583, #1584, #1585) Triage of Vikunja P19 #228 and its c78, the gaps Jason's first PM request hit. Co-Authored-By: Claude Opus 5.5 --- docs/plans/2026-10-12_pm-task-flow.md | 244 ++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 docs/plans/2026-10-12_pm-task-flow.md diff --git a/docs/plans/2026-10-12_pm-task-flow.md b/docs/plans/2026-10-12_pm-task-flow.md new file mode 100644 index 000000000..88603b3d0 --- /dev/null +++ b/docs/plans/2026-10-12_pm-task-flow.md @@ -0,0 +1,244 @@ +Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`. +It triages Vikunja P19 #228 (dogfood), which Mos filed from Jason's +first PM request after the PM started (talk fef3fa41, PM RESULT e300f8af, +about 2026-10-12T19:27Z; Mos sage-0100), and the three gaps Mos added in +#228 c78 from the same test (sage-0101). Three rows: the board read, the +requirement ids, and the role checkout. All three block S7 (row 42): the +PM can't carry a request to a task, or the coder work on the project, +until they land. Gitea issues #1583, #1584 and #1585. + +# The PM's task flow on a real board (#228) + +## PM task reads that fit a session + +### Problem + +The PM's `list_tasks` tool takes no arguments +(`packages/harness/src/tools.mjs:116`, `obj({})`). The broker's `tasks` +verb returns every row of `task_current` with all its fields, description +included (`packages/bus/src/broker.mjs:313-329`, `:860`), and the MCP +server sends it as indented JSON (`packages/harness/src/mcp-server.mjs:40`, +`pi-extension.mjs:45`). On the Mosaic Stack board that reply was about +120 KB. Claude Code saved the oversized result to a file outside the +PM's workspace, and the run's file guard refused to read it. That refusal +is right; the reply is the defect. So the PM couldn't see the board. + +### Owner and reviewer + +Owner: Dewey, after row 81 lands. Reviewer: Darkwing. + +### Files owned + +- `packages/bus/src/broker.mjs`, the `tasks` verb and `#taskRows` only +- `packages/harness/src/tools.mjs`, `mcp-server.mjs`, `pi-extension.mjs` +- `packages/bus/README.md`, `packages/harness/README.md`, the sections + on the `tasks` verb and the tool replies +- tests under `packages/bus/tests/` and `packages/harness/tests/` +- `agents/dewey/work/queue-/` + +### What ships + +- The broker's `tasks` verb takes optional arguments. With none, it + returns what it returns today, so the Console and `mosaic` are + unchanged. With any, it filters and pages: `done` (true, false; the + tool's default is false), `assignee` (a role), `label`, `requirement`, + `limit` (default 50, at most 200) and `after` (a task ref, for the next + page). A filtered reply is `{ tasks, total, next }`, and each task is + the short form: ref, title, done, bucket, assignee roles, labels, + priority, requirement and the last change time, with no description. + An unknown argument or a bad value refuses with `invalid-request`. +- One more argument, `task_ref`, returns that one task in full, as today's + row, so the PM reads a description only when it needs one. +- `list_tasks` exposes those arguments and says the default is open + tasks, 50 at a time. A `get_task` tool takes `task_ref`. Both are + typed for the pm role and the same holders as today. +- Every tool reply has a size cap, in `mcp-server.mjs` and + `pi-extension.mjs` both, set below the size at which the pinned Claude + CLI saves a tool result to a file. Dewey finds that size for the + version the bus unit runs (`MOSAIC_CLAUDE_BIN`, decision 91), records + how in the packet, and sets the cap with margin. Over the cap, the tool + returns an error naming the size and the cap and telling the session to + narrow the query, never a cut-off reply. Replies go out as compact + JSON, not indented. +- Tests: a board of 500 fixture tasks with long descriptions, where + `list_tasks` with defaults fits under the cap and pages to the end + with `next`; each filter; `task_ref`; the refusals; the cap error from + both servers; and the no-argument call still matching today's reply. + +Suites: the bus, harness, webui and cli node suites, and every +`scripts/test-*.sh`. + +### Out of scope + +Loosening the run's file guard or adding the tool-results directory to +the workspace. The Console's task views. Any change to who may read +tasks. Server-side search over descriptions. + +### Gate + +Darkwing approves on the row's issue, naming the candidate manifest. The +suites above pass on Sage's gate rerun. The broker and harness change is +live at the next bus restart, which ends the PM session and launches a +new one (decision 109). Sage notifies Mos before the landing and sends +the commit after it; Mos restarts the bus and confirms the PM relaunched. + +## The approved requirement ids as data + +### Problem + +`task.create` needs a requirement id, and `verbs.mjs:187` checks only +its format, `REQ-[A-Z]+-[1-9][0-9]*`. Nothing tells the PM which ids +exist. The PRD (`docs/prd/mosaic-stack.md`, version 1.0, approved +2026-10-05, lead decision 61) holds 24 of them, but no tool reads it. So +the PM refused to create a task, and asked Jason in its RESULT instead +of raising a decision to his Inbox. In the same test it later created +P19 #229 and #230 under `REQ-QA-1`, which passes the format check and +isn't in the PRD. + +Two more gaps from #228 c78 are words, not missing paths: + +- `task_create`'s `request` says "Id of the message or human input this + task answers" (`tools.mjs:41`). `create()` accepts only a `human.input` + event id (`verbs.mjs:352`, `broker.mjs:460`), so the PM's message id + refused `request-not-found`. Each message `message.receive` returns + already carries that id as `request` (`broker.mjs:790-797`); the tool + doesn't say to use it. +- A blocking decision needs a task (`broker.mjs:596`, `task-required`), + and the PM read that as no Inbox path for a missing requirement. A + non-blocking decision needs none. `prd.approve` is gated + (`broker.mjs:29`), so it routes to the human. Nothing tells the PM + this. + +### Owner and reviewer + +Owner: Dewey, after the row above (both touch `broker.mjs`). Reviewer: +Filbert. + +### Files owned + +- `packages/tasks/requirements.json`, new +- `packages/tasks/src/requirements.mjs`, new, and + `packages/tasks/src/verbs.mjs`, the `task.create` requirement check only +- `packages/bus/src/broker.mjs`, a `requirements` read verb only +- `packages/harness/src/tools.mjs`, a `list_requirements` tool and + `task_create`'s `request` description only +- `roles/pm.md`, the paragraphs on creating tasks and raising decisions + only +- `packages/tasks/README.md`, `packages/bus/README.md` +- tests under `packages/tasks/tests/`, `packages/bus/tests/` and + `packages/harness/tests/` +- `agents/dewey/work/queue-/` + +### What ships + +- `packages/tasks/requirements.json` lists the PRD version and path and + each id with a one-line title. A test reads the PRD's `**REQ-...**` + bullets and fails unless the list matches them exactly, ids and order. + A later PRD version is a new list in a reviewed commit, like the PRD. +- `requirements.mjs` checks the file's shape and loads it frozen, as + `fields.mjs` does for `fields.json` (row 61). A bad file refuses at + load. +- `task.create` refuses with `unknown-requirement` when the id isn't in + the list. The format check stays. +- The broker's `requirements` verb returns the list, a read for every + session that can create tasks. The `list_requirements` tool exposes it + to the pm role. +- `roles/pm.md`: before creating a task, pick the id from + `list_requirements`. When no id fits the request, raise a decision with + action `prd.approve`, `blocking` false and no `task_ref`, naming the + request and the nearest ids, and don't create the task; don't ask in + a RESULT. +- `task_create`'s `request` says it is the `request` field of the human + message being answered, not the message id. `pm.md` says the same. +- Tests: the PM's role session raises that `prd.approve` decision with + no task and it routes to the human; `task.create` with a message id + still refuses `request-not-found`, and with that message's `request` + it passes the request check. + +Suites: the tasks, bus, harness, webui and cli node suites, and every +`scripts/test-*.sh`. + +### Out of scope + +Editing the PRD or adding ids. Mapping Jason's past requests to ids. +Showing the list in the Console. Letting a blocking decision go without a +task. What happens to #229 and #230; their id is the PM's and Jason's to +correct. + +### Gate + +Filbert approves on the row's issue, naming the candidate manifest. The +suites above pass on Sage's gate rerun. Live at the next bus restart and +PM launch, so the same decision-109 notices as the row above. + +## A project checkout in each role workspace + +### Problem + +A launched role session works in +`/workspaces///` +(`packages/seat/src/session.mjs:20,46`), which the launcher creates +empty (`packages/cli/src/launcher.mjs:159-160`). Nothing puts the +project there. In #228's test the coder's workspace held only a fresh +`.git` and its own new files, so it worked beside the source rather than +in it, and the PM asked Jason for a checkout (dad88d23). Slice 1's goal +is the stack building pieces of itself (`2026-10-04_slice-1.md`, line +20), which needs the coder in the project. The business file names each +project's `root` (`packages/business/src/business.mjs:170-177`); that is +the only source the launcher has. + +### Owner and reviewer + +Owner: Darkwing, after row 63 and ahead of row 88. Reviewer: Filbert. + +### Files owned + +- `packages/cli/src/launcher.mjs`, the workspace step in `start()` only +- `packages/seat/src/checkout.mjs`, new +- `packages/seat/README.md`, `packages/cli/README.md`, the workspace + paragraphs +- tests under `packages/seat/tests/` and `packages/cli/tests/` +- `agents/darkwing/work/queue-/` + +### What ships + +- At every launch, before the bundle is built, the host makes sure each + project in the business file has a clone at + `//`. The host runs git, outside the session's + sandbox. A missing clone is `git clone --no-hardlinks `, at the + root's checked-out branch. An existing clone with a clean tree on that + branch fast-forwards from its origin. Anything else (a dirty tree, + another branch, a fast-forward that fails) is left exactly as it is, + and the launch log records `checkout-kept` with the project id and the + reason. The host never resets, cleans, deletes or force-updates a + clone. +- A clone's origin is the local root path. No credential, token or + Gitea remote goes into it. The root is never written. +- A clone that can't be made refuses the launch with `checkout-failed` + and the project id, through the broker's `refuse()` like the other + launch steps. No partial clone is left behind. +- The bundle's session text names the clone's path, so the session + knows where the project is. +- Tests: a first launch clones; a second fast-forwards after a commit + to the root; a dirty clone and one on another branch are kept and + logged; an unreadable root refuses `checkout-failed` with nothing + left; the root's tree and refs are unchanged after each case; no + remote URL in a clone carries a credential. + +Suites: the seat, cli, bus and harness node suites, and every +`scripts/test-*.sh`. + +### Out of scope + +How the coder's change reaches the reviewer and the repository: pushing +a working branch (`git.push.working`), the reviewer reading the coder's +change, and landing. That is the next row, which Sage briefs once this +one lands. Credentials in a clone. The PM's workspace contents beyond +the clone. + +### Gate + +Filbert approves on the row's issue, naming the candidate manifest. The +suites above pass on Sage's gate rerun. The launcher runs in the bus +host, so the change is live at the next bus restart: the same +decision-109 notices as the rows above.