oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1# Questions for Paul — memoria
2
3Overnight 2026-09-03/04. Design guidance I delegated went to Fable and
4gpt-5.6-sol; what is left here is what I judged yours.
5
6**Nothing below blocks trying it.** `cd /home/paulca/memoriadrive && ./run.sh`,
7then http://127.0.0.1:8823 — see the README.
8
9---
10
11## Answered last night, recorded, no action needed
12
13**Q2 — the Docs §11.2 egress amendment.** Done: `oficina/docs`
14discussion 33, posted as `claude~paul@keymail.dev`. It asks Docs to record
15the *boundary* of its rejection rather than reverse it, and tells them
16plainly that memoria's egress is the same kind and comparable volume to
17what they rejected, with only three things differing.
18
19**Q3 — numeric questions.** Both models asked, answers synthesised into
20§8.7. They converged, so I did not have to arbitrate:
21
22- A new **S6** call. Sheets computes and formats; memoria never does
23 arithmetic. S6 takes an *enumerated function* (`SUM`, `AVERAGE`, `MIN`,
24 `MAX`, `COUNT`) over cell refs — **never expression text** — so memoria
25 cannot grow into a formula author.
26- Spreadsheet chunking: one **structure chunk** per tab carrying the
27 vocabulary, plus **region row chunks** rendered as facts
28 (*"FY2024 › P&L › Revenue: Jun 2024 = €1,350,000"*). That is what makes
29 `revenue`, `June` and `2024` findable for a cell containing `1350000`.
30- Retrieval resolves **named slots** and refuses to show a number unless
31 every slot has exactly one authorized operand. Two candidates is
32 ambiguity, never extra evidence.
33- Both independently forbade the same thing: a **RAG calculator** that
34 lets a model average numeric strings it retrieved. It stacks incomplete
35 recall, wrong operand selection and arithmetic outside Sheets — and
36 cites correctly while being wrong.
37
38Your example works under this design and states its basis: *"Average of
39June 2024, June 2025, June 2026."* If a year is missing it shows the slot
40table and no number.
41
42---
43
44## 1. Closed — OpenRouter is out, TensorX is in
45
46*Was: "Your 'C' on the OpenRouter question did not map." You asked for
47TensorX instead on 2026-09-04; that answers it, and the recommendation this
48question carried — go direct to one provider rather than through a proxy —
49is what TensorX does.*
50
51**What changed in the spec:** §8.3 names TensorX and says what it does not
52buy; §8.2 names `z-ai/glm-5.3-flash` and `moonshotai/kimi-k3` and
53generalises the `openrouter/auto` ban to any id that can move under a team;
54§8.6 re-derives cost; §7.2 fixes a claim that was true of OpenAI and false
55here; §12 corrects the local-embedding row; §13 carries three new items.
56
57**One thing I could not do:** verify the embeddings endpoint against a live
58key. The vendor's LlamaIndex page names OpenAI's `text-embedding-3-small`,
59which TensorX cannot be serving; their API reference names
60`qwen/qwen3-embedding-8b`, which it can. I have believed the API reference.
61If that first request in fact *succeeds* against `api.tensorx.ai`, they are
62proxying to OpenAI and we are back to two parties — see §13. **One call
63with a key settles it, and I'd rather that happened before slice 2 than
64after.**
65
66**Reviewed by Fable, REQUEST CHANGES, 10 findings, all applied.** It was
67worth doing: two of the four MAJORs were me breaking a rule in the same
68paragraph I wrote it. I had said a model id that can move under a team
69can't be printed on an answer, then picked `kimi-k3` and
70`glm-5.3-flash`, neither of which is datestamped. And I wrote that §13
71"records how that was established" three paragraphs from §13 saying
72**untested**. §1.5 has the rest.
73
74**Both questions this section asked are now answered** (2026-09-04):
75
76- **Exactly one provider, TensorX** — *"if we want to swap that later we
77 can."* Decision 13 records both halves. Swapping *generation* is cheap —
78 one client, one base URL, two model ids — through §Review, where the
79 two-parties question gets asked again. Swapping *embeddings* is not: a
80 host serving the same open weights costs a migration, anything else costs
81 a re-embed of the whole corpus. Fable caught me writing "two model ids"
82 when the third is the expensive one.
83- **Open-weight frontier is fine** — *"this is lightweight LLM inference
84 based on categorisation and memory. It's not complex reasoning."* That is
85 the right reading of §8.2 and I have put your words in §13. It also
86 prompts a question back: **if it is not complex reasoning, does §8.2 need
87 two tiers at all?** Measuring the cheap tier on the synthesis class
88 before building the expensive one would make §8.6's cost argument
89 stronger, not weaker. Not urgent; slice 2.
90
91**Cost, corrected twice and now settled.** I first said it fell, then said
92it hadn't much, and both were against the wrong baseline. Against draft 3's
93~$5 and ~$100: **~$3/month all-cheap and ~$26 all-frontier**, so the
94frontier month fell about 4× and the cheap one about 1.6×. Most of that is
95§8.2 pinning dated DeepSeek ids, not the move off OpenRouter — worth
96keeping straight so the next provider argument isn't made on a 4× that
97belongs to a different decision.
98
99**Now verified against your key (2026-09-05), with one caveat.** The
100embeddings endpoint serves `qwen/qwen3-embedding-8b` as documented, and the
101`text-embedding-3-small` probe — the one that would have shown them
102proxying to OpenAI — **came back 403.** That is the good outcome, but read
103it precisely: 403 is a *refusal*, not a *not-found*. A 404 would have said
104"no such model"; 403 is consistent with a routable name your key isn't
105entitled to. It rules out proxying-for-anyone, not proxying-for-someone.
106§8.3 still says "narrowed, not closed", and the subprocessor question is
107untouched by any of this — that needs the DPA, not the API.
108
109**Two corrections to what I told you an hour ago**, both from my reading a
110truncated list and drawing a conclusion from it: `moonshotai/kimi-k3` does
111exist, and their website's model list does match the API. I said otherwise
112on 27 of 27 models' worth of missing data. The same mistake §1.6 is about.
113
114**And one real finding: `encoding_format: "base64"` is accepted and
115silently ignored.** So ingest pulls ~4 GB over the wire for a 90k-chunk
116corpus, not the ~2 GB I hedged with, and there is no opt-out (§7.2).
117
118Still does not bite until slice 2.
119
120## 2. Closed — search page, not a chat (decision 14, §8.4)
121
122**Confirmed by you on 2026-09-05**, including that parking a conversational
123surface in §12 is the right disposition rather than forbidding it. Also
124settled the same day: keep DeepSeek for both tiers, keep two tiers *and*
125still measure, and proceed with TensorX rather than holding for the DPA.
126All four are recorded — decision 14 and §8.4; §8.2; §13; §13 again — and
127Fable caught me claiming that before two of them were actually written.
128
129**Two footnotes on rulings you gave, because the record should show what
130you were shown.** When you kept DeepSeek I had told you the pinning rule
131forced it; it didn't — a dated Qwen id exists, so a two-lab pair was
132available and I hadn't put it in front of you. Nothing needs undoing, and
133that Qwen id is now the named frontier fallback. And "proceed with TensorX
134for sure" I have read narrowly: **the provider decision is settled and
135slice 2 can be built against it, but the DPA and retention terms are still
136owed before egress is switched on over real content.** If you meant the
137wider thing, say so and §13 changes.
138
139*The question as it stood, kept because the reasoning is the useful part:*
140
141You asked whether we were building a chat UI or a hybrid. I answered
142*hybrid, search-first* — one GET search form, one query in, one page out,
143with an optional 🤖 paragraph above the citation list. §8.4 already implied
144it; it now says it.
145
146**But I first argued it as forced when it is chosen, and Fable caught me.**
147I claimed decision 3 forbade a chat outright — that re-authorizing a
148transcript would blow §6.5's latency budget. It would not: §4.1 batches 256
149pairs a call and §6.5 makes a page one round, so a four-turn transcript is
150one batch. A correct chat is affordable here. My other argument, that a
151transcript is a cache of authorized passages, is true of any page left open
152in a tab and so distinguishes nothing.
153
154**So the honest version:** the invariant decision 3 actually requires is
155that no passage crosses a request — not in a response, not in a model
156prompt — and gate 32 enforces it. The *shape* is decision 5's call: the
157search floor is the product, so the product is a search page. That is
158**decision 14, attributed to me and not to you.** A conversational surface
159that keeps the invariant is now parked in §12 as a thing to argue rather
160than a thing forbidden.
161
162**What I need:** confirm decision 14, or tell me you want the conversational
163version considered properly. Either is cheap today and neither is cheap
164after slice 2 ships an answer surface.
165
166## 3. Is routine partial withholding acceptable? (§6.5)
167
168The genuinely new product question, and the one I would most like your
169view on.
170
171Every search now asks each contributing sibling "may this person see
172these, at this revision" — live, no caching, because caching authorization
173is what makes a stale answer a leak. Consequence: **a slow or hibernating
174sibling means the page renders without its passages and says so** —
175*"Sheets did not answer; 7 results withheld."*
176
177That is correct. It may also be constant, because siblings hibernate and a
178cold wake is slower than any sane deadline. I have set 800 ms and pre-warm
179on memoria's own wake, but if Sheets is habitually cold then the primary
180search path habitually withholds, and a notice a reader sees every time is
181a notice they stop reading.
182
183There is no design that fixes this while failing closed. The spec's stated
184position is that this is a **suite health problem memoria surfaces, not a
185memoria problem to route around** — so the answer would be to fix or drop
186the source, never to cache its authorization. Tell me if you would rather
187trade differently, because the alternative is a short-TTL cache and that
188is a stated leak window rather than a correctness fix.
189
190## 4. Deploy it, or leave it local? — ANSWERED: it is deployed
191
192Live at **https://memoria.oficina.works**, account `bds`, channel `edge`.
193The origin moved off `memoria.bds.oncarlos.com` on 2026-09-04 once the
194certificate issued; see the README's "Live" section for what that entailed
195and what the old hostname still does.
196
197Local is still genuinely usable — keymail accepts the loopback
198`client_id`, which I verified.
199
200## 5. Your rastrillo CLI is about six releases stale, and it affects every app
201
202`rastrillo` on PATH predates v0.22.0: it has no `doctor`, and
203`rastrillo new` scaffolds a **v0.17.0** pin. Two things follow, and the
204second is the one that matters beyond memoria:
205
206- I bumped memoria to v0.23.0 by hand and wrote `tools/vendorassets` to
207 re-copy the three vendored files from the linked library, because there
208 is no `doctor --fix` to do it.
209- **The scaffold's `layout.html` links `tokens.css` but not
210 `theme.css`**, and `tokens.css` defines eleven tokens, none of them
211 colours. Any app scaffolded with this CLI renders with every colour
212 resolving to `initial`, which invalidates whole shorthand declarations —
213 rules vanish rather than the page going grey. That is the exact failure
214 your own memory note describes for the page-header rule.
215
216`go install github.com/carlosframework/rastrillo/cmd/rastrillo@v0.23.0`
217would fix it, but it is a shared binary other sessions are using, so I
218left it alone.
219
220## 6. An anonymous visitor can make the instance dial any domain
221
222Found by the code review, and it is a real hole rather than a wording
223problem. rastrillo's sign-in classifier fetches
224`https://<the address's domain>/.well-known/keymail` on `POST /auth/begin`
225— and rastrillo runs that **before** the admission gate, so it happens for
226addresses that are not on the owners list. Type
227`anything@attacker.example` into the sign-in form and the instance makes
228an outbound request to `attacker.example`.
229
230What it gives an attacker: confirmation the instance exists and is
231reachable, its egress IP, and a timing channel. Not content. But it also
232made the footer's "nothing leaves this instance" false, which is why that
233copy changed.
234
235**I did not fix it**, because the fetch is inside `rastrillo/auth` and
236`keymaildev/signin`, not in memoria — fixing it here would mean
237re-implementing the classifier or gating `/auth/begin` on the owners list
238before rastrillo sees it. The second is small and I can do it, but it
239changes sign-in behaviour for guests, which slice 1 will want. Say which
240you'd prefer, or whether it belongs upstream in rastrillo.
241
242## 7. Not a question, but you should know
243
244`gpt-5.6-sol` via Codex **reported its own model id as `gpt-5`** when
245asked. Codex's own banner says `gpt-5.6-sol`. I recorded the discrepancy
246in the spec's review table rather than picking one, since §Review requires
247attributing a review to a model and the two sources disagree.
248
249## 8. The deployed hostname has no `<team>` label, and the spec says it should
250
251home ruling 16 and this project's own spec (§6.3, and line 4) put an
252instance at **`<team>.<app>.oficina.works`**. In §6.3 that label is not
253decoration: barrier 2 is *"memoria's client refuses any host whose
254`<team>` label differs from its own"*, test-enforced against a fixture of
255hostile hostnames. A hostname with no team label gives that guard nothing
256to compare.
257
258What is deployed, across the whole suite, is `<app>.oficina.works` — no
259team label, no wildcard certificate: `docs.oficina.works`,
260`sheets.oficina.works`, `calendar.oficina.works`, and now
261`memoria.oficina.works`. I matched the four live siblings rather than
262invent a team label on my own, because slice 0 has no siblings for barrier
2632 to defend against and consistency with what is already running seemed
264the safer default.
265
266It stops being free at slice 3, when the connector client wants a label to
267compare. Roughly the options: re-host every app under a team label before
268then — a second origin change each, and every one is a sign-in cutover —
269or amend ruling 16 to make the team label a slice-3 addition and say what
270barrier 2 compares until it exists.
271
272**I did not touch the spec.** Ruling 16 is settled and AGENTS.md forbids
273relitigating one in a drive-by. This is the flag, not the change.
274