# SDK

> Import GNO directly into a Bun or TypeScript app with createGnoClient and the same local retrieval engine.

Section: Reference  
Canonical: https://gno.sh/docs/sdk  
Markdown: https://gno.sh/docs/sdk.md

The SDK is for apps that want GNO in-process. Use it when a local Bun or TypeScript app needs search, retrieval, document access, indexing, or graph navigation without shelling out to `gno` or running `gno serve`.

## What this is

- Same retrieval core as CLI, Web UI, API, and MCP.
- Direct control over config, database path, download policy, and lifecycle.
- Best for local apps, research tooling, desktop shells, and private automation where HTTP is unnecessary.

## Install

```
bun add @gmickel/gno
```

## Basic usage

```
import { createDefaultConfig, createGnoClient } from "@gmickel/gno"

const config = createDefaultConfig()
config.collections = [{
  name: "notes",
  path: "/Users/me/notes",
  pattern: "**/*",
  include: [],
  exclude: [],
}]

const client = await createGnoClient({
  config,
  dbPath: "/tmp/gno-sdk.sqlite",
})

try {
  await client.index({ noEmbed: true })
  const results = await client.search("authentication")
  for (const hit of results.results) {
    console.log(hit.uri, hit.score)
  }
} finally {
  await client.close()
}
```

The client runs indexing and search in your process. Local models run in a helper process that GNO starts and stops for you. Always close the client in `finally`; if you create an adapter yourself, call `LlmAdapter.dispose()` when done. Loaded models stay warm for five minutes of inactivity by default, and the next inference after that reloads them. Metadata calls do not keep them warm. GNO does not retry a failed local model call on its own; call again to retry.

Without `dbPath`, the client opens the normal per-index location. If your config omits collections that location already holds, `createGnoClient` fails with an error naming them and changes nothing, so give a test or sandbox config its own `dbPath`. A long-running client refuses calls the same way if another client later indexes a collection its config omits.

## Cancellation and deadlines

`search`, `vsearch`, `query`, and `ask` accept `signal?: AbortSignal` and `deadlineAt?: number` in their options. The deadline is an absolute Unix epoch time in milliseconds spanning admission, queueing, loading, evaluation, and publication. Nested retrieval and verification cannot relax it. Caller abort uses `INFERENCE_FAILED` with an `AbortError` cause; expiry uses `TIMEOUT` with a `TimeoutError` cause. An aborted or expired call never returns a late result. Model work that was already running keeps its capacity until it finishes.

Supported owner filters apply before candidate budgets, including matching-language chunks for vector/hybrid retrieval. Short or empty results are valid. Hybrid fallback diagnostics distinguish unavailable semantic work from successful vector retrieval with no matches. Ask keeps its existing evidence, context, and generation budgets.

Local embedding checks the model file, dimensions, context and truncation settings, and runtime before it embeds. HTTP and custom embedding ports may not report a verified model identity. GNO does not reuse stored vectors under an unverified or different identity.

## Client APIs

