Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0620087
feat: query/search across several collections
ArnauvGilotra Oct 9, 2026
8ec3a76
fix: intersect collection scopes before graph expansion and reranking
ArnauvGilotra Oct 9, 2026
3dd8eb0
fix: raise the collections allowlist bound to 1024
ArnauvGilotra Oct 9, 2026
64e5bea
fix(mcp): scope HTTP egress to the collections allowlist
ArnauvGilotra Oct 9, 2026
073fa60
docs(mcp): adapt to closed tool inputs for gno_query_diagnose
ArnauvGilotra Oct 12, 2026
e0b956d
feat(gateway): principals and asserted subject
ArnauvGilotra Oct 9, 2026
9635271
fix(gateway): refuse the plain token when a principal shares it
ArnauvGilotra Oct 9, 2026
9d96884
fix(gateway): check token clashes against the request's own snapshot
ArnauvGilotra Oct 9, 2026
881eb40
Merge branch 'feat/gateway-principals-asserted-subject' into feat/col…
ArnauvGilotra Oct 12, 2026
6bc06b3
feat: collection access and scoped tool profile
ArnauvGilotra Oct 9, 2026
df27439
fix: deny accessRef collections and exclude invalid provenance before…
ArnauvGilotra Oct 9, 2026
7eadfa1
fix(context): accept up to 1024 collections in a Context Capsule scope
ArnauvGilotra Oct 11, 2026
0b3cae2
fix(mcp): adapt to supersededBy links for scoped callers
ArnauvGilotra Oct 12, 2026
85dc23c
fix(mcp): name only readable collections in egressLineage for scoped …
ArnauvGilotra Oct 12, 2026
d4004ac
fix(mcp): omit metadata coverage warnings for scoped callers
ArnauvGilotra Oct 12, 2026
d9968ec
fix(mcp): count only readable graph neighbours for scoped callers
ArnauvGilotra Oct 12, 2026
824f995
fix(store): bound the scoped supersession walk
ArnauvGilotra Oct 12, 2026
78f909b
test: cover cycles in the scoped walk and pin access-unset output to …
ArnauvGilotra Oct 12, 2026
c0b1d08
fix(mcp): resolve graph links over only the documents a scoped caller…
ArnauvGilotra Oct 12, 2026
d27c276
fix(mcp): never name unlisted collections to scoped callers
ArnauvGilotra Oct 12, 2026
5b09d32
docs(mcp): scoped supersededBy also covers the gno-context-agent-v2 p…
ArnauvGilotra Oct 12, 2026
227b7e7
fix(context): derive scoped Capsule fingerprints from readable state …
ArnauvGilotra Oct 12, 2026
2615e8a
feat: access hook
ArnauvGilotra Oct 9, 2026
ee0b64e
fix: pass the caller's GNO-Request-Id to the access hook
ArnauvGilotra Oct 9, 2026
58d37b2
docs(mcp): note the readable-corpus listing before scoped retrieval
ArnauvGilotra Oct 11, 2026
607a850
fix: adapt to supersededBy links for the access hook
ArnauvGilotra Oct 12, 2026
a8321af
test(mcp): budget access hook calls for supersededBy
ArnauvGilotra Oct 12, 2026
0952e83
fix: resolve graph links over only the documents the access hook allows
ArnauvGilotra Oct 12, 2026
142fb94
test(mcp): pin hook-scoped supersededBy in the gno_context agent proj…
ArnauvGilotra Oct 12, 2026
7efff5d
feat(mcp): await access hook answers without holding the tool mutex
ArnauvGilotra Oct 9, 2026
0bee31e
fix(mcp): count access-hook queue time toward the deadline
ArnauvGilotra Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `gno search`, `gno vsearch` and `gno query` can search several collections at once: repeat `-c` (`-c work -c notes`). Results come only from those collections, and other collections no longer take result slots before the limit. The MCP tools `gno_search`, `gno_vsearch` and `gno_query`, the `/api/search` and `/api/query` endpoints, and the SDK accept the same filter as `collections: [...]`. A single `-c` or `collection` works as before. Passing both `collection` and `collections` searches only their intersection, including graph neighbours and reranker input. One `collections` list takes up to 1024 names over MCP and REST, the same bound as collection egress, so a caller scoped to several hundred collections fits in one request. Over the HTTP MCP gateway, egress policy is checked on the listed collections, not on every configured collection.
- The resident MCP gateway can give named callers their own tokens with `gateway.principals`. A principal marked `mayAssertSubject` can say which user a request is for with the `GNO-Subject` and `GNO-Context` headers; any other caller that sends them gets 403. GNO keeps the principal and the user with each request, and stores the user only as a hash in request receipts. Tool results do not change yet. Without `principals`, the gateway behaves exactly as before. If a token rotation makes two credentials equal, including a principal and the plain `tokenFile`, neither is accepted until one is rotated again, also while another request is reloading the token files.
- Collections can declare who a shared gateway serves them to with `access`: `{public: true}`, `{owner: <user>}` (that user, in 1:1 conversations only), or `{accessRef: {sys, ref}, firmwide?: bool}`. A principal without `admin` then reads only collections its user may read, and only notes whose `gno.metadata.derived_from` lists their sources; a note it may not read gets the same `NOT_FOUND` answer as one that does not exist. Such a principal can call only `gno_query`, `gno_search`, `gno_get`, `gno_multi_get`, `gno_context`, `gno_recall` and `gno_remember`; the new `scoped` tool profile advertises exactly those. `accessRef` collections, firm-wide or not, are not served yet; they wait for an access hook. `gno audit provenance` reports notes in such collections without a valid `derived_from`. `supersededBy` names a newer note only when the principal may read it, following at most 32 supersession hops. `egressLineage` names only collections the principal may read. Typed-metadata coverage warnings are left out for such a principal, and graph expansion resolves and counts links over only the notes it may read, so a note it may not read never takes a link or changes its confidence. `gno_context` without `collections` uses every collection the principal may read, and a Context Capsule scope takes up to 1024 collections, the same bound as search and query. Collections another client indexed but the config does not list are never named to such a principal: its tool calls answer as if they were absent, while other callers still get the error that lists them. While such a collection keeps the server from applying an edited config, such a principal's calls are refused without naming anything, so it never reads under `access` rules the file has since changed. The daemon's `/api/resident/status` answers it 404. Without `gateway.principals`, nothing changes.
- A shared gateway can check access with your own service: set `gateway.accessHook` (an HTTP URL, optionally over a unix socket, with a deadline). For each request GNO asks the hook which of a note's `derived_from` sources the user may read, and serves the note only if every source is allowed; `accessRef` collections become readable when the hook allows their reference (in shared conversations only `firmwide` ones). A slow, failing or unreachable hook denies. Denied notes are dropped before graph expansion, reranking, capsules and result counts, and asking for one returns the same `NOT_FOUND` as a missing note. A collection can name a different sources key with `access.provenanceKey`. A `mayAssertSubject` caller can send `GNO-Request-Id` with its subject; every hook call for that request then carries it as `request_id`, so the hook can match its own turn. Scoped search, query and context first list every readable collection to leave out notes without a valid `derived_from`, so their latency grows with the readable corpus. `supersededBy` names a newer note only when the hook allows it too. Without `accessHook`, nothing changes.

