@gmickel/gno 1.40.0 → 1.42.0

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.
Files changed (62) hide show
  1. package/README.md +1 -0
  2. package/assets/skill/SKILL.md +22 -1
  3. package/assets/skill/cli-reference.md +48 -0
  4. package/assets/skill/mcp-reference.md +31 -0
  5. package/browser-extension/artifacts/{gno-browser-clipper-v1.40.0.zip → gno-browser-clipper-v1.42.0.zip} +0 -0
  6. package/browser-extension/artifacts/gno-browser-clipper-v1.42.0.zip.sha256 +1 -0
  7. package/browser-extension/dist/manifest.json +1 -1
  8. package/package.json +3 -2
  9. package/spec/cli.md +146 -7
  10. package/spec/db/schema.sql +17 -0
  11. package/spec/mcp.md +350 -6
  12. package/spec/output-schemas/memory-recall.schema.json +159 -0
  13. package/spec/output-schemas/memory-remember.schema.json +164 -0
  14. package/spec/output-schemas/status.schema.json +269 -54
  15. package/src/cli/commands/daemon.ts +1 -0
  16. package/src/cli/commands/mcp.ts +3 -1
  17. package/src/cli/commands/memory.ts +491 -0
  18. package/src/cli/commands/status.ts +23 -4
  19. package/src/cli/options.ts +4 -0
  20. package/src/cli/program.ts +155 -0
  21. package/src/config/types.ts +10 -0
  22. package/src/core/audit-provenance.ts +91 -0
  23. package/src/core/audit-workspace.ts +17 -0
  24. package/src/core/connector-verifier.ts +2 -4
  25. package/src/core/memory-diagnostics.ts +144 -0
  26. package/src/core/memory-fence.ts +239 -0
  27. package/src/core/memory-recall.ts +269 -0
  28. package/src/core/memory-record.ts +435 -0
  29. package/src/core/memory-remember.ts +425 -0
  30. package/src/core/memory-types.ts +211 -0
  31. package/src/core/memory.ts +87 -0
  32. package/src/ingestion/sync.ts +17 -0
  33. package/src/mcp/AGENTS.md +7 -1
  34. package/src/mcp/CLAUDE.md +7 -1
  35. package/src/mcp/context.ts +37 -7
  36. package/src/mcp/http-egress.ts +2 -0
  37. package/src/mcp/http-modern.ts +214 -0
  38. package/src/mcp/http-security.ts +5 -0
  39. package/src/mcp/http-session.ts +4 -3
  40. package/src/mcp/http-transport.ts +81 -12
  41. package/src/mcp/resources/index.ts +3 -6
  42. package/src/mcp/server.ts +18 -16
  43. package/src/mcp/stdio-serving.ts +45 -0
  44. package/src/mcp/tool-descriptions-core.ts +56 -0
  45. package/src/mcp/tool-profile.ts +112 -0
  46. package/src/mcp/tools/index.ts +286 -126
  47. package/src/mcp/tools/memory-recall.ts +122 -0
  48. package/src/mcp/tools/memory-remember.ts +177 -0
  49. package/src/mcp/tools/memory-shared.ts +86 -0
  50. package/src/pipeline/search.ts +2 -0
  51. package/src/pipeline/types.ts +8 -0
  52. package/src/sdk/client.ts +94 -1
  53. package/src/sdk/index.ts +13 -0
  54. package/src/sdk/types.ts +28 -0
  55. package/src/serve/routes/api.ts +167 -0
  56. package/src/serve/routes/mcp.ts +1 -0
  57. package/src/serve/server.ts +27 -0
  58. package/src/store/migrations/027-memory-scopes.ts +37 -0
  59. package/src/store/migrations/index.ts +2 -0
  60. package/src/store/sqlite/adapter.ts +127 -3
  61. package/src/store/types.ts +54 -0
  62. package/browser-extension/artifacts/gno-browser-clipper-v1.40.0.zip.sha256 +0 -1
package/spec/mcp.md CHANGED
@@ -1,9 +1,13 @@
1
1
  # GNO MCP Specification
2
2
 
3
3
  **Version:** 1.0.0
