oficina / memoria Public
π€ memoria
The Oficina suite's memory. One instance per team.
Ask a question, get an answer grounded in your team's own documents β with a citation you can open, always.
What exists today is a search engine, not an assistant, and it has exactly one model call in it, switched off unless you switch it on. With it off β the default β the trust claim is as strong as it gets:
> memoria's passages and its index are sealed at rest under this team's > own key, and no document ever leaves this instance β not at query > time, and not at indexing time, because nothing is sent anywhere to be > indexed.
With MEMORIA_AI=on, one thing changes, and this is all of it. When a search asks for a postal address β "office address", "postal address" β memoria first looks for one with its own checks. Addresses have no checksum, so those checks cannot find one. It then sends up to eight passages per search request β the top eight that search returned, passages you were already allowed to read β to TensorX, the one named provider, which points at any addresses in them. That includes the live searches that run while you type, not only the one you submit: each pause in typing a query that asks for an address is its own request. memoria shows only text it finds byte-for-byte in those passages, marks each such row π€ with TensorX and the model's name, and never shows the model's own words. Nothing else is sent: not the rest of your documents, not your query's other results, and nothing at all for phone numbers, eircodes, IBANs, VAT numbers or email addresses, which memoria checks itself. TensorX can read what it receives; we rely on its retention terms and cannot verify them.
An earlier draft of the first sentence said "nothing leaves this instance", which was false and a code review caught it. Signing in fetches https://<your address's domain>/.well-known/keymail before anyone is admitted, so an anonymous visitor can make the instance dial any domain they type. No document leaves; a DNS name does.
The full assistant changes this again, by much more, and the design spec is explicit about how: embeddings come from a hosted API, so switching it on will send every document in the corpus to the named provider once, at indexing time β not just the passages behind an answer. That is not built. When it is, this paragraph is replaced by Β§3.6 of the design, and not before: Β§3.6 describes egress this binary does not yet perform, and saying it early would overstate what leaves as surely as the old sentence here understated it.
Live
https://memoria.oficina.works β account bds, channel edge, serving 71bebef. Sign in with paul@keymail.dev, paul@rslw.com or paul@tito.io; real email goes out through the platform's SES, so no sign-in link is ever written to a log.
It starts empty. Paste or upload on Add content (/add, linked from the front page) to fill it β the seed tool needs filesystem access to the database, so it only works against a local instance.
The origin moved here from memoria.bds.oncarlos.com on 2026-09-04, once the certificate issued β the move docs, sheets and calendar had already made, along with id.oficina.works and the apex. It was MEMORIA_ORIGIN and a restart and nothing else: no hostname is compiled into this binary.
memoria.bds.oncarlos.com still answers. It is the platform route the custom hostname sits in front of, not a second deployment. Signing in there still starts β but it does not leave you signed in there, because MEMORIA_ORIGIN is the base of every emailed link, so the link lands you on oficina.works and the session cookie is set for that host. Use the canonical name.
That value is also the CSRF yardstick and the OAuth client_id keymail validates redirects against, so it must match the host the browser is actually on. Be precise about how much the CSRF half is doing, though: csrf.SameOrigin reads Sec-Fetch-Site before it compares origins, so a browser sitting on the old hostname sends same-origin and passes. What refuses is a scripted post carrying a stale Origin header. The mailed link, not the CSRF check, is what actually funnels people onto one host.
The DEK is the whole corpus. It lives in carlos secrets for this app, and a copy is at memoriadrive/DEK-production.txt. Lose both and the data is unreadable β exactly as with SHEETS_DEK.
Trying it locally
cd /home/paulca/memoriadrive
./run.shrun.sh sets MEMORIA_ALLOW_LOGGED_MAIL=1, which is a local-dev-only opt-in: without SMTP configured, rastrillo logs the magic link instead of mailing it, and that link is a live credential. The app now refuses to start without either real SMTP or that explicit flag, because the default had been to log sign-in credentials silently.
Then open http://127.0.0.1:8823 and sign in. Sign-in goes through keymail, so use an address on the owners list in run.sh (paul@tito.io or paul@keymail.dev as it stands).
It comes pre-seeded with 55 documents and 649 passages β every .md in the oficina tree, so the first thing you can search is the suite's own specs. Try sealed at rest, grants join, revoke, escritorio, plausible empty list.
Things worth poking at, because each is a decision rather than an accident:
- Search ORs its terms. Ask it a question in words β "what is the worst available failure shape" β and it works. ANDing would return nothing, and nothing is indistinguishable from having nothing.
- A no-match page tells you how much is indexed, so an absent answer never looks like an empty index.
- Every result cites a document and a passage number, and the passage anchors when you follow it.
Adding your own
Paste or upload text on Add content (/add), or seed a directory:
cd /home/paulca/memoriadrive
MEMORIA_DEK="$(cat DEK.txt)" ./seed --db m.db --dir /path/to/notes --source notesThe seed tool uses the same store, chunker and sealing as the app. It is the honest ancestor of slice 3's connectors, and it bypasses nothing β without the right DEK it refuses to run.
The DEK
DEK.txt in memoriadrive is the key everything is sealed with. Lose it and the corpus is gone, exactly as with Sheets' SHEETS_DEK. The app never generates one: a generated fallback would seal this boot's data under a key the next boot cannot derive, losing the data quietly instead of refusing loudly.
Deploying
make release
carlos deploy --app memoria --account bds --channel edge --timeout 5m releases/memoria-linux-arm64Trust the command make release prints over the one written here: it names the path it actually built. This section used to say ./releases/memoria-arm64, which no build has ever produced β the binary is named for its target, linux-arm64.
carlos deploy refuses to run when the tree is unclean, and it counts untracked files. A stray editor directory or dotfile in the checkout is enough to stop it, with a message about the version defaulting to the git sha. When the tracked tree is genuinely clean, pass the sha make release stamped β --version $(git rev-parse --short HEAD) β rather than committing noise to get past it.
Flags go before the binary path; Go's flag package stops parsing at the first non-flag argument, so a path first makes --account invisible. carlos deploy, never carlos ship on its own. Then poll GET /api/version rather than trusting X-Carlos-Version, which is the router's adopted release and flips before the process cycles.
The design
docs/superpowers/specs/2026-09-03-memoria-design.md. Draft 3, which answers independent reviews of drafts 1 and 2 β four passes across Fable and GPT Sol, every one returning REQUEST CHANGES. Β§1.2 records what changed and why β the central correction being that authorization is asked of whichever app owns a document, per person, per query, rather than derived from a membership table memoria keeps.
QUESTIONS-FOR-PAUL.md is what I could not decide alone.
What is not here
No assistant, no embeddings, no sibling apps, no external connectors, no snapshot of the index (it rebuilds at boot β milliseconds at this size, and Β§12 carries the trigger for changing that). The sequencing is Β§11.