@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.
- package/README.md +1 -0
- package/assets/skill/SKILL.md +22 -1
- package/assets/skill/cli-reference.md +48 -0
- package/assets/skill/mcp-reference.md +31 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.40.0.zip → gno-browser-clipper-v1.42.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.42.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +3 -2
- package/spec/cli.md +146 -7
- package/spec/db/schema.sql +17 -0
- package/spec/mcp.md +350 -6
- package/spec/output-schemas/memory-recall.schema.json +159 -0
- package/spec/output-schemas/memory-remember.schema.json +164 -0
- package/spec/output-schemas/status.schema.json +269 -54
- package/src/cli/commands/daemon.ts +1 -0
- package/src/cli/commands/mcp.ts +3 -1
- package/src/cli/commands/memory.ts +491 -0
- package/src/cli/commands/status.ts +23 -4
- package/src/cli/options.ts +4 -0
- package/src/cli/program.ts +155 -0
- package/src/config/types.ts +10 -0
- package/src/core/audit-provenance.ts +91 -0
- package/src/core/audit-workspace.ts +17 -0
- package/src/core/connector-verifier.ts +2 -4
- package/src/core/memory-diagnostics.ts +144 -0
- package/src/core/memory-fence.ts +239 -0
- package/src/core/memory-recall.ts +269 -0
- package/src/core/memory-record.ts +435 -0
- package/src/core/memory-remember.ts +425 -0
- package/src/core/memory-types.ts +211 -0
- package/src/core/memory.ts +87 -0
- package/src/ingestion/sync.ts +17 -0
- package/src/mcp/AGENTS.md +7 -1
- package/src/mcp/CLAUDE.md +7 -1
- package/src/mcp/context.ts +37 -7
- package/src/mcp/http-egress.ts +2 -0
- package/src/mcp/http-modern.ts +214 -0
- package/src/mcp/http-security.ts +5 -0
- package/src/mcp/http-session.ts +4 -3
- package/src/mcp/http-transport.ts +81 -12
- package/src/mcp/resources/index.ts +3 -6
- package/src/mcp/server.ts +18 -16
- package/src/mcp/stdio-serving.ts +45 -0
- package/src/mcp/tool-descriptions-core.ts +56 -0
- package/src/mcp/tool-profile.ts +112 -0
- package/src/mcp/tools/index.ts +286 -126
- package/src/mcp/tools/memory-recall.ts +122 -0
- package/src/mcp/tools/memory-remember.ts +177 -0
- package/src/mcp/tools/memory-shared.ts +86 -0
- package/src/pipeline/search.ts +2 -0
- package/src/pipeline/types.ts +8 -0
- package/src/sdk/client.ts +94 -1
- package/src/sdk/index.ts +13 -0
- package/src/sdk/types.ts +28 -0
- package/src/serve/routes/api.ts +167 -0
- package/src/serve/routes/mcp.ts +1 -0
- package/src/serve/server.ts +27 -0
- package/src/store/migrations/027-memory-scopes.ts +37 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +127 -3
- package/src/store/types.ts +54 -0
- 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-
|
|
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
|
|
71
|
-
store, jobs, and model lifecycle
|
|
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
|
+
}
|