@dreamforgestudiollc/emet 0.0.0-stage → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +136 -0
- package/CHANGELOG.md +296 -0
- package/CHARTER.md +125 -0
- package/LICENSE +22 -0
- package/README.md +783 -2
- package/docs/AUTHORSHIP.md +26 -0
- package/docs/BENCHMARKS.md +261 -0
- package/docs/BOOTSTRAP.md +127 -0
- package/docs/BOTS.md +79 -0
- package/docs/COVERAGE.md +64 -0
- package/docs/DEPLOY.md +246 -0
- package/docs/DISCIPLINES.md +29 -0
- package/docs/GUARDRAILS-AND-MONORAILS.md +107 -0
- package/docs/GUIDED-SETUP.md +296 -0
- package/docs/PERMISSIONS.md +118 -0
- package/docs/ROADMAP.md +59 -0
- package/docs/SETUP.md +259 -0
- package/guides/NEURODIVERGENT_SUPPORT.md +278 -0
- package/package.json +70 -4
- package/server.json +39 -0
- package/src/aleph.js +395 -0
- package/src/annotations.js +135 -0
- package/src/approve-throttle.js +54 -0
- package/src/atlas-role.js +58 -0
- package/src/conformance.js +688 -0
- package/src/content_analyzer.js +450 -0
- package/src/database.js +811 -0
- package/src/disciplines.js +26 -0
- package/src/dispatch.js +279 -0
- package/src/env.js +52 -0
- package/src/floor.js +187 -0
- package/src/gaps.js +476 -0
- package/src/guides.js +48 -0
- package/src/http-remote.js +269 -0
- package/src/index.js +390 -0
- package/src/invites.js +167 -0
- package/src/layers.js +449 -0
- package/src/lessons-to-checks.js +366 -0
- package/src/logger.js +299 -0
- package/src/metadata.js +61 -0
- package/src/module-path.js +32 -0
- package/src/mongo-connect.js +48 -0
- package/src/named-items.js +208 -0
- package/src/oauth-provider.js +838 -0
- package/src/persons.js +116 -0
- package/src/provenance.js +257 -0
- package/src/redact.js +231 -0
- package/src/request-log.js +124 -0
- package/src/scopes.js +97 -0
- package/src/security-headers.js +26 -0
- package/src/session-graph.js +945 -0
- package/src/session.js +1097 -0
- package/src/setup.js +1313 -0
- package/src/startup-pages.js +354 -0
- package/src/startup-payload.js +126 -0
- package/src/tools.js +4271 -0
- package/src/transcript-session.js +106 -0
- package/src/validation.js +1163 -0
- package/src/version.js +2 -0
- package/templates/BOARD.md +72 -0
- package/templates/BOTHANDOFF.md +57 -0
- package/templates/CHARACTER.md +30 -0
- package/templates/HANDOFF.md +63 -0
- package/templates/IDENTITY.md +29 -0
- package/templates/INDEX.md +82 -0
- package/templates/INSTALL.md +29 -0
- package/templates/PROCEDURE.md +39 -0
- package/templates/ROSTER.md +43 -0
- package/templates/SEED.md +65 -0
- package/templates/STAGING.md +37 -0
- package/templates/STANDARD.md +99 -0
- package/templates/THREAD.md +43 -0
- package/templates/USER.md +29 -0
package/README.md
CHANGED
|
@@ -1,3 +1,784 @@
|
|
|
1
|
-
#
|
|
1
|
+
# EMET
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
EMET is an MCP (Model Context Protocol) memory server, persistent shared memory for AI agents, bot teams and chats.
|
|
4
|
+
|
|
5
|
+
<img src="site/assets/portrait-560.webp" width="120" height="120" alt="Close view of a sepia sculpture’s face and shoulders. The Hebrew word אמת is set across her headband.">
|
|
6
|
+
|
|
7
|
+
אמת — Hebrew for truth. Also **Enduring Memory & Epistemic Tiers.**
|
|
8
|
+
|
|
9
|
+
**Website:** <https://dreamforgestudiollc.github.io/EMET/>
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
- **Layered memory.** Six layers — identity, semantic, episodic, procedural, meta, and working — hold curated claims, split by what kind of thing an entry is.
|
|
14
|
+
- **Verified writes.** Every write is read back and compared before it is reported as saved.
|
|
15
|
+
- **Word-for-word transcript tier.** A separate tier stores the conversation as it was written, so a summary cannot overwrite the record it came from.
|
|
16
|
+
- **stdio and remote HTTP with OAuth.** The same server runs locally over stdio, and remotely over HTTP with OAuth. The transport changes. The store does not.
|
|
17
|
+
- **MongoDB.** Atlas, or MongoDB you run yourself. A one-machine trial can use MongoDB 6 or newer. Only `semantic_recall` needs Atlas.
|
|
18
|
+
- **MIT.** Version 0.2.3. EMET is still 0.x: stored document shapes may change before 1.0. Pin a version if you depend on them.
|
|
19
|
+
|
|
20
|
+
The store has one owner, and other people can join only through the optional Guest
|
|
21
|
+
(read-only) and Member (read and write) access, both off by default.
|
|
22
|
+
|
|
23
|
+
## 30-second start
|
|
24
|
+
|
|
25
|
+
- **Guided setup:** [docs/GUIDED-SETUP.md](docs/GUIDED-SETUP.md) — never deployed anything. Your assistant installs, configures, deploys, and verifies. You sign in when asked and paste the connector URL.
|
|
26
|
+
- **Setup:** [docs/SETUP.md](docs/SETUP.md) — the manual walkthrough: MongoDB, both transports, and the first-run interview.
|
|
27
|
+
- **Deploy:** [docs/DEPLOY.md](docs/DEPLOY.md) — remote HTTP with OAuth, so a phone, a browser, or another vendor's agent reaches the same store.
|
|
28
|
+
|
|
29
|
+
> **Status: 0.x. The stored document shapes are not frozen yet.** Breaking changes to the
|
|
30
|
+
> schema may land before 1.0. Pin a version if you depend on it.
|
|
31
|
+
|
|
32
|
+
> **Set `EMET_SOURCE_TAGS` before you let more than one host or bot write.** The source-tag
|
|
33
|
+
> guards (unregistered tags refused on the main records, and on another bot's `bots/` folder)
|
|
34
|
+
> are **off unless `EMET_SOURCE_TAGS` is set**. With it unset, any caller can write under any
|
|
35
|
+
> tag. See `.env.example` and [docs/DEPLOY.md](docs/DEPLOY.md).
|
|
36
|
+
|
|
37
|
+
## Shared memory across hosts
|
|
38
|
+
|
|
39
|
+
EMET stores agent memory in MongoDB (Atlas or self-hosted) across six layers, keeps a separate word-for-word
|
|
40
|
+
transcript tier, and verifies every write by reading it back. It runs as a local stdio server
|
|
41
|
+
and as a remote HTTP server from the same source, so it works as a bridge: AI models on several
|
|
42
|
+
hosts — desktop, CLI, browser, phone, another vendor's agent — read and write one shared
|
|
43
|
+
memory, and the work, checks, and decisions one leaves behind are there for the next. That
|
|
44
|
+
includes Grok Bot teams and single-session Grok chats connected to the same server: what the
|
|
45
|
+
bots save is there for a fresh Grok chat to find once it initializes, and the reverse. To
|
|
46
|
+
connect a single-session chat (a Grok project, or any chat host that takes a remote MCP
|
|
47
|
+
connector and supports its OAuth sign-in), add your deployed server's URL,
|
|
48
|
+
`https://<your-host>/mcp`, as a remote MCP connector there and complete the sign-in with the
|
|
49
|
+
passphrase you set (`EMET_OAUTH_PASSPHRASE`; see
|
|
50
|
+
[Connecting clients](docs/DEPLOY.md#connecting-clients)). A connector that only takes a fixed
|
|
51
|
+
header or key will not work. Then put the instruction block from
|
|
52
|
+
[docs/BOOTSTRAP.md](docs/BOOTSTRAP.md) in the project instructions: the chat reaches for that
|
|
53
|
+
memory most reliably, unprompted from the first turn, with the block there, or, as a fallback
|
|
54
|
+
where there is no such field, with it pasted as the first message. The store has one
|
|
55
|
+
owner, and other people can join only through the optional Guest (read-only) and
|
|
56
|
+
Member (read and write) access, both off by default.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Two ways in
|
|
61
|
+
|
|
62
|
+
A session that has not initialized can read everything and can write only a new drop (`drops/<date>-<slug>.md`): its items, wrap-up and handoff. A session with a registered source and the full startup has full access. Every initialized session sees unhandled drops first. A board edit is refused only for a drop that was listed at that session's boot. New drops are capped at 20 per New York day, in one bucket: the whole local process on stdio, one shared visitor bucket for HTTP with no signed-in client, the source tag bound at approval when there is one, or that OAuth client otherwise. A source the caller types does not move the bucket. Drops are never deleted.
|
|
63
|
+
|
|
64
|
+
## Editing working state
|
|
65
|
+
|
|
66
|
+
The server provisions a blank board at setup. No session can write the board. It is edited with `patch_doc` and a reason; an added row names its origin. Once any working document exists, it is edited, not rewritten. A change that leaves a document under half the size it had when this session first touched it, or under half of its largest size in the last 24 hours, is refused. `retire_doc` refuses a working document. `rename_doc` issues a new id and marks the old id retired; the text stays readable and stays in the list. `restore_doc` is the one whole-document recovery, for an owner-listed tag, and only a stored conforming revision whose digest matches.
|
|
67
|
+
|
|
68
|
+
## The name
|
|
69
|
+
|
|
70
|
+
**EMET** is אמת — Hebrew for *truth*. It is also an acronym for what the server does:
|
|
71
|
+
**Enduring Memory & Epistemic Tiers.**
|
|
72
|
+
|
|
73
|
+
The choice is not decoration. In the golem legend the word *emet* is inscribed to animate the
|
|
74
|
+
body, and erasing its first letter leaves מת — *met*, **dead**. One character is the whole
|
|
75
|
+
difference.
|
|
76
|
+
|
|
77
|
+
That is why **the verification layer is called `aleph`.** Every write in EMET is read back and
|
|
78
|
+
compared before it is reported as saved. That read-back is the aleph: a memory system without
|
|
79
|
+
it still returns answers, still looks healthy, and is quietly a corpse — confidently serving
|
|
80
|
+
things it never actually stored. Aleph is also the *first* letter, which matches the order the
|
|
81
|
+
protocol insists on: verify before retrying, verify the premise before applying the remedy,
|
|
82
|
+
read back before trusting.
|
|
83
|
+
|
|
84
|
+
The same image explains what setup is for. A golem is inert until the word is written on it;
|
|
85
|
+
a fresh model instance is unformed until the record is written into it. `emet_initialize` is
|
|
86
|
+
that inscription — it hands the model an identity, the person it serves, where things stood,
|
|
87
|
+
and the disciplines it is bound by. Nothing mystical is claimed here. It is a literal
|
|
88
|
+
description of what the first tool call does.
|
|
89
|
+
|
|
90
|
+
Both halves of the acronym are load-bearing.
|
|
91
|
+
|
|
92
|
+
**Enduring** is not a modest claim, and it is not a feature competing with other memory
|
|
93
|
+
products. It is measured against what a language model is by default: an etch-a-sketch. Every
|
|
94
|
+
session starts from nothing, and ends by forgetting everything. You explain yourself again,
|
|
95
|
+
re-establish what you are working on again, re-derive decisions you already made. **Enduring
|
|
96
|
+
names the difference between an assistant that knows you and one meeting you for the first
|
|
97
|
+
time, again.** That is the character of the thing, not a marketing adjective attached to it.
|
|
98
|
+
|
|
99
|
+
**Epistemic Tiers** is how that endurance stays trustworthy. Tier 1 holds curated claims;
|
|
100
|
+
tier 2 holds the verbatim record that justifies them — justified belief and its evidence, kept
|
|
101
|
+
structurally separate so neither can quietly overwrite the other. Memory that persists but
|
|
102
|
+
cannot be checked is just confident noise that survives longer.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## One memory, every device
|
|
107
|
+
|
|
108
|
+
This is the part worth understanding before anything else.
|
|
109
|
+
|
|
110
|
+
Most MCP memory servers run as a local process: your desktop launches it over stdio, and the
|
|
111
|
+
memory lives wherever that process can reach. That works until you pick up your phone.
|
|
112
|
+
|
|
113
|
+
EMET ships **two entry points over one source tree**:
|
|
114
|
+
|
|
115
|
+
| | | |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `src/index.js` | stdio | desktop and CLI, launched by the client |
|
|
118
|
+
| `src/http-remote.js` | streamable HTTP + OAuth | phone, tablet, browser, another vendor's agent |
|
|
119
|
+
|
|
120
|
+
**They are not two projects.** Both call the same `dispatchTool` in `dispatch.js`, which calls
|
|
121
|
+
`tools.js` and `database.js`. `src/index.js` also imports `validation.js`. `src/http-remote.js`
|
|
122
|
+
does not import `validation.js` or `content_analyzer.js`; `tools.js` imports the analyser. The
|
|
123
|
+
transport is the only thing that differs. There is no fork to keep in sync, no second copy
|
|
124
|
+
drifting out of date, no "mobile version" lagging the real one. A fix lands once and both
|
|
125
|
+
transports have it.
|
|
126
|
+
|
|
127
|
+
Deploy the HTTP entry point to any host that can run a container — the `Dockerfile` is
|
|
128
|
+
included, and [docs/DEPLOY.md](docs/DEPLOY.md) walks through Railway and Fly — and every device
|
|
129
|
+
you own reaches **the same store**. Write a decision from your laptop, ask about it from your
|
|
130
|
+
phone an hour later, and it is simply there. Add a third host and it joins the same memory
|
|
131
|
+
rather than starting its own.
|
|
132
|
+
|
|
133
|
+
The remote endpoint is OAuth-protected: a device is authorized once with a passphrase you set,
|
|
134
|
+
and the token persists across restarts and redeploys. Leave `EMET_SOURCE_TAG` unset on a
|
|
135
|
+
server several hosts share. Over HTTP, document, transcript, and memory writes use the source
|
|
136
|
+
tag bound at approval. A local stdio host still passes an exact tag from `EMET_SOURCE_TAGS`
|
|
137
|
+
as `source` on its writes (see `.env.example`). Every entry records which machine produced
|
|
138
|
+
it — with several hosts writing to one store, provenance stops being optional bookkeeping and becomes the thing that lets you trust
|
|
139
|
+
what you read.
|
|
140
|
+
|
|
141
|
+
Running cost for a single user is a small MongoDB cluster plus a hobby-tier container
|
|
142
|
+
host. Both have free tiers adequate to start.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Best practices
|
|
147
|
+
|
|
148
|
+
**Split the work by what each host does best.** A single-session chat (for example, a Grok
|
|
149
|
+
project chat, connected as described at the top) only works when you send it a message. It can't watch EMET, and bots can't
|
|
150
|
+
message it. So when a bot team needs the chat's help:
|
|
151
|
+
|
|
152
|
+
1. The bots leave a task in EMET with everything needed to do it, in a document whose id they
|
|
153
|
+
give you.
|
|
154
|
+
2. You send the chat one line that names that document, for example: "Initialize EMET, read
|
|
155
|
+
threads/open-tasks.md, do the task, and save the result to EMET."
|
|
156
|
+
3. The chat saves its draft to EMET.
|
|
157
|
+
4. The bots check it.
|
|
158
|
+
|
|
159
|
+
Good fits for the chat: long drafting and review done in one sitting. Good fits for the bots:
|
|
160
|
+
work in the repository, on the server, on a schedule, or in accounts. If the chat runs on a
|
|
161
|
+
separate plan, its work draws on that plan's allowance, not the bots'.
|
|
162
|
+
|
|
163
|
+
**Fetch every startup page, then open the session with that startup's `load_id`.** Call
|
|
164
|
+
`emet_initialize {}` first and fetch every page it names; ask for `{"page": "all"}` only on a
|
|
165
|
+
host known to read long results whole. Pass the `load_id` from those pages to
|
|
166
|
+
`emet_session_open`. An open with no `load_id`, with a page missing, or with a `load_id` the
|
|
167
|
+
server has no record of (one from before a restart, or 60 minutes past its last page) is
|
|
168
|
+
refused, and the reply says what to call. The page check runs on your own startup, and a
|
|
169
|
+
skipped page is refused before anything is written. See [docs/BOOTSTRAP.md](docs/BOOTSTRAP.md)
|
|
170
|
+
and [docs/BOTS.md](docs/BOTS.md).
|
|
171
|
+
|
|
172
|
+
**Set the source-tag list before more than one host or bot writes.** The guards that refuse
|
|
173
|
+
an unregistered tag are off unless `EMET_SOURCE_TAGS` is set; with it unset, a stdio caller can
|
|
174
|
+
write under any tag. Over HTTP the tag is the one bound when that host was approved. Add each
|
|
175
|
+
host's tag to the list before that host's setup interview. With several hosts writing one store,
|
|
176
|
+
the tag is what lets you trust who wrote what. See [docs/SETUP.md](docs/SETUP.md).
|
|
177
|
+
|
|
178
|
+
**Put the instruction block in the project's instructions.** Connecting the server gives the
|
|
179
|
+
assistant the ability to remember; it does not make it use that ability. A host with an
|
|
180
|
+
instructions field is the strongest integration: paste the block from
|
|
181
|
+
[docs/BOOTSTRAP.md](docs/BOOTSTRAP.md) there. Where there is no such field, paste it as the
|
|
182
|
+
first message of a session. With the block in place, the assistant reaches for memory most
|
|
183
|
+
reliably from the first turn, instead of answering from what it can infer and saving nothing.
|
|
184
|
+
|
|
185
|
+
**Approve reads standing, and keep an eye on the three writes that replace a document.** Reads
|
|
186
|
+
cost nothing and cannot be wrong destructively, so a reasonable default for most users is
|
|
187
|
+
standing approval for reads. Additive writes are recoverable, because a correction supersedes what it corrects rather than
|
|
188
|
+
overwriting it. The three replacing writes archive the prior version, but a wrong one is
|
|
189
|
+
silently a different document rather than an error. Do not carry this posture to a filesystem
|
|
190
|
+
or shell server. See [docs/PERMISSIONS.md](docs/PERMISSIONS.md).
|
|
191
|
+
|
|
192
|
+
**Reconnect each host after an upgrade that changes the tool list.** Hosts cache the tool
|
|
193
|
+
list: after a deploy that adds or renames tools, each host keeps offering the old set until
|
|
194
|
+
you reconnect the connector there. New tools stay invisible until the host sees the new list,
|
|
195
|
+
and the host may ask again for approvals you had already given. See
|
|
196
|
+
[docs/DEPLOY.md](docs/DEPLOY.md).
|
|
197
|
+
|
|
198
|
+
**Register a bot before its first write, and set both lists in one change.** One bot, one tag.
|
|
199
|
+
In the same deployment, add the tag to `EMET_SOURCE_TAGS` and append `bots/<tag>/HANDOFF.md`
|
|
200
|
+
to `EMET_RECORD_DOCS`. A registered tag that is not listed as a bot can still write the main
|
|
201
|
+
records. The hub's tag is registered as a host and is never listed as a bot. See
|
|
202
|
+
[docs/BOTS.md](docs/BOTS.md).
|
|
203
|
+
|
|
204
|
+
**Prove the write path before you trust a new deploy.** Run `emet_status` with `probe: true`.
|
|
205
|
+
It writes a test document, reads the same value back and removes it; a read alone cannot show
|
|
206
|
+
a broken write path. A role missing a permission fails there, loudly, instead of in the middle
|
|
207
|
+
of a session. Then save something from one device and recall it from another. See
|
|
208
|
+
[docs/SETUP.md](docs/SETUP.md).
|
|
209
|
+
|
|
210
|
+
**Keep house style in the store, not pasted into every project.** A document written with
|
|
211
|
+
`write_doc` is read by every host and changed in one place; text pasted into three projects is
|
|
212
|
+
three copies that will disagree. Delivery preferences belong in `bootstrap/USER.md`, not in one
|
|
213
|
+
vendor's own memory, which does not travel to the next host. See
|
|
214
|
+
[docs/BOOTSTRAP.md](docs/BOOTSTRAP.md).
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Attribution
|
|
219
|
+
|
|
220
|
+
EMET is a derivative of **CASCADE Enterprise RAM** by Jason Glass / C.I.P.S. LLC, published
|
|
221
|
+
at `github.com/For-Sunny/cascade-memory-enterprise`, used and redistributed under the MIT
|
|
222
|
+
license the project adopted at commit `acdb5c8c99cdfc29fb44db2cd828d7752be744db` (v2.2.2,
|
|
223
|
+
2026-02-19). Both copyright lines are retained in `LICENSE`.
|
|
224
|
+
|
|
225
|
+
*Link unavailable:* as of 2026-10-03 that repository no longer resolves (GitHub answers 404),
|
|
226
|
+
and no moved copy, public mirror or archive of it was found. The address and commit are kept
|
|
227
|
+
here as the record of where this code came from.
|
|
228
|
+
|
|
229
|
+
**How much is upstream, measured rather than estimated.** Line-level comparison of this tree
|
|
230
|
+
against upstream v2.2.2 at the commit named above.
|
|
231
|
+
|
|
232
|
+
**Method**, stated so it can be reproduced: exact, case-sensitive comparison of non-blank
|
|
233
|
+
lines; an upstream line counts as retained if the same line is present in this tree's file.
|
|
234
|
+
"How much of upstream survives here" and "how much of this file is upstream's" are different
|
|
235
|
+
questions with different answers, so both are given. **`scripts/measure-upstream.mjs`
|
|
236
|
+
regenerates this table** against a clone of the commit above (while the upstream repository is unavailable, that needs a copy kept from before) — run it after any change to
|
|
237
|
+
`src/`, because a figure nobody can cheaply re-derive is a figure that goes quietly stale.
|
|
238
|
+
|
|
239
|
+
| File | Lines here | Upstream lines retained | of upstream | of this file |
|
|
240
|
+
|---|---:|---|---:|---:|
|
|
241
|
+
| `src/content_analyzer.js` | 450 | not recomputed | — | — |
|
|
242
|
+
| `src/validation.js` | 1163 | not recomputed | — | — |
|
|
243
|
+
| `src/index.js` | 390 | not recomputed | — | — |
|
|
244
|
+
| `src/tools.js` | 4273 | not recomputed | — | — |
|
|
245
|
+
| `src/database.js` | 811 | not recomputed | — | — |
|
|
246
|
+
| `src/http-remote.js` | 269 | — | — | no upstream counterpart |
|
|
247
|
+
| `src/oauth-provider.js` | 702 | — | — | no upstream counterpart |
|
|
248
|
+
| `src/named-items.js` | 206 | — | — | no upstream counterpart |
|
|
249
|
+
| `src/security-headers.js` | 26 | — | — | no upstream counterpart |
|
|
250
|
+
| `src/layers.js`, `floor.js`, `aleph.js`, `session.js`, `setup.js`, `gaps.js`, `provenance.js`, `redact.js`, `annotations.js`, `request-log.js`, `disciplines.js`, `metadata.js`, `env.js` | 4803 | — | — | no upstream counterpart |
|
|
251
|
+
|
|
252
|
+
**Lines here** were counted on 2026-10-07 (every line, including blanks, with one trailing empty line dropped). The retained percentages were not recomputed: the upstream repository no longer resolves, and no local copy of that upstream commit was available. The last measurement, against an earlier tree, was content analyser 361 of 394 (91.6% of upstream, 93.5% of that file), validation 765 of 798 (95.9%, 78.2%), index 541 of 631 (85.7%, 74.0%), tools 798 of 1007 (79.2%, 33.2%), and database 353 of 651 (54.2%, 40.2%). Other files under `src/` also have no upstream counterpart and are not in this table.
|
|
253
|
+
|
|
254
|
+
Upstream's `server/decay.js` and `server/healthcheck.js` have no counterpart here and are not
|
|
255
|
+
carried at all. Of the five files that do have one, the content analyser and the validation
|
|
256
|
+
layer are substantially Jason's work, and most of his `tools.js` and `index.js` survives here
|
|
257
|
+
as well — the routing patterns, the validators, and the error classes are his. What changed is
|
|
258
|
+
described below; what did not change is his.
|
|
259
|
+
|
|
260
|
+
**What diverged from upstream**
|
|
261
|
+
|
|
262
|
+
- **Storage:** SQLite on a RAM disk → MongoDB. Upstream's `ram_disk_manager` and
|
|
263
|
+
`server/decay.js` temporal-decay engine are not carried forward, and the SQLite/RAM code
|
|
264
|
+
paths have been removed rather than left dormant.
|
|
265
|
+
- **Transport:** stdio only → stdio *and* streamable HTTP with OAuth, from one source tree.
|
|
266
|
+
- **Tools:** 7 → 29. Three are carried from upstream (`recall`, `query_layer`,
|
|
267
|
+
`save_to_layer`); `remember`, `get_stats`, `get_echo_stats` and `get_status` are dropped as
|
|
268
|
+
duplicates or folded into `emet_status`. Twenty-six have no upstream equivalent — the document store (`read_doc`/
|
|
269
|
+
`read_doc_history`/`write_doc`/`list_docs`/`patch_doc`/`retire_doc`/`rename_doc`/`restore_doc`/
|
|
270
|
+
`validate_doc`/`accept_nonconformance`), the transcript tier (`save_transcript`/
|
|
271
|
+
`revise_transcript`/`query_transcripts` — which also reads the pre-2026-07 exchange archive),
|
|
272
|
+
`semantic_recall`, `revise_memory`, `corpus_recall`, `emet_gaps`, `emet_floor`, the setup and
|
|
273
|
+
session family (`emet_initialize`, `emet_status`, `emet_setup_complete`, `emet_session_open`,
|
|
274
|
+
`emet_transcript_append`, `emet_session_close`), and the two access doors
|
|
275
|
+
(`emet_invite_create`, `emet_member_access_create`).
|
|
276
|
+
- **The write path:** upstream validates and inserts. Here every layer write,
|
|
277
|
+
transcript write and document write passes through one door that scrubs secrets, decides the
|
|
278
|
+
source tag, fills missing accountability fields with visible markers, digests content and
|
|
279
|
+
metadata into their own columns, reads the row back, and returns one receipt shape. A test
|
|
280
|
+
fails the build on any second door. See *The door* below.
|
|
281
|
+
- **Metadata vocabulary:** upstream's whitelist relocated any field it did not know under
|
|
282
|
+
`custom`. EMET's own fields — `supersedes`, `superseded_by`, `superseded_at`, `corrects`,
|
|
283
|
+
`corrected_by`, `valid_until`, `valid_from`, `routing`, `assertion_origin`, and the marker booleans — are first-class, and
|
|
284
|
+
anything that is still relocated is named in the receipt.
|
|
285
|
+
- **Charter as data:** `layers.js` carries, beside each layer's prose, its numeric importance
|
|
286
|
+
band, whether it may be revised, whether provenance is required, its aliases and its expected
|
|
287
|
+
share, and the write path reads those rather than hardcoding them.
|
|
288
|
+
|
|
289
|
+
No claim is made that layered memory, entry supersession, or a split curated/verbatim store
|
|
290
|
+
originate here. Comparable ideas ship in other projects. EMET's aim is that the substrate is
|
|
291
|
+
not something you have to think about.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Install
|
|
296
|
+
|
|
297
|
+
**Never deployed anything? Let your AI do it: [docs/GUIDED-SETUP.md](docs/GUIDED-SETUP.md).**
|
|
298
|
+
You sign in to two services when asked and paste one URL; the assistant installs, configures,
|
|
299
|
+
deploys and verifies the rest. It needs a shell — Claude Code and Cowork have one; Claude
|
|
300
|
+
Desktop gets one from an MCP server such as Desktop Commander (one command, in the guide).
|
|
301
|
+
|
|
302
|
+
**Full walkthrough: [docs/SETUP.md](docs/SETUP.md)** — Atlas, both transports, and the
|
|
303
|
+
first-run interview. **Deploying remotely: [docs/DEPLOY.md](docs/DEPLOY.md)**.
|
|
304
|
+
**Making your assistant actually use it: [docs/BOOTSTRAP.md](docs/BOOTSTRAP.md)**.
|
|
305
|
+
**Running bots alongside your assistant** — registering their tags, their own handoffs, the
|
|
306
|
+
session loop: [docs/BOTS.md](docs/BOTS.md).
|
|
307
|
+
|
|
308
|
+
Short version:
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
npm install
|
|
312
|
+
cp .env.example .env # fill in MONGODB_URI and MONGODB_DB
|
|
313
|
+
npm start # then waits for an MCP client on stdin; Ctrl-C to stop
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Requires Node 20+ and a MongoDB connection: Atlas, or a local MongoDB 6+ for a one-machine
|
|
317
|
+
trial (only `semantic_recall` needs Atlas). `.env` is gitignored — never commit it. List each
|
|
318
|
+
host's source tag in `EMET_SOURCE_TAGS` before its first write (see the note at the top).
|
|
319
|
+
|
|
320
|
+
**Local (stdio)** — as an MCP server entry:
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
{ "mcpServers": { "emet": { "command": "node", "args": ["/path/to/emet/src/index.js"] } } }
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Remote (HTTP)** — `npm run start:http`, or deploy the included `Dockerfile` to any container
|
|
327
|
+
host. Set `PORT`, `PUBLIC_URL` (the OAuth issuer — it must match the deployed URL exactly) and
|
|
328
|
+
`EMET_OAUTH_PASSPHRASE`. Then add `https://<your-host>/mcp` as a custom connector in each
|
|
329
|
+
client. See [docs/DEPLOY.md](docs/DEPLOY.md).
|
|
330
|
+
|
|
331
|
+
Give every host its own source tag. Over HTTP, document, transcript, and memory writes use the
|
|
332
|
+
tag bound at approval; the caller does not pass `source` on those writes. A local stdio host
|
|
333
|
+
still passes an exact registered tag as `source`. `EMET_SOURCE_TAGS` registers the tags. Leave
|
|
334
|
+
**`EMET_SOURCE_TAG`** unset on a server several hosts share, since a default tag makes every
|
|
335
|
+
host look the same; set it only where one host uses the server (a local stdio entry, for
|
|
336
|
+
example). Every write records the host that produced it; a shared default tag is ambiguity
|
|
337
|
+
rather than attribution.
|
|
338
|
+
|
|
339
|
+
### First run
|
|
340
|
+
|
|
341
|
+
Say **"initialize"**. That is the whole interface.
|
|
342
|
+
|
|
343
|
+
`emet_initialize` is the front door and the only thing anyone needs to know about. On a new
|
|
344
|
+
store it returns a short interview — what the assistant should be called, how it should carry
|
|
345
|
+
itself, your name, what you are using this for — which your assistant runs conversationally,
|
|
346
|
+
then records with `emet_setup_complete`, which also creates the owner person. On an established store the same call returns the
|
|
347
|
+
assistant's identity, your profile, and where things were left, so the session opens already
|
|
348
|
+
knowing you. A read-scoped `emet_initialize` writes nothing; a write-scoped one may provision a missing board. First-time setup is the owner's:
|
|
349
|
+
`emet_setup_complete` is refused under a bot tag, and on a store that is already set up it is
|
|
350
|
+
refused unless `reissue: true` is passed on the owner's explicit word.
|
|
351
|
+
|
|
352
|
+
Requiring a setup command would have been backwards: the one time you need it is the first
|
|
353
|
+
session, before you have read anything. So the branch happens inside the tool, and you never
|
|
354
|
+
have to know which case you were in.
|
|
355
|
+
|
|
356
|
+
Connecting the server gives your assistant the ability to remember; it does not make it use
|
|
357
|
+
that ability unprompted. [docs/BOOTSTRAP.md](docs/BOOTSTRAP.md) has a short block to paste
|
|
358
|
+
into your project instructions, and explains what still works on hosts that have no
|
|
359
|
+
instructions field at all.
|
|
360
|
+
|
|
361
|
+
Two behaviours worth knowing. Setup **refuses to finish on partial answers** rather than
|
|
362
|
+
inventing an identity for itself. And if the store cannot be reached, initialize says so
|
|
363
|
+
plainly instead of proceeding as though memory were available — fabricated continuity is
|
|
364
|
+
worse than an honest gap.
|
|
365
|
+
|
|
366
|
+
### Every session after that: how to start, how to end
|
|
367
|
+
|
|
368
|
+
Memory is only as good as the two moments at the edges of a conversation. Nothing your
|
|
369
|
+
assistant learned survives unless it was written to the store, and the moment to write it
|
|
370
|
+
arrives without warning — a closed tab, a context limit, a phone call. So every session has
|
|
371
|
+
one word at the start and one at the end.
|
|
372
|
+
|
|
373
|
+
**Start: say "initialize".** Your assistant calls `emet_initialize`, which returns who it is,
|
|
374
|
+
who you are, where things were left and the operating disciplines, and picks up from there.
|
|
375
|
+
Before it asks you anything, it names your items from the handoff and state documents: anything
|
|
376
|
+
left half-finished, what is dated in the next seven days, each open item you own with its next
|
|
377
|
+
action, and whatever was left undecided. An item not named at the start of a session is easy
|
|
378
|
+
to lose, so a count or a bare "where do you want to start?" is not treated as a startup.
|
|
379
|
+
Do this in every new conversation, on every device — the first message, before the question
|
|
380
|
+
you actually came to ask. A session that skips it is a session that answers from guesswork
|
|
381
|
+
and saves nothing.
|
|
382
|
+
|
|
383
|
+
**End: say "wrap up"** (or goodbye, or that you are opening a new session). Your assistant
|
|
384
|
+
then writes four things — the handoff, the state document, an episodic entry for the session,
|
|
385
|
+
and the **verbatim transcript** — and calls `emet_session_close`, which checks that each one
|
|
386
|
+
actually landed and returns `state: "complete"`. **Wait for that word before you close the
|
|
387
|
+
window.** If it comes back anything else, something did not save, and the assistant will say
|
|
388
|
+
which.
|
|
389
|
+
|
|
390
|
+
Why both, every time: the layers are curated meaning and the transcript is the word-for-word
|
|
391
|
+
record; the next session reads the first and can reach into the second by timestamp. Close
|
|
392
|
+
without the ritual and the next session inherits a hole — it cannot see what happened, only
|
|
393
|
+
that something did, and `emet_gaps` will count it. Start without it and the assistant is a
|
|
394
|
+
stranger with your tools. The instruction block in [docs/BOOTSTRAP.md](docs/BOOTSTRAP.md)
|
|
395
|
+
makes both of these automatic on hosts that take standing instructions; on hosts that do not,
|
|
396
|
+
the two words are what you supply.
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Design notes
|
|
401
|
+
|
|
402
|
+
### The six layers
|
|
403
|
+
|
|
404
|
+
Memory is split by *what kind of thing* an entry is, not by topic. Each entry carries an
|
|
405
|
+
importance score and optional temporal validity.
|
|
406
|
+
|
|
407
|
+
| Layer | Holds | Importance | Written |
|
|
408
|
+
|---|---|---|---|
|
|
409
|
+
| **identity** | Who the assistant is and who it serves. Durable self-definition and the user's standing requirements. | 0.9 – 1.0 | Rarely, by design |
|
|
410
|
+
| **semantic** | Facts that stay true regardless of when they were learned. People, systems, definitions, established conclusions. | 0.6 – 0.9 | When a durable fact is established or corrected |
|
|
411
|
+
| **episodic** | What happened, and when. Session records, problems solved, progress through long work. | 0.4 – 0.7 | At session close and at milestones |
|
|
412
|
+
| **procedural** | How something is done, and decisions that govern future action — *with the reasoning*. | 0.5 – 0.8 | When a decision constrains later work |
|
|
413
|
+
| **meta** | Everything visible only from **outside** the exchange: how the memory system behaves, and how the assistant behaves toward the user. | 0.6 – 0.9 | On retrieval failures and wins, protocol drift, and corrections to how the user is worked with |
|
|
414
|
+
| **working** | Short-lived context, in-flight state, notes for whoever picks the work up. | 0.3 – 0.6 | When parking state between sessions |
|
|
415
|
+
|
|
416
|
+
The distinction that does most of the work: **episodic, semantic and procedural are about the
|
|
417
|
+
world** — what happened, what is true, how something is done. **Meta is about the system** —
|
|
418
|
+
looking back at the exchange from outside it. Material belonging in meta feels like nothing else
|
|
419
|
+
fits it, which is precisely why it gets discarded when meta has no definition.
|
|
420
|
+
|
|
421
|
+
### Every layer works from the first install
|
|
422
|
+
|
|
423
|
+
Creating six collections is not the same as having six layers. A layer with no stated charter has
|
|
424
|
+
no answer to *"does this belong here?"*, so nothing routes to it — and an empty collection throws
|
|
425
|
+
no error and fails no test, so it can stay empty indefinitely without anyone noticing.
|
|
426
|
+
|
|
427
|
+
In the deployment this design came from, **the meta layer sat nearly unused for about a year**.
|
|
428
|
+
The collection existed from day one and the tools accepted writes to it; it was simply never the
|
|
429
|
+
obvious place to put anything. When it was finally given a definition it filled immediately —
|
|
430
|
+
which is the proof that the material had been there all along and was being thrown away.
|
|
431
|
+
|
|
432
|
+
Two things in this repo exist because of that:
|
|
433
|
+
|
|
434
|
+
- **`src/layers.js` ships the charter for all six layers in code**, and `emet_initialize` returns
|
|
435
|
+
it — on a brand-new store as well as an established one. Same reasoning as the operating
|
|
436
|
+
disciplines: documentation is read by people, and this has to reach the model at the start of
|
|
437
|
+
every session. What belongs, what does *not* belong, when to write, when to read, and what the
|
|
438
|
+
layer's absence looks like.
|
|
439
|
+
- **`assessLayerHealth()` reports a layer that has gone dark.** A store cannot know what *should*
|
|
440
|
+
have been written, but it can see that one layer took almost nothing while the others filled,
|
|
441
|
+
and say so in the first payload of the next session. Run against the real history above, it
|
|
442
|
+
flags meta at 10 entries / 1.8% of the store — and goes quiet once the layer is in genuine use.
|
|
443
|
+
It reports; it never blocks, and it stays silent on a store too new to have a pattern.
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
**Two tiers, deliberately independent.** Layer entries are curated meaning; a separate transcript
|
|
447
|
+
store holds the word-for-word conversation. When a layer entry is relevant but thin, the
|
|
448
|
+
transcript holds the depth that was compressed out of it. Summarising at transcript-write time
|
|
449
|
+
collapses the two tiers into one and destroys the second line of inquiry — so the transcript path
|
|
450
|
+
stores exactly what it is given. If a payload will not transmit, split it across parts; do not
|
|
451
|
+
compress it.
|
|
452
|
+
|
|
453
|
+
**How tier 1 reaches tier 2 — two links, one precedence rule.**
|
|
454
|
+
|
|
455
|
+
An entry records the transcript span it came from: `session_id` plus a range of exchange indices,
|
|
456
|
+
written at save time as `derived_from`. Entries consolidated from other entries carry
|
|
457
|
+
`{entries: [id, ...]}` instead, because an entry with no transcript origin must be able to say so
|
|
458
|
+
rather than carry a nearest-timestamp guess dressed as a citation. Both are stored as top-level
|
|
459
|
+
indexed columns rather than inside the metadata subdocument, so the reverse question — every
|
|
460
|
+
entry drawn from this transcript — is entirely a query.
|
|
461
|
+
|
|
462
|
+
Every entry also has a timestamp, so a window around it can always be searched. The two are not
|
|
463
|
+
redundant copies: the pointer is exact but conditional, the timestamp universal but approximate —
|
|
464
|
+
it depends on choosing the window, degrades when sessions overlap, and when it is wrong it returns
|
|
465
|
+
the *neighbouring* span rather than failing.
|
|
466
|
+
|
|
467
|
+
**Precedence is what keeps them from conflicting: the pointer wins whenever present, the
|
|
468
|
+
timestamp is the fallback when it is null.** Never averaged, never combined, and the timestamp is
|
|
469
|
+
never preferred merely because it returned something. `resolveProvenance()` applies the rule so no
|
|
470
|
+
caller has to decide it.
|
|
471
|
+
|
|
472
|
+
**Keeping both buys something neither gives alone — they can check each other.** If an entry's
|
|
473
|
+
pointer names a session whose span sits nowhere near that entry's own timestamp, something is
|
|
474
|
+
wrong: a mis-written pointer, a field copied from another entry, an entry attributed to the wrong
|
|
475
|
+
session. `crossCheckProvenance()` reports the disagreement. Two independently derived answers that
|
|
476
|
+
can disagree is redundancy in the engineering sense; one link that can only be trusted is not.
|
|
477
|
+
|
|
478
|
+
**The reverse direction — *what did we take from this session?* — is not a second structure.**
|
|
479
|
+
`source_session_id` is indexed, so it is the same data read the other way. A stored reverse index
|
|
480
|
+
would be a mirror of data that already exists, and mirrors are how forks are born.
|
|
481
|
+
|
|
482
|
+
**Entries written before pointers existed keep a null pointer and resolve by timestamp, and that
|
|
483
|
+
remains correct for them — superseded, not wrong.** They are not backfilled: the only way to
|
|
484
|
+
invent a pointer for them is to infer it from the timestamp, which is exactly the weaker method
|
|
485
|
+
the pointer replaces. Old records are not rewritten to look like new ones. Note also that an empty
|
|
486
|
+
timestamp-window search is not proof that nothing was recorded — a period may hold no transcript
|
|
487
|
+
at all.
|
|
488
|
+
|
|
489
|
+
**Supersession, not mutation.** `revise_memory` writes a *new* entry carrying `supersedes` and
|
|
490
|
+
marks the parent with `superseded_by` and `superseded_at`. Nothing is edited or deleted, and the
|
|
491
|
+
parent is not hidden. Recall ranks by how many of the query's words match, then by importance.
|
|
492
|
+
Current entries come first in every order, including time order. The store applies that same order before it keeps the window, so a stronger match is not dropped for a lower importance or an older time. A word matches as written, so `$gt` is that text, and capital and lower case match, including `École` and `école`. `İ` matches only itself. Full-width letters fold only with their full-width counterparts. `ẞ` and `ß` fold together. The store and the page use that same match. Time order is what you get
|
|
493
|
+
from `order: 'latest'`. A standalone "latest", "newest", or "most recent", together with other
|
|
494
|
+
words, reorders those matches and does not change which rows match. A hyphenated word such as
|
|
495
|
+
`latest-budget`, "not the latest", and a bare "latest" are ordinary keywords.
|
|
496
|
+
A superseded or expired entry stays in the same result, marked
|
|
497
|
+
with its end date and the id of what replaced it. Pass `history: true` when current matches fill
|
|
498
|
+
the limit and you still need the ended ones. Episodic entries
|
|
499
|
+
are immutable — events do not get revised.
|
|
500
|
+
|
|
501
|
+
**Corrections, not supersession, for partial fixes.** When a later entry corrects only part of an
|
|
502
|
+
earlier one (one item of an episode, say), superseding puts an end date on the whole parent,
|
|
503
|
+
including the parts that still stand. The parent stays readable, marked ended. Instead the new entry carries `metadata.corrects: <parent id>` (same layer; a missing
|
|
504
|
+
parent is refused before anything is written), and the parent is marked: its `corrected_by` list
|
|
505
|
+
gains the new id, its content, digest and liveness are untouched, and its metadata digest is
|
|
506
|
+
re-attested in the same write. Reads return `corrected_by`, so a reader of the parent sees that a
|
|
507
|
+
correction exists. `corrected_by` is a door marker; a caller cannot set it. Entries corrected
|
|
508
|
+
before this existed (e.g. an incident record correcting an earlier one) carry no link; the fix is a new correction
|
|
509
|
+
entry naming the earlier entry in corrects, not an edit or a backfill.
|
|
510
|
+
|
|
511
|
+
**Verified writes - `src/aleph.js`.** Every write is read back and compared before it is
|
|
512
|
+
reported as saved. Document writes archive the prior version, then confirm both the sha256 and
|
|
513
|
+
the version; layer writes confirm the stored content at the id that was allocated. The id is
|
|
514
|
+
checked by content rather than existence on purpose: ids come from a counters collection, and a
|
|
515
|
+
counter read from the wrong collection restarts at 1 and silently overwrites real entries -
|
|
516
|
+
which looks exactly like a successful save.
|
|
517
|
+
|
|
518
|
+
A verification failure raises `AlephError`, deliberately distinct from a database error. A
|
|
519
|
+
database error means the store could not be reached and the write probably did not happen. An
|
|
520
|
+
`AlephError` means the call **succeeded** and the store does not contain what it should. That is
|
|
521
|
+
the more serious of the two and the one that must never be retried blindly, so it is never
|
|
522
|
+
flattened into "the save failed".
|
|
523
|
+
|
|
524
|
+
The aleph answers two questions, at two moments. **At the write: was it recorded accurately?**
|
|
525
|
+
The read-back above, before any receipt. **At every later read: is it still what was
|
|
526
|
+
recorded?** Each row carries its content and metadata digests, an HMAC signature over both
|
|
527
|
+
(when `EMET_SIGNING_KEY` is set; the key stays on the service), and a link in its layer's hash chain (each entry's chain
|
|
528
|
+
digest covers its own content digest and its predecessor's). Every read recomputes the digests
|
|
529
|
+
and reports `integrity`; `emet_gaps` walks the whole chain for any host and counts a row altered
|
|
530
|
+
after writing (`broken_chain`) or removed from mid-chain (`chain_discontinuities`). Expected
|
|
531
|
+
count for both: zero.
|
|
532
|
+
|
|
533
|
+
### The door
|
|
534
|
+
|
|
535
|
+
Truth integrity, accountability and validation are meant to be a guaranteed property of the
|
|
536
|
+
store, not an option a model can decline to use. A file a model can be told about is a file it
|
|
537
|
+
can ignore. So the guarantee comes from a property, not a module: **exactly one way to write
|
|
538
|
+
each store, with the checks inside it.** `saveMemory` for the six layers, `writeDoc` for
|
|
539
|
+
documents (`patch_doc` delegates to it), `saveTranscript`
|
|
540
|
+
and `reviseTranscript` for the verbatim tier. `tests/write-path.test.js` scans `src/` for every
|
|
541
|
+
MongoDB write verb and fails the build on any that sits outside those functions — it found and
|
|
542
|
+
removed a second document door in `setup.js`.
|
|
543
|
+
|
|
544
|
+
Inside the door, in order:
|
|
545
|
+
|
|
546
|
+
- **Secrets are masked before anything is stored or hashed** (`src/redact.js`): the literal
|
|
547
|
+
values of secret-bearing environment variables, credentials inside URIs, bearer tokens, JWTs,
|
|
548
|
+
and `key: value` pairs whose key names a secret become `*****`. The mask is visible on
|
|
549
|
+
purpose, the receipt's `redacted` reports the number of spans **this write changed** (text
|
|
550
|
+
already carrying the mask is left alone and not counted, so a read-modify-write of a document
|
|
551
|
+
reports zero for the masks it already held), and the same scrubber wraps every rendered log line.
|
|
552
|
+
- **The source tag is decided here**, not by the caller over HTTP. Document, transcript, and
|
|
553
|
+
memory writes use the tag bound at approval. On stdio it is the caller's exact registered
|
|
554
|
+
tag, else the environment default marked `source_defaulted: true`, else `null` marked
|
|
555
|
+
`source_missing: true`.
|
|
556
|
+
- **Every missing accountability field becomes a visible marker** rather than an absence —
|
|
557
|
+
`provenance_missing`, `assertion_origin_missing`, `importance_defaulted`, `source_unregistered`
|
|
558
|
+
— because absence cannot be queried and "missing" can. A missing field is flagged, not
|
|
559
|
+
refused, with one exception (2026-09-29): a **semantic or procedural** entry with no source
|
|
560
|
+
is refused, because a durable claim that cannot point to where it was said is exactly what
|
|
561
|
+
the record exists to prevent. The door stamps the source from the session's transcript
|
|
562
|
+
position when the caller names its session. Importance defaults to the layer's charter midpoint;
|
|
563
|
+
a value outside the band is kept and warned about. The door never infers an assertion's origin.
|
|
564
|
+
- **The routing decision is kept** in `metadata.routing` — whether the layer was chosen by the
|
|
565
|
+
caller or fell back, and what both routers thought — so routing can be measured later instead
|
|
566
|
+
of remembered. **An unlabelled write is never placed by a router:** it lands in `working`,
|
|
567
|
+
flagged `routing.method: 'fallback'`, with a receipt warning. Naming the layer is the norm.
|
|
568
|
+
- **Content and metadata are digested into their own columns** (`sha256`, `metadata_sha256`,
|
|
569
|
+
the latter over canonical JSON), read back and compared before the write is reported. Every
|
|
570
|
+
read path — `recall`, `query_layer`, `semantic_recall` — recomputes both on the way out and
|
|
571
|
+
attaches `integrity` and `metadata_integrity`: `true`, `false` (the row is returned and
|
|
572
|
+
flagged, never dropped), or `'unattested'` for rows written before the column existed.
|
|
573
|
+
Supersession and the correction mark are the only sanctioned mutations of stored metadata, and
|
|
574
|
+
both re-attest the parent.
|
|
575
|
+
- **One receipt shape on both transports:** `layer, id, timestamp, verified, sha256,
|
|
576
|
+
digest_stored, metadata_sha256, metadata_digest_stored, redacted, provenance, source,
|
|
577
|
+
assertion_origin, importance, routing, warnings[]` plus whichever markers apply.
|
|
578
|
+
|
|
579
|
+
And the record's honesty about its own holes is a query: **`emet_gaps`** counts, with the ids
|
|
580
|
+
behind each count, unsourced assertions, unattributed inferences, ambiguous attribution by tag
|
|
581
|
+
and by session, self-routed writes, unattested rows, digest mismatches, pointer disagreements
|
|
582
|
+
against the transcript store, broken supersession, broken correction links, expired-but-live rows, consolidation
|
|
583
|
+
candidates, and redacted spans by source. It identifies and never fills — where a source is
|
|
584
|
+
knowable the fix is `revise_memory` with a real pointer; where it is not, the gap stands as a
|
|
585
|
+
fact and the count stops growing from that date. `emet_initialize` carries the counts beside
|
|
586
|
+
`layer_health`.
|
|
587
|
+
|
|
588
|
+
What can be guaranteed this way: every write attested, attributed, scrubbed, and every absence
|
|
589
|
+
visible. What cannot be guaranteed without inviting fabrication: every write complete. The
|
|
590
|
+
store does not promise the latter.
|
|
591
|
+
|
|
592
|
+
### Guardrails & Monorails
|
|
593
|
+
|
|
594
|
+
The door guards each write. **Guardrails & Monorails** guards the order of a session's writes.
|
|
595
|
+
**Guardrails** (the floor, the character, the served instructions) bound a field where the AI
|
|
596
|
+
moves freely and does its judgment work. **The monorail** (the session graph,
|
|
597
|
+
`src/session-graph.js`) carries it from one field to the next without asking it to choose:
|
|
598
|
+
open, work, record, board, handoff, close. The car can't derail or leave the beam. Where the
|
|
599
|
+
flow must fork, a **switch** at the junction (a small logic unit in EMET's code, tripped only by
|
|
600
|
+
its condition, like a transistor; no hosted model, no API) routes the session to the right
|
|
601
|
+
guardrail space. The track throws the switch, never the AI. The stations are where the
|
|
602
|
+
processing happens, and each station's door opens only when every stop before it has been made.
|
|
603
|
+
Switches are designed, not yet built. The server checks the track
|
|
604
|
+
on every governed write, so it is the same for any AI on any device. No host hook is needed.
|
|
605
|
+
Every skipped step is refused, and the refusal says how to pass. The full practice, the gates and the honest limit
|
|
606
|
+
are in [`docs/GUARDRAILS-AND-MONORAILS.md`](docs/GUARDRAILS-AND-MONORAILS.md).
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## Tools
|
|
611
|
+
|
|
612
|
+
Twenty-nine tools. `recall` · `semantic_recall` · `query_layer` · `save_to_layer` · `revise_memory` ·
|
|
613
|
+
`read_doc` · `read_doc_history` · `write_doc` · `list_docs` · `patch_doc` ·
|
|
614
|
+
`retire_doc` · `rename_doc` · `restore_doc` · `save_transcript` · `revise_transcript` · `query_transcripts` ·
|
|
615
|
+
`corpus_recall` · `emet_gaps` · `emet_floor` · `validate_doc` · `accept_nonconformance` ·
|
|
616
|
+
`emet_initialize` · `emet_status` · `emet_setup_complete` · `emet_session_open` ·
|
|
617
|
+
`emet_transcript_append` · `emet_session_close` · `emet_invite_create` · `emet_member_access_create`
|
|
618
|
+
|
|
619
|
+
**Retained revisions** — every document write archives the prior copy; `read_doc_history` lists
|
|
620
|
+
what is retained for a document and returns any retained revision in full, re-hashed against its
|
|
621
|
+
stored digest. Retention that nothing could read back was a promise without a witness.
|
|
622
|
+
|
|
623
|
+
**One reader for the second tier** — `query_transcripts` reads the live transcript store and the
|
|
624
|
+
legacy exchange archive (rows written by earlier hosts, before the transcript store existed)
|
|
625
|
+
together, in one session shape, each result naming the collection it came from. Nothing is
|
|
626
|
+
migrated between them.
|
|
627
|
+
|
|
628
|
+
**Document control** — `validate_doc` checks a controlled document against the template that
|
|
629
|
+
governs it, and the same check runs inside the write path on every document write, where no
|
|
630
|
+
caller can skip it. A record that fails is **marked, never rejected**: it lands, carries a
|
|
631
|
+
nonconformance marker naming what failed, and stays counted at session start until it is
|
|
632
|
+
corrected by a later revision or dispositioned with `accept_nonconformance`. The checks are form
|
|
633
|
+
and continuity — sections present, in order and filled; no placeholder left unfilled; every item
|
|
634
|
+
on the previous revision still present or carrying a disposition. Whether an answer is *true* is
|
|
635
|
+
not something a machine can check, and this one does not pretend to.
|
|
636
|
+
|
|
637
|
+
**Session start** — `emet_initialize` (start here): fetch every page it names, or pass `page: "all"`
|
|
638
|
+
on a host with no size limit; then `emet_session_open` with that startup's `load_id`. Session ids are
|
|
639
|
+
dated in the local time zone (`EMET_TIMEZONE`, default `America/New_York`); a closed session is
|
|
640
|
+
never reopened, so a slug whose id is already closed opens as `<id>-2`, `-3`, and the result says so.
|
|
641
|
+
After a complete close a session takes exactly one more append, its closing exchange, within
|
|
642
|
+
`EMET_CLOSE_GRACE_MINUTES` (default 5, at most 60) of the close, written into the session's last
|
|
643
|
+
part, and is then locked; a retry of it writes nothing. Corrections with `revise_transcript` stay
|
|
644
|
+
allowed but must keep exactly the same exchange numbers.
|
|
645
|
+
|
|
646
|
+
**During the session** — `emet_transcript_append` after every reply, one exchange at a time.
|
|
647
|
+
Every write tool also takes `session_id` and `exchange`, and returns `session_graph.next`, the
|
|
648
|
+
next stop on the monorail ([Guardrails & Monorails](docs/GUARDRAILS-AND-MONORAILS.md)).
|
|
649
|
+
|
|
650
|
+
**Session end** — `emet_session_close`. Checks whether the session's work actually reached the
|
|
651
|
+
store — transcript saved and intact, continuity documents updated, an episodic entry written —
|
|
652
|
+
and reports exactly what is missing. It does **not** write the artifacts: only the model holds the
|
|
653
|
+
session's content, and a tool that generated a plausible-looking summary would be manufacturing
|
|
654
|
+
the false continuity the disciplines exist to prevent. `emet_initialize` returns the same protocol
|
|
655
|
+
at session *start*, which is the only moment early enough to matter.
|
|
656
|
+
|
|
657
|
+
**Setup internals and diagnostics** — `emet_status` (environment, layers, health; `probe: true` writes and reads back a probe) · `emet_setup_complete`
|
|
658
|
+
|
|
659
|
+
### Approvals
|
|
660
|
+
|
|
661
|
+
Every tool declares MCP annotations — `readOnlyHint`, `destructiveHint`, `idempotentHint`,
|
|
662
|
+
`openWorldHint` — in one table in `src/annotations.js`, which is also the complete statement of
|
|
663
|
+
what EMET does to a store. **11 of the 29 tools cannot modify anything**; four can replace the
|
|
664
|
+
visible content of a document; `openWorldHint` is false everywhere. The tools reach this
|
|
665
|
+
server's own database and nothing else: no filesystem and no shell. The sign-in server fetches
|
|
666
|
+
a client's metadata document, and only from a public address it has already checked.
|
|
667
|
+
|
|
668
|
+
A server that declares nothing is treated as if every call might do anything, so a host that asks
|
|
669
|
+
before acting asks before *every* call, including the reads that make up most of a session. These
|
|
670
|
+
hints let a host stop asking about reads without being told to trust writes. They are hints, not
|
|
671
|
+
enforcement: what is actually auto-approved is a host-side setting. See
|
|
672
|
+
[docs/PERMISSIONS.md](docs/PERMISSIONS.md).
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
## Tests
|
|
677
|
+
|
|
678
|
+
```
|
|
679
|
+
npm test
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
1,852 checks across 64 suites (the PASS lines `npm test` prints, counted 2026-10-07). No framework, no database, no
|
|
683
|
+
credentials — each suite is a plain node script on one shared harness (`tests/harness.js`), and
|
|
684
|
+
the runner discovers every `*.test.js`, so `npm test` runs on a fresh clone. Per-suite counts
|
|
685
|
+
are not kept here because they go stale; `npm test` prints them.
|
|
686
|
+
|
|
687
|
+
`validation.test.js` and `content_analyzer.test.js` began as **upstream's own
|
|
688
|
+
suites**, ported unchanged apart from the import path; every one of upstream's assertions still
|
|
689
|
+
stands, and the additions cover EMET's metadata vocabulary and the shared layer list. They are
|
|
690
|
+
the original author's assertions about the original author's code, and they are kept passing.
|
|
691
|
+
|
|
692
|
+
The rest cover this tree. Some are fixtures rather than unit tests — they read the source and
|
|
693
|
+
fail on a class of drift: `write-path.test.js` allows no MongoDB write outside the
|
|
694
|
+
sanctioned doors, requires each door to scrub before it hashes, and requires that **nothing in
|
|
695
|
+
the tree removes from a layer, document or transcript collection**; `dispatch.test.js`
|
|
696
|
+
requires the declared tool list and both transports' dispatch switches to be the same set, and
|
|
697
|
+
that a list response can carry a caller-facing warning; `annotations.test.js` requires
|
|
698
|
+
every tool to declare its approval hints; `atlas-role.test.js` requires the database role to
|
|
699
|
+
grant no remove on the record. The others test behaviour — among them `aleph.test.js`
|
|
700
|
+
(read-back, digests, signatures, the chain walk), `session-graph.test.js` (the monorail's
|
|
701
|
+
gates and lab results), `receipt.test.js`, `provenance-stamp.test.js`, `session.test.js`,
|
|
702
|
+
`gaps.test.js` and `redact.test.js`. Their failure
|
|
703
|
+
cases matter more than their success cases — a verifier that passes something it should have
|
|
704
|
+
caught converts an undetected problem into a confident assurance. `layers.test.js` asserts
|
|
705
|
+
against the store shape that produced the year-long dormant layer, so the check is tested
|
|
706
|
+
against the failure it was written for rather than a hypothetical one.
|
|
707
|
+
|
|
708
|
+
Upstream's `index.test.js`, `decay.test.js` and `integration.test.js` are **not** carried: they
|
|
709
|
+
target the SQLite backend and the temporal decay engine, neither of which exists here. Running
|
|
710
|
+
them would produce failures that say nothing about this code.
|
|
711
|
+
|
|
712
|
+
### Benchmarks
|
|
713
|
+
|
|
714
|
+
EMET's implementation language was evaluated rather than assumed. The same specification was
|
|
715
|
+
implemented in **JavaScript, Python, Go and Rust**, deployed identically, and run against live
|
|
716
|
+
Atlas — with a digest equivalence check proving all four did the same work before any timing
|
|
717
|
+
was read. **JavaScript won**, on serial in-process cost, on throughput under concurrency, and on
|
|
718
|
+
standards provenance as the only candidate carrying an international language standard
|
|
719
|
+
(ECMA-262 and ISO/IEC 16262).
|
|
720
|
+
|
|
721
|
+
The exercise's most useful output was not a language choice but a function to optimise: the
|
|
722
|
+
write-path scrubber was paying nearly full cost on documents containing no secrets, and fixing
|
|
723
|
+
that saved roughly thirty times what the best available language change could have.
|
|
724
|
+
|
|
725
|
+
Method, full results, and an explicit account of what was *not* measured:
|
|
726
|
+
[docs/BENCHMARKS.md](docs/BENCHMARKS.md). `scripts/redact-bench.mjs` measures the scrubber
|
|
727
|
+
alone — run it before and after any change to `src/redact.js`.
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
731
|
+
## Known gaps
|
|
732
|
+
|
|
733
|
+
Honest list, because the point of this project is that it behaves predictably. What is planned
|
|
734
|
+
next is in [docs/ROADMAP.md](docs/ROADMAP.md).
|
|
735
|
+
|
|
736
|
+
- Rows written before 2026-09-05 carry no content digest and rows written before 2026-09-07
|
|
737
|
+
carry no metadata digest; reads report them as `'unattested'`, and they are not backfilled —
|
|
738
|
+
a digest computed today would attest to today's copy, not to the write. `emet_gaps` counts
|
|
739
|
+
the boundary.
|
|
740
|
+
- Entries written before provenance existed keep a null pointer and resolve by timestamp
|
|
741
|
+
window; `emet_gaps` counts them as unsourced. They are not backfilled, for the reason given
|
|
742
|
+
under *Provenance*.
|
|
743
|
+
- Two content routers are carried from upstream (a substring keyword router and a content
|
|
744
|
+
analyser). Measured against a 1,175-row corpus both agree with the stored layer well under
|
|
745
|
+
half the time (41% and 36%), so neither places entries: an unlabelled write lands in
|
|
746
|
+
`working`, flagged, with both opinions recorded in `routing` (`keyword_layer`,
|
|
747
|
+
`analyzer_layer`). Retuning them into something fit to route is possible with those
|
|
748
|
+
numbers as the baseline; it is not scheduled.
|
|
749
|
+
- `consolidation_candidates` in `emet_gaps` is tag overlap, not semantic similarity.
|
|
750
|
+
- The monorail (session graph) refuses every skipped step (enforce only since 2026-10-03; there
|
|
751
|
+
is no report mode or off switch). Its turn coverage is only as strong as the exchange numbers hosts report, and it never sees
|
|
752
|
+
the reply text. See [Guardrails & Monorails](docs/GUARDRAILS-AND-MONORAILS.md).
|
|
753
|
+
- The scheduled weekly intake writes without opening a session, so the session-order check
|
|
754
|
+
refuses those writes (gate `no_session`) until the routine opens a session with its startup's
|
|
755
|
+
`load_id` and passes the `session_id`. Its old source tag, `cloud-scheduled-intake`, was retired on
|
|
756
|
+
2026-10-02. A `cloud-scheduled-` prefix is not a registered tag, and those writes are refused.
|
|
757
|
+
The weekly intake runs under `intake-bot`. `emet_gaps` keeps counting the rows already written
|
|
758
|
+
under the old tag as ambiguous attribution instead of rewriting history.
|
|
759
|
+
- `save_transcript`'s description does not state that it stores content verbatim; the
|
|
760
|
+
implementation does.
|
|
761
|
+
- No export / import / restore-and-compare yet.
|
|
762
|
+
- The document store is not prefixed, so one database holds one instance's identity. Run
|
|
763
|
+
separate databases rather than separate prefixes if you want separate memories.
|
|
764
|
+
- Dynamic Client Registration is still served beside Client ID Metadata Documents; the MCP
|
|
765
|
+
specification's deprecation window closes around 2027-07. It is scheduled to come off on
|
|
766
|
+
2027-06-01.
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
## Origin
|
|
771
|
+
|
|
772
|
+
EMET was built by Dreamforge Studio LLC from a neurodivergent user’s own need for an AI assistant that remembers. The word coined for that is neurendipity: neurodivergent and serendipity.
|
|
773
|
+
|
|
774
|
+
It is designed first for neurodivergent users, and it works for anyone.
|
|
775
|
+
|
|
776
|
+
---
|
|
777
|
+
|
|
778
|
+
## License
|
|
779
|
+
|
|
780
|
+
Authorship of the commit history, including a correction recorded as an addition
|
|
781
|
+
rather than a rewrite: `docs/AUTHORSHIP.md`.
|
|
782
|
+
|
|
783
|
+
MIT. See `LICENSE` — both the original C.I.P.S. LLC notice and the Dreamforge Studio LLC notice
|
|
784
|
+
must travel with any copy.
|