Files
stack/docs/prd/mosaic-stack.md
T
jason.woltjeandClaude Opus 5.5 d7723e2237 docs(prd): PRD draft 0.3; slice 1 addendum A; lead decision 49
Darkwing's addendum as a record. Broker verbs enforce field ownership,
broker push from a bare repo, polling without webhooks, Vikunja probes
before the adapter interface is fixed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 14:56:14 -05:00

264 lines
9.8 KiB
Markdown

# PRD: Mosaic Stack
- Status: draft, version 0.3. Jason approves it, and once approved it is
never edited in place. Changes after approval 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. Design inputs are lead decisions 43, 44,
46, 47 and 49.
## 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.** He launches agents, relays between them,
finds credentials and corrects them.
2. **Jason can't see what the agents are doing or what's waiting on
him.**
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 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>.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. 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.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). In v1 Jason creates these tokens by hand. The broker
holds them, and they never enter an agent's environment or files.
- **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 `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 Jason right
away: in the CLI inbox, plus a Discord DM. Everything else goes into one
daily digest. (Round 2, answers 2A and 3A.)
### 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/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 for changes since the last check is the source
of truth, with an hourly full reconcile. Slice 1 uses no webhooks.
- **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.** `mosaic` is 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 `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.
### 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
Round 3 sets these. Known so far:
- Agents running as the same OS user as Jason can write anything that
user can write. Until seats run as another user or in a container, the
broker is a rule they follow, not a wall.
- 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.