---
title: "Knowledge Packs"
---

> Documentation Index
> Fetch the complete documentation index at: https://truememory-docs.mvstg.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Knowledge Packs

A **knowledge pack** is a document you upload to Truememory — PDF, Markdown, or plain text — so an agent can quote from it when it answers. Truememory parses the file, splits it into chunks, embeds them, and stores them. At request time it pulls the passages most relevant to what the user asked and adds them to the agent's system prompt as a **Relevant Document Context** section.

The point is grounding: instead of hoping the model already knows your handbook, pricing sheet, or SOP, you attach the source and let the agent answer from it. Your app decides which packs apply to a given request; Truememory handles storage, retrieval, and access.

To attach a whole set of packs at once instead of listing IDs on every request, group them under a [brain](/ai-gateway/brains/).

**Think of it like** a shared reference shelf. You put a document on the shelf (upload), tag which agents or skills may pull it down (see [Retrieval](#retrieval)), and the agent quotes the relevant page when it answers.

**Where that metaphor breaks down:** the agent never receives the whole document. Retrieval returns only the highest-scoring chunks for the current question, not the full file.

```mermaid
flowchart TD
    subgraph Ingest["1 · Upload once"]
        direction LR
        U["Upload a file<br/>PDF · Markdown · text"] --> P["Parse → chunk → embed"]
        P --> S["Stored &amp; searchable<br/>status: ready"]
    end
    subgraph Ask["2 · Every chat request"]
        direction LR
        R["POST /v1/chat/completions<br/>skill_id and/or knowledge_pack_ids"] --> E["Embed the user's message"]
        E --> H["Hybrid search<br/>vector + keyword (RRF)"]
        H --> C["Inject top chunks as<br/>Relevant Document Context"]
        C --> A["Grounded answer"]
    end
    S -.->|"only visible, ready packs"| H

    style U fill:#f0f9ff,stroke:#3b82f6,color:#181818
    style P fill:#e6f4ff,stroke:#0a99ff,color:#181818
    style S fill:#ecfdf5,stroke:#10b981,color:#181818
    style R fill:#f0f9ff,stroke:#3b82f6,color:#181818
    style E fill:#e6f4ff,stroke:#0a99ff,color:#181818
    style H fill:#e6f4ff,stroke:#0a99ff,color:#181818
    style C fill:#e6f4ff,stroke:#0a99ff,color:#181818
    style A fill:#ecfdf5,stroke:#10b981,color:#181818
```

All knowledge pack endpoints require authentication. User-scoped endpoints (`/knowledge-packs`) accept either:

- a user Bearer token from [Truememory Auth](/authentication/overview/)
- the calling app's service token plus `X-On-Behalf-Of: <user_id>` when your backend is proxying for a user

Admin endpoints (`/admin/knowledge-packs/*`) require the service token or an admin user token — see [Admin endpoints](#admin-endpoints).

## Retrieval

This is how a pack actually reaches the model. On a `/v1/chat/completions` request, opt in with any of these fields:

| Field | Type | What it does |
|---|---|---|
| `skill_id` | string \| null | Pull packs tagged with this skill — the `id` of a [skill registry](/ai-gateway/skills/) entry (see [Assigning packs to a skill](#assigning-packs-to-a-skill)) |
| `knowledge_pack_ids` | string[] \| null | Pull these specific packs by id |
| `brain_id` | string \| null | Pull every pack in a [brain](/ai-gateway/brains/). Ignored when `knowledge_pack_ids` is also set |
| `brain_ids` | string[] \| null | Pull every pack in several brains, merged and deduplicated. `brain_id` is the singular alias — both are honoured together |

All are optional. `skill_id` and `knowledge_pack_ids` can be combined; `brain_id`/`brain_ids` resolve to a set of pack IDs and are a shorthand for the second. **When all are absent, no document context is injected** — retrieval only runs when you ask for it.

```bash
POST https://your-tie-host/v1/chat/completions
Authorization: Bearer $TOKEN
Content-Type: application/json

{
  "model": "anthropic/claude-sonnet-4-5",
  "messages": [
    { "role": "user", "content": "What's our refund window for annual plans?" }
  ],
  "knowledge_pack_ids": ["7f3c..."]
}
```

Under the hood: Truememory embeds the user's latest message, runs a hybrid search (vector similarity + keyword match, fused with reciprocal rank fusion) over the chunks you're allowed to see, and injects the top results into the system prompt under a **Relevant Document Context** heading. Only packs with status `ready` are searched.

A pack is retrievable on a request only if the caller can see it — see [Visibility](#visibility). `skill_id` and `knowledge_pack_ids` narrow *within* what's visible; they never widen it.

## Uploading a pack

```bash
POST https://your-tie-host/knowledge-packs?skill_id=<uuid>&app_id=vibrantly
Authorization: Bearer $TOKEN
Content-Type: multipart/form-data

file=@handbook.pdf
```

Accepted file types are PDF (`application/pdf`), Markdown (`text/markdown`), and plain text (`text/plain`). The `skill_id` and `app_id` query params are optional — they tag and scope the pack (see [Visibility](#visibility) and [Assigning packs to a skill](#assigning-packs-to-a-skill)).

Ingestion runs in the background, so the response comes back immediately with `status: "pending"`. The status then moves through:

`pending` → `processing` → `ready` (searchable) — or `error` if parsing or embedding fails.

Poll `GET /knowledge-packs` (or the admin detail endpoint) to watch the status. A pack is only returned by [Retrieval](#retrieval) once it reaches `ready`. If ingestion fails, re-upload — there is no automatic retry.

```bash
GET https://your-tie-host/knowledge-packs
Authorization: Bearer $TOKEN
```

Returns the packs the calling user owns — both the ones they uploaded and the ones they've been [granted access to](#ownership-and-entitlements). Delete a pack you own with `DELETE /knowledge-packs/{pack_id}`.

## Visibility

Every pack has three tiers of visibility, derived from `user_id` and `app_id`:

| `user_id` | `app_id` | Who can retrieve it |
|---|---|---|
| set | — | Only that user |
| `null` | `vibrantly` | Every user of that app |
| `null` | `null` | Everyone, every app (global) |

Retrieval always applies this filter first, then narrows by `skill_id` / `knowledge_pack_ids`. A user cannot pull a pack scoped to another user or another app, even if they pass its id.

## Ownership and entitlements

Visibility answers "who is allowed to see this." **Entitlements** answer a different question: "which specific users own this pack." A pack can be a product that's granted (or sold) to many users, and ownership is **cross-app** — a grant made through any app makes the pack show up for that user everywhere they go.

**Think of it like** a per-seat license. The pack is the product; an entitlement is one user's seat, valid across every app on the platform. The pack's own `user_id` still records who *uploaded* it; entitlements are a separate grant layer on top.

The `app_id` on an entitlement records the app the user acquired it through — attribution for billing, nothing more. It does not restrict where the pack appears; a user with a grant sees it from every app.

```bash
# Grant a user access to a pack (idempotent)
POST https://your-tie-host/admin/knowledge-packs/{pack_id}/entitlements
Authorization: Bearer $AUTH_SECRET
Content-Type: application/json

{ "user_id": "1111...", "app_id": "blinklife" }
```

| Endpoint | Behavior |
|---|---|
| `POST /admin/knowledge-packs/{id}/entitlements` | Grant ownership to a user. Re-granting updates the acquiring `app_id` |
| `DELETE /admin/knowledge-packs/{id}/entitlements/{user_id}` | Revoke a user's access |
| `GET /admin/knowledge-packs/{id}/entitlements` | List everyone with access, enriched with each user's name and email |

Truememory stores the grant only — it takes no part in pricing or purchase flow. Your backend confirms a sale (or any access decision) and records it here.

## Managing the document

These admin endpoints let an operator review and revise a pack after upload.

| Endpoint | Behavior |
|---|---|
| `GET /admin/knowledge-packs/{id}` | Fetch a single pack's metadata |
| `GET /admin/knowledge-packs/{id}/content` | The extracted text (reconstructed from the stored chunks), plus `has_original` |
| `PUT /admin/knowledge-packs/{id}/content` | Replace the extracted text and re-embed it in place |
| `PUT /admin/knowledge-packs/{id}/file` | Re-upload a file; re-ingests in place, keeping the same id and its entitlements |
| `GET /admin/knowledge-packs/{id}/file` | Download the original uploaded file |
| `GET /admin/knowledge-packs/{id}/events` | Change log: `uploaded`, `text_edited`, `file_reuploaded` with timestamps |

Editing the text or re-uploading a file wipes the old chunks and re-embeds, so retrieval reflects the change once the pack returns to `ready`. Both keep the same pack id — so entitlements and any `skill_id` tag survive the change.

**Original file availability.** Truememory only retains the raw uploaded file when object storage is configured (`GCS_BUCKET` set). Without it, ingestion keeps the extracted text and embeddings but discards the original — `has_original` is `false` and the download endpoint returns 404. The extracted-text endpoints work either way.

## Assigning packs to a skill

A pack carries an optional `skill_id` tag. Tagging a pack to a skill is what lets a chat request pull it in by passing that `skill_id` (see [Retrieval](#retrieval)). Set or clear the tag without re-uploading:

```bash
PUT https://your-tie-host/admin/knowledge-packs/{pack_id}/skill
Authorization: Bearer $AUTH_SECRET
Content-Type: application/json

{ "skill_id": "9a2b1f6e-1111-2222-3333-444455556666" }
```

The tag is a soft association, not ownership: deleting the skill clears the tag but keeps the document. A pack holds one `skill_id` at a time, so re-assigning moves it.

## Admin endpoints

The `/admin/knowledge-packs/*` block manages packs across all users without the per-user visibility filter. It's gated by the Truememory service token (`AUTH_SECRET`) or an admin user token; end-user tokens cannot reach it. Use it to create global or app-scoped packs, list across users, and run the management and entitlement operations above.

```bash
# Create a global pack (user_id = null) scoped to one app
POST https://your-tie-host/admin/knowledge-packs?app_id=vibrantly
Authorization: Bearer $AUTH_SECRET
Content-Type: multipart/form-data

file=@pricing.md
```

`GET /admin/knowledge-packs` lists across everyone with `user_id`, `app_id`, `skill_id`, and `status` filters plus `limit`/`offset`.

## Common pitfalls

- **Expecting a pack to be used automatically.** It isn't. A request injects document context only when it passes `skill_id`, `knowledge_pack_ids`, `brain_id`, or `brain_ids`. Remember the precedence: explicit `knowledge_pack_ids` override (and disable) the brain fields. Uploading a pack does nothing on its own.
- **Searching a pack that isn't `ready` yet.** Ingestion is asynchronous. A freshly uploaded pack is `pending`/`processing` and is skipped by retrieval until it reaches `ready`.
- **Passing an id you can't see.** `knowledge_pack_ids` narrows within your visibility; it never bypasses it. An id scoped to another user or app returns nothing.
- **Confusing the acquiring `app_id` with a scope.** On an entitlement, `app_id` is attribution only. Ownership is cross-app — the pack shows up for that user from every app regardless.
- **Expecting the original file back without object storage.** When `GCS_BUCKET` is unset, only the extracted text is retained; the original download is unavailable.
- **Deleting a skill to remove its documents.** Deleting a skill only clears the `skill_id` tag on its packs; the packs remain. Delete the packs themselves if that's the intent.

Source: https://truememory-docs.mvstg.com/ai-gateway/knowledge-packs/index.mdx
