Files
stack/docs/prd/mosaic-stack.md
jason.woltje 03fe321b12 docs(prd): land version 1.20, the unified workspace (#1873)
Jason approved draft 2 on 2026-11-09 at 22:57Z: "The file looks fine
other than that." The exception is the Montage module name, deferred
since 19:26Z. Amends REQ-ASSIST-1, REQ-USER-1, REQ-TASK-1 and REQ-WEB-3
and adds 17 requirements (101 to 118): REQ-WS-1..8, REQ-AGENT-1..3,
REQ-SYS-1..2, REQ-ACCT-1..2, REQ-SETUP-1..2. requirements.json and the
tasks and bus count pins follow.

Tests: 68 pass, 1 fail. The failure is stack-copy.test.mjs (20/1), which
fails the same way on origin/refactor 483eccb3d: npm's pinned install
can't find node-gyp/bin/node-gyp.js in the test Node installation.
2026-11-09 17:04:31 -06:00

2544 lines
162 KiB
Markdown

# PRD: Mosaic Stack
- Status: approved, version 1.20. Jason approved draft 2 on 2026-11-09.
- Version 1.20 adds the unified workspace: every organization visible at once,
cross-organization views, three levels of agent configuration, per-org task
stores behind the task tracker kind, system type plug-ins and self-service
operation. It amends REQ-ASSIST-1, REQ-USER-1, REQ-TASK-1 and REQ-WEB-3.
- Prior approval history, through version 1.19: 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. 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). 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. Version 1.4
adds REQ-WEB-3 and REQ-INST-1, approved by Jason in decisions
021af0c8 (prd.approve, yes; request dfebf8ce) and 1ea053cc (prd.approve,
yes; request b28979c9). Version 1.5 records Jason's Q167 ruling and
his added Q167 text, relayed by Mos on 2026-10-15. Under Q167 Jason's
confirmation of the text is the approval, so there is no in-Stack
prd.approve for it. Version 1.6 records Jason's ruling Q169 of
2026-10-16, on who removes and who configures the assistant. It is
never edited in place. Changes are a new version. Version 1.7 routes the
chief of staff's hire and fire actions and the coder's push to its own
fork, and adds agent host access through the credential store, approved
by Jason in decision d52febb5 (prd.approve, yes). Version 1.8 records
Jason's ruling Q188 of 2026-10-20: trusted actions within role
guardrails by default, a manual mode for per-use raises, and grants the
chief of staff requests and the user approves. Jason approved it in T3,
relayed by Mos on 2026-10-21 (DECISIONS Q192), so there is no in-Stack
prd.approve for it. Version 1.9 adds REQ-WEB-2 and amends REQ-DEC-3:
the Console decide buttons Jason approved in decision 00ed58e2
(vikunja:19/266), signed in as his Q175 ruling set, and OIDC-only as he
chose in Q203, relayed by Mos on 2026-10-21 (mos-1202). Version 1.10
adds REQ-INST-2, post-v1: the second sentence of Jason's Q203 answer,
with Mos's reading, relayed by Mos on 2026-10-21 (mos-1202, mos-1204).
Version 1.11 records Jason's ruling Q211 of 2026-10-21: v1 is the 10-27
goal. It updates the lines that the 10-27 rulings settle and adds
REQ-INST-3. Mos's reading of Q211 sets the path: one seat drafts, a
second seat reviews, and Sage lands it. So there is no in-Stack
prd.approve for it. Version 1.12 adds requirement ids for the Q176
items the PRD lacked, drafted from the approved designs, and updates
two stale lines. It takes the same path as 1.11 (mos-1229,
vikunja:19/462). Version 1.13 adds the session requirements from the
approved T1 item 7 design and replaces REQ-ROLE-2 and REQ-THREAD-2. It
takes the same path as 1.11 (mos-1231, vikunja:19/463). Version 1.14
adds the playground levels to REQ-LIB-2 and a draft universe to
REQ-LIB-3, from the approved playground ruling. It takes the same path
as 1.11 (mos-1233, mos-1238, mos-1241, vikunja:19/464). Version 1.15
rewrites REQ-LIB-6 to Jason's Q223 and Q226 rulings and adds supporting
folders to REQ-LIB-1 and REQ-LIB-2. Mos ordered it in mos-1320 item 8,
and Jason gets a report. Version 1.16 routes the new action
`library.write` under REQ-LIB-6 (REQ-WAIT-3). A change to the hard waits
needs Jason's approval under REQ-WAIT-2, so Mos puts the text to him as
written, and it lands only after his yes (mos-1328, mos-1329). Version
1.17 records Jason's rulings Q235 to Q237 of 2026-10-23: Case 2 story
files are DOCX, Markdown stays for agent files and documentation, and a
human-started import converts a Markdown story file one way. It also
adds REQ-LIB-9, the Console's DOCX editor, from Jason's rulings Q195,
Q226 and Q234. It changes what Case 2 requirements allow, so Mos puts
the text to Jason as written, and it lands only after his yes (mos-1366,
Vikunja 570). Version 1.18 corrects Mos's reading in REQ-INST-2 to match
the approved row 255 design. The stack prints the providers entry and
never writes config.json. What the requirement allows or forbids is
unchanged (mos-1671, sage-4418). Doc-only, so it takes the 1.11 path:
one seat drafts, a second reviews, Sage lands. Version 1.19 adds
disk-space governance, launch-capacity holds, human-controlled retention
rules, and the protected `data.delete.policy` route. It preserves per-batch
`data.delete` hard waits. Jason approved the exact text at `e5400456` under
REQ-WAIT-2: "1.18: yes" (DECISIONS Q289, relayed by Mos in mos-2300).
Q288 approved the recurring-rule principle. Jason approved the exact text in Q289.
- Version 1.20 source: Mos asked Jason to approve draft 2 in
`docs/plans/2026-11-09_prd-1-20-draft.md`, with the Montage name deferred.
Jason replied on 2026-11-09 at 22:57Z: "The file looks fine other than that."
The exception is the module name, which he had deferred at 19:26Z: "I don't
like these module names either. It isn't clicking for me. That may need to be
a later decision."
- Version 1.19 source, Jason's DECISIONS Q287, verbatim:
> All of the learnings from this drive space issue need to be integrated into Mosaic Stack. We need watchers, cleanups, enforcement tooling and agents to check for space waste, etc.
Jason's DECISIONS Q288, verbatim:
> The Stack may run a recurring cleanup under the approved rule that a user is able to revoke or change. It should be tunable in the system settings.
These rulings are the recorded rationale. Jason approved the mapped 1.19
text under REQ-WAIT-2 in Q289, relayed by Mos in mos-2300. Jason approved
the text as 1.18 in Q289, and it was renumbered to 1.19 at landing because
row 335 landed 1.18 first.
- Version 1.18 source: Mos's ruling mos-1671 on 2026-10-25, answering
sage-4418. REQ-INST-2's second bullet said the stack installs the
bundled Pocket ID "and writes it as the Console's one providers entry."
It now says the stack "prints the Console's one providers entry for the
user to add to config.json. The stack never writes config.json (canon
invariant 2)."
- Version 1.17 source: Jason's Q235, answering Q36: 'How about we see a
markdown file, we convert the markdown file to docx, then archive or
mark the markdown file as converted. The new standard for files in the
creating writing use case is to use docx to handle versioning and
WYSIWYG. We eliminate the potential for confusion based on the files.
Agents must understand which files are what format and where they live.
The system must allow for import and conversion. We will not run the
systems in parallel. There will be an irreversible conversion of the
files.' His Q236 says: 'For creative writing chapters, we need to
utilize docx as the standard. Markdown is acceptable for agent files,
instructions, PRDs, documentation, etc. The creative writing files
should be docx for familiarity for the user.' His Q237 says 'Q38: It is
correct for now (subject to future change)', which confirms Mos's
reading P1 to P6. REQ-LIB-9 rests on his Q195 request for 'a full
WYSIWYG editor within Mosaic Stack', his Q226 answer 'F41: We want to
move editing into the WYSIWYG in the WebUI. All changes are instantly
committed and available to the agents.' and his Q234 answer 'Q37: yes'
to the exact security header for the editor's frame. The storage follows
the O2 design D10 to D12 as rewritten under Vikunja 571, at b99ca86b9 on
`refactor`.
- Version 1.16 source: Webby's review webby-0164 B2, from Scrooge's row
285. Version 1.15 lets creative agents write in supporting folders, but
the write action stayed a hard wait under REQ-WAIT-3, and REQ-WAIT-2
refuses every role file that grants one. Mos ruled in mos-1328 and
mos-1329 that the routing is its own version. The bounds come from
REQ-LIB-6 and the Case 2 design's ruling after r11, f6e3e7f20 on
`gosalyn/case2-design`.
- Version 1.15 source: Jason's Q223, answering Q29: "The agents are not
allowed to write the prose into the writing projects. Agents are
allowed to CRUD character sheets, info summaries, cross-link info, and
supporting documentation. They are NOT allowed to actually write any of
the story." and "They are permitted to freely converse with the writer
via chat interface." His Q226: "Q30: yes." to Mos's reading that a
folder nobody has marked counts as story, and "F43: citations should be
requireable. Tunable setting." Mos's reading of F43: "require-citation
is a setting changed by a human only. Mos's default is off." The text
is drafted from the Case 2 design's ruling after r11, "agents never
write story", b68d79663 on `gosalyn/case2-design`.
- Version 1.14 source: Jason's Q176 lines TARGET.md:92 "Brainstorm ideas
in a "playground" where new universes can be created", :93 "Brainstorm
ideas in a "playground" for books in a universe" and :94 "This means
universe-level and book-level playgrounds must be provided". Mos asked
for a ruling (mos-1233). Mos ruled that the levels come in 1.14, after
1.13, once Webby approves the ruling (mos-1238 A, mos-1241). The text
is drafted from the playground ruling after r11 in the Case 2 design,
51fdfb7e2 on `gosalyn/case2-design`, approved by Webby in webby-0089
(7c00c8345 on `webby/reviews`).
- Version 1.13 source, Mos's ruling Q1 in mos-1231 on 2026-10-21: "T1
item 7 is v1." It quotes Jason's Q176 lines, TARGET.md:57 "Agents can
have multiple independent sessions within a given project (thread)",
:104 "User must be able to see the number of compactions that have
occurred for a session" and :105 "User must be able to easily clear the
context for the agent to trigger a new session". Mos's reading of Q211:
"where REQ-ROLE-2 and REQ-THREAD-2 differ from line 57, line 57 wins."
And: "The ids and the REQ-ROLE-2 and REQ-THREAD-2 changes come in 1.13
from the approved design." Mos's further readings, mos-1235 and
mos-1236, are in the Version 1.13 technical note.
- Version 1.12 source, Mos's answers to the 1.11 questions in mos-1229 on
2026-10-21: "M2: agreed. 1.11 adds no ids for the Given list or Case 2
items. You draft 1.12 from the approved #414 and #416 designs as soon as
Della approves 1.11. Vikunja 462." and "M4: fold both items into 1.12,
Vikunja 462." The two M4 items are the slice 1 Risks line and the
REQ-DEC-3 title in `packages/tasks/requirements.json`.
- Version 1.11 source, Jason's answer to Q23 in T3, relayed by Mos on
2026-10-21 (DECISIONS Q211, mos-1227): "It appears that I have expanded
the requirements for v1. My recent goal for the 10-27 release is the
goal. The PRD may need to be updated to reflect this change. The PRD can
be considered partly stale." Mos's reading, which this version follows:
- The Console's own OIDC sign-in (REQ-WEB-2) is v1.
- v1 is the 10-27 goal: Q176 (the Given list and Cases 1 and 2) plus the
later rulings that put items on the 10-27 path, such as Q179, Q184,
Q187, Q188, Q189 and Q190. Where the PRD and those rulings differ, the
rulings win.
- The answer adds to v1 and removes nothing. Cutting a v1 item goes to
Jason.
- Rulings that keep an item off the 10-27 path still hold: Q186, Q198's
timing, Q203's dragon-lin line and Q208. REQ-INST-2 stays post-v1.
- Version 1.10 source, the second sentence of Jason's Q203 answer quoted
under 1.9 below, Mos's reading of it in mos-1202, and his ruling in
mos-1204 on 2026-10-21 to add it as drafted and mark it post-v1 the
way REQ-FED-1 is (vikunja:19/458).
- Version 1.9 source, Jason's answer to Q203 in T3, relayed by Mos on
2026-10-21 (DECISIONS Q203, mos-1202): "Let's use O6. If a user does
not have an OIDC provider to configure, we will need to use a bundled
Pocket ID instances to handle this traffic." O6, as Mos put it to him:
"keep the REQ-WEB-2 wording OIDC-only. Identity is the (issuer,
subject) pair from the Console's own verified OIDC sign-in. Local
accounts come from a provider, and the Console never holds passwords."
The second sentence is not part of 1.9. It becomes its own requirement
in a later version (vikunja:19/458).
- Version 1.8 source, Jason's approval in T3, relayed by Mos on 2026-10-21
(DECISIONS Q192): "PRD 1.8: I am preemptively approving it. Do not
surface in the Mosaic Stack board. Just proceed."
- Version 1.8 source, Jason's ruling Q188 (DECISIONS, 2026-10-20), his
answers to Q8 and Q10, in part. Q8: "I would rather grant the grant
right to an agent that runs with strict guardrails. That agent presents
the final authorization for the user. Upon approval, the grant is
created programmatically. I see this as: User configures their PATs, API
tokens, authorizations within the WebUI. The agent presents the request
for grant with X rights for another agent. The user reviews, then clicks
Approve/Deny. Click of Approve fires off protected mechanical methods to
handle the grants on Github, Gitea, .... services as needed. The tooling
would need to exist and must be guarded." And "Disallowing the agents to
create grants autonomously is a necessary evil for better security.
Requiring the user to manually perform the steps is a no-fly." Q10:
"Agents should have tools and command access based on roles. Those roles
dictate the allowed tools and commands. The use of a tool or command
should not be raised to the user unless the system is configured with a
manual mode that requires user interaction. By default I want to have
the agents capable of running commands and tools at will within their
sandbox/guardrails. This allows autonomous actions by agents within
their lanes. Perhaps a "Trusted actions within approved guardrails"
methodology." And "The main agent or chief of staff would be responsible
for surfacing the request to the user's inbox for approval. Not all
agents are capable or authorized to request directly. It must follow the
chain of command properly." He agreed with Mos's Q10 points 3 to 6:
protected stack code does every step on Approve, his tokens are entered
in the WebUI and agents never see them, a command on the approved list
runs without a click and any other is refused, and he, Mos or the tester
can revoke a grant at once.
- Version 1.8 source, Jason's ruling Q190 (DECISIONS, 2026-10-20), his
answer "Q12: O1": each agent seat gets its own account on his Gitea,
created through the Q188 approval path once the Gitea grants exist.
- Version 1.8 source, Jason's ruling Q191 (DECISIONS, through Mos in
mos-1149), that the assistant and the chief of staff are separate roles,
and only the chief raises a grant request: "Q13: that looks better".
- Version 1.7 source, TARGET.md line 66: "Chief of staff agent has
hire/fire authority for agents as needed; unless user forbids per
agent". And Jason through Mos (mos-1067), DECISIONS Q183, in part: "I
will still want to allow infra agent(s) on my .53 instance to access
other servers, etc. However, not all agents will need to have that
access. I envision a credential store within Mosaic Stack that works as
some sort of variable for the agent to call. The system will enforce
access to the credentials to keep the agents sandboxed with proper
security. I need to be able to trust my agents with assisting me. This
will require sudo access to several systems. If we can utilize the
credential store in Mosaic Stack and allow agents to perform their jobs
with NOPASSWD sudo on various machines, I am a happy man." DECISIONS
Q184, on whether that is in place by 10-27: "Q5: by 10.27"
- Version 1.6 source, Jason's ruling Q169 (DECISIONS, brain repo,
2026-10-16): "The only agent that should be able to remove the
assistant role is the chief of staff. The COS and the user should have
the ability to (re)configure the assistant role and seat name. The
user via WebUI or CLI, the COS via the proper tooling."
- Version 1.5 source, Jason's words relayed by Mos on 2026-10-15:
- Q167: "Me having to approve every decision within the Stack is not
working for me. I gave a directive, I want it to be done." and "Use
the T3 seats to make these updates and changes, so I can have a
functional system."
- Q167, added text: "All north star specs should be visible and
modifiable by the user within each given Project. I need to be able
to read and make the changes ad-hoc without an agent when desired.
This will allow review and revise to occur outside the chat. The
agent will be able to see the changes of course. Changes made should
use git (at least locally) to maintain the change history.
Organization of data in a Project is critical. We need a data
structure with json files and templates for everything. Markdown
files with a WYSIWYG editor for the north star/spec files.
Documentation within the project. Consistent storage of docs, reqs,
decisions, etc. should be kept in the project for reference as
needed. We will need to have these agents for a coding workflow: -
coder - code review - security review - merge gate. The appropriate
agent profile will be assigned to the agents. The roles and tools
allow agentic control of the needed workflow steps. Autonomy within
the rails is encouraged. Agents are proactive. The rest of your
reading is accurate."
- On the revised row list (task 314, comment 225): "Your decisions look
good. Proceed."
- Jason's waits list, as Mos gave it: spending money, deleting data,
anything irreversible, new internet exposure, new external accounts,
messages to people outside the fleet, and force-push or history
rewrite. Mos's reading, Q5: "Q167 covers shrinking GATED_ONLY and the
role grants through rows 135 and 139." Sage's plan for the rows adds
that any change to the list itself, and any grant of a listed action
to a role, also goes to Jason.
- Mos's reading, Q4: agents land into the business working
repository's protected branch through the host. This repository's
canonical `refactor` branch stays the lead's landing.
- 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
structure, etc. ... This necessitates the creation of a new role for
user interaction. ... The user should be allowed to have 1 - N
sessions with this agent to allow for topics of focus in a given
thread. ... The interaction agent in each session is the user's main
agent."
- Q148, Mos's read-back of Q147: "Q148: Yes".
- Q149, on the 1.2 draft: "approved as long as the agent limits are
tunable via "Settings" in the WebUI and via the CLI tooling."
- Q150: "No hard cap on agent limits. All tunable." The five kinds of
limit are quoted under Launching.
- Q151, Mos's read-back of Q150, with the default that launches over a
limit are refused and logged and a run that hits its token limit is
stopped: "Q151: yes".
- Owner: Jason. Sage writes it from the PRDY interview.
- Template: PRDY "software" (`v1/packages/prdy/src/templates.ts`), filled
by hand until PRDY is ported.
- Interview record: Sage's thread 1ef1e4f8. Round 1 is lead decision 45.
Round 2 is lead decision 48. Round 3 is lead decision 53. Design
inputs are lead decisions 43, 44, 46, 47 and 49 to 52.
## Introduction
### Objective
The working north star, ratified as lead decision 44: "Jason declares
businesses, projects and roles. Agents in those roles carry the work end
to end under declared policy, and only gated decisions reach him."
### Context
See `docs/plans/2026-10-04_foundation-direction.md`. Agents already do
real work here. But a person has to launch them, relay between them, hand
them credentials and correct them. Jason has no CLI and no complete WebUI,
and outstanding decisions don't collect anywhere.
## Users
v1 has two users, each on their own instance: Jason on Case 1, the
software factory, and Melanie on Case 2, the creative writing hub (Q176,
Q211). The stack must still be installable by outside users later, which
means:
- an installer;
- no paths or names specific to Jason;
- configuration through the variable layers.
(Round 1, answer D. The 1.10 text began "For now the only user is
Jason.")
## Problem statement
In priority order (round 1):
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 for
Jason.**
3. **Agents drift from what the business needs.** Nothing ties a piece of
work to a stated goal.
## Scope and non-goals
### Hard limits
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 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.
### Where it runs
Mosaic Stack stays self-hosted open source. The licence is open, and Jason
decides it later.
Self-hosted, on Jason's machines and homelab (round 1, "A first"). Cloud
workers aren't ruled out later.
v1 runs as two instances on VMs on the PVE node: Case 1 on VM 1008 and
Case 2 on VM 1012. Each instance runs under its own OS user without sudo.
Jason's workstation, dragon-lin, is for dev work only. (Q180, Q181, Q211.)
### Out of scope for v1
Round 2, "all", as version 1.11 updates it (Q211):
- businesses other than Mosaic Stack on Case 1 (SetSpark joins after
v1). Case 2's hub runs its own organization on its own instance, and
that is v1 (Q176);
- Codex (v1 covers Pi and Claude Code, and Codex comes next);
- the bundled Pocket ID (REQ-INST-2, Q203).
Version 1.11 moves three round 2 items into v1:
- Minting credentials. On the user's Approve, protected stack code makes
the key, the token or a seat's own Gitea user (REQ-GRANT-2; Q188, Q190).
- SSO. The Console runs its own OIDC sign-in (REQ-WEB-2; Q175, Q211).
- Access from beyond localhost. Each case runs on its own VM, and people
get access across the two instances (Q176, Q179, Q180). The Console's
own sign-in (Q175) comes first for any instance that a second user or
another instance can reach (Q176). A new listener reachable from outside
the host is still a `network.expose` hard wait (REQ-WAIT-1).
## Requirements
Every task the stack creates cites one of these ids. A task with no
parent requirement is refused.
### Roles
- **REQ-ROLE-1.** Each role is a reviewed file, `roles/<name>.json` at
version 2. The file holds:
- a contract;
- an authority map over a closed vocabulary of actions;
- the credentials it needs, by service and scope.
Any action the file doesn't list is gated.
- **REQ-ROLE-2.** Each role instance has one holder at a time. The
assistant is the exception: within a thread it can have several
independent sessions, each with one holder at a time. No other role
holds a thread session. A claim takes a lock and logs it. An assistant
claim names the organization, thread, instance and session. Releasing
the role or ending the run frees only that holder. Creating or clearing
a session grants no role authority, and a pending approval never moves
to another session. (Version 1.13.) The chief of staff may end another role's run, and that act is
logged with its reason. Anyone else who revokes a stuck holder needs a
resolved decision, and the revoke cites it. The assistant is the
exception here too (version 1.6, Q169): the chief of staff is the only
agent that removes it. The chief of staff removes it by ending its
runs, and that end needs no decision. A revoke of the assistant, the
chief's included, is a hard wait under REQ-WAIT-1. Anyone else's revoke
of the assistant must cite a decision the human resolved through the
CLI. A decision an arbiter resolved doesn't count, and that revoke is
refused.
- **REQ-ROLE-3.** An organization file declares the role instances,
the arbiters and the credential references. The user and the chief of
staff write it. The chief of staff's writes go through a broker verb
that logs each change. No other role writes it. A missing or invalid
file fails closed. (Q133, confirmed as Q140: "We need the human and the
Chief of Staff to be able to edit the Business file(s)".)
- **REQ-ROLE-4.** The first organization is Mosaic Stack, with six role
instances:
- chief of staff;
- assistant;
- PM, held by Sage;
- CTO, held by Darkwing;
- coder;
- reviewer.
The schema can also declare CEO, CFO and the other C-suite roles. The
assistant's name is set in the organization file. Jason's may be named
Mos. Since version 1.6 (Q169), the user and the chief of staff can
configure the assistant's role entry and its seat name: the user
through the WebUI or the CLI, the chief of staff through its broker
tools, which log each change. No other role can, and a change can't
remove the assistant's protection under REQ-ROLE-2.
Added in version 1.5 (Q167):
- **REQ-ROLE-5.** The coding workflow has four roles, each with its own
role file:
- coder, which writes the change;
- reviewer, which gives the code review verdict;
- security-reviewer, which reviews a candidate for security only and
approves or blocks with the reason. It has read-only tools and the
verdict, and no shell, no write tools and no push;
- merge-gate, which takes a candidate the reviewers approved, raises
the merge decision for it in the technical domain, so the CTO
decides, and lands it once the decision is resolved (REQ-LAND-1).
Otherwise it reports the refusal. Its one grant is
`git.merge.protected` as a cross-role action, which is what landing
checks. Raising a decision needs no grant. It has no write tools.
Because only the instance that raised an approval can consume it
(REQ-DEC-5), the role that lands is the role that raises.
Instances of these roles are added through the chief of staff's
organization change verb (REQ-ROLE-3).
- **REQ-ROLE-6.** Agents are proactive. Every role contract says: within
your grants, act on what you see without waiting to be asked, and
raise a decision or send a message for anything outside them.
Added in version 1.2:
- **REQ-COS-1.** The chief of staff runs the organization. It hires and
terminates role instances, owns the organization's structure and is
the only role that launches sessions (REQ-LAUNCH-1). It takes work
from the assistant and assigns it through the roles that do it. It
never talks to the user. Gated decisions still reach the human
through the inbox (REQ-DEC-2, REQ-DEC-4).
- **REQ-COS-2.** The PM asks the chief of staff for staff. A staff request
names the roles and how many of each. The chief of staff launches them
through role.launch, within the agent limits (REQ-LIMIT-1). (Version
1.5.)
- **REQ-ASSIST-1.** The assistant is the user's main agent. Each user has one
assistant across all of the organizations they
belong to. It knows the user's organizations, projects and sessions and
keeps one shared memory. It talks with the user and passes each
organization's work to that organization's chief of staff. It doesn't
direct other agents, create or assign tasks, or launch sessions.
- **REQ-ASSIST-2.** The broker limits the assistant's messages, not its
contract alone:
- the assistant may send only to the user, in the thread, and to the
chief of staff;
- only the user and the chief of staff may send to the assistant;
- every refused send is logged with its reason.
- **REQ-ASSIST-3.** The assistant tells the user what other roles sent it
in its own words. It summarizes and doesn't relay their text. (Version
1.5.)
Added in version 1.7:
- **REQ-COS-3.** The chief of staff hires and fires role instances without
a decision each time. Hiring adds a role instance to the organization
file and lists it for launch. Firing ends the instance's runs and
removes it from the launch list. It stays in the organization file. Only
the user deletes an instance. These actions are `role.hire` and
`role.fire`. They leave the hard waits and route under this requirement,
within these bounds:
- The chief acts only in its own organization.
- The chief hires only role definitions that need no credentials, until
a later version adds credentialed roles.
- The chief hires only the role kinds its role file lists. Changing that
list is a reviewed commit.
- The chief hires only a role whose every action is on a fixed list of
plain work actions: sending messages, writing records, asking for
review, updating its assigned tasks, and the scope and working-push
actions of an assigned task. A role holding any other action, such as
launch, end, organization, naming, hire, review verdict or merge
authority, or any action added later, is never hired. So the chief
never hires a chief, never picks its own reviewers or merge gate, and
the author is never the reviewer. This holds whatever the role file's
list says. Adding to that list is a PRD change.
- The chief may set a hired instance's model and narrow its limits, and
nothing else about it.
- The user can forbid per agent. The user marks an instance so the chief
can't fire it, end its runs or change its model, and marks role kinds
the chief can't hire. Only the user changes these marks, through the
WebUI or the CLI.
- The kill switch stops every hire and fire while it is on.
- Every hire and fire is logged with the chief's instance, its run and
the reason.
### Agent levels
Added in version 1.20.
- **REQ-AGENT-1.** Agents are configured at three levels: the user's root,
the organization and the project. A project's available agents are the
union of the three.
- **REQ-AGENT-2.** The user can see and change the configuration of agents,
organizations, projects and sessions, and can override any setting at any
time, through the write path (REQ-ROLE-3).
- **REQ-AGENT-3.** The chief of staff hires and fires agents to meet the
user's needs. At the organization and project levels it acts under
REQ-COS-3. At the root level its hire or fire changes the user's root
configuration, which every organization of that user sees, so it reaches
outside the chief's own organization. A root-level hire or fire is a
decision the user approves in the inbox (REQ-DEC-2) before it takes
effect. The user can also change root-level agents directly.
### Variables
- **REQ-VAR-1.** Variables come in four layers: system, business, project
and agent. Every key is declared in code with a type, the layers allowed
to set it, and a merge rule.
- The most specific layer wins for plain values.
- Limits intersect, so a lower layer can only narrow them.
- An unknown key is refused, and so is a key set at a layer it isn't
allowed in.
- **REQ-VAR-2.** `~/.config/mosaic-dev/config.json` stays the only system
config, and nothing writes it automatically. Since version 1.19, only
an authorized human Console or CLI settings action outside an agent run
may write the disk settings under REQ-DISK-3 and REQ-DISK-4. No agent or
automatic writer may write them. Rule settings stay system-only. Revision
approvals and immutable inventories live in the decision and event stores,
keyed by revision ID, not a second system configuration. A secret appears
only as a reference, either a file path or an environment variable name. Only
the broker reads a secret's contents. It holds them in memory and
never logs or writes them. Everything else checks a referenced file
with `stat`.
#### Disk settings (proposed version 1.19)
All proposed keys below are human-only. Read access follows role grants. No agent-layer or environment override is allowed.
Use duration values, positive integer counts, bounded numeric percentages, and integer byte and inode floors. Path sets contain canonical paths.
For plain values, the most specific allowed layer wins. Limit floors merge by maximum and percentage thresholds by minimum. Sets merge by union. Required coverage cannot be removed.
| Key | Default | Allowed layers | Merge rule |
| --- | --- | --- | --- |
| `disk.coverage` | Required instance roots, with registered additions | System, business | Union |
| `disk.watch.interval` | 15 minutes | System, business | Most specific wins |
| `disk.watch.used_percent`, `disk.watch.inode_percent` | Breach at or above 85% | System, business | Minimum |
| `disk.watch.free_bytes` | 0 extra floor on other filesystems | System, business | Maximum |
| `disk.launch.free_bytes` | 2 GiB on each runtime temp, home, and workspace filesystem | System, business, project | Maximum |
| `disk.launch.free_inodes` | 1,024 on each launch filesystem with inode accounting | System, business, project | Maximum |
| `disk.watch.timeout` | 15 seconds per filesystem | System, business | Most specific wins |
| `disk.alert.repeat_interval`, `disk.alert.change_percent` | 6 hours or a 2-point increase | System, business | Most specific wins per key |
| `disk.alert.recovery_margin_percent` | 2 percentage points for byte-use and inode-use percentages | System, business | Most specific wins |
| `disk.alert.recovery_margin_bytes` | 2 GiB above each applicable byte floor | System, business | Most specific wins |
| `disk.alert.recovery_samples` | 2 consecutive valid samples meeting recovery margins | System, business | Most specific wins |
| `disk.alert.routing` | Runtime temp or byte-floor breach immediate, others daily digest | System, business | Most specific wins |
| `disk.monitor.stale_after` | 45 minutes since completion or first undelivered alert | System, business | Most specific wins |
| `disk.monitor.supervisor_interval` | 5 minutes | System | System value |
| `disk.monitor.watchdog_interval` | 5 minutes | System | System value |
| `disk.monitor.fallback` | Require a tested existing channel and independent writable destination at setup | System | System value |
| `disk.retention.policies` | Empty. Offer disabled daily scratch template, older than 48 hours | System only | System map keyed by policy ID, human-approved revisions only |
| `disk.paths.temporary`, `disk.paths.retained` | Installer instance roots, no operator path in product code | System, business | Union per key |
| `disk.audit.interval`, `disk.audit.timeout` | Weekly plus each new capacity incident, 180 seconds per root | System, business | Most specific wins per key |
| `disk.failure.unexplained_limit` | 3 failed attempts before investigation | System, business | Most specific wins |
| `disk.filesystem_overrides` | Empty typed map | System only | Exact filesystem entry replaces corresponding system base values before layer merging |
- Type `disk.filesystem_overrides` as a map keyed by instance host identity and resolved filesystem identity. Each entry may set watcher percentages, watcher free bytes, launch free bytes, and launch free inodes with the types above.
- Resolve all path aliases to that identity. Refuse unknown override fields, unmatched identities, and conflicting aliases. There are no separate path overrides.
- Watchers use system and business values only. They also apply system and business launch floors on registered launch filesystems. One filesystem has one effective watcher configuration and one incident per condition.
- Project floors apply only to that project's launch checks. A project's stricter floor cannot change shared watcher thresholds or create a second filesystem incident.
- Type routing as a condition-to-channel map, fallback as a channel and destination record, and policies as records defined by REQ-DISK-4. Routing merges as one plain value. Blocking hard waits always retain immediate delivery.
Two recovery samples resist brief swings. The 2 GiB launch floor and 1,024-inode floor reserve startup room on smaller instance disks. They are defaults, not guarantees of workload fit.
Validate positive durations and counts, bounded percentages, nonnegative extra byte floors, and positive launch floors. Reject recovery margins that make recovery impossible. Reject liveness deadlines shorter than a complete configured check, including supervisor and watchdog intervals.
Reject a temp root that contains, or sits inside, a retained root. Repeated audit proposals update existing work.
### Credentials
- **REQ-CRED-1.** Every role instance has its own token for each service
(Gitea, Vikunja). The broker holds them, and they never enter an agent's
environment or files. In v1 Jason creates them by hand from a runbook
Sage writes (round 3, 1A):
- in Gitea, a new bot user per role, named `<business>-<role>-bot`,
for example `mosaic-stack-pm-bot` (round 3, 2A; lead decision 58);
- in Vikunja, a `bot-<business>-<role>` user per role, plus one
read-only `bot-<business>-sync` that does all polling (lead
decisions 52 and 58).
Since version 1.8, a token or key for a grant is made by the grant code
on the user's Approve (REQ-GRANT-2), not by hand. Tokens that exist stay
as they are.
Since version 1.11, Gitea follows Q190: each agent seat gets its own
Gitea user through a grant (REQ-GRANT-1, REQ-GRANT-2), not a bot user
per role made by hand. Vikunja is unchanged.
Since version 1.19, each instance also has the protected disk-service
principal of REQ-DISK-12 with its own broker-held Vikunja bot. A human
creates its bot and token by hand from a runbook. This adds no automatic
account creation or role-decision authority.
- **REQ-CRED-2.** A session that finds only founder credentials stops.
Added in version 1.7:
- **REQ-CRED-3.** An agent with a grant can run commands with sudo on
another host. The agent asks for the host credential by name. The stack
checks that a grant exists for that agent and that host before the
credential is used. The agent never sees the key or any other secret
value (REQ-CRED-1, REQ-VAR-2). An agent without a grant for that host is
refused. Not every agent gets a grant. Who grants a host credential, and
how, is set by the #424 design, which Jason approves.
Added in version 1.8 (Q188):
- **REQ-CRED-4.** An action a role definition lists within-role, and that
uses a grant, runs without a decision for each use only when all of
these hold:
- the definition declares the credential kind the action uses;
- a grant names that agent, and that host or service;
- for a command, the command matches the grant's allowlist. A grant
lists exact argv prefixes. A wildcard grant is refused;
- the agent's key or token for that grant is issued and not revoked.
A command off the allowlist is refused, and adding one takes a new grant
request (REQ-GRANT-1). An action whose definition doesn't list it
within-role still needs a decision for each use. Only a reviewed role
change adds an action to a definition. `host.sudo` routes under this
requirement.
Later remote privileged disk cleanup uses only the reviewed fixed helper
of REQ-DISK-6, with policy ID and candidate-list hash as its only inputs.
A raw deletion or shell prefix is never a grant. (Version 1.19.)
- **REQ-GRANT-1.** No agent creates or writes a grant. A grant request
follows the chain of command, and only the chief of staff raises it to
the user's inbox. Any other role that needs a grant for itself or for an
agent it directs asks the chief of staff, through its chain: a worker
role through its PM, the PM and the CTO directly. The assistant passes
the user's own request to the chief of staff (REQ-ASSIST-1). The request
names the agent, the grant method and its target, the exact rights, and
a rotate-by or expiry date. The chief raises one only for an agent whose
reviewed role definition allows that kind of credential, and the broker
refuses any other. A grant can't name a wildcard, a shell or an empty
command. The request is a decision with the action `credential.mint`, so
it goes to the user as a hard wait (REQ-WAIT-1). A grant whose method
makes an account, such as a seat's own Gitea user (Q190), is the same
one decision: the method in the grant says it makes an account, the
request names the account, and the inbox shows the account name before
Approve. The user's one resolution covers the account and its
credential, so the hard-waits list doesn't change. An `account.create`
raise isn't a grant request and routes as version 1.7 has it. The broker
checks only that the chief of staff raised the request. The chain of
command before that is in the messaging lists and the role contracts,
not a broker check. The assistant and the chief of staff are separate
roles. No agent holds both, and the broker refuses a claim that would.
- **REQ-GRANT-2.** The user approves or denies each grant request in the
inbox. Approve is the only thing that creates a grant, and the code uses
a grant only when it has a matching Approve record. No agent creates a
grant. A grant written into the business file by hand makes nothing. Row
210's hand-written grants stay a test path until Vikunja #436a moves
`host.run` to grants made by Approve. On Approve, protected stack code
does every step, not an agent and not the user: it records the grant,
makes the key or token, installs it on the machine or creates it on the
service, and logs each step. On Deny nothing is made, and the denial is
logged. The grant methods are:
- machine key: a key for one agent on one enrolled host, with the
command allowlist of REQ-CRED-4. A host's identity comes from the host
side when the user approves its enrollment, never from a first
connection. A host user with sudo receives a grant only after the user
approved that user by name;
- Gitea: a token for one agent, and for an agent seat its own Gitea user
(Q190), with an expiry. The all scope and the admin scope are never
granted;
- GitHub: a grant for one agent.
The Gitea and GitHub rights units come in a later design that Jason
approves.
The approval binds the exact rights the user saw and the target or its
enrolled identity. A change to the agent, its definition, the target,
the target's identity or the rights after the raise voids it. An
approval is used once. A grant the code couldn't finish is undone at
once on the stack's side, so it is never used. What it left on the host
or service stays visible as pending until it is removed, as for a revoke
(REQ-GRANT-4). A new grant needs a new approval, never an automatic
retry. The grant code runs only on a request the user resolved. A
request resolved by an arbiter, the tester or any agent makes nothing. A
`credential.mint` decision raised before version 1.8, or without the
fields of REQ-GRANT-1, makes nothing when it is approved. Until the
Console knows the signed-in human (Vikunja #412), Approve is
`mosaic decide` in the CLI (REQ-DEC-3).
- **REQ-GRANT-3.** The user enters their own personal access tokens and
API tokens in the WebUI, or the CLI. Only the grant code reads them, to
make grants. No agent ever sees a token value or a key's private half.
Values appear in no event, log, message, prompt or agent file, and no
agent can read them back (REQ-VAR-2). Until the Console knows the
signed-in human, they are entered through the CLI.
- **REQ-GRANT-4.** A grant can be revoked at once by the user, by the
assistant for a grant in its own organization, or by the tester on a dev
instance (Q173). A revoke needs no decision and no new trust. It takes
effect at once on the stack's side, without waiting on the network, so
the next use is refused. Removing the key or token at the host or
service follows. Until that removal is done it stays visible as pending.
A revoke while the grant is being made stops it, and nothing it made
stays usable. Every revoke is logged with who made it.
### Decisions
- **REQ-DEC-1.** Every decision is a record in an append-only SQLite
table. The record holds:
- the role and run that raised it;
- its class and action;
- the options and a recommendation;
- who resolved it;
- its timestamps.
A correction is a new row. Since version 1.19, human-approved retention
revisions and their immutable inventories are keyed by revision ID in
the decision and event stores under REQ-DISK-4. A policy run binds to
its live approved revision, never a reused ordinary decision.
- **REQ-DEC-2.** Routing follows the decision's class and whether it
cites the project's spec (REQ-PROJ-2). Version 1.5 replaces the 1.0
text, which sent every gated decision to the human:
- routine and within-role decisions are logged;
- cross-role decisions go to the business's arbiter for that domain;
- a gated decision whose raise cites the project's current spec commit
goes to the business's arbiter for its domain, technical or delivery,
and not to the human;
- a gated decision with no spec citation goes to the human;
- a hard wait (REQ-WAIT-1) goes to the human whether or not it cites
the spec.
A raise that cites a spec commit other than the current one is
refused, and so is a raise citing a spec whose last commit isn't the
human's.
- **REQ-DEC-3.** A human resolution, by an authorized human, comes only from a `mosaic` CLI session
or a Console session signed in under REQ-WEB-2, both started outside
any agent run. Agents reach the decision store only
through a broker. The broker stamps role and run from the launch record.
- **REQ-DEC-4.** A gated decision that blocks work reaches an authorized human
right away: in the CLI inbox, plus a Discord DM sent through the existing
connector (#1509). Everything else goes into one daily digest at 08:00
Central. (Round 2, answers 2A and 3A; round 3, 4A and 5A.)
Added in version 1.5 (Q167):
- **REQ-DEC-5.** An approval outlives the run that raised it. It binds to
the decision and to the role instance and thread that raised it. A
later run of the same instance, on the same thread, may consume it
once. The first consuming action is
recorded, a second consume is refused, and another instance can't
consume it. Superseding a decision follows the same rule.
- **REQ-DEC-6.** A new spec commit never reopens a settled decision. Each
decision records the spec path and commit its raise cited. A decision
settled under one commit stays settled when a later commit lands, and
new raises cite the new commit.
### Trusted actions
Added in version 1.8 (Q188).
- **REQ-TRUST-1.** Agents act within their role guardrails without asking.
The default mode is "trusted actions within approved guardrails". An
action a role file grants within-role is done at will, inside the
agent's sandbox, its tool limit and its grants, and is logged. It raises
nothing to the user. An action the file doesn't grant within-role still
routes by REQ-DEC-2, and a hard wait still goes to the user
(REQ-WAIT-1). Only a reviewed role change adds an action to a role file,
and REQ-WAIT-2 still keeps every hard wait out of every role file.
- **REQ-TRUST-2.** The user can put chosen actions in manual mode. The
organization file holds the list of actions in manual mode, empty by
default. While an action is on the list, every use of it by any role
becomes a decision for that use, raised to the user, and the action
waits until the user resolves it. A decision denied, or not resolved,
refuses the action. Each decision is used once. For a command on another
host, the decision names the host, the command and its input. Actions
not on the list are unchanged. Only the user changes the list, through
the WebUI or the CLI. The chief of staff's organization writes
(REQ-ROLE-3) can't change it, and no agent can. Every change is logged
with who made it. A change applies to the next use, and a run already in
progress keeps no exemption. Since version 1.19, manual mode for
`data.delete.policy` requires a fresh per-use PM decision before the
protected executor dispatches under REQ-DISK-5. Protection settings
remain human-only under REQ-DISK-3.
### Hard waits
Added in version 1.5 (Q167).
- **REQ-WAIT-1.** These are the hard waits. Each always goes to the
human, except the protected policy route of REQ-DISK-5 since version
1.19, and each maps to actions in the closed vocabulary:
| Jason's wait | Actions |
| --- | --- |
| Spending money | `spend` |
| Deleting data | `data.delete`, new in 1.5, and `data.delete.policy`, routed under REQ-DISK-5 since 1.19 |
| Anything irreversible | `data.delete`, `data.delete.policy` under REQ-DISK-5, `git.push.protected` and REQ-WAIT-3 |
| New internet exposure | `network.expose`, new in 1.5: a listener, port, tunnel, DNS record or proxy route reachable from outside the host. `deploy` stays a hard wait too. |
| New external accounts | `account.create`, new in 1.5, and `credential.mint` |
| Messages to people outside the fleet | `message.external` |
| Force-push or history rewrite | `git.push.protected`, which covers a push to a protected branch and any force push or history rewrite on any branch |
The always-gated lists in the broker and the business vocabulary hold
exactly these actions. `git.merge.protected`, `policy.change`,
`prd.approve` and `role.revoke` leave those lists and route by
REQ-DEC-2. REQ-ROLE-2 still requires a resolved decision for a revoke.
Since version 1.6 (Q169), a `role.revoke` of the assistant is a hard
wait: its raise goes to the human, cited or not, and no arbiter
resolves it. Every other cited revoke routes by REQ-DEC-2.
- **REQ-WAIT-2.** No role file may grant a hard-wait action, at any
class, and the validator refuses a file that does. A change to the
hard-waits list, or a grant of a hard-wait action to a role, is itself
a hard wait. The list changes only by a reviewed commit to the
vocabulary, under a new PRD version that Jason approves.
- **REQ-WAIT-3.** An action added to the vocabulary is a hard wait until a
PRD version says it isn't. The reviewed commit that adds it names which
it is.
`role.hire` and `role.fire` were added as hard waits and route under
REQ-COS-3 since version 1.7. `git.push.remote` was added as a hard wait
and routes under REQ-LAND-2 since version 1.7.
`host.sudo` was added as a hard wait and routes under REQ-CRED-4 since
version 1.8.
`library.write` was added as a hard wait and routes under REQ-LIB-6
since version 1.16. It is granted within-role to the six creative roles
only, and only for Markdown files under the supporting folders a human
marks. It never writes a story file. A write also needs the writing
agent's own live Gitea grant on the library project. Each write is one
commit by that agent's own Gitea account with a trailer naming the
agent, and the user sees a notice for it and can revert it.
`data.delete.policy` was added as a hard wait and routes under
REQ-DISK-5 since version 1.19.
#### Disk hard waits (proposed version 1.19)
- DISK-H1. Out-of-policy deletion requires the PM's exact-batch human approval and single-use consume. `data.delete` stays always gated and never enters a role grant.
- DISK-H2. Policy creation, change, enablement, and revocation require an authorized human Console or CLI session. The approved revision binds scope and executor. Agents cannot approve, change, enable, or revoke a rule.
- DISK-H3. Root access follows REQ-CRED-4 and never removes DISK-H1 or DISK-H2. Existing spending, exposure, account, and external-message hard waits remain.
- DISK-H4. Protection settings follow REQ-DEC-3 and REQ-TRUST-2. Granted read-only checks and reporting need no per-run approval.
Keep `data.delete` on the broker and vocabulary always-gated lists. Add
`data.delete.policy` to both lists with a protected-executor route requiring
a live human-approved revision. Every other caller is refused. No role file
may grant either action. Update the reviewed vocabulary and broker together
under REQ-WAIT-2 only after Jason approves version 1.19. Never reuse a
consumed ordinary decision as standing permission.
### Messages
- **REQ-MSG-1.** Messages are addressed to a role. They are append-only
and go to whoever currently holds the role. A message to or from the
assistant also names its thread and goes to that thread's holder. When
the assistant sends work to the chief of staff, the reply comes back to
the same thread. T3 and Discord carry messages; the address is still
the role.
- **REQ-MSG-2.** Messages can also be addressed by agent name. A role
instance may have a name, unique in its business, that equals no
instance key and no role name. A send takes a name or a role. A name
resolves to its instance, the message records both, and a role still
delivers as REQ-MSG-1 says. The human sets names through the CLI. The
chief of staff sets them once its role file grants that within-role.
(Version 1.5.)
### Tasks
- **REQ-TASK-1.** Each organization stores its own tasks, through the task tracker
plug-in kind (REQ-PLUG-1). The default module is a store built into Mosaic
Stack. Vikunja is an optional module, and when an organization selects it
the 1.19 rules for Vikunja (bot tokens per role, one writing role per field,
REQ-TASK-2 polling) apply to that organization. Every task tracker module
supports create, read, update, archive and delete, ordering and grouping.
Archiving or removing an organization removes its tasks without touching
another organization's.
Each task cites a requirement id. Each task
field has one writing role, listed in the slice 1 brief's field table,
and the assigned role writes the task's state. Since version 1.19, the
scoped disk-service principal in REQ-DISK-12 may initialize incident
fields from a PM-approved template and update only its exclusive fields
in the extended slice 1 brief table. It gains no role-decision authority
and never writes later task state or closure. Vikunja's token scopes
cover whole route groups, so the broker's verbs enforce this, not the
tokens. A person's edits come in as events.
- **REQ-TASK-2.** Polling is the source of truth. Every 30 seconds the
sync bot reads each project's open-task board and the tasks updated
since the last check, so column moves and deletions of open tasks show
up within one poll. An hourly full reconcile catches the rest. Slice 1
uses no webhooks. (Lead decisions 51 and 52.)
- **REQ-TASK-3.** The installer offers two choices: point at an existing
Vikunja, or deploy the bundled one. The bundled one is the unmodified
upstream image. No Vikunja code enters the repository.
- **REQ-TASK-4.** The queue keeps the agents' lock, review rounds and
audit log. No field syncs in both directions.
### Projects
Added in version 1.5 (Q167).
- **REQ-PROJ-1.** Each project keeps its records in its own local git
repository, separate from its code checkout, with no remote. The
layout is fixed and versioned: `project.json`, `spec/north-star.md`,
`docs/`, `requirements/` and `decisions/` with one JSON file per
record, and `templates/` with a template for each kind. Each JSON kind
has a schema, and a record that fails it is refused. `mosaic project
init` creates the repository with its first commit as the business's
human.
- **REQ-PROJ-2.** The north-star spec is `spec/north-star.md` in the
project repository. Only the human commits to it. Agents read it and
never write it. The spec counts only when its last commit's author is
the business's human. Agents write docs, requirements and decisions as
agent-authored commits, through the layout's validators.
- **REQ-PROJ-3.** The Console has an editor for the spec and the
Markdown files in `docs/`. The user opens a file at its current commit,
edits its Markdown source beside a rendered preview, and saves it as
one local commit with the human as author. A save made from a commit
older than the file's last commit is refused and writes nothing. The
preview runs no script from the file and is built from code in this
repository. A WYSIWYG editor may replace the source view only with a
library that is vendored and pinned with a permissive license and a
recorded hash for each file, loads nothing at runtime, and needs no
change to the Console's content security policy. No library passed
that for 1.5.
- **REQ-PROJ-4.** Agents review the human's spec commits. Each human
commit to the spec saved through the Console editor (REQ-PROJ-3) sends
one notice to the PM and the CTO. A commit the human makes with git
outside the Console sends no notice yet. They check
the change against settled decisions and work in flight, and each
answers with an accept or a BLOCK that names the conflict. A BLOCK
doesn't revert the commit. It goes to the assistant through the chief
of staff (REQ-ASSIST-2), and the assistant tells the user (REQ-ASSIST-3).
### Landing
Added in version 1.5 (Q167, Mos's reading Q4).
- **REQ-LAND-1.** Agents land approved merges into the business working
repository. The run that holds an approved `git.merge.protected`
decision asks the host to land the work ref the decision names into
the working repository's `main`. The host runs the landing checks and
the project's gate:
- the decision is resolved with the approval option;
- the ref is at the reviewed commit, and a verdict approves it;
- the decision isn't superseded;
- the base hasn't moved.
A refusal is logged on the task's trail with its code, and a project
with no gate is refused. A landing is recorded as the allowed action
with the landed commit, and it consumes the decision (REQ-DEC-5). This
repository's canonical branch is never a target.
Only a role whose role file grants `git.merge.protected` as a
cross-role action can land. A merge decision raised by a role without
that grant is gated, and its approval never lands, whether the arbiter
or the human gave it. So an arbiter's judgment can't move landing to a
role REQ-ROLE-5 didn't give it. The land operation refuses a gated
decision; the check ships with whichever of rows 137 and 139 lands
second.
Added in version 1.7:
- **REQ-LAND-2.** A coder pushes its own work to its own fork. The action
is `git.push.remote`.
- What it pushes: one commit, by its oid, never a ref name. That commit
is the latest work commit the bus recorded for the coder's task, and
the coder made that push. A decision that authorizes a push names that
commit, and it doesn't authorize any other.
- Where it lands: only the branch
`refs/heads/<its Gitea user>/<branch>`. The host builds that name, the
agent never does. A push never forces.
- Which remote: the agent's own fork of the project's upstream. The
business file names the upstream, and the host never contacts it. For
each push the host builds the fork's address over https from the
upstream's host, port and repository name and the agent's own Gitea
user. The history check, the push and the credential match all use
that address. So the agent's own user owns the receiving repository on
every push, and the agent never names it.
- The host refuses an upstream on a host or port other than the agent's
Gitea, and an upstream the agent's own user owns. It refuses a fork
that is missing, empty or doesn't share the project's history. Gitea's
push-to-create stays off, so a push never creates a repository.
- Who may: the action routes under REQ-DEC-2 and is granted within-role
to the coder role only. No other role file grants it.
- Before version 1.7 the action was a hard wait under REQ-WAIT-3, so
each push needed a decision the human resolved, and that decision
named the commit.
Every push is logged with the remote, the ref, the commit and, when one
was given, the decision.
### Interface
- **REQ-CLI-1.** `mosaic` is the front door. From it Jason can:
- talk to the assistant in a project thread they choose or create;
- see the decision inbox and resolve decisions;
- see tasks and agents;
- follow the trail of any piece of work.
Since version 1.19, CLI and Console also show disk coverage, effective
settings and source layers, incidents, cleanup proposals and capacity
refusal reasons. Only authorized humans outside an agent run control
disk settings and policy revisions (REQ-DISK-3, REQ-DISK-4).
- **REQ-CLI-2.** The assistant session that `mosaic` talks to is one the stack
launches and owns, running Pi or Claude Code without a window. The
product doesn't depend on T3. (Round 2, answer 5A.)
- **REQ-WEB-1.** The WebUI shows the same data as the CLI: conversations,
the inbox, tasks, agents and trails. It has a chat area that lists the
user's projects and the named threads in each, with one conversation
view per thread where the user talks to the assistant. The user creates
and names projects and threads there without a terminal. Its Settings
view shows every agent limit, the usage counted against each, and the
model each role instance runs, and lets the user change them
(REQ-LIMIT-1 to REQ-LIMIT-3). Since version 1.19 it also exposes the
disk views and human policy controls specified in REQ-CLI-1 and
REQ-DISK-3 through REQ-DISK-5.
- **REQ-WEB-2.** The Console resolves an open decision with one button
per option, only for a human it signed in itself, recorded via=console.
(Version 1.9, decision 00ed58e2, Q175 and Q203.)
- Identity is the (iss, sub) pair the Console verified from its own
OIDC session after `--sso` sign-in (Q175), and that pair must map to
an authorized human. The Console takes no identity from a proxy
header and none from a shared secret.
- A request that carries an agent session or a seat credential is
refused.
- The decision event records via=console and the (iss, sub) pair.
- Without a signed-in session, the Console resolves nothing and shows
the `mosaic decide` line instead.
- **REQ-WEB-3.** The Console has controls to restart itself, to restart an
organization's bus, to reload the organization file, and to change the model
an agent instance runs on and apply it. Each control is a gated, trailed
action that a human takes. No agent can take it. A reload applies
`launch.instances` changes to running sessions without a manual
`systemctl restart`. On a dev instance (`--dev-local-owner`) the controls
need no sign-in.
### Workspace
Added in version 1.20.
- **REQ-WS-1.** The WebUI shows every organization the user belongs to at
all times. A collapsible Organizations item in the sidebar lists them.
There is no organization switcher.
- **REQ-WS-2.** The Console reads across the buses of the user's
organizations to build shared views. Each organization keeps its own bus
as the tenant boundary (REQ-ORG-1). A shared view shows only
organizations the user belongs to.
- **REQ-WS-3.** The main dashboard sums activity, work and metrics across
all of the user's organizations and projects.
- **REQ-WS-4.** Each organization has a dashboard with its docs, every
configurable setting, its KPIs and metrics, and its active agent
sessions.
- **REQ-WS-5.** The projects list covers every organization. It sorts and
filters by organization, status, name and activity. Each project shows
its owning organization by icon, color and avatar.
- **REQ-WS-6.** One inbox holds every item that needs the user, from every
organization, project and session.
- **REQ-WS-7.** One view shows activity across all organizations and
projects. Organizations and projects can be open side by side.
- **REQ-WS-8.** The tasks view aggregates every organization's tasks, with
Kanban and list layouts, filtered by organization and project. It works
the same for every task tracker module.
### Launching
- **REQ-LAUNCH-1.** The chief of staff launches role sessions within the
agent limits written in the organization file (REQ-LIMIT-1 to
REQ-LIMIT-3):
- role instances only;
- a launch that would exceed any agent limit is refused, and the
refusal is logged with the limit it hit;
- every launch is logged with its harness, provider, model, and the
account or key it runs on;
- revocable with one word.
Assistant thread sessions are the exception to who launches. The stack
starts one when the user opens or writes to a thread, and ends it after
the thread has been idle for the time the organization file sets. A
running thread session counts against the limits like any other. An
idle thread holds no session and counts against none. The stack starts
the chief of staff itself. (Round 2, answer 6A; Q147; Q150.)
Since version 1.19, every role launch, including assistant and chief
startup, also passes the fresh capacity preflight in REQ-DISK-2. The
Stack launcher owns host-capacity holds and releases, and retries pending
launches on verified recovery. It never releases quota or other holds.
Added in version 1.2. Jason, Q150, verbatim: "No hard cap on agent
limits. All tunable. Agent limit: A parallel execution limit arbitrarily
set by the user or COS. This limit can apply to: overall executing agent
limit across all models; provider parallel agent limit; provider parallel
agent limit by model; daily quota limit based on OAuth account limit or
CLIProxyAPI config in Mosaic Stack; absolute token limit per agent,
model, provider, or API key".
- **REQ-LIMIT-1.** Agent limits are settings with no ceiling in code. The
user and the chief of staff view and change them from the WebUI's
Settings view and from a `mosaic` command, without editing a file. The
chief of staff changes them through its organization-file broker verb
(REQ-ROLE-3). The same places view and change the model each role
instance runs. The running-session limits are:
- the total number of sessions running at once, across all models;
- the number running at once per provider;
- the number running at once per provider and model. The default is 4
Opus and 4 Sonnet, as a default that can be changed, not a cap.
Any kind may be left unset, which means no limit of that kind. A value
is a whole number of 0 or more, and 0 stops new launches of that kind.
A change is checked by the same rules as the organization file, then
written to it and logged with who made it and the old and new values.
An invalid value is refused, and the old value stays. A change applies
to the next launch without a restart, and lowering a limit doesn't end
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, 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, 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.
### System types
Added in version 1.20.
- **REQ-SYS-1.** System types are a plug-in kind. Its modules include
coding, creative writing, marketing, Mosaic Montage (the OpenMontage
fork) and business management. An organization enables a list of them.
- **REQ-SYS-2.** A project may use any system type its organization
enables, and none is required. An agent working in a project gets the
toolsets of the system types that project uses.
### Meta-harness
- **REQ-HARN-1.** The meta-harness generates each session's launch bundle
from the role and the resolved variables:
- the prompt;
- the policy;
- skills;
- typed tools for the vocabulary actions;
- a manifest.
Pi comes first, then Claude Code. Codex comes after v1.
- **REQ-HARN-2.** Four layers actually enforce the rules:
- credential scope;
- verbs that refuse without a resolved decision;
- the harness's tool limit;
- a container.
Hooks only refuse early and log, and no document may call them
enforcement. A probe run against the live harnesses must pass before any
build relies on a block. Since version 1.19, durable registration and
the launches requiring it also pass REQ-DISK-7's temp-root checks on
each supported harness. A failed live registration probe refuses them.
Arbitrary shell-write prevention and broader sandbox enforcement are
later work.
### Events
- **REQ-EVT-1.** Every step in slice 1 emits an event to the append-only
store. Measures are computed from events, never from tagging chat
messages by hand. Since version 1.19, disk samples, incidents, policy
revisions, cleanup and temp-path events follow REQ-DISK-14. They remain
append-only, with retained reports outside temp and storage failure
handling under REQ-DISK-10.
### Disk-space governance
Proposed in version 1.19.
- **REQ-DISK-1.** Coverage.
- Register managed runtime temp, homes, workspaces, evidence, application state, logs, and backup paths. Show paths outside the instance's coverage explicitly.
- Resolve aliases to filesystem identities and group them into one capacity incident with every affected path. Missing paths, changed mounts, and denied reads are unknown, never healthy. Remote-host monitoring is outside v1.
- Reconcile coverage at installation, onboarding, and path changes. Warn when runtime temp shares a filesystem with retained or bulk paths.
- **REQ-DISK-2.** Watchers and launch preflight.
- A deterministic boot service measures available bytes, used percentage, and inode use at the configured interval without an agent turn. One path failure cannot stop other checks.
- Compute used percentage as `ceil(100*used/(used+avail))`, matching `df -P`. Zero byte denominators or failed measurements produce unknown status with a reason. Filesystems without inode accounting report inode checks as not applicable.
- Store host, paths, filesystem identity, UTC sample time, measurements, effective thresholds, and result.
- Before every role launch, including assistant and chief-of-staff startup, freshly check the filesystems holding the resolved harness `TMPDIR`, home, and workspace. Deduplicate shared filesystems. Apply a nonzero default byte floor to every registered runtime temp filesystem.
- Refuse launch below the effective byte floor or, where inode accounting exists, the free-inode floor. Byte-use and inode-use percentages trigger alerts only. Unknown required capacity refuses launch with a distinct unverified-capacity reason.
- Log `host.capacity` as the refusal reason with the filesystem, measurement, and failed check, like a REQ-LAUNCH-1 limit refusal.
- The Stack launcher owns the host-capacity hold and release, even when chief-of-staff startup is refused. Refused threads show the reason in CLI and Console. Watcher and liveness services run independently of role launches.
- On verified recovery, the launcher releases only the capacity hold and retries pending launches, including chief-of-staff startup. All other launch limits still apply. A later write can still fail, so retain REQ-DISK-11 classification.
- **REQ-DISK-3.** Settings.
- Declare typed keys and allowed layers under REQ-VAR-1. Show effective values and source layers in Console Settings and CLI.
- Protection settings are human-only under Q222, REQ-DEC-3, and the REQ-TRUST-2 precedent. Chief-of-staff organization writes and environment overrides cannot change them.
- Refuse unknown keys, invalid values, unauthorized writers, and unauthorized layers. Keep the last valid value or declared first-setup default. Record the refusal and notify the owner.
- **REQ-DISK-4.** Cleanup policies.
- Start with no enabled policy. Store rule settings only at the system layer in the existing `config.json` under REQ-VAR-2.
- REQ-VAR-2 permits writes only from an authorized human Console or CLI settings action outside an agent run. No automatic writer or agent may write these settings.
- Store revision approvals and immutable inventories in the decision and event stores, keyed by revision ID. These records are not a second system configuration.
- Each revision names host, canonical root, file classes, age calculation, minimum age, schedule, exclusions, human owner, protected executor, and bounded scope. Attach a dry-run inventory.
- An authorized human approves, changes, enables, or revokes the revision outside an agent run. The human's confirmed change is fresh approval of the exact new revision.
- Agents cannot approve, change, enable, or revoke any rule. Revocation stops the next deletion, including remaining items in a running batch.
- Ownership, cache names, disk pressure, and audit advice grant no authority. Q286 is not a global default approval or automatic import.
- **REQ-DISK-5.** Cleanup execution.
- The protected executor uses `data.delete.policy` only for candidates covered by a live approved revision. Bind every run to that revision and record it.
- Before each deletion, recheck revision, canonical path and mount identity, ownership, exclusions, active use, and candidate identity. Refuse symlink escapes and changed or uncertain candidates. Serialize execution per scope.
- Preserve protected evidence, source, user data, credentials, and active work. Record skipped items and reasons.
- Record proposed allocated bytes and available bytes before and after separately. Do not promise that allocated bytes will become free space. Partial or failed runs create owned follow-up work and never report success.
- Outside an enabled rule, the PM raises and consumes one exact-batch `data.delete` decision under REQ-DEC-5. The service cannot raise or consume that role-bound approval.
- For that batch, the protected executor accepts only a one-shot dispatch bound to the decision ID, the PM's consumed-action record, and the approved candidate hash. It refuses missing or mismatched bindings and replay.
- If a human puts `data.delete.policy` in manual mode under REQ-TRUST-2, the PM raises and consumes a fresh per-use decision before dispatch. The service gets no exemption.
- **REQ-DISK-6.** Ownership and privilege.
- Apply approval checks to agent-owned and root-owned data. Skip inaccessible files and report their measured size or unknown size. Never try another user's credentials, chmod, chown, or implicit sudo.
- On the instance's own host, the service user stays without sudo. Root-owned candidates always become a human task.
- Later remote privileged cleanup under REQ-CRED-4 uses a reviewed fixed helper. Its only inputs are a policy ID and candidate-list hash. It loads the approved revision and immutable inventory itself, verifies both, and refuses extra arguments or paths.
- Never grant raw `rm` or a shell prefix. An argv prefix can admit appended paths. A helper grant supplies access, never deletion authority.
- **REQ-DISK-7.** Temp boundaries.
- Declare temporary and retained roots. At durable registration, the broker refuses an artifact, repository, cited evidence path, collector registration, reusable launched-script path, or retained log destination under temp, including symlink-resolved paths.
- Add proposed broker verbs `artifact.register`, `repository.register`, `evidence.register`, `collector.register`, `script.register`, and `log.register`. Each requires an artifact class and canonical destination. Launch manifests carry the same fields for reusable scripts and retained logs.
- Refuse these registrations and launches that require them on every supported harness if the live registration probe fails. Hooks and CI only refuse early under REQ-HARN-2.
- Filesystem mounts cannot distinguish durable work from scratch. Watchers or audits detect existing git repositories and executables under temp and open migration tasks. Arbitrary shell writes are not claimed as prevented in v1.
- Migration preserves hashes and cited references before cleanup becomes eligible. Ephemeral scratch remains allowed. Sandbox enforcement beyond registration is later work.
- **REQ-DISK-8.** Waste audits.
- Run scheduled and pressure-triggered read-only audits within declared path grants. Return paths, owner, allocated size, UTC measurement time, active-use checks, retention risk, and a proven reinstall or recovery procedure.
- Cover caches, unused toolchains, and dependencies. For every proposed candidate, prove its recovery procedure for that class. Missing proof means keep.
- For caches, require a declared regeneration command and verified retained inputs. For toolchains, require the exact version, an available installation source, and a declared reinstall command. Active toolchains remain protected.
- For `node_modules`, require an adjacent manifest and lockfile, matching package manager, and checks for local changes or linked dependencies. Record the exact locked-install command and available package sources. Local or linked dependencies need separate recovery proof.
- Mark permission failures, timeouts, stale remote refs, hard links, and sparse files as estimate limits. Do not sum overlapping scans. Flag files under agent temp roots owned by another user.
- Do not read credential contents, install, prune, fetch, delete, or approve the proposal. Old names and apparent duplicates prove no waste.
- **REQ-DISK-9.** Alerts and recovery.
- Ensure one open Vikunja task per instance, host, filesystem, and condition. Use monitor identity for monitor failures. Cite the requirement, project, PM, paths, measurements, and needed action.
- A breach on a filesystem holding runtime temp, or below its effective byte floor, goes immediately to CLI inbox and the existing Discord connector. Other breaches go to the daily digest. Routing is a human setting. Blocking hard waits retain REQ-DEC-4 immediate delivery.
- Reconcile before creation and serialize incident keys. Retries after restart or lost responses converge on one task. Repeat observations update service fields. Comments follow the time and percentage-change settings. Condition changes and recovery bypass the throttle.
- Recovery requires consecutive valid samples strictly below each percentage threshold minus its margin, and at least each byte floor plus its byte margin. Where inode accounting exists, free inodes must meet the launch floor. Inode checks marked not applicable do not block recovery. Unknown samples reset the count.
- Emit one recovery event and comment. The Stack launcher releases the host-capacity launch hold on recovery but never a quota hold. The assigned role verifies task closure. Rebreach reuses an open task or creates a new incident after closure.
- **REQ-DISK-10.** Liveness and storage failure.
- A separate local supervisor checks completed watcher samples and delivery acknowledgements.
- The installer configures an independent Stack watchdog timer that checks the supervisor's last successful check against the liveness deadline. It alerts through the tested fallback when the supervisor stalls or stops. No agent turn or external heartbeat is required.
- Cross-host supervision is later work, so v1 cannot claim detection during complete host loss.
- Missed sample or supervisor deadlines, corrupt state, stalled runs, credential failure, task delivery failure, and event-write failure are monitor failures.
- When a store fails, retain pending notices on a separate writable destination and use a tested existing channel. Never depend on writes to the full filesystem. Replay with stable IDs after recovery.
- Apply that rule to stored events and audit reports too. If no writable destination exists, report the storage failure through the fallback and mark the report incomplete. Never claim it was stored.
- Installation cannot claim monitored coverage without a tested local supervisor, independent watchdog timer, and fallback.
- **REQ-DISK-11.** Failure classification.
- Distinguish host capacity, permission, authentication, measured quota hold, and unknown provider failure. `ENOSPC` cites host and filesystem. It never becomes REQ-LIMIT-2 quota exhaustion or a quota-reset wait.
- Generic provider errors stay unknown until evidence distinguishes them. Check host health and authentication before the chief of staff resumes work within existing limits.
- At the configurable unexplained-failure count, stop automatic retries for that operation and create investigation work. Disk recovery does not clear authentication or quota failures.
#### Disk roles, tasks, and events
- **REQ-DISK-12.** Roles and protected services.
- The chief of staff launches audits within REQ-LAUNCH-1. The PM owns triage, follow-up, and per-batch decisions. The assistant explains notices under REQ-ASSIST-1.
- Watcher, supervisor, watchdog, and cleanup executor are deterministic processes, not role instances. Add one protected disk-service principal per Stack instance with its own broker-held Vikunja bot.
- A human creates the Vikunja bot and token by hand from a runbook under REQ-CRED-1. No automatic account creation is added. REQ-TASK-1 admits this scoped service principal without granting role-decision authority.
- Policy runs bind to the approved policy revision and service principal, not a consumed REQ-DEC-5 approval. Ordinary decisions keep their role, thread, and single-use binding.
- The audit worker gets only typed read-only tools: allocated-size scans, filesystem `stat`, `/proc` active-use checks, `git --no-optional-locks status --porcelain`, and manifest or lockfile checks. Each tool enforces path grants and bounds. It gets no shell, write, deletion, privilege, or credential-content access.
Extend the slice 1 brief's field table with these exclusive writers. Incident creation initializes fields from a PM-approved template. Later edits follow the table. Keep REQ-TASK-1's assigned-role state writer.
| Task fields or operation | Writer |
| --- | --- |
| Later incident key, measurements, last observation, recovery status, and service comments | Instance disk-service principal through scoped broker verbs |
| Initial incident fields, including identity, measurements, project, assignee, and initial state | Disk-service create verb using a PM-approved template |
| Later title, description, project, assignee, and priority edits | PM through existing task verbs |
| Later task state and closure | Assigned role through existing task verbs under REQ-TASK-1 |
| Audit result record and cited report | Audit worker through report-only broker verb |
- **REQ-DISK-13.** Tasks.
- Every incident, audit proposal, migration, inaccessible cleanup, or monitor repair is owned Vikunja work citing its requirement. Reuse matching tasks and existing broker verbs.
- Human decisions link their task in the existing inbox. A daily digest never replaces a blocking hard-wait notification.
- **REQ-DISK-14.** Events.
- Extend REQ-EVT-1 with typed samples, breaches, launch refusals, recoveries, monitor failures, audit results, policy approvals, changes, revocations, cleanup attempts and results, and temp-path refusals.
- Record host, paths, filesystem, actor, task, decision or policy revision, UTC time, and outcome as applicable. Never include credentials or file contents.
- Retain events and approved reports outside temp under REQ-DISK-10 storage handling. Deleting them needs its own approved rule. Event records remain append-only under REQ-EVT-1.
### Organizations
Added in version 1.1 (decision 03130d08).
- **REQ-ORG-1.** "Business" is renamed "organization". One install holds
one or more organizations, and each organization has its own bus as the
tenant boundary.
- **REQ-ORG-2.** The data model is install, then organization, then
projects, then threads, so v1 doesn't lock in one human per
organization.
- **REQ-USER-1.** Users and role-based access control exist at install and
organization level. An agent other than the assistant works in one
organization at a time. Its memory, files and credentials are scoped to that
organization, even when its definition comes from the user's root
configuration. It reaches only that organization's allowed projects and
threads. The assistant is the exception in REQ-ASSIST-1, and it reaches only
organizations its user belongs to.
- **REQ-USER-2.** Only the user who owns a thread reads it or writes to
it, plus that user's assistant and the chief of staff's replies on work
the thread sent. This fills in REQ-USER-1's "threads" for the chat
area. Added in version 1.2.
- **REQ-THREAD-1.** Threads live under projects.
- **REQ-THREAD-2.** The user creates projects and names threads inside
them, from the CLI or the WebUI. A thread holds one or more independent
sessions of its assistant, on pi or Claude Code. Each session has its
own messages, native context, workspace and compaction count, and only
one run writes its workspace at a time. A thread's messages are kept.
When the user comes back to a session, after an idle stop or a restart,
the assistant picks up where that session left off, never in another
session. A client that names only the thread uses that thread's saved
default session. One session's conversation never appears in another
session, thread or organization, under REQ-USER-2, REQ-USER-3 and
REQ-USER-4. Added in version 1.2. (Version 1.13: sessions, from the
design `docs/plans/2026-10-21_t1-sessions.md`, 2c843d190 on `huey/t1-sessions-design`, approved by Scrooge in scrooge-0007 and scrooge-0008, D1 to D3 and D9.)
- **REQ-TPL-1.** Templates exist for agent roles and for organization
types.
- **REQ-FED-1.** Installs can federate with each other. Person-to-person
federation is v1: a person gets access to another instance by signing in
to its Console (REQ-WEB-2), the broker enforces the grants that give
that access, and the access can be revoked. Agent-to-agent federation is
post-v1. (Version 1.11; Q179, Q180, Q211. The 1.10 text ended "This is
post-v1.")
Added in version 1.12 (Q176, Q211). Each one names the approved design it
is drafted from and the Q176 item it covers.
- **REQ-USER-3.** An organization can have more than one human. The
organization file's human is the owner. The owner adds other humans as
users and gives each a grant per project and instance, at one of two
levels. A collaborator creates threads and sends in them. A viewer can't
create, open or send in a thread. It only reads threads shared with it
and its own threads from an earlier collaborator grant, and only while
its grant still covers them. Only the owner adds or removes users,
changes grants and restructures the organization. The bus takes a
human's identity only from something it verifies, such as the Console's
own OIDC sign-in, never from a process's own claim. (Row 193 design r5,
e2d161e2e on `morgana/row193`, approved by Duckworth in duckworth-0018,
sections 3 and 4. Q176 Given "RBAC enforcement"; Case 1 "Additional
users can be configured for access".)
- **REQ-USER-4.** A thread is private to the user who owns it unless that
user shares it. A share is read-only, to a named user, to the
organization's owner, or to everyone granted on the thread's project.
REQ-USER-2 still holds for writes. A share never widens a grant: it
counts only while the reader's grant covers the thread, unless the
reader is the owner. If the thread belongs to a granted user rather
than the organization's owner, that user's grant must still cover the
thread too. A revoke or an expiry of either grant, or an unshare, ends
the reader's access on the next request. A user's messages stay private from the owner. What the thread's agent
does with the owner's authority stays visible to the owner: its
decisions, inbox entries, claims, tasks and project files. (Row 193
design r5, sections 5.3, 5.6 and 5.7. Q176 Case 1 "Shared and private
agent sessions available per user; RBAC".)
- **REQ-LIFE-1.** The user creates, launches, terminates, regenerates and
deletes agents whenever they choose, from the CLI and from the Console.
Regenerate starts a fresh session with the same identity and role.
Each verb is a human verb behind the human proof, and it acts on its
own instance only. Every launch goes through the one launch path, so the
agent limits and the kill switch (REQ-KILL-1) apply. Delete leaves run
records write-once. Fire and delete revoke the agent's credentials
first, and a failed revoke stops the verb and leaves the organization
file unchanged. Create, delete, hire, fire and veto changes take effect
without a bus restart. Until REQ-USER-3 is built, these verbs are
owner-only. (Row 194 design r4, 7d0788236 on `honker/row194`, approved
by Webby in e1e64ff01, sections D1, D3 and D4, and the brief's items 7
and 8. Q176 Given "User and main agent (chief of staff, or other main)
can create and spawn agents" and "User maintains ability to terminate,
regenerate, or create agents via WebUI or CLI ad-hoc".)
- **REQ-KILL-1.** One CLI command and one Console button stop all
processing on an instance at once, and it stays stopped until a human
clears it:
- agent verbs that start or continue work refuse with a fixed code.
Reads and the human's own verbs, the clear included, still work;
- every running managed session and task run gets SIGTERM, then SIGKILL
after a bounded wait, and each stop is an event;
- Discord stops and can't restart;
- every launch path refuses, the chief of staff's hires included.
The switch survives a bus restart and a reboot, and it stops one
instance only. Only a human turns it on or clears it, through the human
proof, and each act is one event with who and when. (Row 191 design r2,
approved by Dewey in comment 29473, on the brief
`docs/plans/2026-10-20_kill-switch.md`. Q176 Given "'Kill switch' is
available to stop all processing immediately".) Q198's protected mode
is a separate state. The system manager never sets or clears the
switch, and protected mode can clear itself but never a human kill
(`docs/plans/2026-10-21_q198-system-manager.md` D13 and section 5).
Added in version 1.13 (Q176, Q211, mos-1231). Drafted from the
design `docs/plans/2026-10-21_t1-sessions.md`, 2c843d190 on `huey/t1-sessions-design`, approved by Scrooge in scrooge-0007 and scrooge-0008. They cover MVP plan T1 item 7.
- **REQ-SESS-1.** The user sees how many compactions each session has
completed over its life, on pi and Claude Code. Starts, failed
compactions, branch summaries and repeated reads don't count. An idle
stop or a restart leaves the count as it is. A new session and a clear
successor start at zero, and a retired session keeps its count. When
the count can't be proven, it shows unknown with the reason, never
zero. Anyone who can read the thread sees the count. The organization's
owner can see a count and status, with opaque ids only, for another
user's thread it can't read, but no names, messages or summaries.
(Design D4 and D6, tests T3 and T5. Q176 TARGET.md:104.)
- **REQ-SESS-2.** Clear context ends one session and starts a fresh
successor for the same assistant. Two humans can create or clear a
session: the organization's owner on its own thread, subject to the
instance's policy, and a collaborator who owns the thread, while a
write grant still covers it. The organization's owner on another
user's thread, a viewer, a share recipient and an agent can't. Clear waits until the session stops at a turn boundary
with no child running, and shows that it is clearing until then. It
never force-stops a busy session. The old session's messages, results
and run evidence stay as they are. Its workspace stays at its path with
any unpushed work, and nothing writes there again. The successor gets
new native stores and a fresh checkout of the approved revision. It
gets nothing of the old context: no history, summary, prompt or pending
input. Undelivered old input is cancelled with a receipt, never
replayed. A repeated clear returns the same operation, and one clear
makes at most one successor. (Design D4, D5 and D7, tests T4 and T5.
Q176 TARGET.md:105.)
- **REQ-SESS-3.** Every start of a session goes through the same checks
as any launch: an open, a first send, a restart, an idle wake and a
clear successor. The checks are the human's current authority, the
configured role and harness, launch revocation, the kill switch
(REQ-KILL-1), the agent limits and admission. The claim is checked
again just before the spawn. A session never picks its own route or
priority. An admission service that is switched on but unavailable
refuses. While the kill switch is on, a clear or a new start refuses
with `processing-halted` and changes nothing. A clear never sets or
clears the kill switch. (Design D8, test T6.)
### Accounts
Added in version 1.20.
- **REQ-ACCT-1.** A deployed system requires user sign-in (OIDC, Q203). A
user creates organizations and belongs to any number of them.
- **REQ-ACCT-2.** An organization's owner invites another user into it.
The invited user joins with the grant the owner chose (REQ-USER-3).
### Creative library
Added in version 1.12 (Q176 Case 2, Q211). Drafted from the Case 2
design r11, fb20a61b4 on `gosalyn/case2-design`, approved by Webby in
82f50d127, with the rulings recorded after r11 up to 624212842. The
library is the user's own writing repository plus the stack's writing
store, which holds the DOCX story files (REQ-LIB-8, version 1.17).
- **REQ-LIB-1.** A library project maps the user's writing repository
onto universes, books, entity types such as character and place, the
manuscript, playgrounds and supporting folders. The mapping sits in the
organization file and fits the user's own folder layout. Only the
human changes it, and an agent's organization write that changes it
is refused. So
`universe.book.{character,place,...}` reads as universe, book folder,
type folder. The mapping also names one archive folder, set by the
human, where converted Markdown story files go. A book's DOCX story
files are in the stack's writing store, under the same universe and book
(REQ-LIB-8). (Design 1.1 and M9. Q176 Case 2 "Data is stored in
little-endian organizational scheme". Version 1.15: supporting folders.
Version 1.17: the archive folder and the writing store.)
- **REQ-LIB-2.** Playgrounds are never confused with canon. A playground
has one of three levels, set by where its prefix sits. A book-level
prefix lies inside one of its universe's book folders, and its files
have that universe and that book. A universe-level prefix lies outside
every book folder, and its files have the universe and no book. A
draft universe is where a new universe starts: a universe entry with a
name, one or more playground prefixes and no books. Every file under it
is playground, with no book. When the draft is real, the human edits
the organization file to make it a plain universe with books. The
mapping is refused when a prefix lies inside or holds another
universe's book folder, when a prefix equals or holds a book folder of
its own universe, when two prefixes overlap, when a draft has books or
no prefix, and when a hybrid names a draft as a source. Supporting
prefixes, the folders agents may write in (REQ-LIB-6), follow the same
placement rules. A Markdown file's scope comes from its path and from
past imports, prefixes first: a Markdown file under the archive folder
is archived, one under a supporting prefix is supporting, a Markdown
file at a converted file's old path is unmapped under REQ-LIB-8 but
flagged in the format map, one under a playground prefix at any level is
playground, any other Markdown file in a book folder is canon, and
everything else is unmapped and never returned. A folder in both lists
is supporting. The archive folder follows the same placement rules and
may not overlap another prefix or a book folder. An archived file keeps
the path it had under the archive folder, and its universe and book are
those of that old path. A DOCX document in the writing store keeps its
source's scope: the universe, the book if there is one, and canon or
playground. Its source is the Markdown file it was converted from or,
for one the human starts in the editor, the universe and book or the
playground the human picks (REQ-LIB-8). A playground DOCX follows the
playground rules and is never returned as canon. It stays playground
after its draft universe is made real. In version 1.17 no promotion or
mapping change makes a playground DOCX canon. A later version of this
PRD says how one becomes canon. Lookups return canon unless the caller
asks for playground, supporting or archived. Every answer marks an
archived file as archived and names the DOCX that replaced it. A
citation marked as canon must name a canon file, so an agent can't cite
a playground, supporting or archived file as canon. The Console shows
each cite's scope and format, and whether a cited Markdown file was
converted. (Design 1.2, 5.2 and 7.4, the playground ruling, 51fdfb7e2,
and the ruling after r11. Q176 Case 2, the playground items,
TARGET.md:92-94. Version 1.14: the levels and the draft. Version 1.15:
supporting. Version 1.17: archived scope and DOCX scope.)
- **REQ-LIB-3.** Universes stay separate. Every lookup names one universe,
and its results never leave it. A draft universe (REQ-LIB-2) counts as
a universe here. It has no canon, so a lookup in a draft asks for
playground, and a citation from another universe into a draft is
cross-universe. A universe-level or draft file has no book: a lookup
that names a book leaves it out, and the manuscript lookup doesn't read
it. (Version 1.14.) A crossover is a hybrid universe that
only a human creates, by editing the organization file. It names two or
more source universes. Its lookups see its own files plus the sources'
canon, minus the exclusions the human sets. Code checks two kinds of
constraint: an exclusion keeps a source path out of the hybrid's
lookups, and a change to a pinned value in a hybrid file is reported.
A pin doesn't stop the change. The continuity agent reads the human's other
constraints and cites them. (Design 1.4 and 3. Q176 Case 2, the
crossover items.)
- **REQ-LIB-4.** Git holds every change. The user commits and pushes
Markdown with their own tools, today Obsidian with obsidian-git. A
converted story file's current text is no longer in that repository,
which keeps only its archived Markdown (REQ-LIB-8). The bus keeps a
mirror of the remote, fetched with a read-only token every 60 seconds
and before any lookup when the last fetch is older than that. It only
fast-forwards. A force-push is held as `library-diverged` until a human
accepts it. Agents read the user's repository only at the mirror's
served commit, except a story file during its import under REQ-LIB-8.
They read a DOCX story file only at a committed version in the writing
store, under REQ-LIB-5. They never hold a copy of their own, so they see
the user's Markdown push within about a minute and a DOCX save at their
next lookup after the editor reports it as committed. Bus writes and
pushes come in later rows (C2-02, C2-03b). A DOCX story file is saved in
the Console's editor, and each accepted save is one commit by the human
in the stack's writing store (REQ-LIB-9). Each writing-store commit is
also pushed to a remote off the stack's host, never by force-push. The
remote is a setting only a human changes, and it starts unset. A
repository on the stack's host never counts as that remote. A missing
remote or a failed push never undoes a save, and the user sees each push
that is pending or failed. Only the stack's host pushes to that remote,
and no agent can reach it with any credential. Agents read the local
commit. (Design D5, D6, 7.1, 7.2 and 7.5. Q176 Case 2 "Git is used for
all changes -- local and remote" and the sync items. Version 1.17, with
Q222 for the remote setting.)
- **REQ-LIB-5.** Agents check the library through typed lookup tools:
find, get, search, manuscript and universe. Each answer names the commit
it read. Each answer also names the file's format and where it lives:
its path, or its document id in the writing store. A DOCX story file is
read from its latest committed save, never from unsaved text. A lookup
that starts after the editor reports a save as committed returns that
save or a later one, even while its push to the remote is pending or has
failed, and the answer names the version. Get, search and manuscript
read a DOCX story file as its extracted text with DOCX locators, never
as lines. That text is the file with its tracked changes accepted. An
agent reads a story file's comments, or its text with tracked deletions
shown, only after a human turns on the matching setting. Both settings
are off by default, and only a human changes them. The reader in this
version can't read some things a DOCX story file can hold, such as a web
link, a text box, a field or hidden text. The editor warns the human
when an edit adds one, and the human can still save it. Until a later
save takes it out, lookups give agents none of the text of the part that
holds it, whether that is the chapter's body, a note or a comment. The
manuscript lookup follows the book's chapter order across both formats
(O2 design D10), and the next chapter it names is never an archived
file. Links out and in resolve across both formats. The universe lookup
returns the format map (REQ-LIB-8). Get returns a file's links out and
links in, so the connections are visible to agents. The user sees a
Markdown file's links in Obsidian's graph and backlinks. A DOCX story
file's wikilinks stay clickable in the Console's editor (O2 design D11),
and no graph shows them yet. The Console's Connections view lists the
current links and backlinks of each story file the user may see, in both
formats, with each one's scope and format. (Design 3 and 7.1. Q176 Case
2 "The agents have tools to check character, place, and other data", the
visualization item and "Connections are always visible". Version 1.17,
with Q222 for the two settings and Q239 for the Connections view.)
- **REQ-LIB-6.** The creative roles are ideas, research, proofreader,
fact-checker and continuity, plus a companion role that the user's own
named agents run as. Agents never write the story. A creative agent may
create, edit and delete Markdown files only under the supporting
folders a human marks (REQ-LIB-1), such as character sheets, summaries,
cross-link notes and research notes. Every other path in the user's
repository is read-only to it, chapters and unmarked folders included,
and the broker refuses such a write before any git call. The writing
store and the archive folder are read-only to a creative agent as well,
and the broker's path check treats the archive folder as story. No agent
creates, edits, deletes, converts, archives or restores a story file in
any format, and no agent writes a DOCX. No creative agent starts or
undoes a conversion, and none asks for one through a tool or a decision
it raises, as REQ-LIB-8 says. An agent may still suggest a conversion to
the human in chat. Each creative role's contract states which files are
DOCX and which are Markdown, and cites a DOCX story file by its locator,
never by line. Each agent write is one commit by that agent's own Gitea
account, with a trailer naming the agent. The user sees a notice for it
and can revert it. Its tools are the lookups, a message to the human and
the two write tools, and research and companion also fetch web pages
through the broker. The write tools need the action `library.write`,
which routes under this requirement within the bounds REQ-WAIT-3 names
(version 1.16). Agents talk with the user in free text. A reply may cite
lines of a Markdown file at a commit, a place in a committed DOCX story
file, or pages the agent fetched, and the broker checks every citation
before it stores the message. A DOCX citation never calls a paragraph a
line. It names the document id, the committed version and a DOCX
locator. A citation that names an archived file as current is refused.
The `requireCitation` setting below applies to Markdown and DOCX
citations alike, and so does the rule for a citation marked as canon. A
setting, `requireCitation`, makes every reply carry a citation, except
the runner's fixed notices for no answer, a failed turn, no answer
passing the citation check and an answer too long to send. Only the
human changes it, and it is off by default. The user sees only stored
messages, on every channel. (Design 4, 5.2 and the ruling after r11.
Q223 and Q226. Q180 for Bean and Chet. Q176 Case 2, the agent list,
"User sets strict guardrails for agent capabilities" and TARGET.md:101
"Agents NEVER write prose for the user". Version 1.15. Version 1.17.)
- **REQ-LIB-7.** The user talks to several creative agents in one chat. A
room holds one thread per creative agent in a project. A message goes
to every agent in the room, or only to the agents named by @tags at its
start. An unknown tag is refused, and nothing is sent. The room shows
the threads as one timeline, and each agent sees only its own thread.
(Design D4 and 6, row C2-09b. Q176 Case 2 "converse with N agents in
one chat session with @tagged messages".)
- **REQ-LIB-8.** Story files become DOCX. DOCX is the standard for chapters and
the other story text in a Case 2 writing project, canon and playground alike. A
story file becomes DOCX through a human's import (below), or when the human
starts it in the editor. From then on DOCX is its only working format, edited in
the Console's editor (REQ-LIB-9). A Markdown story file stays Markdown until its
import. Files in a type folder (REQ-LIB-1), such as character and place notes,
stay Markdown. So do supporting files, agent files, instructions, PRDs and
documentation, including the character sheets, summaries and cross-links agents
keep under REQ-LIB-6.
- A converted story file's current text leaves the user's Gitea-mirrored
repository. Its DOCX lives in the stack's writing store, on the stack's host,
and not in the repository the user pushes or opens in Obsidian. Only its
archived Markdown stays there.
- An import converts one Markdown story file, canon or playground, outside every
type folder (REQ-LIB-1), to a DOCX document in the writing store. It refuses
an unmapped, supporting, archived or type-folder file, and a file that is
already converted. Each import has its own fidelity report, the human's
confirmation of that report, a manifest naming the source file and its commit,
and one commit in the store. The DOCX keeps every wiki link of the source as
written, and a lost link to another library file fails the fidelity report. A
web link becomes plain text that keeps its label and address, and the fidelity
report lists it for the human to confirm. A web link that can't be kept safely
as plain text refuses the import.
- The human can also start a new story file in the editor, in a universe and
book or a playground the human picks. It is a DOCX document in the writing
store from its first commit, with no Markdown copy, and it opens under the
same gate as the import.
- The same import marks the Markdown file converted, naming the document, and
moves it to the archive folder under its old path (REQ-LIB-1, REQ-LIB-2).
The mark and the move are one commit authored by the human in the user's
repository. A new human-only bus step (Vikunja 571) on the C2-02 write
plumbing writes it, and C2-03b pushes it. The move keeps the bytes and the
git history, so it isn't `data.delete`.
- The store commit comes first, and the writing store's remote must confirm it
holds that commit. Then come the archive commit and its push, then publication
to the lookups. Until publication the Markdown at its old path keeps its canon
or playground scope. Agents read that file at the commit the import started
from, even after the mirror's served commit reaches the archive commit or a
later one. No lookup returns the file's archived copy before publication, even
one that asks for archived files. A refusal or failure before the archive push
leaves the Markdown unmoved and no DOCX that a lookup returns. A failure after
the archive push leaves a pending import that the human sees and can resume,
never a claim that nothing moved. An archive push whose result is unknown
counts as a failure after the archive push until the stack reads the result
back from the user's remote.
- Each stage of an import writes a logged event. The import's record on the
stack's host names the source file's path, commit and blob and the new
document id, and each event points to that record.
- After a conversion, a Markdown file at the converted file's old path is
neither canon nor playground. Lookups leave it out, the format map flags it,
and the human is told.
- A new Markdown story file in a book folder that holds converted files stays
canon until its import, unless it sits at a converted file's old path or under
a playground prefix. The Console flags it and offers the import. Nothing
converts it on its own.
- A hybrid's exclusion that covers a converted file's old path also covers its
DOCX document (REQ-LIB-3). An exclusion of a whole book folder also covers
every DOCX document in that book, including one started in the editor. An
exclusion can also name a DOCX document by its id.
- Only a human starts an import or a new story file, from the CLI or a Console
session signed in as that human. Both are human verbs outside the agent action
vocabulary. Neither is added to `ACTIONS` in the business vocabulary or to the
broker's always-gated list, including the import's action, `library.convert`.
No role holds them, no agent can request them, and the hard-wait list in
REQ-WAIT-1 doesn't change. In an organization with several humans, who may
start them is a setting only a human changes, and the default is the owner
only.
- Conversion is one way, with no parallel run. Neither the Console nor any agent
edits a converted story file as Markdown, and no DOCX edit syncs back to
Markdown. The Console's Markdown save refuses every file under the archive
folder. The user's own tools can still change the archived Markdown, and no
such change reaches the DOCX. The old Markdown stays in git history, and
nothing rewrites history. A promotion into a book folder (C2-19) runs the
import for a Markdown story file or is refused. A promotion of a playground
DOCX is refused in version 1.17.
- Agents get a format map: for each mapped folder and story file, its format,
scope and where it lives, archived files, flagged files and pending imports
included.
- The import and new story files ship closed. They open only after four things.
The editor ships. The human has set the writing store's remote, and the stack
has checked that it can push there. Agents can read committed DOCX story files
through the lookups and cite them (REQ-LIB-5, REQ-LIB-6). Any earlier agent
system that writes the user's files has stopped, which for Case 2 is the
Hermes retirement (#423 step 5). So the import itself never cuts agents off
from a chapter, the chapter's current text never lives only on the stack's
host, and no second system writes the story. The first import of a real user's
books waits for Jason's go.
(Q235, Q236, Q237: P1 to P6. Q222 for the setting. O2 design D10 to D12.
Version 1.17.)
- **REQ-LIB-9.** The Console's DOCX editor. The human edits a DOCX story file in
the Console's editor, in a split pane with the agent chat alongside it. The
human can pin an agent chat to a document. The human can select a word,
right-click it, and choose a synonym from a thesaurus. Only a human saves. Saves
happen on their own while the human types (Q226), at intervals only a human
changes, and each accepted save is one commit with the human as author, pushed
as REQ-LIB-4 says. A save made against an older version than the current one is
refused. Agents never write a DOCX. Versioning is the editor's tracked changes
plus one git commit per save. The editor's code is vendored and pinned, with a
hash per file and the licenses that its independent security review accepted. At
runtime the editor loads only files that the stack itself serves, and it
requests nothing from any other host. The editor's frame sends exactly the
content security policy Jason approved in Q234, and the WebUI shell's own header
is unchanged. REQ-PROJ-3's project docs editor is unchanged. (Q195, Q226, Q234,
Q236. O2 design D3, D4, D6, D8, D10, D13, D14, D16 and D22. Version 1.17.)
### Installer
Added in version 1.4 (decision 1ea053cc).
- **REQ-INST-1.** The installer offers optional components. Each one is
pinned, health-checked and removable, and is switched on in the
organization file or system config. The first is SearXNG: JSON output
enabled, limiter off, bound to loopback only.
- **REQ-INST-2.** Jason, Q203: "If a user does not have an OIDC provider
to configure, we will need to use a bundled Pocket ID instances to
handle this traffic." This is post-v1. (Version 1.10, Q203.) Mos's
reading (mos-1202), following lead decision 44 items 5 and 6:
- The installer offers two choices: point the Console at one or more
existing OIDC providers, or deploy a bundled Pocket ID.
- The stack installs that Pocket ID as an install component under
REQ-INST-1 and prints the Console's one providers entry for the user
to add to config.json. The stack never writes config.json (canon
invariant 2). No Pocket ID source goes in the repository, and the
Console gets no
sign-in path apart from its own OIDC sign-in (REQ-WEB-2).
- Pocket ID is passkey-only for people. Agents keep per-role service
tokens.
Added in version 1.11 (Q187, Q189, Q211):
- **REQ-INST-3.** Install and upgrade are one command each.
- Install sets up a fresh host as a named instance with its config. The
per-case setup is installer config, a profile per case, not one-off
scripts.
- Upgrade backs up, moves to the new release, and rolls back if its
checks fail.
- Both are proven on a throwaway VM on the PVE node, install, upgrade to
the next release, then rollback, before either v1 VM gets the stack.
- Both v1 profiles enable the Console unit, so the Console starts at
install and at every boot (Q189). dragon-lin is unchanged.
- The container-based install comes after v1.
- Since version 1.19, register the instance's managed roots and warn
when runtime temp shares retained or bulk storage. Test the local
supervisor, independent watchdog timer and fallback before claiming
monitored coverage. Install and upgrade enable no deletion policies
(REQ-DISK-1, REQ-DISK-4, REQ-DISK-10).
### Setup
Added in version 1.20.
- **REQ-SETUP-1.** Quick Setup leaves its project with its records
initialized.
- **REQ-SETUP-2.** After Quick Setup, the assistant offers an onboarding
interview. It asks for the project's outcome and limits and drafts the
north star and a first PRD. The user approves each through the existing
approval path.
## Acceptance criteria and success measures
v1 is done when Jason gives one-sentence requests to the assistant, in a
thread, from the CLI or the WebUI, and Mosaic Stack builds pieces of
itself from them, five pieces in a row. For each piece:
- only gated decisions reach Jason;
- every step leaves a trail in the CLI and the WebUI.
(Round 1, answer A; the assistant and threads from version 1.2.)
Version 1.5 adds Jason's Q167 acceptance run, `docs/acceptance/314.md`.
It passes when one need goes from Jason's spec to a landed commit with
only hard waits in his inbox.
Version 1.11 adds the 10-27 goal (Q176, Q211). v1 is also done only when
both instances are installed and handle their cases: Case 1, the
software factory, and Case 2, the creative writing hub. 2026-10-27 is a
latest-by date and a hard target (Q176).
### Disk acceptance (proposed version 1.19)
DISK-AC1 through DISK-AC9 gate DISK-M1. DISK-AC10 is later and does not
gate that slice.
- DISK-AC1. On two disposable instance hosts, cover separate and shared filesystems, temp, homes, and retained roots. Changed mounts, denied reads, and missing paths produce unknown status without suppressing other checks. Installer warns when runtime temp shares retained or bulk storage.
- DISK-AC2. Verify capacity checks and launch recovery.
- Check 85% and just below, byte-floor equality and one byte below, free-inode floors, and typed filesystem overrides. Percentage-only byte and inode alerts permit launch.
- A filesystem without inode accounting reports not applicable and passes inode checks. A failed required measurement reports unknown and refuses launch.
- Fill the runtime temp filesystem. Refuse launch with reason `host.capacity`, never quota. The Stack launcher holds that host's launches, including chief-of-staff and assistant startup.
- Repeat for home and workspace. Recovery releases only the capacity hold and retries pending chief-of-staff startup. Refused threads show the reason in CLI and Console.
- Console and CLI show identical effective settings. Unauthorized setting writes fail with events. Check system override resolution and business merges. Project floors affect only that project's launches.
- DISK-AC3. Concurrency, restart, corrupt local state, and lost task-create replies converge on one open task. Swing usage between 84% and 85%. It never recovers at those values. With defaults, 83% also fails recovery and two samples at 82% recover if byte and inode margins pass. Check the byte margin, comment throttle, rebreach, and duplicate-free replay.
- DISK-AC4. Verify liveness and storage failures.
- Stop or stall the watcher. Deny credentials, break task delivery, and fill local state and report filesystems. The local supervisor detects watcher failures within its deadline.
- Stop or stall the supervisor. Verify the independent watchdog timer alerts through the fallback within its deadline.
- The fallback works without writes to full storage. Reconcile pending tasks, events, and partial reports after recovery.
- DISK-AC5. Audits and dry runs delete nothing. Policy fixtures cover age boundaries, exclusions, changed revisions, revocation during a batch, ownership, races, and symlink escapes. Root-owned candidates become human tasks. PM one-shot approval cannot be consumed by the service or twice. Manual mode gates each policy run. No production deletion is needed.
- DISK-AC6. On each supported v1 harness, refuse durable artifact, repository, evidence, collector, reusable script, and log registrations under temp, including symlink paths. Ephemeral scratch succeeds. A discovered git repo or executable under temp creates migration work. Retained migration preserves hashes and citations. Hooks alone cannot pass.
- DISK-AC7. Verify read-only audits and recovery proof.
- Use only REQ-DISK-12's fixed read-only tool set against caches, active toolchains, sparse disks, mail, changed repos, and dependencies with and without lockfiles.
- Include hard links, unreadable trees, and another user's temp files. Verify the git index is unchanged.
- Check per-class regeneration or reinstall proof, retained inputs, exact versions, locked installs, and unavailable sources. Report sizes and recovery procedures only where proven.
- Protected and uncertain data remain intact.
- DISK-AC8. Inject disk, permission, expired-authentication, measured-quota, and generic-provider failures. Keep classifications distinct. The configured unexplained-failure count stops automatic retries and creates investigation work. Recovery never clears an unrelated hold.
- DISK-AC9. Trace v1 runs in CLI and Console through requirements, task field ownership, events, human proof, policy revision, and result. Verify separate service identities on both instances, boot persistence without an agent, and no newly enabled policies after install or upgrade. Do not claim cross-host liveness or arbitrary-shell enforcement.
- DISK-AC10. Later remote privileged helper acceptance rejects extra paths, wrong hashes, and changed inventories. This criterion does not gate DISK-M1.
## Technical considerations
- Vikunja v2.7.0 is AGPL-3.0. Its API tokens can't create users or mint
more tokens. A Mosaic-owned account mints one bot user and one scoped,
expiring token per role. The owner password for that account is the one
high-value Vikunja secret. Instance admin features need a paid licence,
and the plan doesn't use them. Source:
`agents/researcher/work/2026-10-04_vikunja-pocketid.md`.
- Gitea tokens never expire, and creating one needs a password or the host
shell. Rotation will be a scheduled step with a record.
- Pocket ID v2.17.0 is BSD-2. It offers passkey-only login for people and
a client-credentials flow, but it can't issue tokens that Vikunja or
Gitea accept. Agents use per-service role tokens. Pocket ID is wired in
after v1.
- Decisions, messages, role claims and events share one SQLite file,
`<dataRoot>/bus/bus.sqlite`. A guard on every table refuses UPDATE,
DELETE and REPLACE. Run records stay write-once files.
- None of the three harnesses' hooks fails closed by default except Pi's
`tool_call`. Source: `agents/filbert/work/meta-harness-survey-2026-10-04.md`.
- Version 1.2:
- The organization file's `projects` key names git repositories to
clone (`packages/seat/src/checkout.mjs:282-296`). A REQ-ORG-2 project
is the user's container for threads. The build has to keep the two
apart, or define one in terms of the other, before the chat area
stores projects.
- Threads change the bus schema: messages, claims and deliveries gain a
thread. The bus refuses a database whose schema doesn't match
(`store.mjs:113-115`), so this needs a migration and a re-pin with
downtime.
- Claude Code resumes a conversation only inside one launch today, because
its session directory and HOME are per run (`launcher.mjs:204-206`,
`sandbox.mjs:157`). REQ-THREAD-2 needs a per-thread session store that
outlives the run, or a rebuild of context from the thread's messages.
- Limits today: `launch.max` is keyed by model family, and a launch's
family is the one key its model name contains
(`packages/seat/src/session.mjs:52-57`). Each count must be 0 to
`LAUNCH_CEILING = { opus: 4, sonnet: 4 }`
(`packages/business/src/vocabulary.mjs:86`, `business.mjs:129-137`).
The launcher reads the file once at start (`launcher.mjs:123`), so a
change needs a bus restart. REQ-LIMIT-1 removes the ceiling, replaces
family matching with a model table that names each model's provider,
and re-reads limits at each launch. Jason's file needs a one-time
migration from `{ opus: 4, sonnet: 4 }`.
- The stack doesn't know a session's provider today. The only provider
setting is the system-level `execution.provider` (`vars.mjs:84-87`),
read by `sessionModel` (`packages/harness/src/bundle.mjs:49-55`).
Claude Code sessions reach whatever `ANTHROPIC_BASE_URL` points at, or
Anthropic when it's unset.
- Tokens are on disk today, and nothing reads them:
- Claude Code: the adapter uses `--output-format text`
(`adapters/claude/adapter.sh`), so the runner sees only the answer.
Claude Code's own transcript, in the run's HOME at
`home/.claude/projects/<dir>/<session id>.jsonl`, has `model` and
`usage` (input, output, cache write, cache read tokens) on each
answer line. Checked on run re32fc7bd8143, model claude-sonnet-5-5.
- Pi: the session file in the run's session directory has `provider`,
`model` and `usage` (input, output, cacheRead, cacheWrite,
totalTokens, cost) on each answer line. Checked on a Researcher
session, provider zai.
- The launch log has harness, model and family per launch, and no
tokens (`launcher.mjs:269`, `:294`).
- Today every Claude Code session runs on one credential, which the bus
unit's environment file sets (`SESSION_CREDENTIALS`,
`packages/seat/src/session.mjs:76`, decision 119). The stack has no
name for that account. A quota per account needs the organization file
to name each account and its credential reference (never the secret,
invariant 3), and the launch to pick one and log its name. More than
one account at a time needs the bus to hand a credential to each
session, which is the open question in the #237 design row.
- CLIProxyAPI, from Researcher's stand-in test (commit 587e64a27,
`agents/researcher/work/i1-proxy-test/REPORT.md`): usage counters are
kept per upstream credential, not per client key. Per-request events
name the client key, but they last 60 seconds, a read removes them, and
they carry the full plaintext key. All of it is behind the management
API, which no agent calls. The stand-in didn't model token counts.
So the stack can't read per-key usage from the proxy today. It counts
its own sessions' tokens instead, and a per-key quota needs the stack
to issue the keys (#237).
- Token limits stop a session at the end of a turn, because the runner
learns usage only when the turn ends. A long turn can go past its
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: `cli` and `webui` are channel plug-in modules, selected by
the organization's `plugins.channel` list. Both retain the core human
proof transport (`via: 'cli'`); Discord isn't connected to the bus.
- Version 1.4:
- Jason approved REQ-WEB-3 in decision 021af0c8 (prd.approve, choice
yes; the approval message was request 2e031716, from request
dfebf8ce). The requirement is the approved sentence, set as three
sentences with the same clauses.
- Jason approved REQ-INST-1 in decision 1ea053cc (prd.approve, choice
yes; the approval message was request 94a11a59, from request
b28979c9, Q162). The text is as approved. The build task under it is
the SearXNG install component, which backs the `searxng-bundled`
web source (`web.source`, vikunja:19/296). This amendment adds the
requirement only.
- Today a change to `launch.instances` reaches running sessions only
after the bus service is restarted by hand. The build tasks under
REQ-WEB-3 are the bus hot reload, the Console controls, and running
sessions picking up `launch.instances` changes. This amendment adds
the requirement only.
- REQ-WEB-2 is held for the Console decide buttons amendment
(vikunja:19/266), so its id is not reused here.
- Version 1.5:
- The 1.0 text of REQ-DEC-2 was: "Routing follows the decision's
class: routine and within-role decisions are logged; cross-role
decisions go to the business's arbiter for that domain; gated
decisions go to the human." On 2026-10-15 the Stack routed 16
decisions, and all 16 went to Jason. 9 of them were `policy.change`
or `prd.approve`, which no role file could grant.
- Before 1.5 the always-gated lists, `GATED` in
`packages/bus/src/broker.mjs` and `GATED_ONLY` in
`packages/business/src/vocabulary.mjs`, held nine actions:
`credential.mint`, `git.merge.protected`, `git.push.protected`,
`deploy`, `spend`, `message.external`, `policy.change`,
`prd.approve` and `role.revoke`. Row 139 (#1633) changes them to
REQ-WAIT-1's list and adds `data.delete`, `network.expose` and
`account.create`.
- The arbiters are the organization file's `arbiters.technical` and
`arbiters.delivery` (`packages/bus/src/broker.mjs`).
- Where each requirement is built, all under Vikunja P19 task 314:
REQ-DEC-5 is row 136 (#1630); REQ-PROJ-1 is row 147 (#1641);
REQ-PROJ-2 and REQ-DEC-6 are row 138 (#1632); REQ-COS-2 is row 140
(#1634). Those are landed. REQ-LAND-1 is row 137 (#1631);
REQ-DEC-2 and REQ-WAIT-1 to REQ-WAIT-3 are row 139 (#1633);
REQ-MSG-2 is row 141 (#1635); REQ-PROJ-4 and REQ-ASSIST-3 are row 143
(#1637); REQ-PROJ-3 is row 148 (#1642); REQ-ROLE-5 and REQ-ROLE-6
are row 149. This amendment adds the requirements only.
- The spec counts as confirmed when its last commit's author name is
the business's `human` (`packages/project/src/read.mjs`). The broker
refuses a raise that cites another commit with `stale-spec`, and an
unconfirmed spec with `spec-unconfirmed`. Until `mosaic project
init` runs for a project, spec reads and record writes for it are
refused.
- `git.push.protected` covering force pushes on any branch is new in
1.5. Today the host refuses a non-fast-forward push to the working
repository (`packages/seat/src/working.mjs`), so no role can force
push. REQ-WAIT-1 makes any future path to one a hard wait.
- REQ-PROJ-4 covers Console saves only (row 143). Noticing spec commits
made with git outside the Console is a later row.
- REQ-LAND-1's refusal of a gated merge decision is pinned in rows 137
and 139. Row 139 carries it, or row 137 if 139 lands first.
- Version 1.6:
- The 1.5 text of REQ-ROLE-2 ended at "and the revoke cites it." The
1.5 text of REQ-ROLE-4 ended at "Jason's may be named Mos." The 1.5
text of REQ-WAIT-1 ended at "REQ-ROLE-2 still requires a resolved
decision for a revoke."
- Removing the assistant, in this version, means ending or revoking
its runs. Deleting the instance from the organization file is not
covered.
The protected definitions are `END_PROTECTED` in
`packages/bus/src/broker.mjs`, which holds `assistant`.
- Configuring the assistant covers its organization entry's `vars`
and `tracker`, and its `definition` among those the broker treats as
the assistant. `holder` and `credentials` are not covered.
- Where each part is built: the refusal and the hard wait are row 139
(#1633). Configuring the assistant and its seat name, and making the
chief of staff's end or a human-approved revoke stop an assistant
run, are row 155 (#1649), using row 141's (#1635) instance names.
Until row 155 lands, the assistant's runs hold thread claims that
the end and revoke paths don't read, so neither can stop one. The
human stops one with `mosaic stop <run>`.
- Version 1.9:
- The 1.8 text of REQ-DEC-3 began: "A human resolution, by an
authorized human, comes only from a `mosaic` CLI session started
outside any agent run."
- The first text on vikunja:19/266 had the Console trust the
`X-authentik-uid` header. Q175 replaced that: the Console runs its
own OIDC sign-in and takes no identity from a proxy header. Q203 (O6)
keeps the requirement OIDC-only, so the Console holds no passwords.
- The requirement covers every open decision. The first build, row
252 (#1753), covers grant requests only (Q188). It resolves through
the human boundary row 196 (#1694) builds.
- The live Console runs without `--sso` and without `--send`, so it
resolves nothing until the Q175 phase 2 window turns both on and a
re-pin carries row 252. This amendment adds the requirement only.
- Version 1.10:
- REQ-WEB-2 (1.9) uses the Console's own OIDC sign-in from Q175. The
out-of-scope list above then still named 'Pocket ID and SSO'. Jason
ruled on that line in Q211, and version 1.11 updates it
(vikunja:19/459).
- The design row for REQ-INST-2 is row 255 (#1760). This amendment adds
the requirement only.
- Version 1.11:
- The 1.10 out-of-scope list, under Round 2, "all", was: businesses
other than Mosaic Stack (SetSpark joins after v1); minting credentials
(v1 hands out tokens Jason creates); Codex (v1 covers Pi and Claude
Code, and Codex comes next); Pocket ID and SSO; access from beyond
localhost.
- Q211 names Q176's Given list and Cases 1 and 2 as v1. They are
tracked on Vikunja #414, #415 and #416. This version doesn't add
requirement ids for the items in them that the PRD lacks.
- The Case 2 MVP for 10-27 is the Markdown library (#416). The WYSIWYG
editor (Q195, Q196) is an add-on that doesn't gate it.
- Today the stack has an upgrade tool with activate, rollback and a
practice run, and nothing that sets up a fresh host (Q187). The
installer row is Vikunja #432, and the throwaway VM proof is #433.
- REQ-INST-3 replaces the hand start of the Console for the two v1 VMs
only (Q189, mos-1097 item 1).
- Which OIDC provider the two v1 VMs use is open (vikunja:19/461).
REQ-INST-2 moves into v1 if Jason picks the bundled Pocket ID for
either instance.
- Version 1.12:
- The 1.11 Risks line was: "In slice 1, agents run as Jason's OS user
(round 3, 3A). They can write anything that user can write. Until
seats run in a container, which comes next, the broker is a rule they
follow, not a wall. What does hold in slice 1 is that agents never
hold service tokens." Managed sessions now run in a sandbox (#1586,
`packages/seat/src/sandbox.mjs`), so 1.12 states the risk as the
shared OS user outside that sandbox. Q181 and Q182 settle the host
user and the accepted workstation risk.
- The REQ-DEC-3 title in `packages/tasks/requirements.json` was
"Authorized-human resolutions come only from a mosaic CLI session
outside any agent run". It left out the Console session that 1.9
added. 1.12 changes the title only. The requirement text is
unchanged.
- Q176 items the PRD already covered, so 1.12 adds no id for them:
projects and threads (REQ-ORG-2, REQ-THREAD-1, REQ-THREAD-2);
federation and shared access across instances (REQ-FED-1); custom and
templated roles assigned to agents (REQ-ROLE-1, REQ-ROLE-3,
REQ-TPL-1); the credential store (REQ-CRED-1 to REQ-CRED-4,
REQ-GRANT-1 to REQ-GRANT-4); each agent's own git credentials
(REQ-LAND-2, REQ-CRED-1); the chief of staff's hire and fire with
the user's per-agent forbid, and restructuring (REQ-COS-3,
REQ-ROLE-3, REQ-USER-3); the main user and the assistant
(REQ-ASSIST-1).
- Q176 items with no approved design, so 1.12 adds no id for them:
several independent sessions per thread, a compaction count, and
clearing an agent's context to start a new session. They are MVP
plan T1 item 7 and are v1 (TARGET.md:57, :104, :105). Several
sessions per thread differs from REQ-ROLE-2 and REQ-THREAD-2, where
each thread is one assistant session. Under Mos's reading of Q211,
TARGET.md:57 wins. Row 194 refuses regenerate for a thread instance
and leaves clearing its context to that item. Huey designs it, and
Scrooge reviews (vikunja:19/463, mos-1232). Its ids and the
REQ-ROLE-2 and REQ-THREAD-2 changes come in 1.13.
- Writing in the WebUI: Q176 allows "WYSIWYG, Obsidian, or some other
method". The Case 2 design keeps Obsidian (design 7.1). The WYSIWYG
editor is the Q195 and Q196 add-on and doesn't gate 10-27.
- The Case 2 design's playground prefixes all sit inside a book folder
(design 1.1). Q176 also asks for universe-level playgrounds "where
new universes can be created". The design doesn't state one, so
REQ-LIB-2 doesn't name the level.
- The design's 10-27 slice (design 9.2) is a capacity estimate, not a
cut (Mos's reading of Q211, mos-1231). Rooms with @tags (C2-09b,
C2-10c; TARGET.md:99) and hybrid universes (C2-15; TARGET.md:82)
stay v1. If one misses 10-27, it is reported late, not cut. Bus
writes and the Console editor (C2-02, C2-03b, C2-11a, C2-11b, C2-19)
don't gate 10-27: TARGET.md:97 allows Obsidian, and Jason called the
editor an add-on that "may not make the EOW deadline" (Q195). C2-16
to C2-18 stay later, because no Q176 line asks for them.
- REQ-PROJ-1's "no remote" covers project record repositories. A
library project is a separate kind with no record repository, and its
mirror fetches from the remote (design 2.1, 7.2).
- REQ-LIB-6 has a stated residual: a supporting doc can hold any text,
a drafted scene included, and code can't tell a scene from a note.
Every agent write shows as a notice and a commit trailer, and the user
can revert it. Nothing an agent writes lands in a story path. The
agent's Gitea token can write any path of the repository, and the
bus's path check is the only path limit, which holds while the bus
code does (design, ruling after r11). (Version 1.15 replaced the
item-list residual. The old text is in the Version 1.15 note.)
- Where each id is built: REQ-USER-3 and REQ-USER-4 are rows 193a to
193d (#1691), with FED R1 to R6. REQ-LIFE-1 is row 194 (#1692) and
its follow-up row 239. REQ-KILL-1 is row 191 (#1689). The REQ-LIB ids
are the C2 rows in `docs/plans/2026-10-20_case2-rows.md` (#416). This
amendment adds the requirements only.
- Version 1.13:
- The 1.12 text of REQ-ROLE-2 began: "Each role instance has one holder
at a time. The assistant is the exception: it has one holder per
thread at a time. A claim takes a lock and logs it, and releasing the
role or ending the run frees it." The rest, from "The chief of staff
may end another role's run", is unchanged. Who may end or revoke the
assistant is unchanged.
- The 1.12 text of REQ-THREAD-2 was: "The user creates projects and
names threads inside them, from the CLI or the WebUI. Each thread is a
separate assistant session. A thread's messages are kept. When the
user comes back to a thread, after an idle stop or a restart, the
assistant picks up where it left off with that history. One thread's
conversation never appears in another's. Added in version 1.2."
- The titles of REQ-ROLE-2 and REQ-THREAD-2 in
`packages/tasks/requirements.json` change with their text.
- Mos's readings, which the design records in section 8 (mos-1235).
They are reported to Jason after this version lands and don't go to
him as questions:
- Q1: the count is for the session's whole life. A clear successor is
a new session under TARGET.md:105 and starts at zero.
- Q2: clear waits for a proven safe stop and shows that it is
pending. It adds no force-stop, and the kill switch stays separate.
- Q3: the organization's owner doesn't clear another user's session
without that user's write authority, because "A user's messages
stay private from the owner" (REQ-USER-4).
- Mos's ruling mos-1236: sessions are for the assistant (the
`THREAD_DEFINITION` instance) only. Letting other roles hold thread
sessions would change launch policy and is outside Vikunja 463.
- Build dependency (design N2, vikunja:19/466): Claude Code's format
for a completed compaction has to be pinned offline, and a reader for
the managed session stores added. Today's reader covers pi only.
REQ-SESS-1 can't pass its acceptance test until both exist, and the
count isn't shipped for either harness until then.
- Until Q198's admission step is built, REQ-SESS-3 uses the existing
launcher and limit checks, plus row 191's kill checks once they land
(design D8).
- Moving existing threads keeps their history: each becomes one
default session, bound to its existing native store, with nothing
rewritten. The old shared instance workspace is drained and kept as
history (design D9).
- The build row is Sage's to file under Vikunja 463. This amendment
adds the requirements only.
- Version 1.14:
- The 1.13 text of REQ-LIB-2 was: "Playgrounds are never confused with
canon. A file's scope comes from its path alone: a Markdown file
under a playground prefix is playground, any other Markdown file in a
book folder is canon, and everything else is unmapped and never
returned. Lookups return canon unless the caller asks for playground.
An agent can't cite a playground file as a fact, a finding or a
correction. The Console shows each cite's scope. (Design 1.2, 5.2
and 7.4. Q176 Case 2, the playground items.)"
- REQ-LIB-3 gains the two sentences on a draft universe and a file
with no book. The rest of its text is unchanged.
- This settles the Version 1.12 note's item that "REQ-LIB-2 doesn't
name the level". That note is history and stays as written.
- The ruling's refusal codes: `library-playground-other-universe`
replaces `library-playground-outside-book`, for a prefix inside or
holding another entry's book folder. `library-mapping-invalid` covers
a prefix that holds its own entry's book folder, a draft with `books`
or no `playground` prefix, a draft with keys other than `kind`,
`name` and `playground`, and a `kind` other than absent, `"hybrid"`
or `"draft"`. A prefix equal to a book folder, a prefix listed twice
and a hybrid whose `sources` name a draft stay refused.
- Lookup details from the ruling: a lookup in a draft passes
`scope: "playground"`, since the canon default returns nothing there.
A universe-level file returns `book: null`. `library_manuscript`
needs a book. `citation-cross-universe` treats a draft as its own
universe. R5 covers a canon file that refers into any playground, and
R6 one that refers into a draft. `library_universe` on a draft
returns its kind, name and prefixes.
- The titles in `packages/tasks/requirements.json` don't change. The
REQ-LIB-2 title still holds at every level.
- The build is row C2-01c in `packages/library` (`mapping.mjs`,
`scope.mjs`), before C2-05a and C2-06 read `classify`, or in their
candidates if they have started. C2-12's setup sheet gets one line
per level (design section 8). Sage files it under Vikunja 464. This
amendment adds the requirements only.
- Version 1.15:
- The 1.14 text of REQ-LIB-6 was: "The creative roles are ideas,
research, proofreader, fact-checker and continuity, plus a companion
role that the user's own named agents run as. No creative agent can
write to the user's repository. Its tools are the lookups and a
message to the human, and research and companion also fetch web pages
through the broker. Every message an agent sends the user is a short
list of items. Each item cites a line of the repository at a commit,
or a page the agent fetched, and the broker checks every cite before
it stores the message. The one exception is a message with no items
and one of three fixed notices: no cited answer, no answer, or the
turn failed. The Console shows each notice as a fixed line, with no
text from the model. The user sees only stored messages, on every
channel. (Design 4, 5.1, 5.2 and 5.3; Q180 for Bean and Chet. Q176
Case 2, the agent list and "User sets strict guardrails for agent
capabilities".)"
- The 1.12 note's REQ-LIB-6 residual was: "an `idea` item carries free
text, up to 5 items of 200 characters per reply, and code can't tell
story text from advice (design 5.1). Accepting it is Jason's or
Melanie's (design section 10, step 8). It goes to Jason through Mos
(vikunja:19/465)." Q223 removed the item list, and the residual went
with it. The note now states the new one.
- REQ-LIB-1 gains "and supporting folders" in its first sentence and a
version tag. REQ-LIB-2 gains the supporting scope, its placement rule
and the canon-cite rule, which replaces "An agent can't cite a
playground file as a fact, a finding or a correction." The rest of
both is unchanged.
- REQ-LIB-3, REQ-LIB-4, REQ-LIB-5 and REQ-LIB-7 are unchanged. REQ-LIB-4
already says bus writes and pushes come in later rows (C2-02,
C2-03b), which covers agent writes and the user's Console saves. Its
fetch still uses a read-only token, now the chief of staff's read
grant.
- The titles of REQ-LIB-1, REQ-LIB-2 and REQ-LIB-6 in
`packages/tasks/requirements.json` change with their text. The id
count stays 85.
- The new refusal codes, settings and row order are in the design's
ruling after r11 and in the Q226 briefs of
`docs/plans/2026-10-20_case2-rows.md`. Sage files the rows under
Vikunja 416. This amendment adds the requirements only.
- Version 1.16:
- REQ-WAIT-3 gains one paragraph, the `library.write` routing and its
bounds. REQ-LIB-6 gains one sentence naming the action. No other
requirement text changes.
- `packages/tasks/requirements.json` changes its version to 1.16 and
nothing else. The titles stay and the id count stays 85.
- The commit that adds `library.write` to `ROUTED` in the business
vocabulary and the broker is C2-08b, row 285. It lands only after this
version.
- Version 1.17:
- Jason confirmed the reading 'for now (subject to future change)' (Q237). The
rulings stand until he revises them.
- The 10-27 Case 2 release ships on Markdown as built (P6). The 1.11 line 'The
Case 2 MVP for 10-27 is the Markdown library (#416). The WYSIWYG editor (Q195,
Q196) is an add-on that doesn't gate it.' still holds. Conversion opens after
10-27, and only once REQ-LIB-8's four conditions are met. The editor ships,
the writing store's remote is set and the stack has checked that it can push
there, agents can read and cite committed DOCX story files through the
lookups, and Hermes is retired, which is #423 step 5.
- The 1.12 note says five bus write and Console editor rows, C2-02, C2-03b,
C2-11a, C2-11b and C2-19, don't gate 10-27. Jason's Q226 F42 later moved
C2-02, C2-03b and C2-11b into the 10-27 slice. The Case 2 design brings C2-11a
in with them, because C2-11b needs it. C2-19 stays outside the slice. Mos's
reading of F42 is that C2-02 and C2-03b now gate 10-27, because design 9.2
puts them in 'the smallest slice that lets her start'. C2-11a and C2-11b are
slice rows that still don't gate 10-27, because the editor is the Q195 add-on.
The DOCX change in 1.17 leaves this slice as it is, and C2-11b's Markdown
editor never edits a converted story file.
- Q236 narrows Q176's editor choice to DOCX for story files (REQ-LIB-9). The
1.12 note that the Case 2 design keeps Obsidian now holds only for Markdown
files, including story files not yet imported. A DOCX story file is edited
only in the Console's editor under REQ-LIB-9.
- This replaces two parts of the O2 design at 25c09f19f. The superseded-notice
proposal from Fenton's F1 in D12 is withdrawn, and the
keep-Markdown-at-its-path model of D11 is replaced. Gyro's rewrite of the O2
design for Vikunja 571, landed on `refactor` at b99ca86b9, carries the
details, and where it differs from this text, this text holds:
- the import and archive steps, the mark, and the new bus step;
- links from supporting files to a moved chapter;
- the DOCX reader;
- the user's unpushed or later changes to an archived file or at its old path,
and pushing and closing the file in Obsidian before each import;
- the writing store's remote in W4 section 7.3, which replaces the no-remote
and no-backup rule that W4 section 7.4 held before the rewrite, at
062646487;
- import events counted by a bus restore, and recovery when a bus restore or a
restore of the whole host predates an import;
- manifest order for the manuscript lookup and its next chapter;
- `library.convert` kept out of `ACTIONS`, out of the hard-wait note and out
of the pinned `GATED_ONLY` list in packages/business/src/vocabulary.mjs.
- Agents read a converted story file through the Case 2 lookups under the same
project and universe access as Markdown canon, in any thread or room. W4's
earlier O2-only conversations, per-document grants and per-send sharing don't
gate these lookups. The 571 rewrite replaced them in W4 sections 7.1 and 7.2.
The creative tool list in REQ-LIB-6 doesn't change.
- The six creative role contracts row 213 landed say 'Every item cites the
lines of the user's material it is about'. A build row under 571 changes that
line for DOCX story files (REQ-LIB-6).
- A new Markdown file in a book folder is still canon in 1.17. The 1.17
exception is a file at a converted file's old path, which REQ-LIB-2 makes
unmapped.
- Story files convert whether canon or playground. Type-folder and supporting
files stay Markdown.
- Measured 2026-10-23 at about 01:21Z: the Case 2 user's library repository
tracks 213 files, 203 of them Markdown, and none is a .docx or .doc. It has no
untracked files. The user's .doc files (Q196) are outside it. So the landed
code has no DOCX there to report as unreadable at 10-27.
- Text replaced in 1.17, kept here as in earlier notes:
- Creative library lead, the 1.16 sentence: "The library is the user's own
writing repository."
- REQ-LIB-2, the 1.16 sentences: "A file's scope comes from its path alone,
prefixes first: a Markdown file under a supporting prefix is supporting,
one under a playground prefix at any level is playground, any other
Markdown file in a book folder is canon, and everything else is unmapped
and never returned. A folder in both lists is supporting." "Lookups return
canon unless the caller asks for playground or supporting." "so an agent
can't cite a playground or supporting file as canon" "The Console shows
each cite's scope."
- REQ-LIB-4, the 1.16 sentences: "The user commits and pushes with their own
tools, today Obsidian with obsidian-git." "Agents read only the mirror's
served commit and never hold a copy of their own, so they see the user's
push within about a minute."
- REQ-LIB-5, the 1.16 sentence: "The user sees them in Obsidian's graph and
backlinks." A converted chapter's connections show in Obsidian only as they
stood at conversion, and the Console's Connections view shows their
current links.
- REQ-LIB-6, the 1.16 sentence: "A reply may cite lines of the repository at
a commit or pages the agent fetched, and the broker checks every citation
before it stores the message."
- REQ-LIB-1 only gains sentences.
- REQ-LIB-3's text and REQ-LIB-7 are unchanged. REQ-LIB-8 adds one form of
exclusion REQ-LIB-3 doesn't have: naming a DOCX document by its id. A path
exclusion also covers DOCX documents, through a converted file's old path or a
whole book folder. An archived file and a DOCX document keep a universe, and a
book where their source has one, so every lookup still names one universe.
REQ-PROJ-3, the Console editor for a project's spec and docs, is unchanged.
C2-11b opens the library's Markdown files in that same editor.
- In `packages/tasks/requirements.json` the titles of REQ-LIB-1, REQ-LIB-2 and
REQ-LIB-4 change, and those of REQ-LIB-5 and REQ-LIB-6 stay. REQ-LIB-1 becomes
'A human-set library maps the user's repository onto universes, books, types,
playgrounds, supporting and archive folders'. REQ-LIB-2 becomes 'Scope comes
from the path or a DOCX's source, lookups default to canon, and only canon
files are ever cited as canon'. REQ-LIB-4 becomes 'Git holds every change. The
bus fast-forwards a read-only mirror every 60 seconds, the one place agents
read Markdown'. REQ-LIB-8 and REQ-LIB-9 add two ids, so the count goes from 85
to 87. REQ-LIB-8 is titled 'Case 2 story files become DOCX by a human-only
one-way import or in the editor; Markdown stays for notes and docs', and
REQ-LIB-9 'The Console's DOCX editor: only a human saves, each save is one
commit, and the editor code is vendored and pinned'.
- Open points O1 to O4 of draft 1 were ruled by Mos in mos-1367 B1 to B4.
- Sage files the build rows under Vikunja 570 and 571 after the rewrite is
approved. This amendment adds the requirements only.
## Risks and open questions
- The final name for the Mosaic Montage module is deferred to a later owner
decision. Mosaic Montage remains a working name; the existing four-tile
logo stays. (Version 1.20.)
- On each v1 instance, the stack runs as that instance's own OS user,
without sudo (Q181). A managed role session runs in a sandbox
(REQ-HARN-2): it writes only its workspace and run directory, sees no
data root, config or other run, and holds no service token
(REQ-CRED-1). A process that runs as that OS user outside a managed
session has no sandbox and can write anything the user can. On
dragon-lin, dev agents run that way, as Jason's OS user, with his keys
and access to the other hosts. Jason accepts that risk (Q182).
(Version 1.12.)
- Vikunja doesn't enforce `If-Match`. The broker compares before it
writes, and a person editing in the same moment can still be
overwritten in an owned field (lead decision 51).
- The Vikunja owner password and Gitea token creation are gated. Jason
holds them.
- With no ceiling in code, the chief of staff can raise its own launch
capacity and spend (REQ-LIMIT-1, Q150). The controls are the change
log, with who made each change and the old and new values, and the
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).
- Trusted mode lets an agent run its allowed commands at will. On another
host, an allowed command that writes a file as root or starts a shell
can plant a key, and the seat sandbox doesn't confine the network, so
root can outlive the grant and a revoke. The allowlist narrows this. A
seat sandbox with its own network namespace closes it. Until then the
host's own audit log is the check after the fact (row 210 design section
6).
- A DOCX file is a zip archive, so git can't show a line diff of a chapter.
Review of a story change goes through the editor and the committed reader,
not `git diff`. (Version 1.17.)
- A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the
chapter only through its archived Markdown, and no DOCX edit reaches that file's
links. The DOCX's current links show in the Console's editor and its
Connections view, and no graph shows them yet. (Version 1.17.)
- Conversion can't be undone in place. Front matter, wiki link forms and
unsupported blocks may not survive it, and the mirror can't refuse the user's
push to an archived file. The controls are the fidelity report, the human's
confirmation, an untouched original on refusal, and the kept archive.
(Version 1.17.)
- A restore of the stack's bus alone from a backup taken before an import drops
the import's logged events, while the archive commit and the DOCX stay. A
restore of the stack's whole host from such a backup also takes the writing
store and the host's import records back to before the import. The archive
commit stays in the user's Gitea repository and the DOCX stays on the writing
store's remote, but the host keeps no record of the import. A bus restore counts
import events, and the import's recovery covers both cases (Vikunja 571).
(Version 1.17.)
## Milestones
### DISK-M1: disk-space governance
- DISK-M1 lands as version 1.19, which Jason approves under REQ-WAIT-2.
- v1 includes REQ-DISK-1, 2, 3, 8, 9, and 11, plus REQ-DISK-4 and 5 dry runs and Q288 policy execution.
- v1 includes the supporting ownership, registration checks, service identity, tasks, events, local supervisor, and independent watchdog timer defined above. DISK-AC1 through DISK-AC9 gate this slice.
- Cross-host supervision, remote privileged execution, and sandbox-level temp enforcement follow later. DISK-AC10 and those later capabilities do not gate DISK-M1.
Slice 1, in the order on the foundation direction page. The slice 1 brief
maps each step to requirement ids. CHAT-03's Gate E demonstration happens
during the slice 1 WebUI step, not separately (round 3, 7B).