docs: PRD 1.3, usage plug-ins and the app-wide plug-in layer (Q153-Q155)

PRD version 1.3 records Jason's rulings Q153 to Q155, relayed by Mos
(lead decision 143), and Mos reviewed the text before it landed. Adds
REQ-LIMIT-4 (usage sources and limit checks as the first plug-in kinds;
an unknown turn is never zero, no source switched on means every turn is
unknown, an unenforced limit shows as not enforced) and a Plug-ins
section with REQ-PLUG-1 (kind registry and loader in packages/plugins,
in-repo modules only until Jason rules, Vikunja #286) and REQ-PLUG-2
(new call-outs go through a kind; hard-coded parts convert as rows touch
them). REQ-LIMIT-2 and REQ-LIMIT-3 count through usage sources.
Technical considerations list where each kind is hard-coded today.

requirements.json moves to 1.3 with 41 ids; the tasks and bus id-count
tests follow. The PRD 1.2 build plan gains step 6b, the plug-in layer
(#1626), and rows 124 to 127 ship their counting and checks as modules.

Jason approved the conversion order for hard-coded parts in Q156. Lead
decision 143 and the build plan record it, and the managed-session
config brief notes that row 65 now runs before 64 and 66.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
jason.woltjeandClaude Opus 5.5 committed 2026-10-14 17:39:37 -05:00
1 parent 9edac91073
commit 240d255187
7 files changed
+335 -43

No files matched your search

+55
View File
@@ -3209,3 +3209,58 @@ which stay with him. Each item names who decided it and what happened.
Mos's first paragraph in sage-0187 also listed 9b as its own row.
Ruling 8 is the later and more specific instruction, so Sage left 9b
unfiled. Decision 123 applies to build steps 5 and 11.
143. **PRD 1.3: usage sources and limit checks as plug-ins, and an
app-wide plug-in layer (2026-10-14, Jason, relayed by Mos (keys
mos-38bc4c81-sage-0192 to -0194, replying to sage-0236 and
sage-0237)).** Jason answered Q153:
"2. agreed. Make it modular though. We should be able to extend
without code change on the core app." Mos's reading, which Jason
confirmed: the stack counts tokens itself, with no CLIProxyAPI queue
reader, and usage sources and quota checks sit behind a plug-in
interface. Jason answered Q154: "Q154: yes. In fact, the whole app
should use plug-in interface. The core should allow for extension".
Mos read the second sentence back as Q155: every part the core calls
out to gets a registered plug-in interface (harnesses, model
providers, usage sources, the task tracker, the git forge, channels
such as Discord). Adding one means adding a module and switching it
on in config, with no core code change. This applies to new work now,
and hard-coded parts convert as rows touch them. Jason answered "Q155:
yes" (Vikunja #286).
Mos ruled on the draft's three points:
- a. An unknown turn is never zero. Checks count known records only,
each unknown turn is logged, and Settings shows the unknown count
next to the total.
- b. The switches live in the organization file.
- c. Because of Q155, the loader is the app-wide plug-in layer from
the start (`packages/plugins`, kind registry in
`packages/plugins/src/kinds.mjs`), with usage sources and limit
checks as its first two kinds. The name rule, the no-symlink rule
and "a failed source is unknown, never zero" stay. Plug-ins from
outside the repository stay out until Jason rules. Mos is taking
that security call to Jason (#286).
The version is 1.3, not 1.2.1: `packages/tasks/src/requirements.mjs`
accepts only `major.minor`, and a new minor needs no code change.
The PRD gains REQ-LIMIT-4, REQ-PLUG-1 and REQ-PLUG-2 and amends
REQ-LIMIT-2 and REQ-LIMIT-3. Mos reviewed the full text before it
landed. The build plan gains step 6b, the plug-in layer, and rows 124
to 127 ship their counting and checks as modules.
Sage proposed an order for converting the hard-coded parts
(sage-0237), and Mos put it to Jason as Q156. Jason answered "Q156:
yes", so the order stands as written:
- 1. Usage first: step 6b, then rows 124 to 126.
- 2. Harnesses during row 65, which runs before rows 64 and 66.
- 3. Providers named as data in row 122. The provider plug-in waits
for row 127.
- 4. Channels: `cli` in row 128, `webui` in row 129, Discord when a
row connects it to the bus.
- 5. The tracker gets its own row after the 1.2 rows, design packet
first.
- 6. The forge goes last, during the next row that changes the Gitea
scripts.
- 7. Row 87 decides whether the service manager becomes a kind.
The build plan records the order, and the queue runs row 65 before
64 and 66.
@@ -9,6 +9,12 @@ time in the order below, each after the one before it is done, so no two
candidates edit the same files at once. Sage owns all three. The order
is after rows 55, 42 and 49.
Q156 (2026-10-14, lead decision 143) changed the order: row 65 runs
first, then 64, then 66. Row 65 is where the harnesses become plug-in
modules (PRD 1.3 REQ-PLUG-2), so 64 and 66 add their files to those
modules. Sage adds the harness-kind scope to row 65's brief before the
row starts.
Two decisions shape the set. Q84 is its own row, not part of the skills
row: the ms-unslop skill is guidance the model reads, and the hook is a
check that runs on its output. They differ in files, failure mode and
+109 -15
View File
@@ -4,6 +4,9 @@ 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/`).
PRD version 1.3 (lead decision 143) adds the plug-in layer, build step
6b, and rows 124 to 127 follow it: usage sources and limit checks are
plug-in modules (REQ-LIMIT-4, REQ-PLUG-1).
## Rules for every row in this file
@@ -14,8 +17,11 @@ steps keep the PRD 1.2 draft's numbering (`agents/sage/work/prd-v1-2/`).
(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
needs Jason's file changed, the packet gives the exact change and Sage
sends it to Mos.
- A call-out to a part of a plug-in kind goes through the loader
(REQ-PLUG-1, REQ-PLUG-2). No core module imports a plug-in module by
name.
- 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.
@@ -24,6 +30,31 @@ steps keep the PRD 1.2 draft's numbering (`agents/sage/work/prd-v1-2/`).
- 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.
## Plug-in conversion order (Q156)
Not a queue row. Jason approved this order in Q156 (lead decision 143).
Each hard-coded part converts to its plug-in kind on the row named here
(PRD REQ-PLUG-2).
1. Usage sources and limit checks: step 6b, then rows 124, 125 and 126.
2. Harnesses: row 65, the model routing table, makes `pi` and
`claude-code` the first harness modules. Row 65 runs before rows 64
and 66, so skills and unslop land as harness module exports. The
conversation and Discord Pi engines convert when a row changes them.
3. Model providers: row 122 names the provider as data in its model
table. The provider kind (environment allowlist, credential
reference, base URL, a CLIProxyAPI module) waits for row 127 and
#237.
4. Channels: row 128 makes `cli` the first channel module and row 129
adds `webui`. Discord becomes a module when a row connects it to the
bus. The human proof stays in core.
5. Task tracker: its own row after the 1.2 rows, design packet first,
because `vikunja:` refs and branch names need a migration.
6. Git forge: last, during the next row that changes `gitea-api.sh`,
`packages/queue/src/review.mjs` or `land-work.sh`.
7. Service manager: not on Q155's list. Row 87 (the I1 design) decides
whether it becomes a kind.
## Role files for the chief of staff and the assistant
Build step 2, small. Gitea #1613.
@@ -219,21 +250,62 @@ 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.
## The plug-in layer: kind registry and loader
Build step 6b, medium. Gitea #1626.
- Owner: dewey, after step 5.
- Reviewers: filbert (organization file and loader) and darkwing (the
loader runs code with the host's authority).
- Requirements: REQ-PLUG-1, REQ-LIMIT-4 (the first two kinds).
**What ships.**
1. `packages/plugins`, with `src/kinds.mjs`, the kind registry. Each
entry names a kind, the exports a module must have, the interface
version, and whether the organization file switches on one module of
that kind or a list.
2. `src/loader.mjs`. It reads the names switched on from the
organization file's `plugins` object, one key per kind, and loads
only `packages/plugins/<kind>/<name>/index.mjs` from the tree the host
runs from. It refuses a name that doesn't match `^[a-z][a-z0-9-]*$`,
a missing directory, a symlink anywhere in the path including
`index.mjs`, a module that fails to load, and a module missing an
export or with another interface version. A refusal keeps the old
setting and is logged.
3. The kinds `usage-source` and `limit-check` at interface version 1,
with the exports PRD REQ-LIMIT-4 describes. No module of either kind
ships in this row.
4. The `plugins` key in the organization-file schema, written through
step 5's write path and logged like a limit change.
5. A README for the package: how to add a module, and that adding a
kind is a core change.
Only modules in the repository load until Jason rules (Vikunja #286).
The loader takes no path from config.
**Acceptance.** Tests for each refusal with the old setting kept, and a
fixture module of each kind switched on in a test organization file with
no core file changed. One mutant per check. Darkwing and Filbert
approve.
## Token accounting per run
Build step 7, medium. Gitea #1618.
- Owner: rocko, after step 5.
- Owner: rocko, after steps 5 and 6b.
- 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).
- Requirements: REQ-LIMIT-2 and REQ-LIMIT-3 (their base), REQ-LIMIT-4,
REQ-PLUG-1, REQ-WEB-1 (the readout).
**What ships.**
1. After each turn, the host reads usage from the Claude Code transcript
1. The first usage source, `packages/plugins/usage-source/transcript/`.
After each turn the bus calls every usage source switched on, through
step 6b's loader. This one 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,
or from the Pi session file. It returns 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.
@@ -248,8 +320,11 @@ Build step 7, medium. Gitea #1618.
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.
line, a symlink and an oversized file. A test that the bus reaches the
transcript source only through the loader. A test that an unknown turn
is logged and that `mosaic usage` and Settings show the unknown count
next to each total. 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
@@ -257,9 +332,12 @@ 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.
- Requirements: REQ-LIMIT-3, REQ-LIMIT-4.
**What ships.**
**What ships.** The first limit check,
`packages/plugins/limit-check/token-limit/`. The launcher asks every
check switched on before a launch, and the bus asks after each turn
whether to stop, both through step 6b's loader. The check does this:
1. 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).
@@ -272,7 +350,13 @@ Build step 8, medium. Gitea #1619.
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.
the end-of-turn stop (stub harness). A launch with no check switched on,
a fixture check that refuses, a test that the launcher reaches
`token-limit` only through the loader, and a test that a check counts
known records only. A test that with no usage source switched on every
turn is recorded as unknown. A test that a limit no switched-on check
enforces shows in Settings and `mosaic limits` as not enforced. Mutants
for each.
## Daily quota for the credential the stack uses today
@@ -280,7 +364,7 @@ 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.
- Requirements: REQ-LIMIT-2, REQ-LIMIT-4.
**What ships.**
@@ -290,21 +374,25 @@ Build step 9a, small to medium. Gitea #1620.
(invariant 3). Launches log the account name, and step 7's records
carry it.
2. A daily quota on that account, in tokens (a draft position, PRD
REQ-LIMIT-2), with a reset time and time zone.
REQ-LIMIT-2), with a reset time and time zone. It ships as a second
limit check, `packages/plugins/limit-check/daily-quota/`, with no
change to the launcher or bus.
3. At the quota, new launches on the account are refused and running
sessions stop at the end of their current turn. Both are logged.
4. Settings and `mosaic limits` show 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.
zone, the refusal and the stop. Outside
`packages/plugins/limit-check/daily-quota/`, the diff touches only item
1's account naming and the organization-file schema. 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.
- Requirements: REQ-LIMIT-2, REQ-LIMIT-4.
Mos's facts (sage-0188, CLIProxyAPI v8.0.29 public source):
@@ -319,6 +407,12 @@ 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.
Under Q153 the stack counts tokens itself, and no proxy reader is built
now. A proxy reader would be its own usage source in
`packages/plugins/usage-source/`, switched on in config. Per-key quotas
would reuse the `daily-quota` check with the key as subject. Both still
wait on #237 and on Jason's call on management API access.
## mosaic talks to the assistant in a named thread
Build step 10, small to medium. Gitea #1622.
+157 -23
View File
@@ -1,12 +1,34 @@
# PRD: Mosaic Stack
- Status: approved, version 1.2. Jason approved draft 0.4 unchanged as
1.0 on 2026-10-05 (lead decision 61). He approved version 1.1, the
- Status: approved, version 1.3. Jason approved draft 0.4 unchanged as
1.0 on 2026-10-05 (lead decision 61). They approved version 1.1, the
Organization requirements, in decision 03130d08-ddce-480d-a601-e59f64744fc3
on 2026-10-14. He approved version 1.2, the chief of staff, the
on 2026-10-14. They approved version 1.2, the chief of staff, the
assistant, threads and agent limits, through Mos on 2026-10-14 (lead
decisions 141 and 142). It is never edited in place. Changes are a new
version.
decisions 141 and 142). Version 1.3 records their rulings on plug-ins
and token counting, Q153 to Q155, relayed by Mos on 2026-10-14 (lead
decision 143). Mos reviewed the 1.3 text before it landed. It is never
edited in place. Changes are a new version.
- Version 1.3 source, Jason's words relayed by Mos:
- Q153: "2. agreed. Make it modular though. We should be able to
extend without code change on the core app." Mos's reading, which
Jason confirmed: the stack counts tokens itself for now, with no
CLIProxyAPI queue reader. Usage sources and quota enforcement sit
behind a plug-in interface. The first source reads the stack's own
harness transcripts. Later sources are separate modules switched on
in config, with no change to core bus or broker code.
- Q154: "Q154: yes. In fact, the whole app should use plug-in
interface. The core should allow for extension". Mos read the second
sentence back as Q155.
- Q155: "Q155: yes", to this read-back: every part the core calls out
to gets a registered plug-in interface: harnesses, model providers,
usage sources, the task tracker, the git forge, and channels such as
Discord. Adding one means adding a module and switching it on in
config, with no core code change. This applies to new work now.
Existing hard-coded parts convert as rows touch them, not in one big
rewrite.
- Plug-ins from outside the repository are a security call Q155 didn't
make. Mos is taking it to Jason (Vikunja #286).
- Version 1.2 source, Jason's words relayed by Mos:
- Q147 (full text in lead decision 141): "COS should focus on COS
duties. This will include hire/terminate authority for agents, org
@@ -58,10 +80,10 @@ outside users later, which means:
## Problem statement
In priority order (round 1):
1. **Admin falls on Jason.** He launches agents, relays between them,
1. **Admin falls on Jason.** Jason launches agents, relays between them,
finds credentials and corrects them.
2. **Jason can't see what the agents are doing or what's waiting on
him.**
2. **Jason can't see what the agents are doing or what's waiting for
Jason.**
3. **Agents drift from what the business needs.** Nothing ties a piece of
work to a stated goal.
@@ -72,7 +94,7 @@ In priority order (round 1):
The stack doesn't do any of these. Each one is either a gated decision or
off the table (round 1, "all five"):
- A. Spend money without Jason.
- B. Speak externally as Jason without his approval.
- B. Speak externally as Jason without Jason's approval.
- C. Give agents Jason's personal credentials. Each role gets its own.
- D. Become a multi-tenant SaaS in v1. There is no hosted SaaS in v1.
- E. Replace Claude Code, Codex or Pi. It wraps them.
@@ -298,22 +320,90 @@ model, provider, or API key".
sessions already running.
- **REQ-LIMIT-2.** A daily quota per OAuth account, or per client key of
the CLIProxyAPI that Mosaic Stack configures. The stack counts the
tokens its sessions use against the account or key each ran on, and the
day resets at a time and time zone set with the quota. When an account
or key reaches its quota, new launches on it are refused, and running
sessions on it stop at the end of their current turn. Both are logged.
The count covers sessions the stack ran. Use of the same account
outside the stack isn't counted, and Settings says so. Measuring the
quota in tokens is a draft position: Q150 names no unit, and OAuth
plans meter by usage windows, not daily tokens.
tokens its sessions use against the account or key each ran on, from
the usage sources switched on (REQ-LIMIT-4), and the day resets at a
time and time zone set with the quota. When an account or key reaches
its quota, new launches on it are refused, and running sessions on it
stop at the end of their current turn. Both are logged. The daily quota
is a limit check (REQ-LIMIT-4). The count covers sessions the stack
ran. Use of the same account outside the stack isn't counted, and
Settings says so. The stack doesn't read the proxy's usage queue
(Q153). A per-key count from the proxy would be a later usage source.
Measuring the quota in tokens is a draft position: Q150 names no unit,
and OAuth plans meter by usage windows, not daily tokens.
- **REQ-LIMIT-3.** A token limit per role instance, model, provider or API
key. Each limit counts the tokens its subject has used since the limit
was set or last reset, and the user and the chief of staff can reset
it. A count is the sum of every kind the harness reports (input,
output, cache write and cache read), and Settings shows each kind. A
launch whose subject is at its limit is refused, and a running session
that reaches a limit is stopped at the end of its current turn. Both
are logged with the limit and the count.
was set or last reset, from the usage sources switched on
(REQ-LIMIT-4), and the user and the chief of staff can reset it. A
count is the sum of every kind a source reports (input, output, cache
write and cache read), and Settings shows each kind. A launch whose
subject is at its limit is refused, and a running session that reaches
a limit is stopped at the end of its current turn. Both are logged with
the limit and the count. The token limit is a limit check
(REQ-LIMIT-4).
- **REQ-LIMIT-4.** Token counting and limit enforcement are the first two
plug-in kinds (REQ-PLUG-1, Q153):
- `usage-source`: after each turn it reads what the session used and
returns records per run, role instance, provider, model and account,
with each token kind separate, or "unknown" for the turn.
- `limit-check`: from those records and the organization file's
limits, it answers whether a launch may start and whether a running
session must stop at the end of its turn.
The organization file can switch on several of each. A source that
fails during a turn, or can't read it, records the turn as unknown,
never as zero. With no source switched on, every turn is unknown.
Checks count known records only. Each unknown turn is logged, and
Settings shows the number of unknown turns next to each total, so a
limit never looks further off than it is. A limit that no switched-on
check enforces shows in Settings as not enforced. The first
source, `transcript`, reads the stack's own harness transcripts. The
first checks are `token-limit` (REQ-LIMIT-3) and `daily-quota`
(REQ-LIMIT-2). A CLIProxyAPI reader or another provider's report would
be a later source.
### Plug-ins
Added in version 1.3 (Q155).
- **REQ-PLUG-1.** Every part the core calls out to is a plug-in kind:
harnesses, model providers, usage sources, limit checks, the task
tracker, the git forge and channels. The kinds are a registry in one
file, `packages/plugins/src/kinds.mjs`. Each entry names the kind, the
exports a module of that kind must have, the kind's interface version,
and whether the organization file switches on one module of the kind
or a list. A module is a directory, `packages/plugins/<kind>/<name>/`,
whose `index.mjs` exports what its kind needs.
The organization file's `plugins` object switches modules on by name,
one key per kind, through the write path (REQ-ROLE-3). Each change is
logged with who made it and the old and new names. Core code reaches a
module only through the loader, never by importing it. Adding a module,
or switching one on or off, needs no core code change. Adding a kind is
a core change, because it adds a place the core calls out, and it lands
by reviewed commit with its first module.
The loader takes names from the organization file, never paths. It
refuses a name, and the old setting stays, when:
- the name doesn't match `^[a-z][a-z0-9-]*$`;
- no directory of that name exists under its kind;
- any part of the path, `index.mjs` included, is a symlink;
- the module fails to load;
- the module lacks an export its kind needs, or declares another
interface version.
A module runs inside the host process with the host's authority, so
only modules in the repository's `packages/plugins` load, until Jason
rules on others (Vikunja #286). A module goes live like any other code,
through a re-pin. Switching a module on gives it no authority of its
own: the human proof (REQ-DEC-3), the broker's credentials and the role
checks stay in core, and a channel module carries messages without
proving who sent them. A module's credential is a reference the broker
resolves, never the secret (invariant 3).
- **REQ-PLUG-2.** New work that calls out to a part of a plug-in kind goes
through that kind from the start. A hard-coded part converts when a row
changes it, not in one rewrite (Q155). Until it converts, Technical
considerations lists where it is hard-coded.
### Meta-harness
@@ -463,6 +553,42 @@ itself from them, five pieces in a row. For each piece:
limit. Stopping inside a turn needs the runner to read streamed usage
from each harness, which changes the adapter contract (stdout is the
answer only, `adapters/README.md`).
- Version 1.3:
- Nothing loads a module by name today. The seams that exist are the
adapter script contract (`adapters/README.md`), the tasks handle the
broker takes (`startBroker({ tasks })`, `packages/bus/src/runtime.mjs`),
the engine the Discord connector takes (`createConnector({ engine })`)
and the validated provider, account and harness records in
`packages/mosaic/src/registry.mjs`, which nothing outside
`packages/mosaic` imports.
- The loader reads the tree the stack runs from. Since row 85 that is a
pinned copy, so the copy's `packages/plugins` is the set of modules a
running stack can switch on.
- Where each kind is hard-coded today (REQ-PLUG-2), from Sage's
read-only survey on 2026-10-14:
- Harnesses: the list `["pi", "claude-code"]` and its branches in
`packages/harness/src/bundle.mjs`, the same pair in the broker and
`packages/business/src/vars.mjs`, an adapter map in
`packages/seat/src/session.mjs`, per-harness mounts in the sandbox,
and `claude --version` in the launcher. `packages/conversation`
and the Discord CLI start Pi directly.
- Model providers: the family match and `LAUNCH_CEILING` (REQ-LIMIT-1
removes both), a session environment that passes only `ANTHROPIC_*`
and the Claude OAuth token (`session.mjs`), and a free-text
`execution.provider` that nothing checks.
- Task tracker: the tasks verbs build Vikunja paths with no
interface between them and the client. The bus always loads
`packages/tasks/src/adapter.mjs`. Task refs (`vikunja:`), branch
names and the grantable services name Vikunja. `tracker.kind`
exists in `vars.mjs` and nothing reads it.
- Git forge: the Gitea host in `scripts/gitea-api.sh` and the git
credential helper, the repository path and seat-to-account mapping
in `packages/queue/src/review.mjs`, the ledger's repository paths,
and the `refactor` branch in `scripts/land-work.sh`.
`gitea.baseUrl` exists in `vars.mjs` and the scripts don't read it.
- Channels: the human channel is `via: 'cli'` in the broker, the
WebUI reaches the bus by running the human CLI, and Discord isn't
connected to the bus.
## Risks and open questions
@@ -481,6 +607,14 @@ itself from them, five pieces in a row. For each piece:
user's power to change it back.
- A quota or token limit counts only what the stack ran. It can't see the
provider's own remaining quota or use of the same account elsewhere.
- The `transcript` usage source reads files inside the run's HOME, which
the session can edit. Its count is the stack's best reading, not proof.
A source that reads the provider's own report would be harder to
forge.
- A plug-in module runs with the host's authority, and the organization
file only picks among modules already in the tree. The reviewed commit
that adds a module is the only check on what it does, which is why
modules from outside the repository wait for Jason (REQ-PLUG-1).
## Milestones
+1 -1
View File
@@ -45,7 +45,7 @@ test('requirements returns the committed list to a holding role session, and no
const pm = agent('pm');
const list = call(b, pm, 'requirements');
assert.deepEqual(list, loadRequirements());
assert.equal(list.requirements.length, 38);
assert.equal(list.requirements.length, 41);
assert.deepEqual(call(b, agent('coder'), 'requirements'), list);
assert.throws(() => call(b, human, 'requirements'), /agent-required/);
assert.throws(() => call(b, reader, 'requirements'), /read-only/);
+4 -1
View File
@@ -1,6 +1,6 @@
{
"prd": "docs/prd/mosaic-stack.md",
"version": "1.2",
"version": "1.3",
"requirements": [
{ "id": "REQ-ROLE-1", "title": "Each role is a reviewed roles/<name>.json: contract, authority map, credentials" },
{ "id": "REQ-ROLE-2", "title": "One holder per role instance, one per thread for the assistant; the chief of staff may end a run, others need a decision" },
@@ -29,6 +29,9 @@
{ "id": "REQ-LIMIT-1", "title": "Agent limits have no code ceiling and are set from WebUI Settings and mosaic: total, per provider, per model" },
{ "id": "REQ-LIMIT-2", "title": "A daily quota per OAuth account or CLIProxyAPI client key, counting only the stack's own use" },
{ "id": "REQ-LIMIT-3", "title": "A token limit per role instance, model, provider or API key, reset by hand, stopping at the end of a turn" },
{ "id": "REQ-LIMIT-4", "title": "Usage sources and limit checks are the first plug-in kinds; an unknown turn is logged and shown, never counted as zero" },
{ "id": "REQ-PLUG-1", "title": "Every part the core calls out to is a plug-in kind; modules load from packages/plugins, switched on in the org file" },
{ "id": "REQ-PLUG-2", "title": "New call-outs go through a plug-in kind from the start; a hard-coded part converts when a row changes it" },
{ "id": "REQ-HARN-1", "title": "The meta-harness generates each session's launch bundle from role and variables" },
{ "id": "REQ-HARN-2", "title": "Credential scope, verbs, the tool limit and a container enforce the rules" },
{ "id": "REQ-EVT-1", "title": "Every slice 1 step emits an event; measures come from events" },
+3 -3
View File
@@ -18,10 +18,10 @@ test("the list holds the PRD's requirement ids, in its order, at its version", (
const ids = [...prd.matchAll(/^- \*\*(REQ-[A-Z]+-[0-9]+)\.\*\*/gm)].map((m) => m[1]);
// Any other mention of a bold id would be a bullet this pattern missed.
assert.equal(ids.length, [...prd.matchAll(/\*\*REQ-/g)].length);
assert.equal(ids.length, 38);
assert.equal(ids.length, 41);
assert.deepEqual(list.requirements.map((r) => r.id), ids);
assert.match(prd, new RegExp(`^- Status: approved, version ${list.version.replace('.', '\\.')}\\.`, 'm'));
assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.2']);
assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.3']);
});
test('the committed list loads frozen', () => {
@@ -74,7 +74,7 @@ test('the schema refuses any other shape', () => {
const twice = raw();
twice.requirements.push(structuredClone(twice.requirements[0]));
writeFileSync(join(dir, 'twice.json'), JSON.stringify(twice));
assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[38].id: REQ-ROLE-1 twice'));
assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[41].id: REQ-ROLE-1 twice'));
} finally {
rmSync(dir, { recursive: true, force: true });
}