oficina / memoria Public

🤖 spec: TensorX replaces OpenRouter as the named provider

Model egress is slice 2 and unbuilt, so this is spec-only: there are no
OpenRouter call sites to change. Decision 4 settled "one named provider",
never which one, and QUESTIONS #1 has been recording since 2026-09-03 that
a proxy means two parties see content while the answer prints one name.

TensorX runs the weights it serves, so the proxy-plus-upstream shape goes
away, and /v1/embeddings covers the larger leak from the same counterparty.
Consequences propagated: §7.2 (qwen3-embedding-8b is 4096 fixed and rejects
`dimensions`, so 512 is a client-side truncation), §8.2 (pinned ids),
§8.3 (the provider, and what it does not buy), §8.6 (recosted), §12, §13.

Reviewed by Fable per AGENTS.md §Review — REQUEST CHANGES, 4 MAJOR, 4
MINOR, 2 NIT, all applied. Two of the MAJORs were rules broken in the
paragraph that stated them: §8.2 wrote "an id that can move under a team
cannot be printed on an answer" and then selected two undatestamped
aliases, and §8.3 called a fact established three paragraphs from §13
calling it untested. A third found gate 22 written in a shape that could
not go red — cosine similarity is scale-invariant, so a gate watching
recall fall cannot detect a skipped L2 renormalise. The spec now commits
search to a raw dot product, which is what §7.3's flop count always
assumed, and asserts unit norm directly. Fourth such gate in this project,
first caught before it was built. §1.5 records it.

The claim is deliberately weaker than the last draft's: one contractual
counterparty is not one organisation seeing the corpus, the DPA and the
retention terms are unread, and none of it is verified against a live key.
§8.3 says "narrowed, not closed".

Needs Paul: decision 13's second half. "Fixed by the team owner" reads as
the owner picking a provider; a configurable base URL lets them reinstate
a proxy while the answer still says "one named provider". Written as
supports-exactly-one, flagged in QUESTIONS #1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EaqFkVdF9fx2mR1s8MEUy3
Paul Campbell pushed by paul@keymail.dev adf7ae8349fa7d3a179d15d3b5a80ab9005a9d96 parent 617eb2a
3 files changed, +338 −40
  • AGENTS.md +11 −4
  • QUESTIONS-FOR-PAUL.md +60 −16
  • docs/superpowers/specs/2026-09-03-memoria-design.md +267 −20
