PRDY round 2 answers, and Researcher's Vikunja and Pocket ID report as a record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
261 lines
9.5 KiB
Markdown
261 lines
9.5 KiB
Markdown
# PRD: Mosaic Stack
|
|
|
|
- Status: draft, version 0.2. 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 and 47.
|
|
|
|
## 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. The
|
|
stack checks a referenced file with `stat` and never reads its
|
|
contents.
|
|
|
|
### 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. Only the
|
|
assigned role writes a task's mutable fields. A person's edits come in
|
|
as events.
|
|
- **REQ-TASK-2.** Polling for changes since the last check is the source
|
|
of truth, and webhooks only trigger a check sooner.
|
|
- **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.
|