- `client.search(query, opts?)` — BM25 keyword search for exact words, phrases, filenames, and identifiers.
- `client.vsearch(query, opts?)` — vector similarity for conceptual matches.
- `client.query(query, opts?)` — hybrid retrieval with expansion, default bounded graph candidates, fusion, and reranking controls. Pass `{ graph: false }` to skip graph expansion or `{ explain: true }` for a retrieval scoring receipt.
- `client.ask(question, opts?)` — retrieval-only or cited answer generation. Pass `{ verify: true }` for the closed-Capsule claim-verification path and `{ explain: true }` for the same retrieval scoring receipt.
- `client.get(ref)` and `client.multiGet(refs)` — fetch indexed documents by URI, doc ID, or path.
- `client.getSections(ref)` — heading outline for a document. Readable anchors stay compatible with the Web UI.
- `client.createSectionTarget(ref, selector)` and `client.resolveSectionTarget(ref, target)` — durable section locators with the same fail-closed citation semantics as REST and MCP. Prefer ordinary get/search for default retrieval.
- `client.links(ref)`, `client.backlinks(ref)`, `client.similar(ref)` — navigate direct and semantic relationships.
- `client.list()` and `client.status()` — inspect document inventory and index health. A collection whose folder is missing or unreadable right now carries `rootError` (`{ code, message }`) and makes `healthy` false; its documents stay indexed and searchable.
- `client.listMetadata({ collection, values })` — typed metadata keys, types, document counts and common values.
- `client.capture(input)` — write a note with provenance and an optional `presetId`, then return the same capture receipt shape as CLI, API, and MCP. Content is text-only, collision policies are runtime-validated, legacy `overwrite` is not accepted, and writes fail on late-arriving files. A lexical sync failure still resolves with `sync.status: "failed"` when no `requestId` is sent.
- `client.requestStatus(requestId)` — look up a `requestId` sent with `client.capture()` or `client.remember()` before retrying it. See [Retry-safe writes](https://gno.sh/docs/retry-safe-writes).
- `client.previewRenameNote()` / `client.previewMoveNote()`, followed by `client.renameNote()` / `client.moveNote()` — inspect a canonical impact plan, then apply only its exact version, digest, and confirmation. Supported reference edits and the source move share one all-or-rollback filesystem boundary.
- `client.update()`, `client.embed()`, `client.index()` — sync files, embed chunks, or do both.
- `client.close()` — release file handles and SQLite connections.

`search`, `vsearch`, and `query` accept `collections` to search several collections at once, filtered before the result limit. Names are trimmed, lowercased, de-duplicated, and sorted, and the result lists them in `meta.collections`. Passing both `collection` and `collections`, an empty list, more than 256 names, or a name that is not configured throws a `GnoSdkError` with code `VALIDATION`. `ask` does not take `collections`.

```
const scoped = await client.query("rollout plan", {
  collections: ["work", "ops"],
})
scoped.meta.collections // ["ops", "work"]
```

`convertFile(path)` converts one file without a client, config, or index. It returns the same object as `gno convert --json` and throws a `GnoSdkError` with the reason in `details.reason`.

```
import { convertFile } from "@gmickel/gno"

const { markdown, mirrorHash } = await convertFile("report.pdf")
```

Retrieval options may include up to 16 `projectHints`. SDK hints are opaque and untrusted, are never resolved or reflected, never trigger filesystem access, and currently have zero ranking effect. Leaving the option absent preserves existing results exactly.

Optional `chunking` in the client config sets index-wide `maxTokens` and `overlapPercent`. Update/index apply it to cached mirrors, and `(await client.status()).chunking` reports configured/applied parameters and pending layout counts. Reopen the client after `CHUNKING_POLICY_CONFLICT`. See [chunking configuration](https://gno.sh/docs/configuration#chunking) for defaults and separate-index comparisons.

```
const verified = await client.ask("Who owns the launch decision?", {
  verify: true,
  contextBudgetTokens: 12_000,
})

if (verified.verification?.claims.answerStatus === "abstained") {
  console.log(verified.verification.claims.abstentionReason)
}
```

Verified SDK results use the same contract as CLI, REST, MCP, and Web: the closed Capsule and freshness receipt, exact evidence IDs, lines, and hashes, four-state claim verdicts, 100% support threshold, and explicit semantic-verifier degradation. Raw `client.ask()` behavior remains compatible when `verify` is not true.

Indexed URIs such as `gno://notes/plan.md?index=research` select and read that named database, even when the client was created for another index. Missing indexes fail without being created. Keep each `multiGet()` batch on one index.

When private retrieval tracing is enabled, search, query, ask, get, and Context Capsule results carry a non-enumerable `RETRIEVAL_TRACE_METADATA` symbol. It exposes the local receipt ID without changing JSON output. Read it with `getRetrievalTraceMetadata(result)`, then pass `traceId` to `client.get()` to record the exact opened line range against an open query receipt.

```
const receipt = await client.capture({
  collection: "notes",
  title: "Customer call",
  content: "Follow up on renewal timeline.",
  presetId: "meeting",
  tags: ["customer", "follow-up"],
  source: {
    kind: "meeting",
    title: "Customer call",
  },
})

console.log(receipt.uri, receipt.contentHash)
```

```
const plan = await client.previewRenameNote({
  ref: "gno://notes/old-note.md",
  name: "new-note.md",
})

if (plan.canApply) {
  const result = await client.renameNote({
    ref: "gno://notes/old-note.md",
    name: "new-note.md",
    schemaVersion: plan.schemaVersion,
    planDigest: plan.planDigest,
    confirmation: "apply",
  })
  console.log(result.status)
}
```

## Configuration patterns

Define collections in code when your app owns the workspace, or point at an existing GNO config when the user already has one.

```
const fromExisting = await createGnoClient({
  configPath: "/Users/me/Library/Application Support/gno/config/index.yml",
})

const noDownloads = await createGnoClient({
  config,
  downloadPolicy: { offline: false, allowDownload: false },
})
```

## Lifecycle

Always call `await client.close()` when your process is shutting down. After close, further calls throw a GNO SDK error. This matters for desktop apps, tests, and short-lived automations because the SDK holds SQLite connections and file handles.

## Typed metadata filters

Retrieval options accept filter using the exported MetadataPredicate type. For example: client.search("rollout", { filter: { op: "gte", key: "confidence", value: 0.8 } }). Scope and authority still constrain eligible documents.

[Read the metadata guide: examples, operators, missing fields, limits, and recovery.](https://gno.sh/docs/typed-metadata)

## Agent session archive

Open a client with the archive config and index, createGnoClient({ configPath, indexName: "sessions" }), then call sessionsStatus(), discoverSessions(), or importSessions({ sourceId }) / importSessions({ paths, collection, format? }) with optional dryRun and limit. Import returns the shared receipt. runSessionsAutomation({ profileId }) runs a configured automation profile now and returns the shared automation run result; status.automation reports daemon state and per-profile hook and schedule status. Switch hooks and schedules on with gno sessions automation enable or the Web UI on the same host; the SDK can only run a configured profile. Sessions errors are GnoSdkError VALIDATION or RUNTIME with the specific code in details.code. [See the session error codes](https://gno.sh/docs/sessions#error-codes).
