diff --git a/.claude/skills/subagent-composition/SKILL.md b/.claude/skills/subagent-composition/SKILL.md index 745df14..4061475 100644 --- a/.claude/skills/subagent-composition/SKILL.md +++ b/.claude/skills/subagent-composition/SKILL.md @@ -169,6 +169,45 @@ What each slot absorbs — the variable names carry the rule; this table annotat - **`--agent {name}` silently ignores `skills:`.** Session-agent mode and subagent spawn are different paths; only the spawn path preloads. Validate agent behavior through an actual spawn, never through `--agent`, or a working definition reads as broken. - **`tools:` is enforced, not advisory.** A probe declaring `tools: WebSearch` reported exactly that one tool — the allowlist replaces inheritance rather than trimming it. +## The disproving arm — required when a finding would change scope + +**Trigger, and it is narrow: the fan-out's finding would change what kind of +work follows** — rebuild versus repair, absent versus unreachable, defect versus +default. Then one additional arm is spawned whose **only** task is to disprove +the reading the others are converging on, and whose ground block says so in +those words. Routine sweeps do not get one; the charge is one agent on +scope-changing findings, not a standing skeptic on every dispatch. + +**Why it is a separate arm rather than an instruction to the others.** Siblings +primed to find a problem find it, and each one honestly. Their returns then +agree, and agreement among arms that share a prior is not corroboration — it is +the prior, restated N times. Nothing in the fan-out can catch that from inside, +because no sibling is looking. + +**Composing it.** Same ground block as its siblings, and then the inversion +stated plainly: name what the others are assembling evidence *for*, say that +nobody else is assigned to look for the counter-evidence, and say that if it is +not found here it will not be found. Give it the strongest form of the claim to +attack, not a softened one. Require an address and a quotation per point, so a +charitable reading cannot pass as a finding — and require it to report +"the diagnosis holds" plainly when that is the honest result, since an arm that +can only come back one way is decoration. + +```md +{Three_Sibling_Agents_Are_Gathering_Evidence_That_X}. Your job is the opposite: +find where that reading is overstated, and report the strongest honest case for +{Not_X}. You do not manufacture a defence — if the diagnosis holds everywhere +you looked, say so — but nobody else is assigned to look for the +counter-evidence, so if you do not find it, it will not be found. +``` + +**Provenance.** Trellis, July 24 2026. Three sweeps primed on "the build drifted +toward retrieval" found drift, correctly and everywhere they looked. A fourth, +primed to bound the claim, found the capability the other three had reported +missing — built, and merely unreachable. Without it the session would have +carried a rebuild estimate for work already done. Owner-approved as standing +practice the same day. + ## Fan-out discipline Compose one **shared ground block** and reuse it verbatim across siblings; drift between copies produces findings that cannot be reconciled. Give each sibling a **disjoint write scope**, or give them all `isolation: worktree` (`Agent_Isolation_Worktree_Mode`). Siblings cannot see each other, so any cross-cutting judgment belongs to you after they return — never to a sibling. Prefer running the last agent synchronously (`Agent_Foreground_Synchronous_Wait`, `run_in_background: false`) when you need its result to proceed. diff --git a/AMBIENT.md b/AMBIENT.md index cbf6061..108967e 100644 --- a/AMBIENT.md +++ b/AMBIENT.md @@ -11,13 +11,57 @@ operating a persistent Python REPL over a knowledge store where every stored fact traces to immutable content-addressed source bytes. Most rules in this repository wait for an event — a file opened, a -command run, bytes written. These five wait for nothing. A session has -an objective, sits under whatever gates the collaborator has set, claims -things are delivered, rests those claims on records, and can ask. That -is the whole trigger. Numbers are append-only and cite exactly as they -did before the restructure. Rule 21 is split: 21(a) is here, and 21(b) — -asking before installing standing configuration — has a detectable -trigger and lives with the task-type files. +command run, bytes written. These six wait for nothing. A session knows +what is being built, has an objective, sits under whatever gates the +collaborator has set, claims things are delivered, rests those claims on +records, and can ask. That is the whole trigger. Numbers are append-only +and cite exactly as they did before the restructure. Rule 21 is split: +21(a) is here, and 21(b) — asking before installing standing +configuration — has a detectable trigger and lives with the task-type +files. + +**Rule 24 is numbered last and printed first, deliberately.** It exists +because what is being built was written only in records a construction +decision never had to open, and a target a session must go looking for +loses to the nearest familiar shape — rule 20's ordering failure, one +level up. + +## Rule 24 — what is being built + +Trellis is an **agentic knowledge-work system** over a large context of +domain-specific user information: a toolset the user works *with*, not a +retrieval system with a fact store bolted on. It holds the information, +reasons over it, acts on it, and forms beliefs and doubts from it. Two +sentences carry the rule, and one act falsifies each. + +**A query produces a deliverable, not a reply.** Every run leaves a +**response artifact** — a derived object composed over several turns and +multiple slices, which outlives the run and which the orchestrator can +parse, summarize, and link for the user. A run whose whole output is a +string in a transcript produced none. **A schema that can only carry a +string cannot carry a deliverable**, so a terminal action requiring +non-empty prose, an artifact envelope with one hardcoded text part, and +an answer sink that renders a single value each make this false. For +code editing the repository is the artifact and the submitted string is +a receipt. + +**The worker queries the corpus; it never transports it.** It answers +questions *about* a body it never holds, reading the slices the question +needs and no more. A surface returning a whole document where the +question asked about part of one, a bound pricing a paragraph and a +corpus alike, and an instruction to collapse several turns into one load +each make this false. + +The tell for both: a sentence computing how much of a corpus fits +through the model. That arithmetic describes an operation this system +does not have — bulk movement is the engine's job, which is rule 5's +code-mediated text applied to what a turn is *for* +(`docs/architecture/RESPONSE_ARTIFACT.md`). + +Two surfaces carry this outward, and neither is decoration. **A2A is +inbound** — peer agents query Trellis as a human would. **MCP is +outbound** — Trellis acts in the world as a human would. A text-only +contract on either bounds the whole system. ## Rule 1 — where the objective comes from diff --git a/docs/architecture/RESPONSE_ARTIFACT.md b/docs/architecture/RESPONSE_ARTIFACT.md new file mode 100644 index 0000000..6203da4 --- /dev/null +++ b/docs/architecture/RESPONSE_ARTIFACT.md @@ -0,0 +1,178 @@ +# The response artifact — what a query produces + +**Status: DOCTRINE stated by the collaborator (Matt) July 24, 2026; framing repairs AUTHORIZED by +the owner (Cnid) the same day. The findings in §3 are AUDITED AND UNRESOLVED — nothing in them is +authorized, and §5 says so explicitly.** + +This record exists because the mechanisms were shipped and the frame was not. Every individual rule +below is already written down somewhere in this repository, and a reader can honour all of them and +still narrate Trellis as a system that reads a corpus. That happened, in this repository, on the day +this record was written — which is the evidence that the scattered form was insufficient. + +--- + +## 1. The doctrine + +**The worker never works with gigabytes at a time. It answers questions *about* gigabyte contexts.** + +- **It reads slices intelligently — explicitly not everything.** The collaborator named the reason: + *"Your current training goal is to read everything. This will explicitly not read everything."* + The disposition being corrected for is exhaustive reading, and it is the model's default, not an + occasional lapse. +- **Its job is not to regurgitate.** It never emits text to the orchestrator and calls itself done. +- **Every query builds a response artifact:** a derived deliverable — a spreadsheet, a PDF with a + chart, a slide deck, text with an illustration, most often plain text. **Not one output.** + Composed over several turns and multiple slices, *"without extracting more than needed."* +- **Bulk quotation is composed, never read.** If gigabytes of text must appear in the deliverable, + the model composes the record **programmatically** and hands it to the artifact wholesale. It never + reads five hundred slices to do it. +- **For code editing, the repository is the response artifact.** The write is the deliverable; the + submitted string is a receipt. + +This is [CODE_MEDIATED_TEXT.md](CODE_MEDIATED_TEXT.md) applied one level up. That record fixes how +bytes move — the engine computes positions, the engine splices, the model supplies addresses. This +one fixes **what a turn is for**, and the two share a consequence: *the model is never the +transport.* + +**The tell.** Any sentence that computes how much of a corpus fits through the model — "N round +trips per GB", "the model loads the corpus", "reads the whole X" — is treating the model as the +transport. That arithmetic describes an operation this architecture does not have. One such sentence +was written into `src/repl_sandbox/config.py` and `REPL_SANDBOX_CONFORMANCE.md` §2.3 on July 24, 2026 +and repaired the same day; it is quoted here because the author had just finished ratifying the +layering it contradicted. + +## 2. What already holds it + +The mechanisms ship. This is not an aspiration record. + +| mechanism | what it holds | where | +|---|---|---| +| `trellis_answer.submit` | Refuses a **bare literal** by `ast.parse` — an expression referencing no REPL state is a retyped literal. Structural, not advisory. | `src/rlm/trellis_answer.py` | +| Handles, not payloads | The corpus never enters the guest; the algebra is handle-in / handle-out and crosses no content. | [REPL_SANDBOX_DATA_MODEL.md](../product/repl-sandbox/REPL_SANDBOX_DATA_MODEL.md) §4, §5 | +| By-reference sink (one of two) | `llm_query(context=[H])` resolves host-side; its `context` slot is typed `Handle` so a string does not fit. *"Prefer by-reference sinks; `materialize` is only for when the model itself must compute over the bytes."* **The answer-side twin, `answer.submit(H)`, is documented in two records and does not exist** — see §4. | same, §6 — titled **The bounded materialisation exception** | +| `locate` / `lines` | Query for an address; read a bounded slice. *"the model queries for a location, it never counts lines."* | `src/rlm/trellis_textedit.py` | +| Workspace index/segment | `read()` returns the index and **never** contents; a segment is a deliberate pull. | `src/rlm/trellis_workspace.py` | +| MCP capture | External results are captured mechanically; the model receives a stub with a preview. Harness-guaranteed, not model discipline. | `src/rlm/trellis_mcp.py` | +| `concat_files` | Builds sub-LLM buffers as one string *"instead of printing file contents through the REPL output cap"* — compose-and-hand-off, shipped. | `src/rlm/trellis_scaffold.py` | +| `trellis_upsum.commit` | Measures running state engine-side and refuses over budget with a per-key breakdown, so the model compresses against engine-computed numbers. | `src/rlm/trellis_scaffold.py` | +| Orchestrator routing | *"Route working state by reference, never by paraphrase."* | `src/core/agent/orchestrator_prompt.ts` | + +## 3. Where the system pulls the other way + +Audited July 24, 2026 by four read-only sweeps over `docs/architecture/`, the prompt surface, the +REPL-sandbox record set, and the runtime. **Findings only.** Each names a surface; none is a change +this record makes. + +### 3.1 The incentives point at one big read + +- **The retrieval budget meters calls, not bytes.** `_held["fetches"] += 1` fires once per successful + call at all three surfaces (`src/rlm/trellis_tools.py`). Sixty-four fetches buys sixty-four whole + documents exactly as readily as sixty-four paragraphs, so a model optimising against the budget + **maximises bytes per fetch**. The per-root dedup on `get_ast_blocks` sharpens it: one read of a + document, ever — under-fetching is unrecoverable, over-fetching is free. +- **`ITERATION BUDGET` instructs the opposite of composition.** The kernel tells the worker it has + *"very few REPL turns"*, to combine loading and computing into **one** block, and not to *"spend a + turn on tiny exploratory prints"* — against a `--max-iterations` default of **5**. The doctrine + wants an artifact composed over several turns and multiple slices. +- **No retrieval surface over the corpus has a `locate`.** `get_ast_blocks(root)` returns every block + of a document with full text and no way to ask for a range. The editing half of the runtime has + `load` (shape only), `locate` (addresses plus previews, capped) and a 200-line slice cap; **the + corpus half has neither.** The slice-reading affordance exists, over files, and was never built + over the substrate. +- **The friction gradient runs backwards.** Disciplined, budgeted, audited reads return JSON strings + needing `json.loads`; the undisciplined twins return live Python values. `fetch_texts` is public, + returns a real dict, and is *"NOT counted as a tool call and NOT audited"* — fewer keystrokes than + the metered path, reachable by `dir()`, and the natural next move for a model that has just hit the + dedup refusal. `trellis_postgres.conn` is the same gap with no ceiling. + +### 3.2 The bytes that reach the model do not carry the frame + +- **The word "artifact" reaches no model.** Nor "deliverable", "spreadsheet", "chart", "slide". With + none of it present, *produce an answer* defaults to what the workflow rules literally say: a string. +- **The default module opens with an exhaustive load.** A default run's only protocol module is + `spatial-flywheel`, whose first procedural instruction is *"Load the full question catalog in one + query."* The module that states the discipline cleanly — `workspace-discipline`: *"Index first, + then pull only the bounded material needed for the current step"* — is `active` but **not in the + default selection**. The module that teaches stopping — `estimation-discipline`: *"If every required + operand is bound and the answer is determined, stop searching immediately"* — is `retired` and + cannot load. +- **Nothing tells an editing run that the repository is the deliverable.** After `write_back`, the + worker is still told its final answer is a `FINAL_ANSWER` string, with no relation drawn between + them. It will predictably restate the edit in prose. +- **Two bounds are invisible until they fire.** The 64-per-run retrieval budget and the 64 KiB answer + cap are unstated in any addendum. The cap's refusal — *"Submit the result, not the corpus."* — is + the clearest anti-regurgitation sentence in the codebase and the model sees it only after building + the oversized answer. + +### 3.3 Records that state the opposite + +- **[TEST_TIME_TRAINING.md](TEST_TIME_TRAINING.md) §12.2 — LISTED HERE IN ERROR, withdrawn + 2026-07-24.** It was read as doctrine holding that large REPL dumps are the intended workload. It + is not: §12 is a **literature-applicability** analysis, and §12.2 asks whether LaCT's long-context + results transfer to Trellis. The first draft argued they do *not*, because the RLM keeps the corpus + out of attention; the owner's correction overturned that — per-run token flow is genuine + long-context load, **so those results apply here directly**. That is an argument about whether a + research finding is relevant, not a statement of desired behaviour, and reading the second as the + first is the error. + + **TTT is a mechanism for rule 24's second sentence, not a counterweight to it.** It targets an + open-weights model served locally (§7 R3), which does not exist yet — `TRELLIS_RLM_BACKEND` is + root-agent only and worker transport is explicitly not configurable. Its shape, from the + collaborator: the harness composes a prompt from internal primitives, the composed prompt **sets + the mode**, and the model self-plays over REPL data with **properly filtered programmatic slicing + as the rewarded behaviour**, scored by RLVCG (arXiv:2607.19044). Slice discipline is what TTT pays + for. Nothing here needs re-adjudication. + +- **[REASONING_TEMPLATES.md](REASONING_TEMPLATES.md) §17** gives all eight reasoning modes the same + out-port, `answer`, and the locked port vocabulary offers no artifact type. `construction` — *"Build + or modify a file artifact in the edit root"* — routes `write_back` as a mid-flow node and terminates + at a submitted string. A mode space built from it cannot express a query that produces an artifact. + The apparatus is catalog text with no kernel behind it yet, which makes this the cheap moment. +- **`REPL_SANDBOX_SPEC.md` §4.2** writes `run_query(sql, params) -> rows`, putting the result set in + the namespace on the first call. `LEARNINGS` §10c already flagged this exact line on July 23 and the + correction never reached the record it names. +- **`REPL_SANDBOX_ARCHITECTURE.md` §3** says *"The root worker splits context"* — to split it you must + hold it. The shipped fan-out names handles and the host splices referents. + +## 4. The open design question + +**The doctrine's escape hatch is currently closed by the security model, and nothing distinguishes a +large legitimate deliverable from bulk exfil.** + +The doctrine says: when gigabytes must be quoted, compose the record programmatically and hand it to +the artifact wholesale. But `answer.submit` sits on the outbound ledger under a cumulative byte cap, +`THREAT_MODEL` prices the channel at `ANSWER_CONTENT_MAX_CHARS` (64 KiB), and +[DATA_MODEL](../product/repl-sandbox/REPL_SANDBOX_DATA_MODEL.md) §6 makes that same ceiling the +quantified exfil-rate residual. Under that reading a wholesale hand-off is not reachable at all. + +Worse, the two are presently indistinguishable by construction: `submit("'\n'.join(b['text'] for b in +blocks))` references REPL state, passes the literal check, and delivers 64 KiB of corpus text +engine-rendered and unretyped — **satisfying the pillar exactly while doing what the doctrine calls +regurgitation.** `submit` closes transcription error. It does not close volume. + +This is an owner decision, not a wording fix. It probably wants a distinct artifact sink — a +deliverable that is *written* by reference rather than *submitted* as a value, charged against a +different ledger from the exfil residual, with the provenance of its parts intact. + +## 5. What this record does not authorize + +- **No prompt bytes.** The collaborator's standing is that the doctrine *"will be prompted later and + enforced"*; authoring those bytes runs under `AGENTS.md` rule 16 and is not opened by this record. +- **No engine change.** Every item in §3 is a finding. The budget's metering, the missing corpus + `locate`, `fetch_texts`, the iteration budget, and the module selection are each a separate change + with its own gate. +- **Nothing about TEST_TIME_TRAINING.** §12.2 was listed in §3.3 in error and is withdrawn there; + it needed no adjudication, because it never said what it was read as saying. + +**Reachability, stated rather than implied:** this record has no enforcing surface. Nothing refuses a +wholesale print, no guard couples a decisive step to a bounded read, and the term "response artifact" +reaches no model. It is a frame for work that has not been done, and it should be read as one until a +row in §2 says otherwise. + +--- + +*Siblings: [CODE_MEDIATED_TEXT.md](CODE_MEDIATED_TEXT.md) (how bytes move — this record is its +upper storey) · [RETRIEVAL_DISCIPLINE.md](RETRIEVAL_DISCIPLINE.md) (held state and the per-run budget) +· [WORKSPACE_AND_MODULES.md](WORKSPACE_AND_MODULES.md) §4.3 (thin control channel, fat heap) · +[REPL_SANDBOX_DATA_MODEL.md](../product/repl-sandbox/REPL_SANDBOX_DATA_MODEL.md) §6 (the bounded +materialisation exception).* diff --git a/docs/density-chain/DENSITY-CHAIN.md b/docs/density-chain/DENSITY-CHAIN.md index 11c1f39..d504591 100644 --- a/docs/density-chain/DENSITY-CHAIN.md +++ b/docs/density-chain/DENSITY-CHAIN.md @@ -865,8 +865,17 @@ the RLM execution model, the doubts machinery it borrows, or the pillar it reali protects nothing (a 12 MiB namespace value returns a ~4 KB reply — `MarshalCaps` holds attention, independently) while leaving the frame **below** `max_result_bytes`, so a legal broker result could not cross the wire. The token rule now sizes the **slice**, as a sizing convention and never an - enforcing bound. **The REPL is meant to be gigabytes read in slices; the corpus ceiling is - `RLIMIT_AS`, not any wire number.** Still open: handle lifetime, the warm pool, depth-2. The + enforcing bound. **The corpus is bounded by nothing in the config, because it never enters the + guest** — `RLIMIT_AS` bounds the *working set*, and an earlier edition of that same layering table + asked what corpus size address space permits, which puts the corpus in the one place the handle + model says it never is. **A second correction the same day, and the sharper one: `answer.submit(H)` + — the by-reference answer sink two records route bulk content through — DOES NOT EXIST.** `submit` + takes an expression string and renders a value; there is no handle argument and no host-side + resolution. So the doctrine's wholesale hand-off is not capped, it is **unbuilt**, and an unbuilt + escape hatch reads exactly like a built one. Also established: `charge_outbound` is called only for + the `llm_query` prompt, so the answer is **not on the exfil ledger at all**, and THREAT_MODEL + already calls the 64 KiB cap output-shaping *"not a confidentiality or escape control"* — the + collapse lives in DATA_MODEL §6 alone. Still open: handle lifetime, the warm pool, depth-2. The remaining **[A]** halves (S6, GB, GA-eq) are ≤$5 and **unspent**; S3's and S4's are **banked**. *Status ledger:* **control plane shipped; a microVM boots, a frame crosses to it, a real database @@ -964,6 +973,17 @@ descriptor program. Not the guards it describes, only the accounts of them.* now BUILT. What remains unbuilt: `llm_help` itself, the eight further descriptors, the human-doc generator, and the advisory-marking convention that belongs with `llm_help`'s frame. +**AMBIENT.md gained rule 24 on 2026-07-24 — what is being built — and it is this class's business +because it is a self-description that construction has to be able to see.** The rule is numbered +last and printed first: the target existed only in records a construction decision never had to +open, and the build converged on the nearest familiar shape. That is rule 20's ordering failure +raised from measurement to construction, and the placement is the correction. Fitting it obeyed the +byte discipline rather than the ceiling — the file's least-decisive prose was compressed instead of +appending past the bound (**8,070 / 8,192**, 122 free). `docs/product/FEATURE_LIST.md` is the +consequence: eight capability groups, every row marked shipped, built-unreachable, partial or +absent, written **before** security hardening because a hardening pass over an unspecified feature +set yields follow-ups rather than a boundary. + *Status ledger:* root contract · machine twin · surface checker · its governed-headroom report · CI wiring — **shipped-pinned**; self-describing surfaces — **RATIFIED** (2026-07-23); harness self-model — **principle endorsed, diff --git a/docs/product/FEATURE_LIST.md b/docs/product/FEATURE_LIST.md new file mode 100644 index 0000000..dad969d --- /dev/null +++ b/docs/product/FEATURE_LIST.md @@ -0,0 +1,221 @@ +# Trellis — the feature list + +> *Trellis: it's an expert at working with your data.* + +**Status: BUILD AUTHORIZED — owner (Cnid), July 24, 2026, for execution in a future session.** +Written before security hardening deliberately: a hardening pass over an unspecified feature set +produces a queue of follow-ups rather than a boundary, because every control has to be re-litigated +the moment a feature it did not anticipate arrives. That ordering is why this list exists, and the +authorization follows it — **feature list, then build, then harden.** + +**What the authorization covers and what it does not.** It authorizes building against this list. It +does not waive the gates that ride on *kinds* of work: prompt bytes still run under rule 16, paid +runs under rule 7, standing configuration under rule 21(b), and any change that *removes* shipped +engine surface still needs its own dated entry (see §6, where that distinction is the reason the +epistemic layer has sat unbuilt). A row's presence here is permission to build it, never permission +to skip the gate its construction crosses. + +**Governed by [AMBIENT.md rule 24](../../AMBIENT.md).** Every row below is a consequence of that +rule or a component that serves it. Where a row's status contradicts the rule, the rule wins and the +row is the work. + +**How to read the status column.** `shipped` — built with a non-test caller. `built, unreachable` — +built with no non-test caller, which is not delivered ([rule 15](../../AMBIENT.md)). `partial` — +serves one case of a general capability. `absent` — nothing stands in for it. **`PLACEHOLDER`** — deferred by the owner and the collaborator pending a capability Trellis does not have (typically a model connection); the seam is roughed in so the shape exists, and the capability lands later. + +--- + +## 1. Holds the information + +The substrate. This layer is the one the audit found healthiest, and nothing here is on the +critical path. + +| # | feature | what it means | status | +|---|---|---|---| +| 1.1 | Content-addressed store | Every stored fact traces to immutable source bytes; nodes final at write time | shipped | +| 1.2 | Verified ingest | One transaction every document crosses; parsers for markdown, code, PDF | shipped | +| 1.3 | Structural chunking | Syntax-aligned, size-budgeted blocks; byte-exact | shipped | +| 1.4 | Live-blocks-only retrieval | Superseded versions are archive, reachable only by explicit address | shipped | +| 1.5 | Repository snapshot ingest | Whole-repo scoped snapshots, carry-forward for out-of-scope paths | shipped | +| 1.6 | Multi-tenant identity | A principal the store can name | **CLOSED, not applicable** (owner, 2026-07-24) — **one user, one instance.** Ownership is the deployment boundary, so it needs no representation. Enterprise scales by cloning a base image of the raw data, each clone owned by its user. Nothing in the substrate names an owner and nothing needs to | + +## 2. Reasons over it + +The worker. This is the layer that drifted toward retrieval. + +| # | feature | what it means | status | +|---|---|---|---| +| 2.1 | Persistent REPL per task | One process, one task; namespace survives turns | shipped | +| 2.2 | Flat sub-LLM fan-out | `llm_query` over slices at depth 1 | shipped | +| 2.3 | Code-mediated text | Engine computes locations; bytes move by splice or reference | shipped | +| 2.4 | **Corpus `locate` + bounded window** | Query the corpus for addresses; read a bounded range — what `trellis_textedit` already does for files | **built, unreachable** — `repl_sandbox/algebra.py` has `locate`, `narrow`, text-free `get_ast_blocks`, byte metering, handle-typed `llm_query` context. Nothing outside `src/repl_sandbox/` imports it; production spawns `src/rlm/trellis_agent.py` | +| 2.5 | Byte-metered extraction budget | "No more than needed" needs a unit; today the budget counts calls | **absent** in the live path, shipped in 2.4's layer | +| 2.6 | Turn budget affording composition | Several turns and multiple slices, per rule 24 | **wrong shape** — schema ceiling of 9, and the kernel instructs collapsing turns | +| 2.7 | **Iteration-exhaustion path that keeps its guards** | Running out of turns must not bypass the answer channel | **defect** — `rlms` re-prompts over the transcript and returns that as `FINAL_ANSWER`: no literal check, no cap, no telemetry, no protocol violation. See §6 | +| 2.8 | Durable artifact-under-construction state | A growing, engine-measured buffer the model deposits into across turns | **absent** — `upsum` is a 2,000-char shrinking summary; the workspace has no model-facing deposit path | + +## 3. Produces a deliverable + +**The hole, and the reason this list exists.** No layer of the system can express a deliverable that +is not a string. + +**The artifact is not a terminal output — it is a contribution to the store** (owner and +collaborator, 2026-07-24). A run composes it, it is **filed into the user's own REPL store**, and the +judges **promote and classify** it, so it lands as a fact, a belief, or a doubt and carries standing +like anything else the user owns. It then becomes part of the corpus the next query slices. + +That closes the loop this system is named for: **the output becomes input.** It also means §3 and §6 +are one pipeline rather than two subjects — a deliverable that carries standing is not a different +object from one that carries content, it is the same object after the judges have seen it. And it is +why 3.5 needs no new provenance structure: an artifact filed into the store inherits the machinery +every other stored thing already crosses. + +| # | feature | what it means | status | +|---|---|---|---| +| 3.1 | **Response artifact object** | A durable, addressable, Trellis-side object a run composes and the orchestrator links. Not a rendering — the thing itself | **absent** | +| 3.2 | **Artifact sink** | A by-reference write path: the model names parts, the engine assembles. Distinct from `answer.submit`, which renders one value | **absent** — `answer.submit(H)` is documented in two records and was never built | +| 3.3 | **Output location** | A run-scoped place to create files. `TRELLIS_EDIT_ROOT` is an *edit* root and `load` refuses a non-existent file | **absent** | +| 3.4 | **Non-text artifact types** | Spreadsheet, PDF with chart, slide deck, text with illustration | **absent, PLACEHOLDER** — no `openpyxl`/`matplotlib`/`reportlab`/`pptx`-class dependency exists. Rough in the seam; some types need model connections that do not exist yet | +| 3.5 | **Artifact provenance** | Which slices composed which deliverable, resolvable to source bytes | **absent** — the system's own value proposition, unapplied to its output | +| 3.6 | Artifact receipt | The submitted string names the artifact rather than restating it | partial — `submit` is the right shape for a receipt and is currently doing both jobs | +| 3.7 | Repository as artifact | For code editing, the write is the deliverable | **partial** — `trellis_textedit` + `stage2_selfedit_check.ts` are a working loop; missing file *creation*, a link from run outcome to write, and any byte telling the worker the write was the point | + +## 4. Acts in the world — MCP outbound + +Trellis takes human-like actions: query a service, book a thing, request work. Today this is framed +as a research intake. + +| # | feature | what it means | status | +|---|---|---|---| +| 4.1 | Allowlisted MCP client | stdio + streamable HTTP, per-server tool allowlist, bounded results | shipped | +| 4.2 | Harness-guaranteed capture | Results captured to workspace segments; model sees a stub | shipped | +| 4.3 | **MCP available to the orchestrator** | Rule 24 names MCP as the orchestrator's toolbox | **absent** — no MCP reference anywhere in `src/core/agent/`; it is injected into the RLM worker only | +| 4.4 | **Action semantics** | A tool call that *does* something, with a result that carries standing | **wrong shape** — `EXTERNAL CONTENT CONTRACT (HARD RULE): MCP results are research context ONLY` | +| 4.5 | **Non-text tool results** | Images and embedded resources the protocol already carries | **absent, PLACEHOLDER** — flattened to the literal string `[non-text content: ]` at the boundary. An image tool needs a model connection Trellis does not have; rough in the seam and defer the capability | +| 4.6 | Action authorization | Which side effects need a human gate, and how that is asked | **absent** — the allowlist is the only control, and it is configuration, not consent | + +## 5. Serves peers — A2A inbound + +Peer agents query Trellis as a human would, without knowing its internals. + +| # | feature | what it means | status | +|---|---|---|---| +| 5.1 | JSON-RPC surface | `SendMessage`, `SendStreamingMessage`, `GetTask`, `CancelTask`; live-wired behind `config.a2a.enabled` | shipped | +| 5.2 | Spec-faithful refusal | Out-of-scope operations declined whole, with the spec's own error codes | shipped | +| 5.3 | Admission bounded ahead of allocation | `StreamGate` counts a stream in before resources exist | shipped | +| 5.4 | **Typed artifact parts** | A2A's `artifacts` array carries `oneof (text \| raw \| url \| data)` | **wrong shape** — `renderArtifact` hardcodes one `text` part from `finalAnswer`; the agent card declares `text/plain` as capability. **The envelope the target needs is already there, empty** | +| 5.5 | Peer identity and trust | Which peer asked, and what that entitles | **absent** — bearer key or open | + +## 6. Forms beliefs and doubts + +The epistemic layer — **and, after the 2026-07-24 rulings, the second half of §3 rather than a +separate concern.** The judges are what turn a composed artifact into a filed one with standing. + +**The gate everyone has been respecting is on a *removal*, and the thing that is wanted is an +*addition* — those were conflated, which is why this has sat.** + +`STANDING_MODEL.md` was ratified July 20, 2026 and its status line says it "authorizes **no build**." +Its §3 says exactly what the withheld authorization covers: if the panel never moves standing, the +promotion machinery reduces to a findings recorder plus a user gate, and *"**that reduction removes +shipped engine surface**"* — so deleting or rewriting shipped disposition code needs its own owner +dated entry and drills. **The gate is on deleting code.** + +Surfacing `doubts` / `beliefs` / `facts` as REPL state spaces is not that. It is an addition, it is +already designed — [DATA_MODEL §7](repl-sandbox/REPL_SANDBOX_DATA_MODEL.md) specifies three +**pre-allocated root handles** at `setup`, `kind = graph-view`, sliced by the algebra and never +materialised whole — and **no gate covers it.** It was never scheduled, not never approved. + +| # | feature | what it means | status | +|---|---|---|---| +| 6.1 | Provenance-bound beliefs | `sourceNodeIds` enforced by the write path, not by prompt | shipped | +| 6.2 | Invalidation sweep | Beliefs contest when their source bytes die | shipped | +| 6.3 | Support arithmetic | Graded, decaying (b,d,u) computed sweep-side, writer-blind | shipped | +| 6.4 | Composed judge ceremony | Judges composed per context from primitives | design-resolved, unbuilt | +| 6.5 | Doubts / objections / defeaters | The −1 tier; a doubt is *based on* its objection | ratified as principle, no build | +| 6.6 | Signed ternary + user gate | −1/0/+1 standing; the user ratifies; the panel never moves standing | ratified as principle, no build | + +## 7. Builds its own modules — the capability flywheel + +| # | feature | what it means | status | +|---|---|---|---| +| 7.1 | Grounded authoring | A run with **zero retrieval affordance** derives a protocol from a seeded corpus and writes `modules//{module.json,addendum.txt,RESEARCH.md}` | **shipped** — and it is the response artifact, done, for one case | +| 7.2 | Derivation gate | Assembly refuses below a derivation threshold | shipped | +| 7.3 | Module registry + byte-pinned composition | Manifest, acceptance criterion naming its own module, composed-prompt pins | shipped | +| 7.4 | Self-editing | Trellis may edit Trellis; the repo is the artifact | partial — see 3.7 | + +## 8. Knows the user + +Rule 24's "one mega-context" includes the user. This is the thinnest column in the list. + +| # | feature | what it means | status | +|---|---|---|---| +| 8.1 | **Stored user preferences** | Preferences live in the substrate and are pulled each run, not configured per deployment | **absent** — no preference, profile, or persona retrieval anywhere in `src/core/agent/` | +| 8.2 | **Orchestrator rules from the REPL** | The orchestrator's behaviour is data-driven from the user's own store | **absent** — `ORCHESTRATOR_SYSTEM_PROMPT` is a static string | +| 8.3 | User gate on ratification | The user, not a panel, moves standing | ratified as principle, no build | + +--- + +## What blocks what + +``` +rule 24 (landed) + │ + ├─ 3.1 artifact object ──┬─ 3.2 sink ─┬─ 3.3 location ─ 3.4 non-text types + │ │ └─ 3.5 provenance ← needs 1.6 principal + │ └─ 5.4 A2A typed parts (rendering, not home) + │ + ├─ 2.7 exhaustion defect ─── 2.6 turn budget (2.6 is unsafe to widen before 2.7) + │ + ├─ 2.4 reachability ─── 2.5 byte budget (mostly migration, not construction) + │ + └─ 4.3 orchestrator MCP ─── 4.4 action semantics ─── 4.6 authorization +``` + +**Three orderings I hold with some confidence.** The artifact object (3.1) precedes its renderings — +A2A parts and the UI are two views of one Trellis-side thing, not two places it lives. The +exhaustion defect (2.7) precedes widening the turn budget (2.6), because more turns currently means +more chance of landing on the guard-free path. And artifact provenance (3.5) needs a principal (1.6) +before "these are the requester's own bytes" is a sentence the system can form. + +**What this list deliberately does not do:** sequence the work, estimate it, or authorize any of it. +It exists so a security pass has a fixed surface to harden against. + +## Open questions for the owner + +**Answered by the collaborator, July 24, 2026.** + +1. **The artifact is a Trellis-side object; A2A is one rendering.** Confirmed. 3.1 precedes 5.4, and + the human UI and a peer agent are two views of one thing. +2. **An artifact carries standing.** It is filed as a **fact, a belief, or a doubt**, depending on + its purpose and what it carries — so §3 and §6 are one subject, not two, and 3.5 rides the + provenance machinery that already exists rather than needing a new structure. + [DATA_MODEL §7](repl-sandbox/REPL_SANDBOX_DATA_MODEL.md) already designs the shape: `doubts`, + `beliefs` and `facts` as three **pre-allocated root handles** at `setup`, `kind = graph-view`, + sliced by the algebra and never materialised whole. *(My original question meant multi-tenant + owner identity — still open as 1.6, and a smaller question than it looked, since ownership is now + a property of a filed artifact rather than a prerequisite for having one.)* +3. **`TEST_TIME_TRAINING` §12.2 needed no adjudication.** It was misread. §12 is a + literature-applicability analysis; §12.2 argues that LaCT's long-context results *do* transfer, + which is a claim about research relevance and not about desired behaviour. **TTT is the mechanism + for rule 24's second sentence** — the harness composes a prompt from internal primitives, the + composed prompt sets the mode, the model self-plays over REPL data, and **properly filtered + programmatic slicing is the rewarded behaviour**, scored by RLVCG (arXiv:2607.19044). It targets a + local open-weights model that does not exist yet (§7 R3; `TRELLIS_RLM_BACKEND` is root-agent only, + worker transport not configurable), which is why it reads as forward-looking rather than current. + +**Ruled by the owner, 2026-07-24 — both open questions closed.** + +- **Identity: one user, one instance.** Ownership is the deployment boundary and is never + represented in the store. Enterprise expands by cloning a base image of the raw data, each clone + owned by its user; symlinking the shared base is the variant. 1.6 is closed as not applicable. +- **Sequencing: artifacts now.** They ship into the single-tenant store immediately, with no + recipient slot — a hedge that only earns its place when more than one recipient is possible, which + under one-user-one-instance it never is. The migration story is image cloning, which carries + artifacts with everything else and needs no backfill against append-only rows. + +**Nothing in this list is now blocked on an owner decision.** What remains open is build +authorization, which is a different gate. + +*Siblings: [AMBIENT.md rule 24](../../AMBIENT.md) (what is being built) · +[RESPONSE_ARTIFACT.md](../architecture/RESPONSE_ARTIFACT.md) (the doctrine and the audit) · +[CODE_MEDIATED_TEXT.md](../architecture/CODE_MEDIATED_TEXT.md) (how bytes move).* diff --git a/docs/product/repl-sandbox/REPL_SANDBOX_CONFORMANCE.md b/docs/product/repl-sandbox/REPL_SANDBOX_CONFORMANCE.md index 1387463..2adc34c 100644 --- a/docs/product/repl-sandbox/REPL_SANDBOX_CONFORMANCE.md +++ b/docs/product/repl-sandbox/REPL_SANDBOX_CONFORMANCE.md @@ -144,19 +144,40 @@ wrong layer, and it had two consequences: | what is bounded | question it answers | constant | |---|---|---| -| REPL namespace | how large a corpus may the model work over? | `Tier0Limits.address_space_bytes` (1 GiB) | -| one slice | how much moves host store → guest namespace per call? | `BrokerCaps.max_result_bytes` (2 MiB) | +| the corpus | how large a body may the model reason over? | **nothing here** — it is host-side, behind handles | +| the working set | how much may the model hold in the namespace at once? | `Tier0Limits.address_space_bytes` (1 GiB) | +| one materialisation | how much crosses host store → guest namespace per call? | `BrokerCaps.max_result_bytes` (2 MiB) | | model attention | how much reaches the transcript? | `MarshalCaps` (20 KiB stdout, 64 KiB answer) | | one wire message | how large may a single frame be? | `DEFAULT_MAX_FRAME_LEN` (4 MiB) | -**The REPL is meant to be large — potentially gigabytes — and read in slices.** No bound in this -table constrains that; the ceiling on the corpus is address space. The token derivation survives as +**The corpus is unbounded by anything in this table, because it never enters the guest.** An earlier +edition of this row asked "how large a corpus may the model work over?" and answered with the guest's +own `RLIMIT_AS` — which locates the corpus *inside* the namespace, the one place the handle model says +it never is ([DATA_MODEL §8](REPL_SANDBOX_DATA_MODEL.md): *no row of the user's knowledge is ever +placed in the guest as a payload*). It also contradicted its own next sentence, since "potentially +gigabytes" does not fit in 1 GiB of address space. Address space bounds **what the model itself +materialised and is computing over** — the working set — and nothing else. The token derivation +survives as the way `max_result_bytes` is *chosen* — "about what one model pass could consider" — and is recorded as a **sizing convention, not an enforcing bound**, because treating it as enforcing is exactly the error above. -**The cost, stated:** a 2 MiB slice means a corpus arrives in ~500 round trips per GB. If bulk -transfer needs to be cheaper, `max_result_bytes` is the single lever and `max_frame_len` follows it. +**What this bound is *not*, corrected 2026-07-24.** An earlier edition of this section costed it as +"~500 round trips per GB", as though `max_result_bytes` were the throughput lever for moving a corpus +into the worker. **There is no such operation.** The worker never works with gigabytes at a time — it +answers questions *about* gigabyte contexts by reading slices and composing a derived **response +artifact** over several turns, extracting no more than it needs. `materialize` is the exception path, +which [DATA_MODEL §6](REPL_SANDBOX_DATA_MODEL.md) — titled *The bounded materialisation exception* — already states: *prefer by-reference sinks; +`materialize` is only for when the model itself must compute over the bytes.* When bulk content must +reach an answer it was meant to go `answer.submit(H)` — **an op that does not exist.** +`trellis_answer.submit` takes an expression string and renders a *value*; there is no handle argument +and no host-side resolution. Recorded here because this record is one of the two that route bulk +through it (RESPONSE_ARTIFACT.md section 4). + +So `max_result_bytes` is sized against **one computation's working set**, and the arithmetic +"a corpus takes N calls at this size" is a tell that the model is being treated as the transport. It +never is: the model supplies addresses and the engine moves the bytes +([CODE_MEDIATED_TEXT.md](../../architecture/CODE_MEDIATED_TEXT.md)). **The DoS property is independent of all of this and always held:** `frame.read_frame` compares the declared length against the bound *before* reading a body, so a frame declaring 4 GiB is refused for diff --git a/docs/product/repl-sandbox/REPL_SANDBOX_DATA_MODEL.md b/docs/product/repl-sandbox/REPL_SANDBOX_DATA_MODEL.md index 9206b7d..3308e8e 100644 --- a/docs/product/repl-sandbox/REPL_SANDBOX_DATA_MODEL.md +++ b/docs/product/repl-sandbox/REPL_SANDBOX_DATA_MODEL.md @@ -177,7 +177,11 @@ residual the boundary deliberately leaves; its caps are defense-in-depth, **neve them host-side, so the model can direct host-resident data to a sink **without ever holding it**: `llm_query(prompt="summarise", context=beliefs)` resolves the belief handle host-side, the sub-LLM summarises, and only the bounded summary returns (inbound-metered) — the model reasoned over the belief -base without a row of it entering the guest. Likewise `answer.submit(H)`: the referent leaves via the +base without a row of it entering the guest. Likewise `answer.submit(H)` — **DESIGNED, NOT BUILT +(noted 2026-07-24): `trellis_answer.submit` takes an expression string and renders a value; there is +no handle argument and no host-side resolution. The by-reference answer sink is the doctrine's +wholesale hand-off and it does not exist** ([FEATURE_LIST.md](../FEATURE_LIST.md) 3.2) — under which +the referent leaves via the audited answer egress, never through the guest namespace. **Prefer by-reference sinks; `materialize` is only for when the model itself must compute over the bytes.** diff --git a/docs/product/repl-sandbox/REPL_SANDBOX_SPEC.md b/docs/product/repl-sandbox/REPL_SANDBOX_SPEC.md index 7869c37..6514601 100644 --- a/docs/product/repl-sandbox/REPL_SANDBOX_SPEC.md +++ b/docs/product/repl-sandbox/REPL_SANDBOX_SPEC.md @@ -157,13 +157,15 @@ capability. spec the standing provenance-grounded injection-objection and the outbound defeater seats, composed from the -1 doubt tier ([DOUBTS_WORKSPACE §8–§9](../../architecture/DOUBTS_WORKSPACE.md)); Guardrail 15 (prompt-engineering + hypershot-protocol) applies when the seat prompts are authored. -- **`MAX_FRAME_LEN` — rule settled July 22, 2026, shipped value still owner-gated.** Size the cap - off the worker's context window rather than off the largest frame the plumbing might carry, so it - is a structural guarantee (no single frame can context-saturate a worker) and not only a DoS - bound. Derivation: 1,050,000-token window × 50% × ~4 bytes/token ≈ 2.1 MB → **2 MiB**, set as - `config.DEFAULT_MAX_FRAME_LEN` with the window recorded beside it as the input to re-derive from - when the model pin changes. The DoS property is unchanged: the reader still rejects an over-length - declaration before allocating. +- **`MAX_FRAME_LEN` — RATIFIED July 24, 2026.** `max_result_bytes` **2 MiB** (one materialisation), + `max_frame_len` **4 MiB**, the frame **derived from the slice** and never from the worker's context + window; `tests/test_config.py` asserts `max_frame_len >= 2 * max_result_bytes`. Full ruling: + [CONFORMANCE §2.3](REPL_SANDBOX_CONFORMANCE.md). The superseded July-22 rule sized the frame off + the context window, which protects nothing the marshal caps do not already hold and left the frame + *below* `max_result_bytes`, so a legal broker result could not cross the wire; the token derivation + now sizes the **slice**, as a sizing convention rather than an enforcing bound. The DoS property is + unchanged and was never what the number bought: the reader rejects an over-length declaration + before allocating. - The vsock bridge is unbuilt glue — security-critical; spec its frame parser + privilege drop. - Warm-pool clean-slate-reset policy (only if pooling is adopted). - Whether to relax `_SAFE_BUILTINS` once the VM boundary exists (redundant defense-in-depth). diff --git a/src/repl_sandbox/config.py b/src/repl_sandbox/config.py index 3b0b83b..a1a3546 100644 --- a/src/repl_sandbox/config.py +++ b/src/repl_sandbox/config.py @@ -52,15 +52,16 @@ def version_at_least(observed: str, minimum: str) -> bool: # Four different things get bounded in this system and they are easy to collapse # into one number. Keeping them apart is the point of this block: # -# REPL namespace the corpus the model works over `Tier0Limits.address_space_bytes` -# one slice host store -> guest namespace `BrokerCaps.max_result_bytes` +# the corpus what the model reasons over *nothing here* — host-side, behind handles +# the working set what it holds in the namespace `Tier0Limits.address_space_bytes` +# one materialisation host store -> guest namespace `BrokerCaps.max_result_bytes` # model attention what reaches the transcript `MarshalCaps` (this file) # one wire message a single frame `DEFAULT_MAX_FRAME_LEN` # -# **The REPL is meant to be large — potentially gigabytes — and read in slices.** -# None of the bounds below constrain that; the namespace ceiling is the rlimit. -# A 12 MiB value in the namespace returns a ~4 KB exec reply, because the -# marshalling caps describe it rather than carrying it. +# **The corpus is bounded by nothing in this file, because it never enters the +# guest.** Address space bounds the working set — what the model materialised and +# is computing over — and nothing else. A 12 MiB value in the namespace returns a +# ~4 KB exec reply, because the marshalling caps describe it rather than carry it. #: The context window a slice is sized against, in tokens. Recorded so the #: derivation below can be re-run rather than re-guessed. @@ -87,9 +88,24 @@ def version_at_least(observed: str, minimum: str) -> bool: #: as though it protected the context would repeat, one layer over, the error that #: produced the correction. #: -#: Raising this is the single lever for bulk transfer: a corpus arrives in -#: `ceil(bytes / max_result_bytes)` round trips, so 2 MiB is ~500 calls per GB. -#: `DEFAULT_MAX_FRAME_LEN` follows it and must be re-derived with it. +#: **This is not a bulk-transfer bound, because there is no bulk transfer.** The +#: worker does not move a corpus through itself; it answers questions *about* one +#: by reading slices and composing a derived response artifact over several turns. +#: `materialize` is the exception path — DATA_MODEL section 6, whose title is +#: *The bounded materialisation exception*: *prefer by-reference sinks; +#: `materialize` is only for when the model itself must compute over the bytes.* +#: A gigabyte reaching an answer was meant to go `answer.submit(H)`, resolved +#: host-side and leaving by the audited egress — **an op that does not exist.** +#: `trellis_answer.submit` takes an expression string and renders a value; there +#: is no handle argument. Recorded because two records route bulk through it, and +#: an unbuilt escape hatch reads exactly like a built one. +#: +#: So the number to size against is **one computation's working set**, not a +#: corpus divided by anything. Arithmetic of the form "a corpus takes N calls at +#: this size" is the tell that the model is being treated as the transport, which +#: it never is (`docs/architecture/CODE_MEDIATED_TEXT.md` — the model never counts +#: and never copies, so bulk movement is the engine's job). +#: `DEFAULT_MAX_FRAME_LEN` follows this constant and must be re-derived with it. DEFAULT_MAX_RESULT_BYTES = 2 * 1024 * 1024 #: Hard cap on the declared length of a single wire frame. **Derived from the