Decision 142 records the PRD 1.2 approval, Mos's rulings on choices 6 to 9, the token-unit draft position, the sage-0188 proxy facts and 9b left unfiled. The brief file holds one section per build row, steps 2 to 11 with 9a and 9c split out, each tied to its Gitea issue. Co-Authored-By: Claude Opus 5.5 <[email protected]>
15 KiB
PRD 1.2 build rows (2026-10-14)
Filed by Sage after Mos's go for build step 1 (sage-0187, lead decision
142). The requirements are PRD version 1.2, docs/prd/mosaic-stack.md,
landed in 7eb5ad0cb. Each section below is one queue row's brief. The
steps keep the PRD 1.2 draft's numbering (agents/sage/work/prd-v1-2/).
Rules for every row in this file
- Cite the PRD 1.2 requirement ids the row meets in the packet, and in a test's name or comment where a requirement drives it.
- Targeted builder gate (decision 136). Sage reruns the full gate at the
landing target. Nothing goes live except through a re-pin on Mos's go
(decision 109). A landing that changes
packages/webui/src/public/waits for Mos's go too (decision 123). - No row edits Jason's organization file or role workspaces. If a row needs his file changed, the packet gives the exact change and Sage sends it to Mos.
- A bus schema change ships a migration, tested on a copy of a schema-current database, and lands with a re-pin that has downtime.
- One mutant per check, listed in the packet. Each fails a test.
- If a row needs a decision its brief doesn't make, stop and ask Sage. Don't widen the row.
- Limits have no ceiling in code (Q150). Don't add one back as a constant, a validator bound or a default that can't be changed.
Role files for the chief of staff and the assistant
Build step 2, small. Gitea #1613.
- Owner: researcher (wrote the chief-of-staff role file in row 84).
- Reviewer: filbert (decision 130: role files, no sandbox change).
- Requirements: REQ-COS-1, REQ-ASSIST-1, REQ-ROLE-4, REQ-LAUNCH-1.
Files owned. roles/assistant.json and roles/assistant.md (new),
roles/chief-of-staff.json and .md, roles/pm.json and .md, and the
tests that pin role files (packages/business/tests/chief-of-staff.test.mjs
and any other the owner finds).
What ships.
roles/assistant.jsonand.md. The contract is REQ-ASSIST-1: the user's main agent, talks with the user in a thread, passes work for the organization to the chief of staff, and never directs other agents, creates or assigns tasks, or launches sessions. Authority ismessage.sendwithin role and nothing else. No credentials. Tools as read-only as the chief of staff's.roles/chief-of-staff.jsongainsrole.launch(decision 139, Q144). Its contract is rewritten to REQ-COS-1: hires and terminates, owns the organization's structure, launches, takes work from the assistant and never talks to the user. Remove every "main point of interaction" duty left from decision 140.roles/pm.jsondropsrole.launch, androles/pm.mdno longer launches. The PM takes work from the chief of staff.- The packet lists the change Jason's organization file needs: the
assistant and chief-of-staff instances, and
launch.bynaming the chief of staff. Until Jason makes it, the broker still gates the chief of staff's launches (broker.mjs:305), and that's expected.
Not in this row. The broker's send limits (step 3), ending runs and the organization-file verb (step 6), threads (step 4).
Acceptance. Every role file loads through the business validator.
Tests pin that the assistant's authority is message.send only, that
the chief of staff holds role.launch, and that the PM doesn't. All
existing suites pass.
The broker limits who the assistant sends to and hears from
Build step 3, small to medium. Gitea #1614.
- Owner: researcher, after step 2.
- Reviewer: filbert (broker message check, no sandbox change).
- Requirements: REQ-ASSIST-2.
Files owned. packages/business/src/role.mjs (one new optional role
field), packages/bus/src/broker.mjs (message.send only),
roles/assistant.json, and tests in both packages.
What ships.
- An optional role field naming the roles a holder may send to and the
roles that may send to it. The owner picks its name and shape and
records why.
humannames the user. - In
message.send, the broker refuses a send when the sender's role has the field and the target isn't in it, or when the target's role has the field and the sender isn't in it. Each refusal is logged with its reason, the way the broker logs other refusals. roles/assistant.jsonsets both lists to the user and the chief of staff.- A role without the field behaves exactly as today.
- The packet says what happens to a tester's send to the assistant. The default is refused, per REQ-ASSIST-2.
"In the thread" in REQ-ASSIST-2 needs threads, so it lands in step 4.
Acceptance. Tests for both directions, for an allowed send each way, and for a role without the field. Mutants: drop each direction's check and drop the refusal log.
Projects and threads in the bus
Build step 4, large. Gitea #1615.
- Owner: darkwing, after step 3. It also follows darkwing's rows 116 and 118.
- Reviewer: dewey (decision 130: a per-thread session store reaches the run's HOME and the sandbox).
- Requirements: REQ-ORG-2, REQ-THREAD-1, REQ-THREAD-2, REQ-USER-2, REQ-ROLE-2 (one assistant holder per thread), REQ-MSG-1, REQ-LAUNCH-1 (thread sessions start on demand and stop when idle).
Design first. Before code, post a design packet on the issue for Sage's approval. It covers:
- the schema: projects and threads, and the thread on messages, claims and deliveries;
- the migration and its downtime;
- how REQ-ORG-2 projects relate to the organization file's
projectskey, which today names git repositories to clone (packages/seat/src/checkout.mjs); - the per-thread session store. Claude Code's session directory and HOME
are per run today (
launcher.mjs:204-206,sandbox.mjs:157). Choose a store that outlives the run, or a rebuild of context from the thread's messages, and say why; - on-demand start and idle stop, with the idle time set in the organization file;
- how REQ-USER-2 holds with the one user there is today.
Files owned. Set by the approved design, within
packages/bus/src, packages/seat/src, packages/cli/src/launcher.mjs,
packages/business/src (the idle setting) and their tests.
Not in this row. mosaic thread (step 10), the WebUI chat area
(step 11), limits (steps 5, 7 to 9a).
Acceptance.
- The migration runs on a copy of a schema-current database, and the bus loads the result.
- Tests: one holder per thread; a message names its thread; the chief of staff's reply comes back to the thread that sent the work; a thread's history survives an idle stop and a restart (stub harness); one thread's messages never reach another; an idle thread holds no session and counts against no limit.
- Mutants for each of those. Dewey approves on the issue naming the candidate manifest.
Agent limits as settings, with the organization-file write path
Build step 5, medium to large. Gitea #1616. Decision 123 applies to the Settings form.
- Owner: rocko.
- Reviewer: filbert (business, launcher, CLI and Console; no sandbox change).
- Requirements: REQ-ROLE-3 (the write path), REQ-LIMIT-1, REQ-WEB-1 (Settings), REQ-LAUNCH-1 (refusals and launch logging).
Design first. A short design note on the issue before code: the
launch schema with its model table, and the migration of Jason's file
from { opus: 4, sonnet: 4 }.
What ships.
- One host module for organization-file writes. It validates the whole file with the existing validator, writes atomically, keeps the previous version, and logs who changed what with old and new values. An invalid change is refused, and the old file stays.
- A
launchschema with a model table, a provider and a model per entry, and limits in total, per provider, and per provider and model. It replaces family matching (packages/seat/src/session.mjs:52-57) andLAUNCH_CEILING(packages/business/src/vocabulary.mjs:86). Any limit may be unset (no limit). A value is a whole number of 0 or more, and 0 stops new launches of that kind. 4 Opus and 4 Sonnet become defaults. - A migration command for an organization file in the old shape. It prints the new file, and writes it only when a human runs it. The row doesn't run it on Jason's file.
- The launcher re-reads limits at each launch (today once at start,
launcher.mjs:123). It refuses a launch over any limit and logs the limit it hit. Each launch is logged with harness, provider and model. Lowering a limit doesn't end running sessions. mosaic limitsto view and set limits and each role instance's model, behind the human proof.- A Settings form in the Console that shows every limit and each
instance's model and changes them through the Console's send checks.
Today
/api/settingsis GET-only (serve.mjs:233-236).
Not in this row. The chief of staff as a caller (step 6), token counts and limits (steps 7 to 9a).
Acceptance. Tests for the write path (atomic, previous version kept,
change log, invalid refused), each limit kind (unset, 0, at limit, over
limit), the re-read at launch, the migration, mosaic limits and the
form's send checks. Mutants for each. Filbert approves. The landing waits
for Mos's go (decision 123).
The chief of staff ends runs and changes the organization file
Build step 6, small to medium. Gitea #1617.
- Owner: researcher, after steps 2 and 5.
- Reviewer: filbert (broker authority, no sandbox change).
- Requirements: REQ-ROLE-2, REQ-ROLE-3, REQ-LIMIT-1, REQ-COS-1.
What ships.
role.revokeby the chief of staff needs no resolved decision. It takes a reason and logs it. For every other role, revoke stays gated and cites a decision (broker.mjs:442,:738).- A broker verb that lets the chief of staff change the organization file, limits included, through step 5's write path. The change log names the chief of staff as the actor. The owner names the verb.
roles/chief-of-staff.jsongrants both.
Acceptance. Tests: the chief of staff ends a run without a decision, logged with its reason; another role still needs one; a chief-of-staff change is validated, written and logged like a human's; an invalid change is refused. Mutants for each.
Token accounting per run
Build step 7, medium. Gitea #1618.
- Owner: rocko, after step 5.
- Reviewer: dewey (decision 130: the host reads files inside the run's HOME, which the session can write).
- Requirements: REQ-LIMIT-2 and REQ-LIMIT-3 (their base), REQ-WEB-1 (the readout).
What ships.
- After each turn, the host reads usage from the Claude Code transcript
in the run's HOME (
home/.claude/projects/<dir>/<session id>.jsonl) or from the Pi session file. It records each kind separately (input, output, cache write, cache read) per run, role instance, provider, model and account (lead decision 142, ruling 6). The account is null until step 9a names one. - A bus schema migration for the records.
mosaic usage, a view by instance, model, provider and account.- A read-only readout in Settings, with each kind shown. Decision 123 applies.
- The host treats these files as untrusted data: bounded reads, no symlinks followed, a parse failure logged and the turn's usage recorded as unknown, never as zero. The packet says plainly that a session can edit its own transcript, so the count is the stack's best reading, not proof.
Acceptance. Fixture transcripts for both harnesses, including a cut line, a symlink and an oversized file. The migration runs on a copy of a schema-current database. Mutants for each guard. Dewey approves.
Token limits per instance, model, provider and key
Build step 8, medium. Gitea #1619.
- Owner: researcher, after steps 6 and 7.
- Reviewer: filbert (launcher and broker checks, no sandbox change).
- Requirements: REQ-LIMIT-3.
What ships.
- Token limits per role instance, model, provider and API key, set through step 5's write path and the chief of staff's verb (step 6). A count is the sum of every kind since the limit was set or last reset (lead decision 142, rulings 6 and 7).
- A reset that the user (
mosaic limits, Settings) and the chief of staff can make, logged. - A launch whose subject is at its limit is refused. A running session that reaches a limit stops at the end of its current turn. Both are logged with the limit and the count.
Acceptance. Tests for each subject kind, the reset, the refusal and the end-of-turn stop (stub harness). Mutants for each.
Daily quota for the credential the stack uses today
Build step 9a, small to medium. Gitea #1620.
- Owner: dewey, after step 7.
- Reviewer: filbert (organization file and launcher, no sandbox change).
- Requirements: REQ-LIMIT-2.
What ships.
- The organization file names the one account the bus uses today
(
SESSION_CREDENTIALS,packages/seat/src/session.mjs:76, decision 119) by a name and a credential reference, never the secret (invariant 3). Launches log the account name, and step 7's records carry it. - A daily quota on that account, in tokens (a draft position, PRD REQ-LIMIT-2), with a reset time and time zone.
- At the quota, new launches on the account are refused and running sessions stop at the end of their current turn. Both are logged.
- Settings and
mosaic limitsshow the quota with the line that it counts only the stack's own use (lead decision 142, ruling 9).
Acceptance. Tests for the count, the day boundary in a non-UTC time zone, the refusal and the stop. Mutants for each.
Quota for more than one account or per proxy key (parked on #237)
Build step 9c, large, not sized. Gitea #1621. Parked until the #237 design settles how the stack hands a credential to each session.
- Requirements: REQ-LIMIT-2.
Mos's facts (sage-0188, CLIProxyAPI v8.0.29 public source):
- Per-client-key token counts exist only in the proxy's usage queue, which is off by default, keeps records 60 seconds (at most 3600), drains on read, carries each client key in plaintext and sits behind the management API.
- The proxy has no per-key quota or rate limit.
So stack-side counting (step 7) stays. A proxy collector or proxy-side enforcement needs management API access, which agents don't use and which is Jason's call. The proxy route is an open design point. Nobody reads the proxy's config or key files for this row.
mosaic talks to the assistant in a named thread
Build step 10, small to medium. Gitea #1622.
- Owner: darkwing, after step 4.
- Reviewer: filbert (CLI, no sandbox change).
- Requirements: REQ-CLI-1, REQ-CLI-2, REQ-THREAD-2.
What ships. mosaic lists projects and threads, creates and names
them, and talks to the assistant in the thread the user picks, with
--follow as talk has today. Plain mosaic talk asks for a thread or
names the default one. The packet records which and why.
Acceptance. Tests for list, create, talk and follow in a thread, and for a send to a thread the user doesn't own (REQ-USER-2). Mutants for each.
The WebUI chat area
Build step 11, medium to large. Gitea #1623. Decision 123 applies.
- Owner: rocko, after steps 4, 5 and 10.
- Reviewer: filbert (Console, no sandbox change).
- Requirements: REQ-WEB-1, REQ-THREAD-2, REQ-USER-2.
What ships. A chat area in the Console that lists the user's projects and the named threads in each, creates and names them, and has one conversation view per thread where the user talks to the assistant. Sends go through the Console's send checks.
Acceptance. Tests for list, create, send and the per-thread view, including a thread the user doesn't own. Mutants for each. Filbert approves. The landing waits for Mos's go (decision 123).