11 KiB
PRD: Mosaic Stack
- Status: approved, version 1.0. Jason approved draft 0.4 unchanged as 1.0 on 2026-10-05 (lead decision 61). It is never edited in place. Changes are a new version.
- 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):
- Admin falls on Jason. He launches agents, relays between them, finds credentials and corrects them.
- Jason can't see what the agents are doing or what's waiting on him.
- 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 his approval.
- C. Give agents Jason's personal credentials. Each role gets its own.
- D. Become a multi-tenant SaaS in v1.
- E. Replace Claude Code, Codex or Pi. It wraps them.
Where it runs
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>.jsonat 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. A claim takes a lock and logs it, and releasing the role or ending the run frees it. Revoking a stuck holder is gated, and the revoke cites a resolved decision.
-
REQ-ROLE-3. A business file declares the role instances, the arbiters and the credential references. The user writes it, the stack never writes it, and a missing or invalid file fails closed.
-
REQ-ROLE-4. The first business is Mosaic Stack, with four role instances:
- PM, held by Sage;
- CTO, held by Darkwing;
- coder;
- reviewer.
The schema can also declare CEO, CFO and the other C-suite roles.
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.jsonstays 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 withstat.
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 examplemosaic-stack-pm-bot(round 3, 2A; lead decision 58); - in Vikunja, a
bot-<business>-<role>user per role, plus one read-onlybot-<business>-syncthat does all polling (lead decisions 52 and 58).
- in Gitea, a new bot user per role, named
- 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:
- 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.
-
REQ-DEC-3. A human resolution comes only from a
mosaicCLI 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 Jason 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.)
Messages
- REQ-MSG-1. Messages are addressed to a role, not a thread. They are append-only and go to whoever currently holds the role. T3 and Discord carry messages; the address is still the role.
Tasks
- REQ-TASK-1. Tasks live in Vikunja, reached through
/api/v2with 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.
Interface
- REQ-CLI-1.
mosaicis the front door. From it Jason can:- talk to the PM;
- see the decision inbox and resolve decisions;
- see tasks and agents;
- follow the trail of any piece of work.
- REQ-CLI-2. The PM session that
mosaictalks 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.
Launching
-
REQ-LAUNCH-1. The PM launches role sessions within limits written in the business file:
- role instances only;
- at most 4 Opus and 4 Sonnet sessions at once;
- every launch logged;
- revocable with one word.
(Round 2, answer 6A.)
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.
Acceptance criteria and success measures
v1 is done when Jason gives one-sentence requests in the CLI 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.)
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.
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.
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).