### Changed

- A slow access hook no longer queues every MCP caller behind it. GNO runs MCP tool calls one at a time, and the hook calls happened inside that turn, so each caller waited for every other caller's hook answers: with a 250 ms hook, 20 concurrent users saw a p95 of about 11 s. A tool call now waits for its hook answer without holding the turn and takes it back before it continues, so hook waits overlap while tool code still runs one call at a time. New `gateway.accessHook.maxConcurrent` (default 16) caps the hook calls in flight; further calls queue in order. `timeoutMs` covers queue time: a call still queued at its deadline is not sent and denies, as does any later answer. Verdicts and fail-closed behavior do not change.

## [2.12.0] - 2026-10-12

### Changed
Expand Down
75 changes: 39 additions & 36 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -3254,6 +3254,7 @@ Keyword search using BM25 algorithm.
"limit": 10,
"minScore": 0.1,
"collection": "notes",
"collections": ["notes", "work"],
"intent": "web authentication and session security",
"exclude": "hiring,reviews",
"since": "last month",
Expand All @@ -3265,20 +3266,21 @@ Keyword search using BM25 algorithm.
}
```

| Field | Type | Default | Description |
| :----------- | :----- | :------ | :-------------------------------------------------------------------------------------------------- |
| `query` | string | — | Search query (required) |
| `limit` | number | 10 | Max results (max 50) |
| `minScore` | number | — | Minimum score threshold (0-1) |
| `collection` | string | — | Filter by collection |
| `intent` | string | — | Disambiguating context for ambiguous queries; steers snippet choice without being searched directly |
| `exclude` | string | — | Comma-separated exclusion terms; matching docs are hard-pruned by title/path/body |
| `since` | string | — | Modified-at lower bound (ISO date/time or token) |
| `until` | string | — | Modified-at upper bound (ISO date/time or token) |
| `category` | string | — | Comma-separated category/content-type filters (ANY match) |
| `author` | string | — | Author contains value (case-insensitive) |
| `tagsAll` | string | — | Comma-separated tags (must have ALL) |
| `tagsAny` | string | — | Comma-separated tags (must have ANY) |
| Field | Type | Default | Description |
| :------------ | :------- | :------ | :-------------------------------------------------------------------------------------------------- |
| `query` | string | — | Search query (required) |
| `limit` | number | 10 | Max results (max 50) |
| `minScore` | number | — | Minimum score threshold (0-1) |
| `collection` | string | — | Filter by collection |
| `collections` | string[] | — | Documents from ANY of these collections, filtered before `limit`; echoed as `meta.collections` |
| `intent` | string | — | Disambiguating context for ambiguous queries; steers snippet choice without being searched directly |
| `exclude` | string | — | Comma-separated exclusion terms; matching docs are hard-pruned by title/path/body |
| `since` | string | — | Modified-at lower bound (ISO date/time or token) |
| `until` | string | — | Modified-at upper bound (ISO date/time or token) |
| `category` | string | — | Comma-separated category/content-type filters (ANY match) |
| `author` | string | — | Author contains value (case-insensitive) |
| `tagsAll` | string | — | Comma-separated tags (must have ALL) |
| `tagsAny` | string | — | Comma-separated tags (must have ANY) |

If query text includes recency intent (`latest`, `newest`, `recent`), results are ordered newest-first by canonical frontmatter date when present, otherwise by source modified time.

Expand Down Expand Up @@ -3527,28 +3529,29 @@ Combined BM25 + vector search with optional reranking. **Recommended for best re
}
```