diff --git a/AGENTS.md b/AGENTS.md
index 6cf9924..758bce6 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -7,7 +7,7 @@ wins. Where it disagrees with the design spec, the spec wins and this file
is wrong — fix it in the same commit.
**The design:** `docs/superpowers/specs/2026-09-03-memoria-design.md`. Its
-§1 is the settled-decisions table, §1.2–§1.4 record what five independent
+§1 is the settled-decisions table, §1.2–§1.5 record what six independent
reviews changed. Don't relitigate a settled decision in a drive-by.
---
@@ -63,6 +63,11 @@ so; it never renders as a confident zero.
line, error string or metric label.** `carlos logs` is platform-held
storage we cannot grep. Gate 4 enforces it; it was missing once, and
what it found when written was that we were logging sign-in links.
+- **A model provider's response body is content too.** An OpenAI-compatible
+ 4xx can echo the input that caused it, so the provider's error path is a
+ content path. Never log one verbatim — status and a fixed string. Gate 25
+ (spec §10); nothing calls a provider before slice 2, so this lands with
+ the first call and not after it.
- **Derived state is refreshed after the transaction commits, under
`context.WithoutCancel`.** A client closing the tab must not leave the
index disagreeing with the database.
@@ -113,9 +118,11 @@ so; it never renders as a confident zero.
Every spec and every branch is reviewed by a model that did not write it,
before it is approved or merged, and the reviewing model is recorded.
-Five passes so far: Fable and GPT Sol on spec drafts 1–3, Fable on the
-code. Every one returned REQUEST CHANGES, and the code review was the most
-useful of them.
+Six passes so far: Fable and GPT Sol on spec drafts 1–3, Fable on the code,
+Fable on the §8.3 provider amendment. Every one returned REQUEST CHANGES.
+The code review was the most useful; the provider review found a rule
+broken in the paragraph that stated it, twice, and a fourth gate that could
+not have gone red (§1.5).
---
diff --git a/QUESTIONS-FOR-PAUL.md b/QUESTIONS-FOR-PAUL.md
index d6f569a..e545a47 100644
--- a/QUESTIONS-FOR-PAUL.md
+++ b/QUESTIONS-FOR-PAUL.md
@@ -41,22 +41,66 @@ table and no number.
---
-## 1. Your "C" on the OpenRouter question did not map
-
-I offered three options — name both parties, go direct, or drop routing —
-as bullets, not letters, so "C" is ambiguous. You also said "fine as long
-as openrouter seems like the right choice", which reads like keeping it.
-
-**What I did:** nothing irreversible. §8.3 and §3.6 now state the honest
-version either way, and §13 records that if embeddings and chat both route
-through OpenRouter then **two parties see content** — the proxy and the
-upstream provider — so decision 4's "one named provider" names one of two.
-
-**What I still recommend:** go direct to one provider. §8.2's router needs
-only two tiers, cheap and frontier, and one provider supplies both, so the
-proxy buys little and costs the claim.
-
-This does not bite until slice 2.
+## 1. Closed — OpenRouter is out, TensorX is in
+
+*Was: "Your 'C' on the OpenRouter question did not map." You asked for
+TensorX instead on 2026-09-04; that answers it, and the recommendation this
+question carried — go direct to one provider rather than through a proxy —
+is what TensorX does.*
+
+**What changed in the spec:** §8.3 names TensorX and says what it does not
+buy; §8.2 names `z-ai/glm-5.3-flash` and `moonshotai/kimi-k3` and
+generalises the `openrouter/auto` ban to any id that can move under a team;
+§8.6 re-derives cost; §7.2 fixes a claim that was true of OpenAI and false
+here; §12 corrects the local-embedding row; §13 carries three new items.
+
+**One thing I could not do:** verify the embeddings endpoint against a live
+key. The vendor's LlamaIndex page names OpenAI's `text-embedding-3-small`,
+which TensorX cannot be serving; their API reference names
+`qwen/qwen3-embedding-8b`, which it can. I have believed the API reference.
+If that first request in fact *succeeds* against `api.tensorx.ai`, they are
+proxying to OpenAI and we are back to two parties — see §13. **One call
+with a key settles it, and I'd rather that happened before slice 2 than
+after.**
+
+**Reviewed by Fable, REQUEST CHANGES, 10 findings, all applied.** It was
+worth doing: two of the four MAJORs were me breaking a rule in the same
+paragraph I wrote it. I had said a model id that can move under a team
+can't be printed on an answer, then picked `kimi-k3` and
+`glm-5.3-flash`, neither of which is datestamped. And I wrote that §13
+"records how that was established" three paragraphs from §13 saying
+**untested**. §1.5 has the rest.
+
+**One question I need you to answer, because it is two different
+products.** Decision 4 says the provider is "fixed by the team owner". If
+that means the base URL is configurable, an owner can point memoria at
+OpenRouter and the answer will still print "one named provider" — the
+exact hole this change was closing. **I have written the spec as: memoria
+supports exactly one provider, and the owner's choice is on or off.** That
+is narrower than decision 4's words and I would rather you confirmed it
+than discovered it. New decision row 13 says so and points here.
+
+**Two things you may want to push back on:**
+
+- **The frontier tier is now open-weight.** `kimi-k3` rather than Claude or
+ GPT. §8.2's synthesis class is where answer quality shows, so this is a
+ product call and it is yours. Fable's line on it is right and I have put
+ it in §13: acceptable as a product call, unacceptable as an unmeasured
+ one, and we have measured everything else in this spec.
+- **Cost did not really fall.** I first wrote that it did. The new
+ per-query figures sit *inside* the ranges §8.6 already carried, near the
+ bottom — the ceilings fell, the estimates didn't. Roughly $3/month
+ all-cheap, $50 all-frontier.
+
+**What I could not do:** verify any of it against a live key. If the
+LlamaIndex page's `text-embedding-3-small` call actually succeeds against
+`api.tensorx.ai`, they are proxying to OpenAI and we are back to two
+parties. And even a clean result there doesn't tell us whose GPUs run the
+model — TensorX is young to own datacentres in three cities, so there is
+probably a subprocessor, which is a DPA question and the DPA is unread.
+§8.3 now says "narrowed, not closed" rather than claiming the win.
+
+Still does not bite until slice 2.
## 2. Is routine partial withholding acceptable? (§6.5)
diff --git a/docs/superpowers/specs/2026-09-03-memoria-design.md b/docs/superpowers/specs/2026-09-03-memoria-design.md
index afbe6b8..0e66473 100644
--- a/docs/superpowers/specs/2026-09-03-memoria-design.md
+++ b/docs/superpowers/specs/2026-09-03-memoria-design.md
@@ -3,7 +3,7 @@
The Oficina suite's memory. One instance per team, at
`<team>.memoria.oficina.works`.
-**Status:** draft 3, 2026-09-03.
+**Status:** draft 3, 2026-09-03; amended 2026-09-04 (§8.3 provider).
Review history, per AGENTS.md §Review. None of these models wrote the
draft they reviewed; all drafts were written by Opus.
@@ -12,7 +12,8 @@ draft they reviewed; all drafts were written by Opus.
|---|---|---|
| 1 | GPT Sol (commandcode), Fable | REQUEST CHANGES ×2 — 12 and 21 findings, three structural gaps found by both |
| 2 | GPT Sol via Codex on a ChatGPT subscription — **it reported its own model id as `gpt-5`, which disagrees with the `gpt-5.6-sol` Codex labelled it; recorded as the discrepancy it is rather than resolved** | REQUEST CHANGES — 4 ANSWERED, 1 REWORDED, 6 PARTIAL, 1 blocker and 6 new findings |
-| 3 | none yet | — |
+| 3 | Fable | REQUEST CHANGES — 22 findings. (This row read "none yet" while §13 recorded the review as done; §13 was right.) |
+| 3, §8.3 provider amendment | Fable | REQUEST CHANGES — 4 MAJOR, 4 MINOR, 2 NIT. All applied; §1.5 records what it changed |
§1.2 records what the draft-1 reviews changed; §1.3 what the draft-2
review changed. **Draft 3 has not been reviewed.**
@@ -67,6 +68,7 @@ embedding is a plaintext cell in a costume — §3.1.
| 10 | **memoria never does arithmetic. A number comes from a cell, or from Sheets' engine via S6.** S6 takes an enumerated function over cell refs — never expression text — so memoria is never a formula author. | memoria computing and showing its working (a second calculator, arrived at one `mean` at a time); writing a formula into a scratch sheet (a write, forbidden by §6.4) | Paul asked for numeric questions, 2026-09-03; shape ruled by Fable and GPT Sol independently, converging |
| 11 | **Admission is derived from the siblings, not from a members table memoria maintains.** A person may enter memoria if at least one sibling says they are a member of this team. | Draft 2's own members table, which left a person removed everywhere else still able to read memoria-owned content | after the draft-2 review, 2026-09-03 |
| 12 | **Name: `memoria`.** House convention is plain Spanish office nouns. A *memoria* is both a memory and, in Spanish office usage, a written record. | `archivo`; `cerebro`; `base`; anything English | Paul, 2026-09-03 |
+| 13 | **The provider is TensorX, and memoria supports exactly one.** A team owner's choice is on or off, not which — a configurable base URL would let an owner reinstate a proxy while the answer still printed "one named provider". Chosen for EU jurisdiction and open weights, criteria decision 4 did not state, at a known cost in id-pinning and frontier quality (§8.3). | OpenRouter (a proxy: two parties see content); OpenAI direct (better id-pinning, worse jurisdiction and cost) | Paul, 2026-09-04. **The "supports exactly one" half is the spec's reading of decision 4's "fixed by the team owner" and needs Paul's confirmation** — §13 |
### 1.1 The proposal review, and what it killed
@@ -224,6 +226,50 @@ not fail for the thing they named.** A gate is a claim about the world,
and an unmutated gate is an unverified claim — including, twice on this
branch, a gate whose subject had been deleted while it stayed green.
+### 1.5 What the provider review changed
+
+The §8.3 amendment — OpenRouter out, TensorX in — was written by Opus and
+reviewed by Fable, which returned REQUEST CHANGES with four MAJOR findings.
+Two of them are the same mistake in different clothes, and it is the
+mistake §1.4 is about: **stating a rule and then breaking it in the
+paragraph that states it.**
+
+**§8.2 wrote a pinning rule its own selections failed.** The rule — a model
+id that can move under a team without the team changing it cannot be
+printed on an answer — is right, and generalises `openrouter/auto`'s
+disqualification properly. Then it selected `moonshotai/kimi-k3` and
+`z-ai/glm-5.3-flash`, neither datestamped, both plausibly aliases over
+dated checkpoints. The fix is a datestamped id where one exists, gate 24's
+canary where none does, and §13 recording that the vendor's id-immutability
+policy is unread. The draft had even quoted the one datestamped id in the
+catalogue as evidence — and it was the model it did not pick.
+
+**§8.3 claimed as established what §13 called untested**, three paragraphs
+apart. Worse, the verification it proposed could not have established it:
+the live call settles whether one model *name* is proxied, not whose GPUs
+run the weights. "One contractual counterparty" is not "one organisation
+sees the corpus" — a subprocessor sees content in §3.2's sense — so the
+claim is now written as narrowed rather than closed.
+
+**§7.2 specified a gate that could not fail.** It said a mutation of the
+4096→512 renormalise would be caught by watching recall fall. But cosine
+similarity is scale-invariant: against a true cosine, skipping the
+renormalise changes no ranking, and the gate stays green forever. The spec
+had never said whether search computes cosine or a raw dot product. It now
+says dot product — which is what §7.3's flop count always assumed — and
+gate 22 asserts unit norm deterministically instead of inferring it from
+quality. This is the fourth gate in this project to have been written in a
+shape that could not go red, and the first caught before it was built.
+
+**Decision 13 exists because of this review.** The amendment named a
+provider in §8.3 without recording the ruling in §1, which AGENTS.md says
+is how a settled decision gets relitigated in a drive-by.
+
+The general lesson, distinct from §1.4's: **a rule and its first
+application should be reviewed by different people.** Whoever writes the
+rule has just persuaded themselves, and is the worst-placed reader to
+notice that the very next paragraph fails it.
+
---
## 2. What memoria is
@@ -292,7 +338,12 @@ edges alone reveal which passages resemble each other.
**From the bucket:** document and chunk counts; sealed blob sizes; source
app and coordinates per chunk, plaintext exactly as Sheets §2.2 keeps
coordinates; ingest and update timestamps, therefore which sources are
-busy and when; embedding dimension, therefore the model.
+busy and when; embedding dimension — though "therefore the model", as this
+line read until draft 3, overstated it even then: the design has always
+stored 512, which under OpenAI was a requested `dimensions` and under §8.3
+is a truncation of a native 4096. Either way the stored width names a
+choice of ours. It narrows the candidate models; it does not name one. Do
+not lean on that.
**From a live host — the largest entry, and new.** A compromised memoria
host reaches the team's corpus across every app at once, plus §5.1's
@@ -878,8 +929,43 @@ wrong by about 4×. The ceiling is a chunk count and only a chunk count.
The token figure follows from the measured chunk (202 words ≈ 270 tokens),
not the 400-token target: **24M, not the 36M draft 3 carried into §8.6.**
-**Vectors.** 512-dim Matryoshka embeddings (OpenAI's `-3` models accept a
-`dimensions` parameter), stored **int8-quantised**:
+**Vectors.** 512-dim Matryoshka embeddings, stored **int8-quantised**.
+
+**512 is a client-side truncation, not a requested dimension.** The
+parenthetical this replaced — *"OpenAI's `-3` models accept a `dimensions`
+parameter"* — is true of OpenAI and false of the provider §8.3 selects.
+`qwen/qwen3-embedding-8b` emits **4096 fixed** and *fails the request* if
+`dimensions` is sent. The pipeline therefore keeps the first 512 values and
+**re-normalises to unit L2**. That is valid because the model is trained
+Matryoshka.
+
+**Search computes a raw dot product over vectors stored unit-normalised**,
+not a cosine that divides by norms at query time. §7.3's `2 × N × D` flop
+figure already assumed this and the spec never said so. It matters here:
+**cosine is scale-invariant, so against a true cosine a skipped renormalise
+changes no ranking at all** — a gate written over cosine could not fail,
+which is the shape AGENTS.md says this codebase has already shipped three
+times. Over a raw dot product the same omission silently reorders results.
+So the earlier draft of this paragraph, which said "cosine similarity then
+degrades smoothly", was wrong twice: cosine does not degrade, and the thing
+that does degrade is the form the spec had not committed to.
+
+Gate 22 is therefore deterministic and cannot be argued past — *every
+stored vector has L2 norm 1 ± ε; mutation: skip the renormalise, expect
+red* — and gate 23 carries the measurement it cannot do.
+
+**It costs ingest bandwidth, not storage.** Truncation happens before
+storing, so the table below is unchanged — 90k chunks × 4096 f32 is
+**~1.4 GB decoded to store 46 MB**, a ratio of about 32×, not the 8× you
+get by mis-reading the kept width as the f32@512 intermediate (184 MB is
+what truncation yields; int8 quantisation is what is stored). On the wire
+it is worse than decoded: **~2 GB base64, ~4 GB as JSON floats**, and
+whether TensorX honours `encoding_format: "base64"` is unverified (§13).
+
+This is ingest-time, not wake-time, so §7.3's ceiling is untouched. It is
+not free either: neither the first embed's wall-clock nor the batch size
+the endpoint accepts is written down anywhere, and §13 asks for the
+re-embed figure while the first embed's is equally absent.
| chunks | int8 @512 | f32 @512 | f32 @768 |
|---|---|---|---|
@@ -1055,10 +1141,36 @@ alongside it. The passages have already passed the gate.
Two configuration names — `model.cheap`, `model.frontier` — with the
route printed on the answer.
-**`openrouter/auto` is disqualified.** It selects by trailing-7-day
-community spend, changes weekly, and cannot be attributed — failing
-AGENTS.md §Review's "record which model" and decision 4's naming
-requirement.
+**The pinning rule bit the first two ids this section chose.** They were
+`z-ai/glm-5.3-flash` and `moonshotai/kimi-k3`; neither is datestamped, and
+Kimi's own history (`k2-0711`, `k2-0905`) says a bare family name is an
+alias over dated checkpoints. By the rule below they are floating aliases,
+so **prefer a datestamped id wherever the catalogue offers one** —
+`deepseek/deepseek-v4-pro-0813` for the frontier tier — and where it does
+not, the id is provisional and §13 carries it. Whether TensorX holds a
+stable id to stable weights is **unread**, not established.
+
+**Where no datestamped id exists, a canary substitutes for one.** At first
+configuration, embed a fixed set of public, non-content sentences — they
+are not team content, so §3.2 is untouched — seal the vectors, re-embed on
+wake, and **state an anomaly when cosine drift exceeds a stated ε**. That
+detects a silent weight swap under a stable id, which is the exact failure
+the rule names, and it is the same instrument §13's single-model item
+needs. Gate 24.
+
+**`openrouter/auto` is disqualified**, and so is anything shaped like it.
+It selects by trailing-7-day community spend, changes weekly, and cannot be
+attributed — failing AGENTS.md §Review's "record which model" and decision
+4's naming requirement. State the rule generally, because a router is not
+the only thing that breaks it: **a model id that can change under a team
+without that team changing it cannot be printed on an answer.** A floating
+alias is disqualified on identical grounds, which is why the two config
+names hold ids and not tiers — pinned where the catalogue permits it, and
+canaried where it does not.
+
+**Noted against ourselves:** OpenAI's dated snapshots satisfy this rule
+more cleanly than the catalogue §8.3 selects. That is a real cost of the
+provider choice, and §8.3 records it as one rather than talking past it.
### 8.3 Egress
@@ -1066,6 +1178,61 @@ Per decision 4: one provider, fixed by the team owner, named on every
answer, disclosed in the README's first paragraph, opt-in per team, with
§8.1's floor working when it is off.
+**The provider memoria supports is TensorX** (`api.tensorx.ai`) —
+open-weight models served on EU infrastructure in Dublin, Helsinki and
+Paris, with **advertised** zero retention. Advertised: no account exists,
+no terms have been read, and §3.6 tells the reader we cannot verify a
+retention promise. That sentence keeps saying so.
+
+**The provider is not team-configurable.** Supporting one is a code change,
+and a team owner's choice is on or off. Decision 4 says "fixed by the team
+owner", which reads as picking one, and these are different products: a
+configurable base URL on an OpenAI-compatible client lets an owner point
+memoria at a proxy and still have the answer print "one named provider",
+which is the failure this whole section is about. The spec takes the
+narrower reading. **It is Paul's to confirm and decision 4's wording needs
+it** — QUESTIONS #1, §13.
+
+**Why this one, honestly stated.** Not because it beats OpenRouter and
+OpenAI on decision 4's own words — on one half of them it loses:
+
+- **Fewer parties, not one party.** OpenRouter is a proxy, so the proxy and
+ the upstream that runs the weights both see content, and "one named
+ provider" would be naming one of two. TensorX runs the weights it serves.
+ But **one contractual counterparty is not one organisation seeing the
+ corpus**: a vendor this young on infrastructure in three cities is
+ plausibly renting it, and a subprocessor sees content in exactly the
+ sense §3.2 means. That is a DPA question and the DPA is unread (§13).
+ The claim is *narrowed*, not *closed*, and §13's item says so.
+- **Named weights — partially.** §8.2's requirement, met by a namespaced
+ catalogue, but only `deepseek/deepseek-v4-pro-0813` and its like are
+ datestamped. On id-pinning **OpenAI's dated snapshots are strictly
+ better**, and §8.2 records that against us.
+- **Open weights, which decision 4 never asked for.** Any host serving the
+ same `qwen3-embedding-8b` produces compatible vectors, so the vendor
+ failing is a *re-host*, not a re-embed of the corpus — see §13. Self-
+ hosting stays reachable (§12). This is the strongest argument here and it
+ is not a decision-4 argument at all.
+- **EU jurisdiction**, likewise not a decision-4 criterion.
+
+**So the honest record is:** TensorX was chosen for sovereignty and open
+weights — criteria Paul added on 2026-09-04, not criteria decision 4 stated
+— at a known cost in id-pinning and in frontier answer quality (§8.2, §13).
+Going direct to OpenAI satisfies decision 4's letter at least as well: one
+counterparty, pinnable dated ids, zero-retention obtainable on request, and
+the subprocessor point cuts both ways since it runs on Azure. It loses on
+jurisdiction, on open weights, and on cost. Written that way the choice
+stands; written as "TensorX wins on decision 4" it would be false.
+
+**What it does not change.** This is still whole-corpus egress at ingest,
+and §3.2's leak list is unaltered: a provider that sees the corpus sees the
+corpus, whatever its jurisdiction. EU jurisdiction and zero retention
+constrain what may lawfully be done with the content *after* it arrives —
+the first thing on this page that constrains anything — but they do not
+reduce what is sent, and a retention term is a promise we cannot verify
+(§3.6 says so to the reader and should keep saying it). Decision 5's AI-off
+floor remains the only configuration in which nothing leaves.
+
**This qualifies Docs §11.2**, which rejected third-party conversion APIs
for shipping "customer documents to a third party, contradicting §2". The
comparison must be like with like: Docs rejected whole-document egress,
@@ -1111,8 +1278,10 @@ so** — an empty paragraph is indistinguishable from AI being off.
On §7.2's measured corpus — 90k chunks, **24M tokens**:
-**Ingest:** 24M × $0.02/M (`text-embedding-3-small`) = **$0.48 once**;
-$3.12 on `-large`. Re-embed only changed chunks.
+**Ingest:** 24M × $0.02/M (`qwen/qwen3-embedding-8b`) = **$0.48 once**.
+Re-embed only changed chunks. The figure survives the move off
+`text-embedding-3-small` unchanged because the two list prices happen to
+match — a coincidence, not a reason to stop rechecking it.
This has been wrong twice, in opposite directions. Draft 1 priced 60M
tokens against a 90k-chunk corpus; draft 3 corrected to 36M by keeping the
@@ -1120,9 +1289,17 @@ tokens against a 90k-chunk corpus; draft 3 corrected to 36M by keeping the
from §7.2's measurement, and if that measurement moves so does this.
**Query:** retrieval is free. The generative layer sends ~8 passages plus
-instructions — ~5k tokens in, ~500 out. Cheap model ~$0.001–0.003;
-frontier ~$0.02–0.10. Five people × 20 queries/day × 22 days = 2,200
-queries: **~$5/month if most route cheap, ~$100 if most route frontier.**
+instructions — ~5k tokens in, ~500 out. At §8.3's list prices
+`z-ai/glm-5.3-flash` ($0.20/$0.50 per M) is **~$0.0013 a query** and
+`moonshotai/kimi-k3` ($3.00/$15.00) is **~$0.023**. Five people × 20
+queries/day × 22 days = 2,200 queries: **~$3/month if most route cheap,
+~$50 if most route frontier** — or ~$26 with
+`deepseek/deepseek-v4-pro-0813` as the frontier tier. Read that carefully:
+both figures sit *inside* the $0.001–0.003 and $0.02–0.10 per-query ranges
+this section already carried, near the bottom. **The ceilings fell; the
+estimates did not move.** Nothing here is a reason to route more queries to
+the frontier tier — §8.2's three model-free classes are still the design,
+and §8.6's closing sentence still holds.
Derived-artefact regeneration (§5.2) adds a synthesis-class call per
invalidated artefact and is unbudgeted until the feature exists.
@@ -1311,6 +1488,28 @@ renders on its own origin. The home never sees `q`.
21. **Admission is relayed, not held.** A person no sibling calls a member
is refused, and `UNKNOWN` from every sibling refuses rather than
admits.
+22. **Every stored vector has L2 norm 1 ± ε.** Deterministic, and the
+ mutation is named: *skip the renormalise after §7.2's 4096→512
+ truncation, expect red.* Written this way on purpose — a gate phrased
+ over cosine similarity **could not fail**, because cosine divides by
+ the norms and is scale-invariant. §7.2 commits the search to a raw dot
+ product so that this gate bites.
+23. **Recall at 512 against recall at 4096**, on a named labelled query
+ set, above a stated fraction. **This gate cannot be written yet**: no
+ labelled set exists in the repo and `docs/BENCHMARKS.md` measures
+ latency only. Recorded here as owed, not as present — §13. Writing it
+ as a threshold over an unlabelled corpus would be gate 2's and gate 8's
+ mistake a third time.
+24. **Embedding-model canary.** A fixed set of public, non-content
+ sentences embedded at first configuration and re-embedded on wake;
+ cosine drift beyond a stated ε **states an anomaly** and does not
+ silently continue. This is what stands in for a datestamped id where
+ the catalogue offers none (§8.2), and the detector for §13's
+ single-model risk. The mutation is a substituted model id.
+25. **No provider response body is logged verbatim.** An OpenAI-compatible
+ 4xx can echo the input that caused it, so a provider error path is a
+ content path. Gate 4 covers our own log lines; this covers the bytes
+ the provider hands back.
---
@@ -1333,6 +1532,13 @@ answer-above-citations, the §3.6 README, the Docs §11.2 qualification
recorded in Docs. **This is the slice that turns on whole-corpus egress**,
so it does not land before its disclosure copy does.
+Four things enter with it rather than after, because each is cheap now and
+a silent wrong answer later: **the live-call verification** §13 asks for,
+against a real key, before anything is signed; **gate 22**, which is
+deterministic and costs nothing; **a labelled query set**, without which
+gate 23 cannot be written at all; and **gate 24's canary**, which is the
+only thing standing between a stable model id and a silent weight swap.
+
**Slice 3 — external connectors (§5.1)** under §4.2, with least-privilege
credentials (§3.5) and gate 7.
@@ -1355,7 +1561,7 @@ rows.
| Mail beyond forward-or-label | Never by sweep. A mail repo exists **and** a per-thread audience maps onto a team |
| Cached answers | Query cost becomes visible. Requires §5.2's provenance invalidation first |
| A runtime model-call ledger | Someone needs to audit routing. Sealed and provenance-stamped when it lands (§8.3) |
-| A local embedding model | A pure-Go or cgo-free runtime exists. Would remove ingest egress entirely, which §3.2 makes the largest single leak |
+| A local embedding model | Would remove ingest egress entirely, which §3.2 makes the largest single leak. **Not the cgo-free in-process runtime this row first imagined:** §8.3's `qwen3-embedding-8b` is open-weight, so self-hosting is possible in principle, but 8B parameters means a second box to run and keep, not a library linked into the one binary |
| Cross-team anything | Never, under §6.4 |
| Fine-tuning on the corpus | Never, under decision 8 |
@@ -1436,11 +1642,52 @@ rows.
the rest of a cold start: opening SQLite, allocating the vector matrix,
parsing the snapshot and building id maps are all outside the 5-second
budget as computed.
-- **OpenRouter's embeddings endpoint** is reported OpenAI-compatible at
- `/api/v1/embeddings`, but the documentation page 404'd when checked. If
- embeddings and chat both route through OpenRouter, **two parties see
- content** — the proxy and the upstream provider — and decision 4's "one
- named provider" then names one of two. Resolve before slice 2.
+- **The two-parties problem is narrowed, not closed.** §8.3 selects a
+ provider that runs its own weights rather than a proxy, which removes
+ OpenRouter's proxy-plus-upstream shape. It does not establish that one
+ organisation sees the corpus: **whether a subprocessor sits under TensorX
+ is a DPA question and the DPA is unread**, as are the retention terms
+ §8.3 calls "advertised". Both before slice 2.
+- **One live call, before anything is signed.** TensorX's *LlamaIndex* page
+ configures embeddings as `text-embedding-3-small` against
+ `api.tensorx.ai` — a closed-weight OpenAI model an open-weight EU
+ provider cannot be serving. It reads as a copy-paste of LlamaIndex's
+ OpenAI documentation with the base URL swapped, and it is not a source;
+ the API reference is, and says `/v1/embeddings` serves
+ `qwen/qwen3-embedding-8b`. **Untested: no API key.** Be clear what the
+ call can settle: a 400 proves only that TensorX does not proxy *that
+ model name*. It says nothing about whose GPUs run `qwen3-embedding-8b`,
+ which is the item above. While there, check `encoding_format: "base64"`
+ (§7.2) and the accepted batch size.
+- **Is the model id stable?** §8.2 selects `moonshotai/kimi-k3` and
+ `z-ai/glm-5.3-flash`, neither datestamped. **Whether TensorX holds a
+ stable id to stable weights is unread.** Ask; and until answered, gate
+ 24's canary is what detects the answer being no.
+- **One embedding model, and the risk is a re-host not a re-embed.** The
+ catalogue offers exactly one. But the weights are open, so any host
+ serving the same `qwen3-embedding-8b` yields compatible vectors — closely
+ rather than bit-identically, since serving stacks and quantisations
+ differ, which is a thing gate 24 would measure. **The vendor failing
+ costs a migration; the model being retired costs the corpus**, and that
+ second exposure is identical to the one the OpenAI design carried. What
+ is written down nowhere is the wall-clock and search-quality gap of a
+ full re-embed, first or subsequent.
+- **The 4096→512 truncation is unmeasured.** §7.2 argues it is sound
+ because the model is trained Matryoshka, which is an argument and not a
+ measurement. Gate 23 is owed and cannot be written until a labelled query
+ set exists; `docs/BENCHMARKS.md` measures latency only.
+- **Qwen3-Embedding is instruction-tuned, and §7–§8 are silent on it.**
+ Queries take an instruction prefix, documents do not. OpenAI's models had
+ no such asymmetry, which is why nothing in the spec mentions one. Getting
+ it wrong costs recall and says nothing while it does.
+- **§7.2's 24M tokens is now an estimate under a different tokeniser.** It
+ came from an OpenAI-style ~1.34 tokens/word; billing is Qwen's. Probably
+ close, unverified, and §8.6 inherits it.
+- **Answer quality for the synthesis class is unmeasured, for any model.**
+ §8.2's frontier tier moved from "Claude or GPT" to an open-weight model
+ with no measurement either side of the change. QUESTIONS #1 puts the
+ product call to Paul, correctly; the measurement is still owed. The spec
+ measures everything else.
- **Docs §11.2's qualification** must be recorded in the Docs repo by
`docs-e6`, or the two specs contradict.
- **A `carlos/platform` discussion** noting the app boundary is the