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 <[email protected]>
This commit is contained in:
2026-10-12 14:38:24 -05:00
co-authored by Claude Opus 5.5
parent ef5d75e9ad
commit 04db738a35
+244
View File
@@ -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-<row>/`
### 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-<row>/`
### 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
`<dataRoot>/workspaces/<business>/<instance>/`
(`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-<row>/`
### What ships
- At every launch, before the bundle is built, the host makes sure each
project in the business file has a clone at
`<workspace>/<project id>/`. The host runs git, outside the session's
sandbox. A missing clone is `git clone --no-hardlinks <root>`, 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.