4
- **Last Updated:** 2026-04-24
5
- **Protocol:** Model Context Protocol (MCP) 2025-11-25
4
+ **Last Updated:** 2026-09-03
5
+ **Protocol:** Model Context Protocol (MCP) 2025-11-25 and 2026-07-28 (dual-era; see
6
+ [Protocol Revisions](#protocol-revisions))
6
7
  **Transport:** JSON-RPC 2.0 over stdio or resident Streamable HTTP
8
+ **SDK:** `@modelcontextprotocol/server` 2.x (tool `inputSchema` /
9
+ `outputSchema` carry the JSON Schema 2020-12 `$schema` stamp; an unknown tool
10
+ name answers JSON-RPC `-32602`)
7
11
 
8
12
  This document specifies the MCP server interface for GNO.
9
13
 
@@ -34,6 +38,83 @@ This document specifies the MCP server interface for GNO.
34
38
 
35
39
  ---
36
40
 
41
+ ## Protocol Revisions
42
+
43
+ GNO serves two protocol eras from one tool registry, on both transports:
44
+
45
+ | Era | Revisions | Opening | HTTP state |
46
+ | ------ | ------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------ |
47
+ | legacy | `2025-11-25` (and the earlier revisions the SDK negotiates down to) | `initialize` handshake | stateful: `Mcp-Session-Id`, GET stream, DELETE |
48
+ | modern | `2026-07-28` | `server/discover` probe, per-request `_meta` envelope | sessionless: one SDK instance per request, no session header |
49
+
50
+ Nothing changes for a 2025 client: the `initialize` result, `notifications/initialized`
51
+ acknowledgement, and `tools/list` bytes are pinned by `test/mcp/legacy-parity.test.ts`
52
+ against a committed golden. A legacy `initialize` never yields a 2026 negotiation - an
53
+ `initialize` naming `protocolVersion: "2026-07-28"` is a 2025-era opening by definition and
54
+ negotiates down to `2025-11-25`.
55
+
56
+ **stdio.** `gno mcp` serves through the SDK's connection-pinned entry (`serveStdio`,
57
+ wrapped by `src/mcp/stdio-serving.ts`). The first message pins the era for the whole
58
+ connection: `server/discover` with a valid envelope answers
59
+ `{ supportedVersions: ["2026-07-28"], capabilities, _meta["io.modelcontextprotocol/serverInfo"] }`
60
+ and every later result carries the `serverInfo` stamp; a claim-less `initialize` pins the
61
+ legacy era and the connection behaves exactly as before. On a modern-pinned connection a
62
+ later legacy `initialize` answers `-32022 Unsupported protocol version` with
63
+ `data.supported = ["2026-07-28"]`.
64
+
65
+ **Streamable HTTP.** `/mcp` classifies each request with the SDK's own predicate
66
+ (`isLegacyRequest`) and routes it: legacy traffic (no envelope claim, or any GET/DELETE) to
67
+ the stateful session store; a request that claims the modern era (a
68
+ `params._meta["io.modelcontextprotocol/protocolVersion"]` key, or an `MCP-Protocol-Version`
69
+ header naming a modern revision) to a strict (`legacy: "reject"`) sessionless handler
70
+ built from the same server factory. A `server/discover` POST without an envelope classifies
71
+ legacy and is refused by the session path like any other session-less non-initialize POST.
72
+
73
+ Modern requests are validated before dispatch and rejected - never silently stripped - with a
74
+ JSON-RPC error body (`400` unless noted):
75
+
76
+ | Condition | Code |
77
+ | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
78
+ | envelope or header names a revision other than `2026-07-28` | `-32022` `Unsupported protocol version: <requested>` with `data.supported` / `data.requested` |
79
+ | envelope present but `MCP-Protocol-Version` header absent | `-32020` (GNO-owned: the header is required on every modern request) |
80
+ | `MCP-Protocol-Version`, `Mcp-Method`, or `Mcp-Name` disagrees with the body, or a required routing header is absent | `-32020` `HeaderMismatch` with `data.mismatch` |
81
+ | `MCP-Protocol-Version: 2026-07-28` header without an envelope | `-32602` (missing `_meta`) |
82
+ | malformed envelope value (`protocolVersion`, `clientInfo`, `clientCapabilities`) | `-32602` `Invalid _meta envelope` |
83
+ | `Mcp-Session-Id` on a modern request | `-32600` (sessions are 2025-era only; the request can never bind to or read a session) |
84
+ | JSON-RPC batch containing modern requests | `-32600` |
85
+ | `subscriptions/listen` (long-lived change stream) | `404` `-32601` `Method not found` with `data.method`, request id echoed |
86
+
87
+ Custom `_meta` keys on a modern request reach tool handlers unchanged (`ctx.mcpReq._meta`);
88
+ the reserved `io.modelcontextprotocol/*` envelope keys are lifted to `ctx.mcpReq.envelope`.
89
+ Modern responses carry `resultType`, `_meta["io.modelcontextprotocol/serverInfo"]`, and the
90
+ cache fields (`ttlMs: 0`, `cacheScope: "private"`) on cacheable results.
91
+
92
+ `subscriptions/listen` is rejected on the modern leg (`404`, `-32601`) before the SDK
93
+ handler is reached. GNO wires no change event source to subscription streams yet, and the
94
+ SDK's listen router would otherwise hold an SSE stream open for the life of the connection
95
+ (15 s keep-alives, no server-side lifetime), pinning one `maxConcurrentRequests` slot and one
96
+ runtime admission handle with nothing to reap them. The rejection releases both like any
97
+ other pre-dispatch refusal and leaves `invalidateAuthenticatedSessions` (2025-era sessions
98
+ only) unaffected. Wiring GNO change events to subscription streams is fn-132's territory; the
99
+ rejection lifts when that lands.
100
+
101
+ Per-caller identity on the sessionless leg: a modern request has no `Mcp-Session-Id`, so the
102
+ transport derives an opaque per-caller label (`http:<16 hex>`, a hash of the server instance
103
+ id and the authenticated security identity) and exposes it to tools as
104
+ `ctx.getRequestIdentity()`. `gno_recall` / `gno_remember` use it as the memory `session`
105
+ when no transport session exists, so two distinct authenticated callers never share one
106
+ memory identity; the raw bearer digest never reaches a stored record. Unauthenticated
107
+ loopback callers share the `loopback` security identity and therefore one label.
108
+
109
+ **Guard parity.** Both legs share one enforcement path in `src/mcp/http-transport.ts`:
110
+ capacity and runtime admission, the write gate (`--enable-write`), per-request egress
111
+ evaluation against the actual peer zone, authorization-epoch invalidation, identity checks,
112
+ and transport metrics all run before the era branch; the bearer/Host/Origin/body-size
113
+ boundary (`src/mcp/http-security.ts`) runs before the transport on every request. A modern
114
+ request cannot create a session, and one that names a session ID is rejected before the
115
+ session store is consulted. `test/mcp/sessionless-guards.test.ts` holds one test per guard;
116
+ `test/mcp/protocol-2026.test.ts` holds the wire assertions for both transports.
117
+
37
118
  ## Security Model
38
119
 
39
120
  ### Write Tool Gating
@@ -46,7 +127,75 @@ gno mcp --enable-write
46
127
  GNO_MCP_ENABLE_WRITE=1 gno mcp
47
128
  ```
48
129
 
49
- When disabled, write tools are not registered and cannot be invoked.
130
+ When disabled, write tools are not registered and cannot be invoked. The
131
+ write gate is independent of the tool profile below: a profile only ever
132
+ narrows the set the gate exposes.
133
+
134
+ ### Tool Profiles
135
+
136
+ A profile decides which tools the server advertises. Both transports honor it;
137
+ the resident gateway applies one profile to every connected client.
138
+
139
+ | Profile | Without `--enable-write` | With `--enable-write` |
140
+ | ---------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
141
+ | `full` (default) | Every read tool below (34) | Every read tool plus every write tool (53) |
142
+ | `core` | `gno_query`, `gno_search`, `gno_get`, `gno_multi_get`, `gno_context`, `gno_changes`, `gno_recall` (7) | The 7 read tools plus `gno_capture`, `gno_remember` (9) |
143
+
144
+ Both lists are exact: `core` advertises nothing else, and `full` is byte-for-byte
145
+ today's registry (names, order, descriptions, annotations, schemas). Both
146
+ profiles negotiate the same protocol revisions (2025-11-25 and 2026-07-28; see
147
+ [Protocol Revisions](#protocol-revisions)) on both transports.
148
+
149
+ Descriptions differ by profile. `full` serves the original strings verbatim
150
+ (`MCP_TOOL_DESCRIPTIONS` in `src/mcp/tools/index.ts`, pinned by the legacy
151
+ golden). `core` serves a micro-instruction per tool from a separate table
152
+ (`MCP_CORE_TOOL_DESCRIPTIONS` in `src/mcp/tool-descriptions-core.ts`): each
153
+ description opens with when to call the tool, names the mechanism it runs, and
154
+ ends with what comes back and any bound the caller must respect (line anchors
155
+ for `gno_get`, `maxBytes` for `gno_multi_get`, the 8-fact / 512-token recall
156
+ budget, the separate embedding step after `gno_capture`). Descriptions are the
157
+ zero-install discovery surface, so they follow the copy rules the skill and
158
+ site use: mechanism first, honest bounds, active voice, no promotional
159
+ vocabulary, no negated framings. The variants table names exactly the nine core
160
+ tools; the set of tools whose description differs between the profiles is the
161
+ core set and nothing else (`test/mcp/tool-descriptions-core.test.ts`). Input
162
+ schemas and annotations are identical across profiles.
163
+
164
+ Core read membership was decided against the Agent Retrieval Playbook: the
165
+ default hybrid path (`gno_query`), the exact-term path (`gno_search`), the two
166
+ read primitives every result hands off to (`gno_get`, `gno_multi_get`), the
167
+ bounded evidence handoff (`gno_context`), the metadata-only change feed
168
+ (`gno_changes`), and memory recall (`gno_recall`). Diagnostics, vector-only
169
+ search, graph, sections, traces, egress, status, and job tools stay in `full`.
170
+
171
+ Core write membership is an exact allowlist: `gno_capture` (new documents) and
172
+ `gno_remember` (durable facts). `gno_job_status` is deliberately absent from
173
+ both core sets because neither exposed write is asynchronous: `gno_capture`
174
+ writes the file and returns, and `gno_remember` returns once the fact is
175
+ lexically searchable; neither starts a `JobManager` job, so the core profile
176
+ never hands out a job ID to poll. If a future core write becomes async,
177
+ `gno_job_status` joins the write allowlist in the same change.
178
+
179
+ Selection and precedence (highest first):
180
+
181
+ 1. CLI flag: `gno mcp --tool-profile core|full` (stdio); `gno serve --mcp-tool-profile core|full` or `gno daemon --mcp-tool-profile core|full` (resident gateway). An unknown value fails with `VALIDATION` before any listener starts.
182
+ 2. Config: `gateway.toolProfile: core|full` under the root `gateway` key (resident gateway only; stdio has no config key).
183
+ 3. Default: `full`.
184
+
185
+ The profile is read once when the listener starts (stdio process start, or
186
+ `serve`/`daemon` gateway start); it is not hot-reloaded from config, so
187
+ changing it requires restarting the server. `--detach` re-executes the same
188
+ argv, so the flag carries over to the detached child. Calling a tool outside
189
+ the active profile answers JSON-RPC `-32602` exactly like an unregistered
190
+ tool.
191
+
192
+ Default-profile decision (deferred): the default stays `full`. `core` ships as
193
+ opt-in so existing clients, installers, and skills see an unchanged surface,
194
+ and the flip to `core` is a separate follow-up that needs dogfood evidence
195
+ (agents running on `core` across real sessions, with the playbook's routing
196
+ holding and no tool outside the core set requested in ordinary retrieval). That
197
+ follow-up carries its own release note and a `gno mcp install` profile flag;
198
+ nothing in this change pre-empts it.
50
199
 
51
200
  ### Collection Root Validation
52
201
 
@@ -63,13 +212,19 @@ All write tools acquire an OS-backed advisory lock at `.mcp-write.lock` under th
63
212
  If another process holds the lock, tools return `LOCKED`.
64
213
  For async jobs, the lock is held for the full job duration.
65
214
 
215
+ `gno_remember` is the one write tool whose adapter takes no lock of its own:
216
+ the core memory service acquires the same `.mcp-write.lock` lease for every
217
+ memory write, so an MCP remember and a CLI writer serialise on one lease. A
218
+ lease that stays busy past the wait window returns `MEMORY_WRITE_LEASE_BUSY`.
219
+
66
220
  ### Resident Streamable HTTP boundary
67
221
 
68
222
  `gno serve` and `gno daemon` mount the same stateful MCP surface at `/mcp`.
69
223
  The default listener is the literal IPv4 loopback address `127.0.0.1`. Each
70
- HTTP session owns one SDK server and transport while sharing the resident
71
- store, jobs, and model lifecycle. POST, GET, and DELETE follow MCP 2025-11-25;
72
- resumption is not advertised.
224
+ 2025-era HTTP session owns one SDK server and transport while sharing the
225
+ resident store, jobs, and model lifecycle; POST, GET, and DELETE follow MCP
226
+ 2025-11-25 and resumption is not advertised. 2026-07-28 requests are served
227
+ sessionless from the same factory (see [Protocol Revisions](#protocol-revisions)).
73
228
 
74
229
  The external boundary runs before JSON parsing or SDK dispatch on every HTTP
75
230
  method. It uses Bun `server.requestIP(request)` as the peer source and never
@@ -1173,6 +1328,85 @@ returned.
1173
1328
 
1174
1329
  ---
1175
1330
 
1331
+ ### gno_recall
1332
+
1333
+ Budgeted, cited, current-state recall from a memory-managed collection
1334
+ (read set; registered without `--enable-write`).
1335
+
1336
+ **Input Schema:**
1337
+
1338
+ ```json
1339
+ {
1340
+ "type": "object",
1341
+ "properties": {
1342
+ "query": {
1343
+ "type": "string",
1344
+ "description": "What you need to know, phrased as the fact would be stated"
1345
+ },
1346
+ "collection": {
1347
+ "type": "string",
1348
+ "description": "Memory-managed collection to recall from"
1349
+ },
1350
+ "scopes": {
1351
+ "type": "array",
1352
+ "items": { "type": "string" },
1353
+ "minItems": 1,
1354
+ "maxItems": 8,
1355
+ "description": "Explicit scopes; visibility is any-intersection, no implicit global scope"
1356
+ },
1357
+ "maxFacts": {
1358
+ "type": "integer",
1359
+ "minimum": 1,
1360
+ "maximum": 64,
1361
+ "description": "Fact budget (default 8)"
1362
+ },
1363
+ "maxTokens": {
1364
+ "type": "integer",
1365
+ "minimum": 1,
1366
+ "maximum": 8192,
1367
+ "description": "Payload token budget (default 512)"
1368
+ }
1369
+ },
1370
+ "required": ["query", "collection", "scopes"]
1371
+ }
1372
+ ```
1373
+
1374
+ **Response (`structuredContent`):** the shared `RecallResult` contract from
1375
+ `src/core/memory.ts` (one schema across CLI, MCP, REST, SDK):
1376
+
1377
+ - `facts[]` — current facts only (superseded records excluded), each with
1378
+ `uri` (`gno://` cite), `docid`, `recordId`, `text`, `scopes`, `caller`,
1379
+ `session`, `createdAt`, `contentHash`, `supersedes`, `score`, `spanHash`,
1380
+ `egressLineage`
1381
+ - `receipt` — content-free fencing receipt: `caller`, `session`, `issuedAt`,
1382
+ `memoryIds`, `spanHashes`, `digest`
1383
+ - `budget` — `maxFacts`, `maxTokens`, `usedTokens`, `omitted`
1384
+ - `retrieval` — `mode` (`lexical` | `hybrid`) and `semanticUnavailable` when
1385
+ the vector leg did not run
1386
+ - `egressLineage` — strictest source policy across returned facts (absent when
1387
+ empty)
1388
+ - `hint` — self-teaching line naming `gno remember`, present only when no fact
1389
+ was returned
1390
+
1391
+ **Identity:** `caller` is the MCP client implementation name from the
1392
+ `initialize` handshake (`mcp` when absent); `session` is the Streamable HTTP
1393
+ session id, or the per-process server instance id on stdio. Tool arguments
1394
+ never carry identity.
1395
+
1396
+ **Notes:**
1397
+
1398
+ - Scope filtering executes inside the retrieval query, before any limit
1399
+ - The MCP adapter runs the lexical leg; `retrieval.mode` reports `lexical`
1400
+ and `retrieval.semanticUnavailable` states why
1401
+ - Annotations: `readOnlyHint: true`, `idempotentHint: true`
1402
+
1403
+ **Errors:** `MEMORY_QUERY_REQUIRED`, `MEMORY_BUDGET_INVALID`,
1404
+ `MEMORY_COLLECTION_REQUIRED`, `MEMORY_COLLECTION_NOT_FOUND`,
1405
+ `MEMORY_COLLECTION_UNMANAGED`, `MEMORY_SCOPES_REQUIRED`,
1406
+ `MEMORY_SCOPES_INVALID`, `MEMORY_QUERY_FAILED`.
1407
+
1408
+ ---
1409
+
1176
1410
  ### gno_capture
1177
1411
 
1178
1412
  Create a new document in a collection (write-enabled).
@@ -1311,6 +1545,112 @@ shared `gno://schemas/capture-receipt@1.0` contract.
1311
1545
 
1312
1546
  ---
1313
1547
 
1548
+ ### gno_remember
1549
+
1550
+ Store one fact with supersession semantics in a memory-managed collection
1551
+ (write-enabled). Remember is fact-granular: `gno_capture` creates documents,
1552
+ file edits update existing notes, `gno_remember` upserts a fact.
1553
+
1554
+ **Input Schema:**
1555
+
1556
+ ```json
1557
+ {
1558
+ "type": "object",
1559
+ "properties": {
1560
+ "text": {
1561
+ "type": "string",
1562
+ "description": "One fact, stated in full (single statement, not a document)"
1563
+ },
1564
+ "collection": {
1565
+ "type": "string",
1566
+ "description": "Memory-managed collection to write into"
1567
+ },
1568
+ "scopes": {
1569
+ "type": "array",
1570
+ "items": { "type": "string" },
1571
+ "minItems": 1,
1572
+ "maxItems": 8,
1573
+ "description": "Explicit scopes; no implicit global scope"
1574
+ },
1575
+ "decision": {
1576
+ "type": "string",
1577
+ "enum": ["add", "supersede"],
1578
+ "description": "Omit to receive candidates without writing"
1579
+ },
1580
+ "predecessorUri": {
1581
+ "type": "string",
1582
+ "description": "gno:// URI of the fact being superseded (supersede only)"
1583
+ },
1584
+ "predecessorHash": {
1585
+ "type": "string",
1586
+ "description": "contentHash of the predecessor as returned by gno_recall (supersede only)"
1587
+ },
1588
+ "receipt": {
1589
+ "type": "object",
1590
+ "description": "Receipt from the gno_recall response the fact derives from",
1591
+ "properties": {
1592
+ "caller": { "type": "string" },
1593
+ "session": { "type": "string" },
1594
+ "issuedAt": { "type": "string" },
1595
+ "memoryIds": { "type": "array", "items": { "type": "string" } },
1596
+ "spanHashes": { "type": "array", "items": { "type": "string" } },
1597
+ "digest": { "type": "string" }
1598
+ }
1599
+ },
1600
+ "derivedFrom": {
1601
+ "type": "array",
1602
+ "items": { "type": "string" },
1603
+ "description": "Declared origins; any gno:// origin is rejected"
1604
+ },
1605
+ "source": {
1606
+ "type": "string",
1607
+ "description": "Free-text evidence for the fact"
1608
+ }
1609
+ },
1610
+ "required": ["text", "collection", "scopes"]
1611
+ }
1612
+ ```
1613
+
1614
+ **Response (`structuredContent`):** the shared `RememberResult` contract:
1615
+
1616
+ - `outcome: "existing"` — exact duplicate in scope; `record` is the stored
1617
+ fact, nothing written
1618
+ - `outcome: "candidates"` — likely matches and no `decision`; `candidates[]`
1619
+ carry `similarity` and `match` (`exact` | `likely` | `weak`), nothing written
1620
+ - `outcome: "added" | "superseded"` — `record`, `absPath`, and
1621
+ `sync.status` (`completed` before the call returns; the fact is lexically
1622
+ searchable)
1623
+ - `matching` — `mode` (`semantic` | `lexical`), `threshold`, and
1624
+ `semanticUnavailable` when lexical matching was used
1625
+
1626
+ **Notes:**
1627
+
1628
+ - `supersede` requires `predecessorUri` + `predecessorHash`; the predecessor
1629
+ must be current in the same collection with a matching hash and no existing
1630
+ successor, otherwise `MEMORY_PREDECESSOR_*` or `MEMORY_SUPERSEDE_CONFLICT`
1631
+ - Context fencing: text whose normalized hash matches a `spanHashes` entry on
1632
+ the presented `receipt` returns `MEMORY_FENCED_REPLAY`; a `derivedFrom`
1633
+ entry starting with `gno://` returns `MEMORY_FENCED_DERIVED`. A paraphrase
1634
+ that carries neither is indistinguishable from an original fact and is not
1635
+ fenced
1636
+ - The core service holds the shared write lease for the write and lexical
1637
+ sync; the MCP adapter takes no lock of its own
1638
+ - Identity mapping is the same as `gno_recall`
1639
+ - Annotations: `readOnlyHint: false`, `destructiveHint: false`,
1640
+ `idempotentHint: false`
1641
+
1642
+ **Errors:** `WRITE_DISABLED`, `MEMORY_TEXT_REQUIRED`,
1643
+ `MEMORY_TEXT_TOO_LARGE`, `MEMORY_COLLECTION_REQUIRED`,
1644
+ `MEMORY_COLLECTION_NOT_FOUND`, `MEMORY_COLLECTION_UNMANAGED`,
1645
+ `MEMORY_SCOPES_REQUIRED`, `MEMORY_SCOPES_INVALID`,
1646
+ `MEMORY_DECISION_INVALID`, `MEMORY_PREDECESSOR_REQUIRED`,
1647
+ `MEMORY_PREDECESSOR_NOT_FOUND`, `MEMORY_PREDECESSOR_HASH_MISMATCH`,
1648
+ `MEMORY_SUPERSEDE_CONFLICT`, `MEMORY_FENCED_REPLAY`, `MEMORY_FENCED_DERIVED`,
1649
+ `MEMORY_WRITE_LEASE_BUSY`, `MEMORY_SYNC_FAILED`,
1650
+ `MEMORY_SUPERSEDE_PROJECTION_FAILED`, `MEMORY_QUERY_FAILED`.
1651
+
1652
+ ---
1653
+
1314
1654
  ### gno_list_tags
1315
1655
 
1316
1656
  List all tags with document counts.
@@ -2402,6 +2742,10 @@ Resource errors use standard MCP error responses.
2402
2742
  - `PATH_NOT_FOUND` — Path does not exist
2403
2743
  - `JOB_CONFLICT` — Another job is already running
2404
2744
  - `LOCKED` — Another MCP process holds the write lock
2745
+ - `WRITE_DISABLED` — Write tool dispatched while writes are disabled
2746
+ - `MEMORY_*` — Memory contract errors from `gno_recall` / `gno_remember`;
2747
+ the stable code set is `MemoryErrorCode` in `src/core/memory.ts` and each
2748
+ tool section above lists the codes it returns
2405
2749
 
2406
2750
  ---
2407
2751
 
@@ -0,0 +1,159 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/memory-recall@1.0",
4
+ "title": "GNO Memory Recall Result",
5
+ "description": "Shared result of `recall` on every surface (CLI --json, MCP gno_recall, REST POST /api/memory/recall, SDK client.recall()). Derived from RecallResult in src/core/memory.ts. Every returned fact carries a gno:// cite and its egress lineage; the receipt is content-free and bound to caller + session.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["facts", "receipt", "budget", "retrieval"],
9
+ "properties": {
10
+ "facts": {
11
+ "type": "array",
12
+ "items": { "$ref": "#/$defs/recalledFact" }
13
+ },
14
+ "receipt": { "$ref": "#/$defs/receipt" },
15
+ "budget": {
16
+ "type": "object",
17
+ "additionalProperties": false,
18
+ "required": ["maxFacts", "maxTokens", "usedTokens", "omitted"],
19
+ "properties": {
20
+ "maxFacts": { "type": "integer", "minimum": 1 },
21
+ "maxTokens": { "type": "integer", "minimum": 1 },
22
+ "usedTokens": { "type": "integer", "minimum": 0 },
23
+ "omitted": { "type": "integer", "minimum": 0 }
24
+ }
25
+ },
26
+ "retrieval": {
27
+ "type": "object",
28
+ "additionalProperties": false,
29
+ "required": ["mode"],
30
+ "properties": {
31
+ "mode": { "enum": ["hybrid", "lexical"] },
32
+ "semanticUnavailable": { "type": "string" }
33
+ }
34
+ },
35
+ "egressLineage": {
36
+ "$ref": "#/$defs/egressLineage",
37
+ "description": "Strictest source policy across every returned fact; absent when no fact was returned."
38
+ },
39
+ "hint": {
40
+ "type": "string",
41
+ "description": "Self-teaching line naming `gno remember`; present only when no fact was returned."
42
+ }
43
+ },
44
+ "$defs": {
45
+ "uri": {
46
+ "type": "string",
47
+ "pattern": "^gno://[^/]+/.+"
48
+ },
49
+ "sha256": {
50
+ "type": "string",
51
+ "pattern": "^[a-f0-9]{64}$"
52
+ },
53
+ "recalledFact": {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "required": [
57
+ "uri",
58
+ "docid",
59
+ "recordId",
60
+ "text",
61
+ "scopes",
62
+ "caller",
63
+ "session",
64
+ "createdAt",
65
+ "contentHash",
66
+ "supersedes",
67
+ "score",
68
+ "spanHash",
69
+ "egressLineage"
70
+ ],
71
+ "properties": {
72
+ "uri": { "$ref": "#/$defs/uri" },
73
+ "docid": { "type": "string", "minLength": 1 },
74
+ "recordId": { "type": "string", "pattern": "^mem-" },
75
+ "text": { "type": "string", "minLength": 1 },
76
+ "scopes": {
77
+ "type": "array",
78
+ "minItems": 1,
79
+ "maxItems": 8,
80
+ "items": { "type": "string", "minLength": 1, "maxLength": 64 }
81
+ },
82
+ "caller": { "type": "string", "minLength": 1 },
83
+ "session": { "type": "string", "minLength": 1 },
84
+ "createdAt": { "type": "string", "format": "date-time" },
85
+ "contentHash": { "$ref": "#/$defs/sha256" },
86
+ "source": {
87
+ "type": "string",
88
+ "minLength": 1,
89
+ "description": "Free-text evidence recorded with the fact, when one was given."
90
+ },
91
+ "supersedes": {
92
+ "type": "array",
93
+ "items": { "$ref": "#/$defs/uri" }
94
+ },
95
+ "score": { "type": "number", "minimum": 0 },
96
+ "spanHash": {
97
+ "$ref": "#/$defs/sha256",
98
+ "description": "Hash of the returned span; replaying it to remember with this receipt is fenced."
99
+ },
100
+ "egressLineage": { "$ref": "#/$defs/egressLineage" }
101
+ }
102
+ },
103
+ "receipt": {
104
+ "type": "object",
105
+ "additionalProperties": false,
106
+ "required": [
107
+ "caller",
108
+ "session",
109
+ "issuedAt",
110
+ "memoryIds",
111
+ "spanHashes",
112
+ "digest"
113
+ ],
114
+ "properties": {
115
+ "caller": { "type": "string", "minLength": 1 },
116
+ "session": { "type": "string", "minLength": 1 },
117
+ "issuedAt": { "type": "string", "format": "date-time" },
118
+ "memoryIds": {
119
+ "type": "array",
120
+ "items": { "type": "string", "minLength": 1 }
121
+ },
122
+ "spanHashes": {
123
+ "type": "array",
124
+ "items": { "$ref": "#/$defs/sha256" }
125
+ },
126
+ "digest": { "$ref": "#/$defs/sha256" }
127
+ }
128
+ },
129
+ "egressLineage": {
130
+ "type": "object",
131
+ "additionalProperties": false,
132
+ "required": ["effectivePolicy", "digest", "sources"],
133
+ "properties": {
134
+ "effectivePolicy": { "enum": ["local_only", "lan", "remote"] },
135
+ "digest": { "$ref": "#/$defs/sha256" },
136
+ "sources": {
137
+ "type": "array",
138
+ "minItems": 1,
139
+ "maxItems": 128,
140
+ "items": {
141
+ "type": "object",
142
+ "additionalProperties": false,
143
+ "required": ["collection", "policy", "source"],
144
+ "properties": {
145
+ "collection": {
146
+ "type": "string",
147
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
148
+ },
149
+ "policy": { "enum": ["local_only", "lan", "remote"] },
150
+ "source": {
151
+ "enum": ["explicit", "config_default", "legacy_default"]
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }