# Multi-project sync: rows A and B (2026-10-11) Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`. Jason answered Q61a, relayed by Mos: agents see and work his nested projects ("just share them. Get it done."). Mos ruled the shape: row A now, row B after row A is done, and task.create takes an optional project. Lead decision 86 records it. Three constraints hold for both rows. A change that breaks one of them is out of scope, whatever else it fixes. - A task's ledger ref stays `vikunja:/`. The ledger checks that form on `task_snapshots`, `decisions` and the `events` subject (`packages/bus/schema.sql`), and `task_snapshots` is append-only. Neither row changes `packages/bus/`. Never key a task by its id alone. - `tracker.descendants` defaults to false. With it false, the adapter behaves exactly as v0 does, and every v0 test passes unchanged. - The adapter never writes to a board. A child project is installed only if its one manual kanban view already has the five buckets (`todo`, `in-progress`, `in-review`, `blocked`, `done`), done and default wired, as the root's install check requires. Any other child is watch-only. No bucket, view or project write ships in either row. ## Multi-project sync: the adapter reads a project tree (row A) ### Problem The adapter watches one project. `settings()` takes one `tracker.project` (`packages/tasks/src/adapter.mjs:21`), startup checks that project alone (`startup.mjs:37`), and sync reads its board and task list only (`sync.mjs:21`, `sync.mjs:45`). A task in any other project is a shape error in the walk (`sync.mjs:59`), and a move to another project is a tombstone (`sync.mjs:123`). Jason's projects nest. The bus binds to #19 "Mosaic Stack", and he wants agents to follow work into its child projects. ### Owner and reviewer - Owner: Darkwing. - Reviewers: Dewey and Filbert. ### Files owned - `packages/business/src/vars.mjs` and `packages/business/tests/vars.test.mjs` - `packages/tasks/src/adapter.mjs`, `startup.mjs`, `sync.mjs`, `fake.mjs`, `vikunja.mjs`, `index.mjs` - `packages/tasks/tests/` (all files and fixtures) - `packages/tasks/README.md`, `packages/tasks/live/README.md` - `agents/darkwing/work/queue-/` `verbs.mjs` is read-only in this row. Verbs keep refusing a task outside the root with `task-project`, as they do now. ### What ships - `tracker.descendants`, a project-layer boolean var, default false. `tracker.project` stays the root. - With descendants on, the tree is the root plus every descendant the sync bot can see through `GET /projects` (key 9 has `projects read_all`), followed by `parent_project_id`. Discovery runs at startup and at each reconcile, not each tick. Archived projects are not in the tree. Record your rule for a project that leaves the tree (archived, moved out, unshared or deleted) in the README and test it. Its tasks become `task.missing` with a reason. The ledger is never rewritten. - Install state per project. The root's install check refuses the business, exactly as today. A child that passes the same check is installed. A child that fails it is watch-only and never refuses the business. Sync records title, done, assignees and comments for a watch-only child, and no bucket state. - Sync covers each project in the tree. A task moved between two projects in the tree gets the `moved` tombstone naming the new project and a snapshot under its new ref. A move out of the tree stays the tombstone it is today. - Poll cost. Probe whether v2.7.0 serves a cross-project task read to the sync key's scopes. If it does, use one read per tick for the tree. If not, read per project and state the per-tick call count in the README. The probe results go in `agents/darkwing/work/queue-/probes.md`. - `fake.mjs` models `parent_project_id`, the project list route, archived projects and access inherited from an ancestor's share. - Tests: descendants off matches v0; a three-level tree; a watch-only child keeps the business ready; a root without buckets refuses; moves root to child, child to child and child to outside; a new child appears at reconcile; a child leaves the tree. - Gate suites: `node --test 'packages/tasks/tests/*.test.mjs'`, `node --test 'packages/business/tests/*.test.mjs'`, `node --test 'packages/bus/tests/*.test.mjs'`, and every `scripts/test-*.sh`. ### Out of scope Verbs on child projects (row B). Any change to `packages/bus/`. Any write to a Vikunja board or project. An archive guard: the adapter has no project verb, so the pm's `projects.update` can't reach a project through the bus. A row that adds a project verb adds the guard. ### Gate Dewey and Filbert approve on the issue, naming the candidate manifest. The suites above pass on Sage's gate rerun. Sage then runs one live read against tasks.woltje.com with descendants on, root #19, read-only, and attaches the tick log. ## Multi-project verbs: task verbs across the tree (row B) ### Problem After row A, the bus sees child projects but can't act in them. `target()` and `relation()` refuse a ref from another project (`verbs.mjs:74`, `verbs.mjs:79`), `read()` refuses a task that isn't in the root (`verbs.mjs:88`), and `task.create` always posts to the root (`verbs.mjs:355`) with no project argument. ### Owner and reviewer - Owner: Darkwing, after row A is done. - Reviewers: Dewey and Filbert. ### Files owned - `packages/tasks/src/verbs.mjs`, `fake.mjs` - `packages/tasks/tests/verbs.test.mjs` and fixtures - `packages/tasks/README.md` - `packages/harness/src/tools.mjs` (the `task_create` tool schema gets the optional project) and its test - `agents/darkwing/work/queue-/` ### What ships - Mos's ruling: `task.create` takes an optional `project`. It must be an installed project in the tree; a watch-only or unknown project refuses with a new code the README names. Without it, the task goes to the root, as today. - `target()`, `relation()` and `read()` accept a task in any installed project of the tree. A bucket move uses that project's own view and buckets, not the root's. - A verb on a task in a watch-only project refuses with the same new code. - Tests for each verb across two installed projects, a watch-only refusal and a relation between projects. - Gate suites as row A. ### Out of scope Project verbs (create, rename, archive, move). They need their own brief, an archive guard and a ruling. ### Gate Dewey and Filbert approve on the issue, naming the candidate manifest, and the suites pass on Sage's gate rerun.