Files
stack/docs/prd/mosaic-stack.md
T
jason.woltjeandClaude Opus 5.5 008746b6ed plugins: channel kind for the CLI and Console chat paths (row 164, #1661)
Adds plugin kind `channel` (interfaceVersion 1, dispatch(request, {call}))
with cli and webui modules. Project and thread surfaces and thread
talk/follow in the CLI and the Console chat go through the selected
channel. An empty or absent channel selection is off: the CLI exits 3 and
the Console answers 503, each naming `mosaic plugins` and `mosaic plugins
set` with the business selector. Management plugins and plugins.set stay
on the core transport. The loader gains a synchronous snapshot(kind)
getter; loaded(), modules() and last-good handling are unchanged. No lock
is held across dispatch. serve.mjs, public/ and the bus are untouched.

Owner Rocko. Reviewer Dewey: r1 changes (comment 28980, B1: the recovery
sentence was never asserted), r2 approve (comment 28984) on candidate
manifest ae2fcfda...1fc8, the B1 assert killing mutant D1. Design accepted
by Mos (mos-0768, mos-0773). Fixture rewrite at project.test.mjs:47 acked
by Mos (mos-0789) on Dewey's line-by-line check: lines 48-51 identical,
one added assert. Sage gate on e5b10d275: 25 steps green, 2081 package tests, 156 queue node tests, 422 script selftests, 0 failed.

Live stays as pinned. The re-pin that takes this live puts `mosaic
plugins set` in Jason's window right after activate (mos-0768).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 14:24:42 -05:00

49 KiB

PRD: Mosaic Stack

  • Status: approved, version 1.6. 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.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

For now the only user is Jason. 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.)

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.

Out of scope for v1

Round 2, "all":

  • 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.

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: 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 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 role per organization. It talks with the user, and it passes work for the organization to the 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.)

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. 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.

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).
  • REQ-CRED-2. A session that finds only founder credentials stops.

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.

  • 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 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.

Hard waits

Added in version 1.5 (Q167).

  • REQ-WAIT-1. These are the hard waits. Each always goes to the human, and each maps to actions in the closed vocabulary:

    Jason's wait Actions
    Spending money spend
    Deleting data data.delete, new in 1.5
    Anything irreversible data.delete, 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.

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. Tasks live in Vikunja, reached through /api/v2 with a bot token for each role. 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. 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.

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.
  • 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).
  • REQ-WEB-3. The Console has controls to restart the bus and to reload the organization file. Each control is a gated, trailed action. A reload applies launch.instances changes to running sessions without a manual systemctl restart. (Version 1.4, decision 021af0c8.)

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.)

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.

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.

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.

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 belongs to one organization and reaches only its allowed projects and threads.
  • 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. 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.
  • REQ-TPL-1. Templates exist for agent roles and for organization types.
  • REQ-FED-1. Installs can federate with each other. This is post-v1.

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.

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.

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>.

Risks and open questions

  • 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.
  • 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).

Milestones

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).