# Knowledge Bases

The **Knowledge Base** (KB) is the RAG system: upload documents → chunk
→ embed (pgvector / app-level cosine for SQLite) → search via
`kb_search` tool during completions.

### KB CRUD

```
GET    /knowledge-bases             → { "data": [KB objects], "total": N }
POST   /knowledge-bases             ← { "name", "description", "scope", "team_id" }
GET    /knowledge-bases/:id         → KB object
PUT    /knowledge-bases/:id         ← partial update { "name", "description" }
DELETE /knowledge-bases/:id
```

**Auth:** All endpoints require a valid JWT. `POST` additionally requires
the `kb.create` permission (checked via `RequirePermission` middleware).

**Scope rules for `POST`:**

| Scope | Requirement |
|-------|-------------|
| `personal` (default) | Any authenticated user with `kb.create` |
| `team` | `team_id` required; caller must be team admin (or system admin) |
| `global` | Caller must be system admin |

KB object (as returned by CRUD endpoints):

```json
{
  "id": "uuid",
  "name": "Product Docs",
  "description": "Internal product documentation",
  "scope": "personal|team|global",
  "owner_id": "uuid|null",
  "team_id": "uuid|null",
  "embedding_config": {},
  "document_count": 12,
  "chunk_count": 48,
  "total_bytes": 245760,
  "status": "active|processing|error",
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T10:30:00Z"
}
```

> **Note:** The `discoverable` field is stored in the DB and present on
> the raw model, but is **not** included in the `kbResponse` struct
> returned by CRUD endpoints. It is managed via the dedicated
> discoverable endpoints below.

### Documents

**Upload:**

```
POST /knowledge-bases/:id/documents
Content-Type: multipart/form-data
```

**Auth:** Requires `kb.write` permission (checked via `RequirePermission`
middleware). Also requires a configured embedding model role — returns
`412 Precondition Failed` if missing.

Field: `file` (max 10 MB). Currently supported types: **TXT, MD, CSV,
HTML**. Binary formats (PDF, DOCX, XLSX, PPTX) are planned but require
an extraction sidecar that is not yet wired in.

Returns the document object with HTTP `202 Accepted`:

```json
{
  "id": "uuid",
  "kb_id": "uuid",
  "filename": "guide.md",
  "content_type": "text/markdown",
  "size_bytes": 12345,
  "chunk_count": 0,
  "status": "pending",
  "error": null,
  "uploaded_by": "uuid",
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T10:30:00Z"
}
```

**List:**

```
GET /knowledge-bases/:id/documents
```

Returns `{ "data": [document objects], "total": N }`.

**Status** (poll during processing):

```
GET /knowledge-bases/:id/documents/:docId/status
```

Returns a subset of the document object:

```json
{
  "id": "uuid",
  "status": "chunking",
  "chunk_count": 0,
  "error": null,
  "filename": "guide.md"
}
```

Status progression: `pending → extracting → chunking → embedding → ready | error`.

**Delete:**

```
DELETE /knowledge-bases/:id/documents/:docId
```

Deletes the document, its chunks, and the stored file. Returns
`{ "deleted": true }`.

### Search

```
POST /knowledge-bases/:id/search
```

```json
{
  "query": "How do I configure SSO?",
  "limit": 5,
  "threshold": 0.3
}
```

`limit` defaults to 5, max 20. `threshold` is the minimum cosine
similarity score (defaults to 0.3).

Returns `{ "data": [...], "query": "...", "total": N }`:

```json
{
  "data": [
    {
      "content": "To configure SSO, navigate to...",
      "source": "admin-guide.md",
      "kb": "Product Docs",
      "similarity": 0.87,
      "metadata": {}
    }
  ],
  "query": "How do I configure SSO?",
  "total": 1
}
```

### Rebuild

Re-chunks and re-embeds all documents. Skips documents whose extracted
text is missing (would need the original file from object store).

```
POST /knowledge-bases/:id/rebuild
```

Returns `202 Accepted`:

```json
{
  "message": "rebuild started",
  "total_docs": 12,
  "rebuilding": 10,
  "skipped": 2
}
```

Returns `503` if the ingestion pipeline is not configured, or `400` if
the KB has no documents.

### Channel KB Bindings

A channel can have KBs explicitly bound to it (in addition to any KBs
coming from the active Persona or parent Project).

**Get:**

```
GET /channels/:id/knowledge-bases
```

Returns `{ "data": [ChannelKB objects] }`:

```json
{
  "kb_id": "uuid",
  "kb_name": "Product Docs",
  "enabled": true,
  "document_count": 12
}
```

**Set** (replace all):

```
PUT /channels/:id/knowledge-bases
```

```json
{ "kb_ids": ["kb-uuid-1", "kb-uuid-2"] }
```

Validates that the caller has access to every referenced KB before
setting the bindings.

### Discoverable KBs

The `discoverable` flag controls whether a KB appears in the user's KB
picker. Non-discoverable KBs are hidden from direct access but still
searchable through Persona bindings (enterprise KB mode).

**List discoverable** (for KB picker UI):

```
GET /knowledge-bases-discoverable
```

Returns `{ "data": [KB objects] }` — only KBs the user can see
(personal + team + discoverable global). Uses the same `kbResponse`
format as the CRUD endpoints.

**Toggle discoverability:**

```
PUT /knowledge-bases/:id/discoverable
```

```json
{ "discoverable": false }
```

**Auth:** Requires `loadAndAuthorize` (KB must exist, caller must have
basic access). Additionally, only the KB **owner** or a **system admin**
may change the flag — all other callers receive `403 Forbidden`.

### KB Resolution Chain

When a completion fires, KBs are resolved in priority order:

```
Persona KBs → Project KBs → Channel KBs → Personal KBs
```

The `BuildKBHint` function assembles all applicable KBs into the
system prompt, and the `kb_search` tool searches across all of them.
The `kb_search` tool additionally resolves project-bound KBs and
personal KBs at search time, so all user-accessible KBs are
searchable even if not explicitly listed in the hint.

### Persona KB Bindings

Managed via the Persona endpoints — see [personas.md](personas.md).
Persona-KB binding is the primary enterprise mechanism for controlled
KB access. The `auto_search` flag on persona KB bindings controls
whether top-K results are prepended to context automatically (planned,
see v0.28.4) vs available only through the `kb_search` tool (current
behavior).

---
