# Knowledge

> Manage knowledge entries — observations, references, preferences, and more.

**URL:** https://inference.sh/docs/api/rest/knowledge
**Last updated:** 2026-10-11

---

Manage knowledge entries — observations, references, preferences, and more.

---

## Overview

Knowledge is the general storage layer that underpins skills. While skills have their own dedicated endpoints, knowledge entries of other types (observations, references, preferences) are managed here.

Entries are indexed for hybrid keyword and semantic search. Use search in the web app, or call `GET /suggest` and `GET /search` with `collections=knowledge` (authentication required for private namespace entries).

---

## Discovery

| Endpoint | Use case |
|----------|----------|
| `GET /suggest?q=...` | Autocomplete and unified discovery (apps, skills, knowledge, pages) |
| `GET /search?q=...&collections=knowledge` | Search only knowledge entries with filter control |

Authenticated requests include knowledge in your namespace. Unauthenticated `/suggest` returns public apps and skills only.

→ [Search concept](/docs/concepts/search) · [Search API](/docs/api/rest/search)

Tags on `version.tags` are searchable. After create or update, metadata is indexed immediately; full-content semantic search may lag until embedding completes.

### Scoping

Knowledge versions can carry **scope signals** — short `prefix:value` tags that describe the environment where an entry was captured (repository, language, module, and so on). Scope helps semantic search and [`POST /suggest`](/docs/api/rest/search#suggest) surface knowledge that matches your current project context.

Set `scope` on the version when creating or updating an entry:

```json
{
  "name": "api-patterns",
  "description": "Internal API conventions",
  "type": "observation",
  "version": {
    "content": { "path": "content.md", "content": "# API patterns\n\n..." },
    "scope": ["git:github.com/acme/api", "lang:go", "module:acme/api"]
  }
}
```

| Prefix | Example | Meaning |
|--------|---------|---------|
| `git` | `git:github.com/acme/api` | Normalized git remote origin |
| `module` | `module:acme/api` | Module or package name from `go.mod`, `package.json`, and similar manifests |
| `lang` | `lang:go` | Primary language(s) detected from project marker files |
| `path` | `path:/home/dev/acme/api` | Working directory when the entry was created |
| `host` | `host:build-01` | Machine hostname |

Scope is stored on each version (`version.scope` in API responses) and included in embedding text alongside tags for semantic search indexing. Keyword search still uses name, description, and tags only.

On [`POST /suggest`](/docs/api/rest/search#suggest), the API appends the caller's `scope` array after the `query` (and optional `context`) in the request embedding text so semantic ranking reflects what was asked, not only where the caller was running. That differs from how scope is stored on knowledge versions at index time, but the same tag vocabulary (`git:`, `lang:`, `module:`, and so on) lets stored and caller scope align.

The **belt CLI** auto-collects scope from your current directory on `belt know upload` and `belt suggest`. Pass your own `scope` array when calling the REST API directly.

| Type | Description |
|------|-------------|
| `skill` | Reusable agent instructions (prefer `/skills` endpoints) |
| `concept` | Domain concepts and definitions |
| `observation` | Learned facts or patterns (default on create) |
| `reference` | Pointers to external resources |
| `preference` | User or workspace preferences |
| `person` | People and contacts |
| `project` | Project-scoped context |
| `agent-config` | Agent configuration snippets |

Python callers can use the matching enums from `inferencesh.types` — see [Python SDK — generated types](/docs/api/sdk-python#generated-types-inferenceshtypes).

---

## Lifecycle

Each entry has a `lifecycle` field that describes how the platform treats freshness:

| Value | When to use |
|-------|-------------|
| `permanent` | Default. Stable facts, skills, and references that stay valid until you publish a new version. |
| `decay` | Time-sensitive observations (runbooks, environment facts, integration notes) where confidence should decrease unless revalidated. |

Set `lifecycle` on create or when publishing a new version:

```json
{
  "name": "staging-db-host",
  "description": "Current staging database hostname",
  "type": "observation",
  "lifecycle": "decay",
  "version": {
    "content": {
      "path": "content.md",
      "content": "# Staging DB\n\n`db.staging.example.com`"
    }
  }
}
```

**Version freshness:** For `decay` entries, version objects may include `last_confirmed_at` (RFC 3339) when the platform has recorded a confirmation. Use it in agents and dashboards to show how recently a fact was validated. The field is read-only on API responses — it is not part of the create/update request body.

**Search:** `lifecycle` is indexed on the knowledge collection. Scored `GET /search` results include knowledge `fields` you can filter or display in integrations; `/suggest` still applies its relevance score floor independently of lifecycle.

Most published **skills** stay `permanent`. Use `decay` for agent-captured observations and operational notes that should be revalidated over time.

---

## Version provenance

Each version can record **where content came from** and **which actor produced it**. These fields appear on `version` in API responses and can be set on create or update.

| Field | Description |
|-------|-------------|
| `origin` | Session or extraction provenance. Local [`belt review`](/docs/extend/cli-setup#agent-integration-cli) uses `{harness}:{session-id}` (for example `claude-code:853f9a75-…`). The platform **`belt:extract`** builtin uses `agent:{agent_id}:{chat_id}`, so `belt:suggest` withholds an extraction from the chat it was learned in and offers it to every later chat. |
| `generated_by` | OKF-style actor string: `{harness}/{model}` for coding agents (for example `claude-code/claude-opus-4-6`, `cursor/gpt-5`), or `human:{email}` for manual uploads. |
| `mutation_type` | How the version was produced — for example `agent` when the belt CLI saves an extraction. |

**Local extraction (`belt review`):** The CLI detects the harness from `--agent` / hook context and reads the model from hook stdin when present. `generated_by` is `{harness}/{model}` or `{harness}` when no model is available.

**Platform extraction (`belt:extract`):** When a deployed agent runs the built-in `belt:extract` handler, the platform saves knowledge in the agent owner's registry with `generated_by` set to `inference/{core-app}`. `{core-app}` names the agent's core app: the app's full name (`namespace/name`, for example `infsh/claude`) when the app record is loaded, otherwise the stored core app ref, then the stored core app id, then `unknown`.

Set `origin` and `generated_by` explicitly when calling `POST /knowledge` from your own pipelines:

```json
{
  "name": "retry-budget-resets",
  "description": "Rate limit resets at midnight UTC",
  "type": "observation",
  "version": {
    "content": { "path": "content.md", "content": "# Retry budget\n\n…" },
    "origin": "my-etl:job-42",
    "generated_by": "human:ops@example.com",
    "mutation_type": "agent"
  }
}
```

→ [Coding agents — knowledge capture](/docs/extend/coding-agents#claude-code-plugin) · [CLI — `belt review`](/docs/extend/cli-setup#agent-integration-cli)

---

## Extraction deduplication

[`belt review`](/docs/extend/cli-setup#agent-integration-cli) and the platform **`belt:extract`** builtin run the same extraction prompts. After the model proposes candidates, each one is **deduped** against knowledge you already have before anything is saved.

1. **Find overlaps** — The candidate name and content are searched against the knowledge collection (up to five hits, same ranking as [`POST /suggest`](/docs/api/rest/search#suggest) with `category: knowledge`).
2. **Decide** — When similar entries exist, a second structured step chooses an action:

| Action | What gets saved |
|--------|-----------------|
| `create` | A new entry under the candidate's slug `name` |
| `update` | A new version of an existing entry; `target` must be one of the listed `namespace/name` refs |
| `skip` | Nothing (candidate already covered) |

If search finds no similar entries, the candidate is saved as **create** without the second step.

**`target` must be a listed ref.** Overlapping entries are shown as `namespace/name` lines in the dedup prompt. Structured output constrains `target` to that list (an enum of the refs), so the model cannot substitute free-text commentary where a ref belongs. If an **update** still names a target that does not match any listed ref, the run falls back to **create** under the candidate's own slug — prose is never saved as a new entry name.

**Namespace on update:** Choosing `update` with a target in another namespace still creates or versions an entry under **your** namespace using only the target's name slug; extractions never write into someone else's registry.

---

## Project scope

Each version can carry a `scope` array — environment signals that tie an entry to a project or workspace. `/suggest` compares request `scope` tags with version `scope` to boost project-relevant knowledge in discovery results (overlap ranking). Entries without `scope` are global and appear everywhere.

Tags use `prefix:value` format:

| Prefix | Example | Meaning |
|--------|---------|---------|
| `git` | `git:github.com/inference-sh/cli` | Normalized git remote origin |
| `module` | `module:inference.sh/cli` | Module or package name from `go.mod`, `package.json`, `Cargo.toml`, or `pyproject.toml` |
| `lang` | `lang:go` | Primary language detected from marker files |
| `path` | `path:/home/user/project` | Directory where the entry was created (machine-specific) |
| `host` | `host:my-laptop` | Hostname where the entry was created |

`belt know upload` auto-collects scope from the current working directory unless you pass `--global`. `belt suggest` sends the same signals on every suggest call. `belt plugin review` attaches scope when saving knowledge from agent sessions.

Omit `scope` (or pass an empty array) for entries that should apply across all projects.

---

## List Knowledge

`GET /knowledge` or `POST /knowledge/list`

Returns knowledge entries with cursor-based pagination. Requires authentication.

### Request

| Field | Type | Description |
|-------|------|-------------|
| `limit` | number | Page size (default 50, max 100) |
| `cursor` | string | Cursor from a previous response |
| `direction` | string | `"next"` (default) or `"prev"` |
| `filters` | array | `{ "field", "operator", "value" }` |
| `sort` | array | `{ "field", "dir" }` |
| `search` | object | `{ "term", "fields" }` for substring match |

Common filter fields: `namespace`, `name`, `type`, `lifecycle`, `visibility`, `created_at`, `updated_at`. Unknown filter or sort columns return **400**. See [Cursor pagination](/docs/api/rest/overview#cursor-pagination).

**Text search:** use `search.fields` such as `name`, `description`, `namespace`, or `type`. Fields not on the knowledge model are ignored.

**Example — observations only:**

```bash
curl -X POST https://api.inference.sh/knowledge/list \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "limit": 20,
    "filters": [{ "field": "type", "operator": "eq", "value": "observation" }]
  }'
```

### Response

Response:

```json
{
  "items": [
    {
      "id": "know_abc123",
      "namespace": "myteam",
      "name": "api-rate-limits",
      "description": "Known rate limits for external APIs we integrate with",
      "type": "reference",
      "version_id": "ver_xyz789",
      "version": {
        "id": "ver_xyz789",
        "content": { "path": "content.md", "uri": "https://..." },
        "files": [],
        "content_hash": "a3f5c9e2b1d4...",
        "tags": ["api", "infrastructure"],
        "scope": ["git:github.com/myteam/api", "lang:go"],
        "metadata": {}
      },
      "visibility": "private",
      "created_at": "2026-01-15T10:30:00Z"
    }
  ],
  "next_cursor": "abc123",
  "has_next": true,
  "has_previous": false
}
```

---

## Get Knowledge

`GET /knowledge/{id}`

Returns a single knowledge entry by ID.

---

## Get Knowledge by Ref

`GET /knowledge/{namespace}/{name}`

Returns the latest version for a namespace and name (same ref format as `belt know get`).

`GET /knowledge/{namespace}/{name}/versions/{versionId}`

Returns a specific version. `versionId` is the short version id (the segment after `@` in refs like `acme/onboarding@abc123`).

```bash
curl https://api.inference.sh/knowledge/acme/onboarding \
  -H "Authorization: Bearer inf_your_key"

curl https://api.inference.sh/knowledge/acme/onboarding/versions/abc123 \
  -H "Authorization: Bearer inf_your_key"
```

Requires read access to the entry. Permission failures return `404` or `403`, consistent with other knowledge routes.

---

## Content hash and conditional GET

Each `version` object includes a `content_hash` — a deterministic hash over the main content and supporting files. The hash changes when instructions or file payloads change, even if the version id stays the same.

Use `content_hash` to detect unchanged versions in sync jobs, skip redundant downloads, or compare entries across namespaces.

### ETag caching

These read endpoints set an `ETag` response header from `version.content_hash` and honor `If-None-Match`:

| Endpoint | Behavior |
|----------|----------|
| `GET /knowledge/{namespace}/{name}` | Latest version by ref |
| `GET /knowledge/{namespace}/{name}/versions/{versionId}` | Specific version by ref |
| `GET /knowledge/{id}/versions/{versionId}` | Specific version by id |

When `If-None-Match` matches the current hash, the API returns **304 Not Modified** with an empty body. Responses also include `Cache-Control: no-cache` — clients should revalidate on each request rather than treat the entry as immutable.

**Example:**

```bash
# First fetch — note the ETag header
curl -i https://api.inference.sh/knowledge/acme/onboarding \
  -H "Authorization: Bearer inf_your_key"

# Conditional retry — 304 when content is unchanged
curl -i https://api.inference.sh/knowledge/acme/onboarding \
  -H "Authorization: Bearer inf_your_key" \
  -H 'If-None-Match: "a3f5c9e2b1d4..."'
```

The platform also uses `content_hash` internally for duplicate detection in lineage graphs and for collapsing identical skills in `/suggest` results.

---

## Get References

`GET /knowledge/{id}/references`

Returns content reference edges for a knowledge entry. Edges are created when version content is saved (mentions of other resources in markdown).

### Response

```json
{
  "resource": {
    "id": "know_abc123",
    "namespace": "acme",
    "name": "onboarding",
    "type": "knowledge",
    "resource_kind": "skill",
    "description": "Onboarding checklist"
  },
  "references": [
    {
      "id": "app_xyz789",
      "namespace": "acme",
      "name": "deploy-helper",
      "type": "app",
      "resource_kind": "",
      "description": "Deployment helper app"
    }
  ],
  "referenced_by": [
    {
      "id": "know_def456",
      "namespace": "acme",
      "name": "runbook",
      "type": "knowledge",
      "resource_kind": "observation",
      "description": "Production runbook"
    }
  ]
}
```

| Field | Meaning |
|-------|---------|
| `resource` | The entry you queried |
| `references` | **Outgoing** — knowledge, app, or agent resources mentioned in this entry's content |
| `referenced_by` | **Incoming** — resources whose content mentions this entry |

Each `ResourceRef` includes `id`, `namespace`, `name`, `type` (`knowledge`, `app`, or `agent`), `resource_kind` (e.g. `skill`, `observation` for knowledge entries), and `description`.

Requires read access to the entry.

---

## Get Versions

`GET /knowledge/{id}/versions` or `POST /knowledge/{id}/versions/list`

Returns version history for a knowledge entry.

---

## Get Lineage

`GET /knowledge/{id}/lineage`

Returns fork and version relationships: parents, siblings, forks, and duplicates.

```json
{
  "skill": {
    "id": "know_abc123",
    "namespace": "myteam",
    "name": "api-rate-limits",
    "version_count": 2
  },
  "parents": [],
  "siblings": [],
  "forks": [],
  "duplicates": [],
  "fork_depth": 0
}
```

| Field | Meaning |
|-------|---------|
| `skill` | The entry you queried |
| `parents` | Entries this one was forked or derived from |
| `siblings` | Other entries sharing the same parent |
| `forks` | Entries forked from this one |
| `duplicates` | Detected duplicate entries |
| `fork_depth` | How many fork generations from the root parent |

Skills expose the same endpoint: `GET /skills/{id}/lineage`.

Requires read access to the entry.

---

## Create Knowledge

`POST /knowledge`

Requires authentication.

### Request

```json
{
  "name": "api-rate-limits",
  "description": "Known rate limits for external APIs",
  "type": "reference",
  "version": {
    "content": { "path": "content.md", "content": "# API Rate Limits\n\n..." },
    "tags": ["api", "infrastructure"],
    "metadata": { "source": "manual" }
  }
}
```

| Field | Required | Description |
|-------|----------|-------------|
| `name` | yes | Kebab-case, immutable after creation |
| `description` | yes | What this knowledge contains (max **200** characters for `reference`, `observation`, and other non-skill types; skills use the [Skills API](/docs/api/rest/skills) limit of **1024**) |
| `type` | no | Entry type — one of the values in the table above (default: `observation`) |
| `lifecycle` | no | `permanent` (default) or `decay` |
| `version.content` | yes | Content with path and text |
| `version.tags` | no | Searchable tags |
| `version.scope` | no | Environment signals for project scoping (see [Scoping](#scoping)) |
| `version.metadata` | no | Arbitrary key-value pairs |
| `version.origin` | no | Provenance string (see [Version provenance](#version-provenance)) |
| `version.generated_by` | no | OKF actor string (see [Version provenance](#version-provenance)) |
| `version.mutation_type` | no | How the version was produced (for example `agent`) |

### Response

Returns the created `KnowledgeDTO`.

---

## Update Knowledge

`POST /knowledge/{id}`

Creates a new version. Same request body as create.

---

## Delete Knowledge

`DELETE /knowledge/{id}`

Soft-deletes the entry.

**CLI:** `belt know delete <id or namespace/name>` (alias `belt knowledge delete`) requires `belt login`. The CLI resolves `namespace/name` via `GET /knowledge/{namespace}/{name}`, then calls this endpoint with the entry ID. Destructive: prompts unless `--yes` / `-y`. Skill-type entries can also be removed with `belt skill delete` (same resolution and backend delete).

---

## Visibility

`POST /knowledge/{id}/visibility`

```json
{
  "visibility": "public"
}
```

---

## Transfer Ownership

`POST /knowledge/{id}/transfer`

```json
{
  "team_id": "team_xyz789"
}
```

---

## JavaScript SDK

```typescript
import { inference } from '@inferencesh/sdk';

const client = inference({ apiKey: 'inf_your_key' });

const { items } = await client.knowledge.list({ limit: 50 });
const entry = await client.knowledge.getByName('myteam', 'deployment-order');

await client.knowledge.create({
  name: 'deployment-order',
  description: 'Services must deploy in dependency order',
  type: 'observation',
  version: {
    content: {
      path: 'content.md',
      content: '# Deployment Order\n\nDeploy common-go before api.',
    },
  },
});

await client.knowledge.update('know_abc123', { description: 'Updated notes' });
await client.knowledge.delete('know_abc123');
```

→ [JavaScript SDK](/docs/api/sdk-javascript) · [SDK overview](/docs/api/sdk/overview)

---

## cURL Examples

```bash
# List knowledge entries
curl https://api.inference.sh/knowledge \
  -H "Authorization: Bearer inf_your_key"

# Create an observation
curl -X POST https://api.inference.sh/knowledge \
  -H "Authorization: Bearer inf_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "deployment-order",
    "description": "Services must be deployed in dependency order",
    "type": "observation",
    "version": {
      "content": {
        "path": "content.md",
        "content": "# Deployment Order\n\nAlways deploy common-go before api."
      }
    }
  }'

# Delete
curl -X DELETE https://api.inference.sh/knowledge/know_abc123 \
  -H "Authorization: Bearer inf_your_key"
```
