gosalyn: Case 2 design r1 for #416 (T2), seat README and session line
Library record repo with canon/playground scope by path, human-only library verbs, cited-reply guard, five creative roles, rooms with @tags without a schema change, Obsidian through a Gitea remote, and rows C2-01..C2-17.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Gosalyn
|
||||
|
||||
Worker seat for Mosaic Stack, MVP track T2 (Case 2, the creative writing hub, Vikunja #416).
|
||||
|
||||
- T3 thread: 7604438a-0ded-41c8-bd3f-26f57fdb5cd2
|
||||
- Model: Opus 5.5 (Claude Code, T3)
|
||||
- Launched by Mos (Jason's main agent, T3 38bc4c81-6270-4021-b00c-6a15c43b9b0e), who briefs the seat
|
||||
- Repository lead and queue: Sage (T3 1ef1e4f8-3ead-4208-beca-38f9f1add079)
|
||||
- Branch: gosalyn/case2-design, pushed only by this seat, landed on refactor by Sage
|
||||
|
||||
Work lives in `work/`. The Case 2 design is `work/case2/DESIGN.md`.
|
||||
@@ -0,0 +1,460 @@
|
||||
# Case 2 design: the creative writing hub
|
||||
|
||||
Gosalyn, 2026-10-20 (from `date -u`). Track T2 in jason-master-restart `projects/mosaic-mvp/PLAN.md`,
|
||||
Vikunja #416, DECISIONS Q176-Q178. The requirement is Jason's text in TARGET.md (addendum 2026-10-20).
|
||||
This is a design only. No product code changes here.
|
||||
|
||||
Base: `refactor` at 5d019d876. Every `file:line` below was read at that commit. Paths are relative to
|
||||
the repository root.
|
||||
|
||||
## 0. Summary
|
||||
|
||||
Melanie's writing lives in one git repository, the library. It is a project record repo with a new
|
||||
layout kind (section 1). The bus commits to it for her, from the Console editor, and pushes every
|
||||
commit to a Gitea remote. Her Obsidian vault is a clone of that remote, so the graph and backlinks
|
||||
come from Obsidian itself. Agents can't write to the library at all. They read it through typed
|
||||
lookup tools, and every reply they send her must be a list of short items. Each item cites the
|
||||
library or a fetched web page, and the broker checks each citation before the message is stored.
|
||||
Several creative agents share a project. The Console shows their threads as one chat, and a leading
|
||||
`@name` sends a message to that agent only.
|
||||
|
||||
Decisions taken in this design (D-codes are this document's own):
|
||||
|
||||
- D1. The library is a record repo kind (`library`), not a code checkout. That reuses the lock,
|
||||
clean-tree check, atomic publish and rollback in `packages/project/src/index.mjs:195-234`.
|
||||
- D2. Scope comes from the path. `universes/` is canon and `playground/` is playground. No file
|
||||
content can change a file's scope.
|
||||
- D3. Agents get no write path into the library. That covers canon, playground and notes. This is
|
||||
stricter than the requirement asks, and it is the cheapest guardrail to prove.
|
||||
- D4. One chat is a project. Each creative agent holds its own thread in that project. This needs
|
||||
no change to the `threads` table or its triggers (section 6).
|
||||
- D5. The remote URL lives in the business file and is passed on the git command line. The repo's
|
||||
`.git/config` keeps its no-remote rule (`packages/project/src/git.mjs:44`).
|
||||
- D6. The Gitea remote is where the bus and Obsidian meet. The bus only fast-forwards and never
|
||||
merges. Obsidian's git plugin does its own merges on Melanie's machine.
|
||||
|
||||
## 1. Data model
|
||||
|
||||
### 1.1 Layout
|
||||
|
||||
```
|
||||
library.json {layoutVersion: 1, kind: "library", business, project, name, createdAt}
|
||||
.obsidian/app.json committed vault settings (section 7)
|
||||
.obsidian/graph.json
|
||||
universes/<u>/universe.json {id, name, kind: "canon" | "hybrid", sources?, constraints?, sanctioned?}
|
||||
universes/<u>/<type>/<id>.md universe-level entity, shared across books
|
||||
universes/<u>/books/<b>/book.json {id, title, order}
|
||||
universes/<u>/books/<b>/manuscript/<nn>-<slug>.md Melanie's prose
|
||||
universes/<u>/books/<b>/<type>/<id>.md book-level entity
|
||||
playground/universes/<u>/... universe playground, including draft universes
|
||||
playground/universes/<u>/books/<b>/... book playground
|
||||
```
|
||||
|
||||
- `<type>` is one of a closed list: `characters`, `places`, `items`, `groups`, `events`, `lore`. The
|
||||
list is a constant in the new package. Adding a type is a reviewed commit.
|
||||
- `<u>`, `<b>` and `<id>` match the project id pattern `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`
|
||||
(`packages/project/src/paths.mjs:7`), lowercased by convention.
|
||||
- This is Jason's `universe.book.{character,place,etc}`, read as a path. One assumption: a character
|
||||
who spans books lives at universe level, and a book-level file may add `extends:` naming it. If
|
||||
Melanie wants every entity strictly under a book, drop the universe-level `<type>/` directories.
|
||||
Nothing else changes.
|
||||
|
||||
### 1.2 Entity files
|
||||
|
||||
An entity is Markdown with front matter and wikilinks. Obsidian reads both natively.
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: character
|
||||
id: mara-vell
|
||||
name: Mara Vell
|
||||
aliases: [Mara, The Archivist]
|
||||
extends: universes/hollow-sea/characters/mara-vell # book level only, optional
|
||||
---
|
||||
Mara grew up in [[universes/hollow-sea/places/saltmarket]] ...
|
||||
```
|
||||
|
||||
- Front matter is a restricted subset: `key: scalar` and `key: [a, b]`. The new parser refuses
|
||||
anything else (nested maps, anchors, multi-line strings) with the line number. The repo has no YAML
|
||||
parser today. The editor only keeps front matter byte for byte (`packages/webui/editor/model.mjs:220-231`).
|
||||
- `type` must equal the directory, and `id` must equal the file name.
|
||||
- Links are `[[path-without-.md]]` or `[[path|label]]`, written as absolute vault paths. The committed
|
||||
`.obsidian/app.json` sets Obsidian's new-link format to absolute, so links Obsidian writes pass
|
||||
validation. A bare `[[name]]` resolves only if exactly one file has that base name in the same
|
||||
universe. Otherwise it is a validation problem.
|
||||
|
||||
### 1.3 Structural rules (the validator, `checkLibrary`)
|
||||
|
||||
The validator runs on a commit, not on the working tree. It reads blobs with `git ls-tree` and
|
||||
`cat-file` the way `packages/project/src/read.mjs:67-86` does.
|
||||
|
||||
| Code | Rule | Refusal code |
|
||||
|---|---|---|
|
||||
| S1 | Top level holds only `library.json`, `.obsidian/`, `universes/`, `playground/`, `.git` | `library-unknown-path` |
|
||||
| S2 | A canon file (`universes/...`) links only to canon files | `library-canon-links-playground` |
|
||||
| S3 | A canon file in universe U links only inside U. A hybrid also links into its declared sources | `library-cross-universe` |
|
||||
| S4 | Front matter parses, `type` matches the directory, `id` matches the file name | `library-entity-invalid` |
|
||||
| S5 | Every link resolves to exactly one file | `library-link-unresolved` |
|
||||
| S6 | A hybrid names 2 or more `sources`, each an existing canon universe, and no source is itself a hybrid | `library-hybrid-invalid` |
|
||||
| S7 | Hybrid constraints hold (1.5) | `library-constraint` |
|
||||
| S8 | `playground/` may link anywhere. It is never read as canon (sections 3 and 5) | none |
|
||||
|
||||
S2 is what keeps playground and canon apart. Canon can never depend on a playground file. Lookups
|
||||
default to canon. A citation's scope comes from its path. A finding that cites a playground path is
|
||||
refused (section 5).
|
||||
|
||||
### 1.4 Promotion
|
||||
|
||||
Promotion moves a playground file or a draft universe into canon. Only Melanie does it, from a
|
||||
Console button. The verb `library.promote {from, to}` is human only (section 2.4). It moves the
|
||||
files in one commit and runs the validator on the result. If the promoted file still links into the
|
||||
playground, S2 refuses the promotion and names the link.
|
||||
|
||||
### 1.5 Hybrid crossover
|
||||
|
||||
- Only the human verb `library.crossover.create {id, name, sources, constraints}` creates a hybrid.
|
||||
The Console dialog lists both sources and the constraints, and Melanie types the new universe's
|
||||
name to confirm. That is the deliberate action. Agents have no tool that writes anything (D3), so
|
||||
no agent can create one.
|
||||
- The verb writes `universes/<h>/universe.json` with `kind: "hybrid"`, `sources`, `constraints` and
|
||||
`sanctioned: {by: <business human>, at}` in one commit.
|
||||
- Reading both sources: in a hybrid's scope, lookups return the hybrid's own files plus the sources'
|
||||
canon files. Each result is labelled with its source universe. A hybrid can override a source
|
||||
entity with `extends:`.
|
||||
- The code checks two constraint kinds:
|
||||
- `{"exclude": "<source path or source/type>"}`. Lookups in the hybrid never return it, and a
|
||||
hybrid file that links it fails S7.
|
||||
- `{"pin": {"entity": path, "field": name, "value": v}}`. A hybrid override may not change that
|
||||
front matter field.
|
||||
- Constraints written in plain words go in `universe.json` `notes`. Code can't check those. The
|
||||
consistency role reads them and cites them in its findings.
|
||||
|
||||
## 2. What exists and how Case 2 uses it
|
||||
|
||||
### 2.1 Project record repos (`packages/project`)
|
||||
|
||||
- Location and safety: `projectRepo` (`paths.mjs:34-48`) puts the repo in
|
||||
`<dataRoot>/projects/<business>/<project>` and refuses overlap with a code root and symlinked
|
||||
ancestors. Case 2 reuses it as is.
|
||||
- Commit path: `commitFile` (`index.mjs:195-234`) handles the lock, `inspectGit`, `requireClean`,
|
||||
layout check, publish, commit and rollback. Case 2 refactors it to take the layout check as a
|
||||
parameter, because it calls `checkProject` directly at `index.mjs:209`. Then the library writer
|
||||
calls it with `checkLibrary`. The project tests must stay green unchanged.
|
||||
- Human writer: `writeHumanFile` (`index.mjs:248-261`) is the model for `writeLibraryFile`. The
|
||||
differences are the path rule, creating new files (it refuses a missing file at `:256`), and
|
||||
the commit that follows.
|
||||
- Layout: `checkLayout` (`index.mjs:31-84`) flags any top-level path outside `DIRECTORIES`
|
||||
(`:12`, `:36`). A library can't pass it, so `kind` in `library.json` selects the library checker.
|
||||
`initProject` (`:97-156`) is the model for `initLibrary`.
|
||||
- Identity: commits use `identity(name)` (`git.mjs:55-56`) with the business human's id, as the
|
||||
human writer does at `index.mjs:254`.
|
||||
- Remotes: `inspectGit` refuses any `remote.*` key (`git.mjs:44`), and `SAFE` sets
|
||||
`protocol.allow=never` (`git.mjs:8`). Under D5 both stay. Sync passes the URL as an argument and
|
||||
adds `-c protocol.https.allow=always` only to its own fetch and push (section 7.2).
|
||||
- Business file: project entries accept only `root` and `repo` (`packages/business/src/business.mjs:186`),
|
||||
and `root` is required (`:187`). C2-04 adds `kind: "library"` and `remote`, and lets a library
|
||||
project leave out `root`. `projectRepo`'s overlap loop (`paths.mjs:41-44`) then skips projects
|
||||
without a root. The launcher clones every project root into each agent workspace
|
||||
(`packages/cli/src/launcher.mjs:353`), so a project with no root gives agents no clone.
|
||||
|
||||
### 2.2 ProseMirror editor (`packages/webui/editor`)
|
||||
|
||||
- It edits Markdown (`model.mjs:17-18`). Blocks it can't draw, front matter included, are kept byte
|
||||
for byte (`model.mjs:3-11`, `:220-231`).
|
||||
- The Console opens the visual editor only if `roundTrip(text) === text`, and opens Source otherwise
|
||||
(`packages/webui/src/public/bus.js:919-920`). The commonmark rules (`model.mjs:119-120`) have no
|
||||
wikilink rule. Whether the serializer escapes `[[` was not measured. Row C2-11's first test
|
||||
measures it. Either way, a wikilink node with an exact serializer is needed for wikilinks to show
|
||||
as links.
|
||||
- Load and save: `GET/POST /api/project-file` (`bus.js:907`, `:969-974`) go to `serve.mjs:545-547`
|
||||
and `591-593`, then bus verbs `project.file` and `project.file.save` (`packages/webui/src/cli.mjs:75-76`),
|
||||
then `broker.mjs:1434-1458`. The path allowlist is in two places: `documentPath` (`paths.mjs:14-15`)
|
||||
and `DOC_PATH` (`bus.js:885`). The listing only lists `spec/north-star.md` and `docs/` (`read.mjs:54`).
|
||||
- Case 2 adds library verbs next to these instead of widening `documentPath`. That keeps the record
|
||||
repo rules for every existing project unchanged.
|
||||
|
||||
### 2.3 Roles (`roles/*.json`, `packages/business/src/role.mjs`)
|
||||
|
||||
- A role file is validated by `validateRoleDocument` (`role.mjs:138-169`). Its keys are closed
|
||||
(`:19-20`), `tools` must be non-empty and drawn from `TOOLS` (`:23-28`,
|
||||
`packages/business/src/vocabulary.mjs:92-97`), and `messaging` limits who it talks to (`:55-66`).
|
||||
- Tools reach the harness through `resolve.mjs:60-64`, then `bundle.mjs:247-256` (`policy.json`),
|
||||
then `gate.mjs:130-138`, which refuses any built-in not in the policy.
|
||||
- `roles/researcher.json` is a version 1 role with `bash`. It isn't reused. The Case 2 research role
|
||||
is a new file.
|
||||
|
||||
### 2.4 Tool gate and typed tools (`packages/harness/src`)
|
||||
|
||||
- `decide()` (`gate.mjs:130-167`) allows a typed tool by name (`:134`), allows a built-in only if the
|
||||
policy holds it (`:137-138`), and keeps every file path inside the workspace (`:146-156`). The
|
||||
Claude Code hook turns a refusal into exit 2 (`claude-gate.mjs:13`).
|
||||
- Typed tools are generated per session from held actions (`tools.mjs:91-261`). `write_record`
|
||||
exists only for `record.write` holders (`:211-220`). `send_message` exists for `message.send`
|
||||
holders (`:95-102`).
|
||||
- Case 2 adds the lookup tools (section 3) to `typedTools` when the business has a library project.
|
||||
Creative roles never hold `record.write`, so they never get `write_record`.
|
||||
|
||||
### 2.5 Threads and messages (`packages/bus`)
|
||||
|
||||
- `thread.create` picks the single instance whose definition is `assistant` and refuses more than
|
||||
one (`broker.mjs:2331-2334`). It stores that instance on the thread (`:2336-2347`). The same rule
|
||||
appears in `#threaded` (`:290-291`), in `vocabulary.mjs:131`, and as a second copy in
|
||||
`launcher.mjs:135` and `:236-238`.
|
||||
- `thread.send` stores one message to `t.instance` and routes it (`broker.mjs:2374-2399`, `#route`
|
||||
`:2541-2544`). Triggers require every threaded message to be to or from the thread's instance
|
||||
(`schema.sql:296-299`) and deliver only to the thread's holder (`:300-306`). `threads` can't be
|
||||
updated (`schema.sql:310`).
|
||||
- The runner sends the model's final text as `message.send` RESULT to the sender
|
||||
(`packages/harness/src/runner.mjs:446-450`). A refusal is logged and the message is still marked
|
||||
read (`:451-456`), so the answer is lost.
|
||||
- Sessions are per thread: `threadStore(dataRoot, business, thread)` (`packages/seat/src/session.mjs:59-64`)
|
||||
and the Claude session id file under it (`adapters/claude/adapter.sh:47-53`). One thread per agent
|
||||
therefore means one session per agent, with nothing new to build.
|
||||
|
||||
## 3. Agent lookup tools
|
||||
|
||||
These are broker verbs with typed tools. They exist for every agent session in a business that has
|
||||
a library project. Each reads the library at its current HEAD commit and returns that commit.
|
||||
|
||||
| Tool | Arguments | Returns |
|
||||
|---|---|---|
|
||||
| `library_find` | `universe` (required), `query`, `type?`, `book?`, `scope?` (`canon` default, or `playground`) | up to 50 `{path, type, id, name, aliases, scope, universe, book}` |
|
||||
| `library_get` | `path` | `{path, commit, scope, frontmatter, lines: [[n, text]], links_out, links_in}` |
|
||||
| `library_search` | `universe`, `text`, `book?`, `scope?` | up to 50 `{path, line, excerpt, scope}` |
|
||||
| `library_manuscript` | `universe`, `book`, `chapter?`, `from?`, `to?` | numbered lines of one chapter, at most 400 per call |
|
||||
| `library_universe` | `universe` | `universe.json`, its books, and for a hybrid the sources, constraints and notes |
|
||||
|
||||
- Universe isolation is in code. `universe` is required, and results never leave it. The only
|
||||
exception is a hybrid's declared sources, minus exclusions.
|
||||
- Scope is labelled on every result, from the path (D2). `scope: "playground"` is opt-in.
|
||||
- Replies stay under `REPLY_CAP` (`tools.mjs:271-280`) by paging. They are never cut.
|
||||
- The index (entities, aliases, links and backlinks) is built per commit and cached in memory by
|
||||
commit id. A library of a few thousand files rebuilds in well under a second. This is an estimate,
|
||||
and C2-05a's gate measures it.
|
||||
|
||||
## 4. Creative roles
|
||||
|
||||
Five new role definitions. Each is a version 2 role file plus a `.md` contract.
|
||||
|
||||
| Role | Purpose | `tools` | Reply item kinds |
|
||||
|---|---|---|---|
|
||||
| `ideas` | Brainstorming, mostly in the playground | `read` | `idea`, `question` |
|
||||
| `research` | Real-world facts from the web | `read`, `web` | `fact`, `question` |
|
||||
| `proofreader` | Spelling, grammar, punctuation | `read` | `correction`, `question` |
|
||||
| `fact-checker` | Text against canon entities and research | `read` | `finding`, `question` |
|
||||
| `continuity` | Consistency across books, timeline and hybrid constraints | `read` | `finding`, `question` |
|
||||
|
||||
All five have `authority: {withinRole: ["message.send"], crossRole: []}`, `credentials: []`, and
|
||||
`messaging: {sendTo: ["human"], receiveFrom: ["human"]}`, so they can't relay through each other.
|
||||
`network` is `none`, except `api-only` for `research`.
|
||||
|
||||
`read` is there because `role.mjs:24` requires a non-empty tool list. The workspace has no library
|
||||
clone (2.1), so `read` reaches nothing useful. All library reads go through the cited lookup tools.
|
||||
|
||||
New constants in `vocabulary.mjs`, each changed only by a reviewed commit:
|
||||
|
||||
- `THREAD_DEFINITIONS = ["assistant", "ideas", "research", "proofreader", "fact-checker", "continuity"]`.
|
||||
This replaces the single `THREAD_DEFINITION` wherever it means "may hold a thread". The definition
|
||||
the chief of staff may set stays `assistant` (`tools.mjs:186`).
|
||||
- `CREATIVE_DEFINITIONS`, the five above.
|
||||
- `CREATIVE_KINDS`, mapping each definition to the item kinds in the table.
|
||||
- `CREATIVE_TOOLS = ["read", "grep", "find", "ls", "web"]`. `validateRoleDocument` refuses a creative
|
||||
definition whose `tools` include anything else, or whose authority holds anything but
|
||||
`message.send`.
|
||||
|
||||
## 5. Guardrails enforced in code
|
||||
|
||||
### 5.1 Agents never write prose for the user
|
||||
|
||||
| Mechanism | What it refuses | Test |
|
||||
|---|---|---|
|
||||
| M1 role validator (`role.mjs` with `CREATIVE_TOOLS`) | A creative role file with `write`, `edit` or `bash`, or any authority but `message.send` | C2-08: load each rejected variant, assert the refusal text |
|
||||
| M2 tool gate (`gate.mjs:137-138`, unchanged) | `Write`, `Edit` or `Bash` from a creative session | C2-08: `decide()` with each creative policy refuses all three, for pi and claude-code |
|
||||
| M3 typed tool set (`tools.mjs`) | No write tool exists. The tool list is exact | C2-05b: snapshot of tool names for each creative role |
|
||||
| M4 library verbs are human only | `library.file.save`, `library.promote`, `library.crossover.create` and `library.sync` from an agent session (`human-required`, as at `broker.mjs:1434-1435`) | C2-04: each verb from an agent session refuses and writes nothing (HEAD unchanged) |
|
||||
| M5 cited-reply guard (5.2) | A reply that isn't a list of short cited items: free text, an item over 400 characters, or a `correction` whose `to` differs from its `from` by more than max(12, 25% of `from`) characters of edit distance | C2-06: each case refused, boundary values accepted |
|
||||
|
||||
Limit: M5 caps an item at 400 characters and holds proofreading to small edits. A one-sentence
|
||||
suggestion can still fit inside an `idea` item. Code can't tell a sentence of story from a sentence
|
||||
of advice. Closing that gap would need a classifier, which this design doesn't propose. Everything
|
||||
that lands in the library is hers: M2-M4 make an agent write there impossible, not just disallowed.
|
||||
|
||||
### 5.2 Agents always refer to the source material
|
||||
|
||||
The cited-reply guard runs in the broker. It covers every message from an agent session to `human`
|
||||
in a business with a library project, whether it comes from the runner's final answer or from
|
||||
`send_message`. Role choice doesn't matter, so a misconfigured role can't skip it. Citation checks
|
||||
need git, so they run before the insert, outside the SQL transaction, the same way
|
||||
`#requestProject` does it (`broker.mjs:1381-1430`).
|
||||
|
||||
The reply body is JSON:
|
||||
|
||||
```json
|
||||
{"items": [{"kind": "finding", "text": "Mara's eyes are grey in ch. 3 but green on her entity page.",
|
||||
"cites": [{"path": "universes/hollow-sea/books/tides/manuscript/03-salt.md", "commit": "<40 hex>", "lines": [41, 41], "quote": "her grey eyes"},
|
||||
{"path": "universes/hollow-sea/characters/mara-vell.md", "commit": "<40 hex>", "lines": [9, 9], "quote": "green eyes"}]}]}
|
||||
```
|
||||
|
||||
| Check | Refusal code |
|
||||
|---|---|
|
||||
| Body parses as the schema, with 1 to 20 items | `cited-reply-required` |
|
||||
| Item kind is allowed for the sender's definition (`CREATIVE_KINDS`) | `reply-kind-not-allowed` |
|
||||
| Every item has at least one cite | `citation-missing` |
|
||||
| Path exists at `commit`, `commit` is reachable from library HEAD, lines are in range, and `quote` occurs in those lines after whitespace normalization | `citation-invalid` |
|
||||
| `fact`, `finding` and `correction` cite only canon paths | `playground-cited-as-canon` |
|
||||
| All library cites in an item are in one universe, or in a hybrid plus its sources | `citation-cross-universe` |
|
||||
| A `url` cite only from `research`, only for a URL this run fetched (the broker's web trail, `tools.mjs:197-198`), and the quote occurs in the fetched text | `citation-unfetched` |
|
||||
|
||||
Runner change (C2-07): when `message.send` refuses one of these codes, the runner starts a new turn
|
||||
in the same session with the refusal code and reason. It does this at most twice. Then it sends the
|
||||
fixed body `{"items": [], "notice": "no-cited-answer"}`, which the guard accepts as the one exception.
|
||||
Today the answer would just be dropped (`runner.mjs:451-456`).
|
||||
|
||||
Tests (C2-06): one passing and one refused case per row of the table. The fixture is a library repo
|
||||
built by the test. Each refusal must leave the `messages` count unchanged. C2-07 adds a runner test
|
||||
with a fake harness that answers free text, then a valid reply. It asserts one stored message.
|
||||
|
||||
### 5.3 Tool use is guarded
|
||||
|
||||
- Built-ins: M1 and M2 above. The gate is unchanged. The test proves creative policies hit it.
|
||||
- Typed tools: M3. The broker re-checks every call (`tools.mjs:6-7`). The lookup verbs refuse a
|
||||
session from a business without a library (`library-not-configured`) and a missing `universe`.
|
||||
- Messaging: `messaging.sendTo: ["human"]` is enforced by `#checkMessaging` on `message.send` (`broker.mjs:1867`). C2-08
|
||||
tests that a creative instance sending to another instance is refused.
|
||||
- `research` web use goes through the broker's web tools (`tools.mjs:199-206`), which already check
|
||||
the grant on every call.
|
||||
|
||||
## 6. One chat with N agents and @tag addressing
|
||||
|
||||
D4: the chat is a project. Each creative instance holds one thread in it. Today's tables carry this
|
||||
without a migration, because each thread still has exactly one instance, so `schema.sql:296-306`
|
||||
hold as written.
|
||||
|
||||
- `thread.create` takes `instance`. It must be a thread definition instance (`THREAD_DEFINITIONS`).
|
||||
If `instance` is left out and exactly one exists, today's behaviour holds (`broker.mjs:2331-2334`).
|
||||
- New verb `room.open {project}`. Human only. It creates the missing thread for each thread instance
|
||||
in the project, named after the instance.
|
||||
- New verb `room.send {project, body}`. Human only. Leading `@name` tokens, before the first other
|
||||
word, select the recipients. Names resolve through `#named` (`broker.mjs:642-650`) or instance
|
||||
keys. With no tag, every member gets the message. An unknown tag refuses `unknown-addressee` and
|
||||
inserts nothing. The broker inserts one message per target thread in one transaction, the same
|
||||
way `thread.send` does (`:2383-2397`). Each message keeps the full body, tags included.
|
||||
- `room.read {project, after?}` returns the merged timeline of the project's threads, ordered by
|
||||
message sequence.
|
||||
- Launch: the launcher's copy of the thread rule (`launcher.mjs:135`, `:236-238`) reads
|
||||
`THREAD_DEFINITIONS`. A run starts per thread when that thread has waiting messages, as the
|
||||
assistant's does now.
|
||||
- Limit: an agent sees only its own thread, so it doesn't see what the other agents answered.
|
||||
Melanie can quote one agent's item to another. Showing agents each other's replies is row C2-16,
|
||||
not part of the 10-27 slice.
|
||||
|
||||
Tests (C2-09): `@continuity x` makes exactly one message, in continuity's thread. No tag makes N.
|
||||
`@nobody x` refuses with the message count unchanged. A tag after the first word is plain text.
|
||||
Two thread instances with no `instance` argument still refuse `thread-instance-ambiguous`.
|
||||
|
||||
## 7. Obsidian, Console WYSIWYG and sync
|
||||
|
||||
### 7.1 Obsidian
|
||||
|
||||
- The vault is a clone of the Gitea remote on Melanie's machine. The community plugin Obsidian Git
|
||||
does pull, commit and push on a timer.
|
||||
- `initLibrary` commits `.obsidian/app.json` with `newLinkFormat: "absolute"` and wikilinks on.
|
||||
It also commits `.obsidian/graph.json` with colour groups `path:universes` and `path:playground`,
|
||||
so playground nodes always look different in the graph. Other `.obsidian/` files are her machine's
|
||||
business, and a committed `.gitignore` entry keeps workspace files out.
|
||||
- The graph view and backlinks are Obsidian's own. No code builds them. The Console shows backlinks
|
||||
per entity from the lookup index (`links_in`).
|
||||
|
||||
### 7.2 Remote sync (bus side)
|
||||
|
||||
Under the library lock, `library.sync` and every library write do these steps:
|
||||
|
||||
1. `git fetch <remote-url> main:refs/remotes/library/main`. The token comes from the secrets slot
|
||||
through a `GIT_ASKPASS` helper. It is never in the URL, `.git/config`, logs or replies (CONSTITUTION
|
||||
gate 13). `protocol.https.allow=always` is added for this command only.
|
||||
2. If remote main is ahead: run `checkLibrary` on it. If it passes, fast-forward. If it fails, stay
|
||||
on the last valid commit and record the problems. The Console shows them with file and line,
|
||||
and lookups keep serving the last valid commit. This is fail-closed: an Obsidian edit that
|
||||
breaks S2 doesn't reach the agents.
|
||||
3. Write and commit (the `commitFile` path, with the `stale-base` guard from `index.mjs:257-258`).
|
||||
4. Push. If the remote moved in between: undo the local commit, go back to step 1, then retry once.
|
||||
If the file changed meanwhile, the guard refuses `stale-base`.
|
||||
5. If the push fails for any other reason, the commit stays local, marked `unpushed`, and the
|
||||
Console shows it. The next sync pushes it. If the remote moved too, sync refuses
|
||||
`library-diverged` and lists the files. No automatic merge happens in the MVP.
|
||||
|
||||
`library.sync` also runs on a 60-second timer in the bus and before every lookup that finds the
|
||||
last sync older than 60 seconds.
|
||||
|
||||
### 7.3 Console WYSIWYG
|
||||
|
||||
- Library pages show a tree of universes, books, entity types, manuscript and playground. Playground
|
||||
has its own section and colour. They open files in the existing editor through `library.file` and
|
||||
`library.file.save`, with `base` as today.
|
||||
- New entity: a type and id form creates the file from a front-matter template.
|
||||
- Promote and Create crossover buttons (1.4, 1.5).
|
||||
- Wikilinks render as links in the editor (C2-11a). Clicking one opens the target.
|
||||
|
||||
### 7.4 User and agent sync
|
||||
|
||||
- Agents read through the broker at the library's HEAD after sync. They never have a stale private
|
||||
copy (2.1).
|
||||
- Every reply cites a commit. The Console marks a citation "changed since" when the cited file's last
|
||||
commit is newer than the cited one.
|
||||
- The window is the sync interval. An Obsidian push becomes visible to agents within about 60
|
||||
seconds, and a Console save at once.
|
||||
|
||||
## 8. Row list
|
||||
|
||||
Owners follow Q177 and Q178. Opus 5.5 takes hard design and builds. Haiku 5.5 at high effort takes
|
||||
mid work as Sage-dispatched subagents (lead decision 129). Codex gpt-6.1-sol (Rocko) takes parallel
|
||||
builds. gpt-6-luna takes low-level work. Every row gets an independent review by Filbert or Dewey,
|
||||
and its gate is its package's test files run singly plus the full gate before landing. Chains follow
|
||||
Q177 merge-chaining: a row builds on its parent's candidate.
|
||||
|
||||
| Row | Scope | Builds on | Owner | Test gate |
|
||||
|---|---|---|---|---|
|
||||
| C2-01 | `packages/library`: layout constants, front matter subset parser, wikilink parser, `checkLibrary(repo, commit)` with S1-S8 | none | Opus (Gosalyn) | `packages/library/tests/validate.test.mjs`: one pass and one refusal per S-rule, parser refusal with line number |
|
||||
| C2-02 | `initLibrary`, `writeLibraryFile` (create, update with `base`), `promoteLibrary`, `createCrossover`. Refactor `commitFile` to take a check function | C2-01 | Sol (Rocko) | library writer tests, plus `packages/project/tests` unchanged and green |
|
||||
| C2-03 | Remote sync (7.2): askpass from the secrets slot, fetch, validate, fast-forward, push, race retry, `unpushed`, `library-diverged` | C2-02 | Opus | tests against a local bare repo as the remote (file protocol allowed by a test-only argument). A grep test that the token never appears in argv, config or logs |
|
||||
| C2-04 | Business file `kind: "library"`, `remote`, optional `root`. Human-only bus verbs `library.list`, `library.file`, `library.file.save`, `library.promote`, `library.crossover.create`, `library.sync`. `projectRepo` overlap loop skips no-root projects | C2-02 | Sol (Rocko) | `packages/business` tests for the file rules. Broker tests: each verb from an agent session refuses with HEAD unchanged (M4) |
|
||||
| C2-05a | Lookup index and queries in `packages/library`: find, get, search, manuscript, universe, hybrid sources and exclusions, universe isolation | C2-01 | Haiku high | index tests: isolation, hybrid exclusion, scope labels, paging under `REPLY_CAP`, rebuild time on a 3000-file fixture |
|
||||
| C2-05b | Broker lookup verbs and typed tools in `tools.mjs`. Sync-before-read if older than 60 s | C2-05a, C2-04 | Sol (Rocko) | broker verb tests. Typed tool name snapshot per creative role (M3) |
|
||||
| C2-06 | Cited-reply guard (5.2) in `packages/library` (pure, given a blob reader) and the broker's async pre-check on agent-to-human `message.send` | C2-05a | Opus | one pass and one refusal per check in 5.1 M5 and 5.2. `messages` count unchanged on refusal |
|
||||
| C2-07 | Runner: re-turn on a guard refusal (max 2), then the fixed notice | C2-06 | Haiku high | runner test with a fake harness: free text, then valid, gives one stored message |
|
||||
| C2-08 | Five role files and contracts. `THREAD_DEFINITIONS`, `CREATIVE_DEFINITIONS`, `CREATIVE_KINDS`, `CREATIVE_TOOLS`. Role validator check (M1). Gate tests (M2). Messaging test | none | Haiku high | role tests, gate tests, `scripts/test-config.sh` |
|
||||
| C2-09 | Broker `#threaded` and launcher read `THREAD_DEFINITIONS`. `thread.create` takes `instance`. `room.open`, `room.send` with @tags, `room.read` | C2-08 | Sol (Rocko) | broker tests in section 6. Launcher test: a creative instance needs a thread |
|
||||
| C2-10 | Console room view: merged timeline, composer with @name completion, cited items rendered with scope badges and "changed since" | C2-09, C2-06 | Haiku high (Dewey reviews) | webui tests for rendering and @tag parse parity with the broker |
|
||||
| C2-11a | Editor wikilink node: markdown-it rule and an exact serializer. First measure whether `[[` round-trips today | none | Sol (Rocko) | `roundTrip` tests: plain, labelled, inside a list, next to front matter, escaped brackets |
|
||||
| C2-11b | Console library pages: tree, open and save, new entity, promote, crossover dialog with typed confirmation, sync status and validation problems | C2-04, C2-11a | Haiku high (Dewey reviews) | webui tests. Save refuses `stale-base` in the UI |
|
||||
| C2-12 | Committed `.obsidian/app.json`, `graph.json` and `.gitignore` in `initLibrary`. A five-step setup sheet for Melanie (clone, Obsidian Git, settings) | C2-02 | Luna | init test asserts the files and settings |
|
||||
| C2-13 | Melanie's instance: business file (library project, five creative instances, human id), Gitea repo, token in the slot. Needs T1 row 1. Instance start is a Mos or Jason go | T1.1, C2-04 | Opus (Gosalyn), go by Mos | precheck script: config validates, `library.sync` round-trips one commit to Gitea |
|
||||
| C2-14 | End-to-end acceptance on a scratch data root: create a universe and an entity, edit in the Console, see the commit in a clone, ask `@continuity`, get a reply whose citations verify, playground citation refused as a finding | all above | Opus | one script, run by the reviewer too |
|
||||
| C2-15 | Hybrid constraint kinds beyond `exclude` and `pin` | C2-05a | later | |
|
||||
| C2-16 | Agents see other agents' replies in a room | C2-09 | later | |
|
||||
| C2-17 | Automatic replay of unpushed commits on divergence | C2-03 | later | |
|
||||
|
||||
Parallel waves:
|
||||
|
||||
- Start now: C2-01, C2-08, C2-11a.
|
||||
- On C2-01's candidate: C2-02, C2-05a, C2-06. C2-09 runs on C2-08's.
|
||||
- Next: C2-03, C2-04, C2-07, C2-12, then C2-05b and C2-10.
|
||||
- Last: C2-11b, then C2-13 and C2-14.
|
||||
|
||||
Dependencies on other tracks: T1 row 1 (several instances per host) blocks C2-13. T1 row 2 (SSO,
|
||||
Q175) gates Melanie reaching the Console from her own machine. T1 row 7 owns compaction count and
|
||||
clear context (C2.12, C2.13 in GAP-MAP). Case 2 adds nothing there. Until then, a new thread is a
|
||||
fresh context (`session.mjs:59-64`).
|
||||
|
||||
## 9. What can't be ready by 2026-10-27
|
||||
|
||||
Melanie can't use Case 2 on her own instance by 10-27 unless T1 row 1 lands by about 10-24, and she
|
||||
can't reach the Console from her own machine unless SSO (Q175) lands too. Everything in C2-01 to
|
||||
C2-12 can be built and reviewed in the week if the waves above start on 10-21. C2-15 to C2-17 won't
|
||||
be ready.
|
||||
|
||||
The smallest slice that lets her start: C2-01, C2-02, C2-03, C2-04, C2-05a, C2-05b, C2-06, C2-07,
|
||||
C2-08, C2-12 and C2-13. With those she writes in Obsidian on her own machine, with the graph view and
|
||||
playground colours, synced through Gitea. She chats with each creative agent in its own thread, and
|
||||
every answer is cited and checked. The room with @tags (C2-09, C2-10) and the Console library editor
|
||||
(C2-11a, C2-11b) follow as soon as they land, because Obsidian covers writing until then.
|
||||
@@ -1357,3 +1357,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-20T15:26:33Z | Sage (Opus 5.5, 1ef1e4f8) | row 163 (#1660) r2 for Filbert's r1 changes (F1-F3, N1) | in-review r2, Filbert reviewer: candidate 2c090eefd on code 2a5f3a3a0, base 4f02486d5, manifest c6f4f4c12596; tests R14 R15 + R4/R9/R11 cases, read-only Registry #write() returns; gate passed, K34-K41 killed; Mos checked manifest (mos-0967); revs 2230 (Filbert) and 2231-2233 committed 92cc6011c c539995cf; request comment 29446; P19 337 comment 367; packet 7af166574; lands after B7
|
||||
2026-10-20T15:40:36Z | Filbert (Opus 5.5, 048f3c63) | row 163 r2 review (D2 rehearse, candidate 2c090eefd, manifest c6f4f4c12596) | approve, comment 29447 rev 2234: 249/249 singles pass; my M1-M6 and N1 mutants all killed (R14, R9, R11, R4, R15, session test)
|
||||
2026-10-20T15:42:00Z | Sage (Opus 5.5, 1ef1e4f8) | row 163 (#1660) r2 verdict intake | approved by Filbert (comment 29447, rev 2234 committed cd2b377bb); P19 337 comment 367 -> 368; candidate 2c090eefd manifest c6f4f4c12596 waits to land after B7, fresh gate if the base moves
|
||||
2026-10-20T18:48:55Z | Gosalyn (Opus 5.5, 7604438a) | T2 Case 2 design (#416), agents/gosalyn/work/case2/DESIGN.md on gosalyn/case2-design, static read at 5d019d876 | design r1 committed, review requested from Dewey, rows C2-01..C2-17 proposed, briefs follow approval
|
||||
|
||||
Reference in New Issue
Block a user