From aaaf45576ab00a0c0beeb3694de905285f9ed827 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 22 Oct 2026 11:10:36 -0500 Subject: [PATCH] gosalyn: Case 2 design ruling after r11, reply limits are settings: every M5 limit is a replyLimits key in the business file with Q219's numbers as defaults, changed only by the human's library.limits.set and fenced in organizationWriter.write as M11, read at check time, with a missing or invalid value using its default. C2-06 carries it and waits on Q29 (Q222, mos-1302) --- agents/gosalyn/work/case2/DESIGN.md | 70 ++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/agents/gosalyn/work/case2/DESIGN.md b/agents/gosalyn/work/case2/DESIGN.md index 897ba113b..05de030ad 100644 --- a/agents/gosalyn/work/case2/DESIGN.md +++ b/agents/gosalyn/work/case2/DESIGN.md @@ -123,6 +123,60 @@ correction edit distance. The residual in 5.1 is now 360 characters of free sugg and 10 step 8 is closed by Jason. C2-06 hasn't landed, so it builds with the new numbers, and its tests refuse one past each new boundary and accept at it. +## Ruling after r11: reply limits are settings (Q222) + +Jason ruled Q222 (DECISIONS Q222, mos-1302): "Things should NOT be hardcoded." Q219 stands for now, and its +numbers become the defaults. Every M5 limit is a setting in a new top-level `replyLimits` object of the +business file: + +| Key | Default | Valid value | +|---|---|---| +| `itemsPerReply` | 10 | an integer from 1 to 1,000 | +| `itemTextPerReply` | 1,200 | an integer from 1 to 100,000 | +| `ideasPerReply` | 3 | an integer from 0 to 1,000 | +| `ideaLength` | 120 | an integer from 1 to 100,000 | +| `itemLength` | 300, for `fact`, `finding`, `correction` and `question` | an integer from 1 to 100,000 | +| `correctionMinDistance` | 12 | an integer from 0 to 100,000 | +| `correctionPercent` | 25 | an integer from 0 to 100 | + +A `correction`'s `to` is within edit distance max(`correctionMinDistance`, `correctionPercent`% of +`from`) of its `from`. + +Where. The object sits beside `projects`, not inside the library entry, because M9 keeps `projects` to +hand edits and Mos asked for a CLI verb. `TOP_KEYS` (`business.mjs:21`) gains `replyLimits`, and +`validateBusinessDocument` accepts any JSON value under it. A bad hand edit then never stops her business +from loading. + +Who changes them (M11). Only the business file's `human`, through a new human verb `library.limits.set` +with `revision` and `replyLimits`. She runs it as `mosaic limits replies --file `, a new verb in +`packages/cli/src/limits.mjs` beside `mosaic limits set`, and later from the Console. The verb replaces the whole object, and a key left out means its default. It sits outside +`ORGANIZATION_KEYS` and `TESTER_ORGANIZATION_VERBS`, as `instances.set` does (`broker.mjs:103`, `:145`, +sage-0918). An agent session gets `human-required` and the tester gets `tester-refused`. A value outside the +table refuses `invalid-request`, and nothing is written. The fence that covers every other path sits in +`organizationWriter.write` (`organization.mjs:177`), next to M9: a write whose `replyLimits` differs from +the file's, compared in canonical JSON, refuses `organization-reply-limits-human-only` unless `actor` equals +the file's `human`. That stops the chief of staff's `organization.set`, any actor's `limits.set`, +`instances.set`, `routes.set` and `plugins.set`, the tester, and a direct call to `write`. Every setter +builds its document from the file, so none of them changes `replyLimits` today. + +Read at check time. The guard reads `replyLimits` from the business file on each check, through the +organization reader, not from the broker's copy loaded at start (`broker.mjs:276-278`). A change applies to +the next reply with no restart. + +Fallback. Each key resolves on its own. A key that is missing, `null`, a string, a fraction, negative, +below its minimum or above its ceiling resolves to its default, and so does every key when `replyLimits` +is missing or isn't an object. Unknown keys are ignored. The guard has no path without a number, so no +value reads as "no limit". `library.status` shows the limits in force and names each key that fell back. +The ceilings are validity bounds, not policy: they keep a value from reading as unlimited. 100,000 +characters is far past any reply, and Mos can change them. + +What agents see. The contracts don't state numbers. A refusal reason names the key and its value in +force, so the runner's retry (C2-07) tells the agent the limit it broke. + +Hold. Q29 asks Jason whether "Agents NEVER write prose for the user" means story text for her books, with +long conversational answers allowed. A yes reworks M5's reply format, and that waits for his answer. The +settings carry over either way. C2-06 isn't built until Mos relays Q29. + ## Changes from r10 Webby returned r10 with B1-B3 and N1-N5 (webby-0036, `agents/webby/work/case2-r10/REVIEW.md` at @@ -417,7 +471,7 @@ the line says so. refuses to open one by id (5.1 M7, C2-10a in the slice). - B5. At most 10 items and 1,200 characters of item text per reply. An `idea` item is at most 200 characters, at most 5 per reply. Other kinds are at most 300 (5.1). Q219 later set ideas to at most 3 - of 120 characters (ruling after r11). + of 120 characters (ruling after r11), and Q222 made every limit a setting with those defaults. - B6. `correction.from` must equal one cite's `quote`, inside that cite's lines (5.2). - B7. The broker keeps fetched page text per session, bounded and in memory, not in a table. The bundle refuses the `harness-builtin` web source for creative definitions (5.2, 5.3). @@ -898,15 +952,18 @@ New constants in `vocabulary.mjs`, each changed only by a reviewed commit: | 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, on pi and claude-code | | M3 typed tool set (`tools.mjs`) | The tool list is exact: lookups and `send_message`. No write tool, no `raise_decision` | C2-05b: a snapshot of tool names per creative role | | M4 library verbs are human only | `library.file`, `library.sync` and `library.status` from an agent session, with `human-required` as at `broker.mjs:1434-1435`. Later write verbs join the list | C2-04 and C2-03a: each verb from an agent session refuses | -| M5 cited-reply guard (5.2) | A reply that isn't a list of short cited items. At most 10 items and 1,200 characters of item text per reply. An `idea` item is at most 120 characters, at most 3 per reply (Q219). A `fact`, `finding`, `correction` or `question` item is at most 300. A `correction`'s `to` is within edit distance max(12, 25% of `from`) of its `from` | C2-06: each limit refused one past the boundary and accepted at it | +| M5 cited-reply guard (5.2) | A reply that isn't a list of short cited items, within the `replyLimits` settings read at check time (Q222). The defaults: at most 10 items and 1,200 characters of item text per reply, an `idea` item at most 120 characters and at most 3 per reply (Q219), a `fact`, `finding`, `correction` or `question` item at most 300, and a `correction`'s `to` within edit distance max(12, 25% of `from`) of its `from`. A missing or invalid setting uses its default | C2-06: each default limit refused one past the boundary and accepted at it. A changed value takes effect on the next reply. Each invalid value in the fallback list uses the default | | M6 no free-text decision channel | `decision.raise` from a creative definition refuses `decision-not-allowed` in the broker. The typed tool is also absent (M3) | C2-08: a creative session's raise refuses and no decision row appears. An assistant's raise still works | | M7 no raw session view | On a business with a library project, the conversation reader leaves creative sessions out of `catalogue()`, and `open()` refuses them with `conversation-not-listed` | C2-10a: a creative session file under the root isn't listed and won't open by id. An assistant session still lists | | M8 no raw Discord engine | On a business with a library project, `discord check` and `discord run` refuse a binding whose engine isn't `mosaic-bus` (`discord-engine-not-allowed`), and a `mosaic-bus` binding with `tools` | C2-10a: both refuse on a library business. A pi binding on a business without a library still checks clean | | M9 no agent write to the mapping | An organization write whose `projects` differ from the file's refuses `organization-projects-protected`, in `organizationWriter.write` | C2-04: `write` called directly with a changed playground prefix, and again with a changed hybrid entry, refuses, and the file and `changes.jsonl` are unchanged | | M10 personas and creative instances are the human's | Any write by an actor other than the business file's `human` that touches a creative instance: the chief of staff's `assistants`, `instances` and `models` keys through `organization.set`, and the tester's `instances.set` and `routes.set`. A persona file that fails its digest, mode or fence check. A harness argv that holds the prompt text | C2-08: each path refuses with its code (4) and the file is unchanged. The human's `instances.set` and `routes.set` on `bean` work, and so does her `limits.set` change to Bean's `launch.models` key. Tester cases through a bound tester session. A mutant without the `models` check fails. No `instances` read, `changes.jsonl` line or adapter argv holds a marker string from a fixture persona | +| M11 reply limits are the human's | `library.limits.set` from an agent session (`human-required`) or the tester (`tester-refused`). Any organization write whose `replyLimits` differs from the file's, unless the actor is the file's `human`, refuses `organization-reply-limits-human-only` in `organizationWriter.write` | C2-06: the human's `library.limits.set` works. The chief of staff's `organization.set`, the tester's `instances.set` and a direct `write` with a changed `replyLimits` refuse, and the file and `changes.jsonl` are unchanged. A mutant without the writer check fails | -Residual, in numbers. One reply can carry up to 3 `idea` items of 120 characters, which is 360 -characters of free suggestion text inside a total of 1,200 characters of item text. Each item must quote +Residual, in numbers. At the defaults one reply can carry up to 3 `idea` items of 120 characters, which +is 360 characters of free suggestion text inside a total of 1,200 characters of item text. The numbers +are settings now (Q222), so the residual is `ideasPerReply` times `ideaLength`, within +`itemTextPerReply`, and only the human sets them (M11). Each item must quote a real line of her repo, but the quote can be any line. Over a chat that adds up. Code can't tell a sentence of story from a sentence of advice, and this design doesn't propose a classifier. Jason wrote "NEVER", and Q176 says the system enforces it. He chose these caps over 5 ideas of 200 characters @@ -1139,7 +1196,7 @@ Chains follow Q177 merge-chaining. S marks a slice row. | C2-03a | S | Fetch-only sync (7.2): `syncCommand`, askpass helper, served ref, `library-diverged`, `--accept`, sync state file, timer, `library.sync` | C2-04 | Opus | the C2-03a tests in 7.2 | | C2-05a | S | Lookup index and queries: find, get, search, manuscript with scope, universe, isolation | C2-01 | Haiku high | isolation, scope labels on every tool, unmapped files never returned, paging under `REPLY_CAP`, rebuild time on a 3,000-file fixture | | C2-05b | S | Broker lookup verbs and typed tools. Sync before a read older than 60 s | C2-05a, C2-03a | Sol (Rocko) | broker verb tests. Typed tool name snapshot per creative role (M3) | -| C2-06 | S | Cited-reply guard (5.2) in `packages/library`, the broker's pre-check on agent-to-human `message.send`, the page store, the three notices | C2-05a | Opus | one pass and one refusal per check in M5 and 5.2, through the RESULT path and `send_message`, and with a non-creative sender. `messages` count unchanged on each refusal | +| C2-06 | S | Cited-reply guard (5.2) in `packages/library`, the broker's pre-check on agent-to-human `message.send`, the page store, the three notices, the `replyLimits` settings and M11 (Q222) | C2-05a, C2-04 | Opus | one pass and one refusal per check in M5, M11 and 5.2, through the RESULT path and `send_message`, and with a non-creative sender. `messages` count unchanged on each refusal | | C2-07 | S | Runner retry on a guard refusal (at most 2), then `no-cited-answer`. `no-answer` and `turn-failed` in a library business | C2-06 | Haiku high | fake harness: free text then a valid reply stores one message. An empty and a failed turn store the fixed notices | | C2-08 | S | Six role files and contracts, the new constants, M1, M6, the `webPlan` refusal, gate tests (M2), messaging test. `persona.name` and `persona.sha256` in `REGISTRY`, the persona file read and checks, and the fenced persona block between the contract and "## This session" in `prompt()`. M10: the `organization.set` refusals, the `models` check, the `routes.set` and `instances.set` creative branches (4). Both adapters pass the prompt as a file, not its text (4) | none | Opus | role tests, gate tests, a creative raise refused, `harness-builtin` refused for `research` and `companion`, `scripts/test-config.sh`. Persona vars refused at other layers, malformed and on a non-creative definition. The persona file refused when missing, a symlink, another uid's, group or other readable, over 8,000 characters, off its digest, holding `` or a `# `/`## ` line. A prompt snapshot pinning contract, persona, session. The M10 tests, tester cases through a bound tester session, the `models` mutant. Adapter tests: argv holds the prompt path and no marker from the prompt, and pi's adapter refuses a missing and an unreadable prompt file. M10's actor case: the human's `limits.set` change to Bean's `launch.models` key works next to the chief's refusal. Business file, `.history` and `changes.jsonl` modes after a write | | C2-09a | S | `#threaded` and launcher read `THREAD_DEFINITIONS`. `thread.create` takes `instance`. The Console new-thread control picks it | C2-08 | Sol (Rocko) | the C2-09a tests in section 6 | @@ -1416,7 +1473,8 @@ What it adds is one bot account with read access to that one repo. start with, and when. A specialist she wants later is added by the human through `instances.set` (4), with no hand edit and no new row. 8. The residual in 5.1. Closed: Jason ordered the lower caps, 3 idea items of 120 characters per reply, - 360 characters of free text within 1,200 total (Q219, mos-1299). + 360 characters of free text within 1,200 total (Q219, mos-1299). These are the defaults of settings + only the human changes (Q222, M11). 9. The instance start. Stopping Hermes comes later, and only when she chooses it. - The bus and the Console are already running from step 2c, and both come back after a boot on their own (Q189, which reverses mos-1097 item 1). Nobody starts the Console by hand here. With a