| Field | Type | Default | Description |
| :--------------- | :------ | :------ | :------------------------------------------------------------------------------------------------------------------------------- |
| `query` | string | — | Search query (required) |
| `limit` | number | 20 | Max results (max 50) |
| `minScore` | number | — | Minimum score threshold (0-1) |
| `collection` | string | — | Filter by collection |
| `lang` | string | auto | Query language hint |
| `intent` | string | — | Disambiguating context for ambiguous queries; steers expansion, reranking, and snippet selection without being searched directly |
| `candidateLimit` | number | 20 | Max candidates sent to reranking (max 100) |
| `exclude` | string | — | Comma-separated exclusion terms; matching docs are hard-pruned by title/path/body |
| `since` | string | — | Modified-at lower bound (ISO date/time or token) |
| `until` | string | — | Modified-at upper bound (ISO date/time or token) |
| `category` | string | — | Comma-separated category/content-type filters (ANY match) |
| `author` | string | — | Author contains value (case-insensitive) |
| `queryModes` | array | — | Optional structured mode entries (`term`, `intent`, `hyde`) |
| `noExpand` | boolean | false | Disable query expansion |
| `noRerank` | boolean | false | Disable cross-encoder reranking |
| `explain` | boolean | false | Include per-result retrieval scoring details |
| `graph` | boolean | true | Enable bounded one-hop graph neighbor expansion |
| `noGraph` | boolean | false | Disable graph neighbor expansion |
| `tagsAll` | string | — | Comma-separated tags (must have ALL) |
| `tagsAny` | string | — | Comma-separated tags (must have ANY) |
| Field | Type | Default | Description |
| :--------------- | :------- | :------ | :------------------------------------------------------------------------------------------------------------------------------- |
| `query` | string | — | Search query (required) |
| `limit` | number | 20 | Max results (max 50) |
| `minScore` | number | — | Minimum score threshold (0-1) |
| `collection` | string | — | Filter by collection |
| `collections` | string[] | — | Documents from ANY of these collections, filtered before `limit`; echoed as `meta.collections` |
| `lang` | string | auto | Query language hint |
| `intent` | string | — | Disambiguating context for ambiguous queries; steers expansion, reranking, and snippet selection without being searched directly |
| `candidateLimit` | number | 20 | Max candidates sent to reranking (max 100) |
| `exclude` | string | — | Comma-separated exclusion terms; matching docs are hard-pruned by title/path/body |
| `since` | string | — | Modified-at lower bound (ISO date/time or token) |
| `until` | string | — | Modified-at upper bound (ISO date/time or token) |
| `category` | string | — | Comma-separated category/content-type filters (ANY match) |
| `author` | string | — | Author contains value (case-insensitive) |
| `queryModes` | array | — | Optional structured mode entries (`term`, `intent`, `hyde`) |
| `noExpand` | boolean | false | Disable query expansion |
| `noRerank` | boolean | false | Disable cross-encoder reranking |
| `explain` | boolean | false | Include per-result retrieval scoring details |
| `graph` | boolean | true | Enable bounded one-hop graph neighbor expansion |
| `noGraph` | boolean | false | Disable graph neighbor expansion |
| `tagsAll` | string | — | Comma-separated tags (must have ALL) |
| `tagsAny` | string | — | Comma-separated tags (must have ANY) |

**Compatibility notes:**

Expand Down
Loading