Compare commits

...
4 changed files with 124 additions and 17 deletions

No files matched your search

+114 -10
View File
@@ -1,6 +1,6 @@
# PRD: Mosaic Stack
- Status: approved, version 1.12. Jason approved draft 0.4 unchanged as
- Status: approved, version 1.13. 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
@@ -36,7 +36,19 @@
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).
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.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
@@ -297,9 +309,13 @@ parent requirement is refused.
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
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
@@ -1007,11 +1023,17 @@ Added in version 1.1 (decision 03130d08).
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.
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
@@ -1085,6 +1107,46 @@ is drafted from and the Q176 item it covers.
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.)
### Creative library
Added in version 1.12 (Q176 Case 2, Q211). Drafted from the Case 2
@@ -1509,6 +1571,48 @@ latest-by date and a hard target (Q176).
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.
## Risks and open questions
+1 -1
View File
@@ -45,7 +45,7 @@ test('requirements returns the committed list to a holding role session, and no
const pm = agent('pm');
const list = call(b, pm, 'requirements');
assert.deepEqual(list, loadRequirements());
assert.equal(list.requirements.length, 82);
assert.equal(list.requirements.length, 85);
assert.deepEqual(call(b, agent('coder'), 'requirements'), list);
assert.throws(() => call(b, human, 'requirements'), /agent-required/);
assert.throws(() => call(b, reader, 'requirements'), /read-only/);
+6 -3
View File
@@ -1,9 +1,9 @@
{
"prd": "docs/prd/mosaic-stack.md",
"version": "1.12",
"version": "1.13",
"requirements": [
{ "id": "REQ-ROLE-1", "title": "Each role is a reviewed roles/<name>.json: contract, authority map, credentials" },
{ "id": "REQ-ROLE-2", "title": "One holder per role instance, one per thread for the assistant; the chief of staff may end a run, others need a decision" },
{ "id": "REQ-ROLE-2", "title": "One holder per role instance, one per assistant session; the chief of staff may end a run, others need a decision" },
{ "id": "REQ-ROLE-3", "title": "The user and the chief of staff write the organization file; the chief of staff's writes go through a logged broker verb" },
{ "id": "REQ-ROLE-4", "title": "The first organization is Mosaic Stack: chief of staff, assistant, PM, CTO, coder and reviewer" },
{ "id": "REQ-ROLE-5", "title": "Q167 adds security-reviewer and merge-gate; merge-gate raises the merge in the technical domain and lands it" },
@@ -67,13 +67,16 @@
{ "id": "REQ-USER-1", "title": "Users and RBAC at install and organization level; an agent reaches only its allowed projects and threads" },
{ "id": "REQ-USER-2", "title": "Only a thread's owning user, their assistant and the chief of staff's replies reach the thread" },
{ "id": "REQ-THREAD-1", "title": "Threads live under projects" },
{ "id": "REQ-THREAD-2", "title": "The user creates projects and named threads; each thread is its own assistant session with kept history" },
{ "id": "REQ-THREAD-2", "title": "The user creates projects and named threads; a thread holds independent assistant sessions, each with kept history" },
{ "id": "REQ-TPL-1", "title": "Templates for agent roles and for organization types" },
{ "id": "REQ-FED-1", "title": "Federation between installs: person-to-person in v1, agent-to-agent post-v1" },
{ "id": "REQ-USER-3", "title": "Several humans per organization: the owner, and users with collaborator or viewer grants per project and instance" },
{ "id": "REQ-USER-4", "title": "A thread is private to its user unless shared read-only; a share never widens a grant and ends on revoke" },
{ "id": "REQ-LIFE-1", "title": "The user creates, launches, terminates, regenerates and deletes agents from the CLI and the Console" },
{ "id": "REQ-KILL-1", "title": "One human command or Console button stops all work, runs, Discord and launches on an instance until a human clears it" },
{ "id": "REQ-SESS-1", "title": "Each session shows its lifetime count of completed compactions on pi and Claude Code; an unproven count shows unknown" },
{ "id": "REQ-SESS-2", "title": "Clear context retires a session after a safe stop and starts a fresh successor with none of the old context" },
{ "id": "REQ-SESS-3", "title": "Every session start passes the launch checks: authority, route, revocation, kill switch, limits and admission" },
{ "id": "REQ-LIB-1", "title": "A library maps the user's writing repository onto universes, books, types and playgrounds; only the human changes it" },
{ "id": "REQ-LIB-2", "title": "Scope comes from the path; lookups default to canon and a playground file is never cited as canon" },
{ "id": "REQ-LIB-3", "title": "Universes stay separate; a crossover is a hybrid universe only a human creates, reading its sources' canon" },
+3 -3
View File
@@ -18,10 +18,10 @@ test("the list holds the PRD's requirement ids, in its order, at its version", (
const ids = [...prd.matchAll(/^- \*\*(REQ-[A-Z]+-[0-9]+)\.\*\*/gm)].map((m) => m[1]);
// Any other mention of a bold id would be a bullet this pattern missed.
assert.equal(ids.length, [...prd.matchAll(/\*\*REQ-/g)].length);
assert.equal(ids.length, 82);
assert.equal(ids.length, 85);
assert.deepEqual(list.requirements.map((r) => r.id), ids);
assert.match(prd, new RegExp(`^- Status: approved, version ${list.version.replace('.', '\\.')}\\.`, 'm'));
assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.12']);
assert.deepEqual([list.prd, list.version], ['docs/prd/mosaic-stack.md', '1.13']);
});
test('the committed list loads frozen', () => {
@@ -74,7 +74,7 @@ test('the schema refuses any other shape', () => {
const twice = raw();
twice.requirements.push(structuredClone(twice.requirements[0]));
writeFileSync(join(dir, 'twice.json'), JSON.stringify(twice));
assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[82].id: REQ-ROLE-1 twice'));
assert.throws(() => loadRequirements(join(dir, 'twice.json')), listError('requirements[85].id: REQ-ROLE-1 twice'));
} finally {
rmSync(dir, { recursive: true, force: true });
}