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

> ## Agent Instructions
> AGENTS.md is HydraDB's maintained agent guide. Read https://docs.hydradb.com/AGENTS.md once, then use its content. /llms.txt and /llms-full.txt redirect to the same guide; they are not separate documentation indexes. If the guide is already loaded, continue with it instead of fetching either alias or reloading the guide.

# Context

> Knowledge and memories: the two types of context HydraDB stores, how to ingest each, and how they work together.

Context is everything you give HydraDB so your AI can find it later. It comes in two types, and most applications use both:

| | Knowledge | Memories |
| - | - | - |
| What it is | What your organization has written down | What your agent has learned about a user |
| Examples | Product docs, policy PDFs, runbooks, Slack threads, tickets, emails | Preferences, decisions, things already tried, past conversations |
| Usually shared by | Everyone who may read it | One user |
| Ingest with | `type=knowledge` | `type=memory` |
| What you send | Files, or records from an app (**app sources**) | Text or a conversation |
| Changes when | The document changes | The user says or does something new |

The two types work the same way. Both go to [`POST /context/ingest`](https://docs.hydradb.com/api-reference/v2/endpoint/ingest-context), both are saved in a [collection](https://docs.hydradb.com/essentials/v2/databases-and-collections.md), both are indexed in the background, and both come back from [`POST /query`](https://docs.hydradb.com/api-reference/v2/endpoint/query). Each item you ingest is a **source**; HydraDB splits it into searchable passages called **chunks** and builds a context graph from it.

They are stored separately, so a query chooses what to read: `type: "knowledge"`, `type: "memory"`, or `type: "all"` for both in one ranked list. A support agent, for example, answers from the help center (knowledge) and from the fact that this customer already tried restarting (a memory).

## 1. Ingest knowledge

There are three ways to ingest knowledge:

* **Files:** upload PDF, DOCX, Markdown, CSV, or TXT files, and HydraDB reads them for you.
* **App sources:** send records your application already holds from an app, such as a Slack message or a Jira ticket.
* **[Connectors](https://docs.hydradb.com/essentials/v2/connectors.md):** connect an app such as Slack, Google Drive, Notion, or GitHub once, and HydraDB keeps it in sync.

### Upload files

The request is **multipart/form-data** with these fields:

| Form field | What to send |
| - | - |
| `type` | `knowledge` |
| `database` | An existing [database](https://docs.hydradb.com/api-reference/v2/endpoint/create-database.md) |
| `collection` | Optional. Leave it out to use the database's default collection. A query that names another collection does not read the default one. |
| `documents` | One or more files |
| `document_metadata` | One item per file, in the same order, as a JSON string. Optional, but this is where a file's `id` and `metadata` go. |
| `upsert` | Leave it out. It defaults to `true`, so a file sent again with the same `id` replaces the saved one. |

> [accordion] Upload files: Python, TypeScript, or cURL
>
> Set up your [SDK client](https://docs.hydradb.com/api-reference/v2/sdks.md#client-setup) before running the Python or TypeScript example.
>
> ```python Python SDK theme={"dark"}
> import json
>
> with open("/path/to/policy.pdf", "rb") as f1, open("/path/to/runbook.pdf", "rb") as f2:
>     result = client.context.ingest(
>         type="knowledge",
>         database="acme_corp",
>         documents=[
>             ("policy.pdf", f1, "application/pdf"),
>             ("runbook.pdf", f2, "application/pdf"),
>         ],
>         document_metadata=json.dumps([
>             {"id": "policy_v2", "metadata": {"document_type": "policy", "status": "approved"}},
>             {"id": "runbook_deploy", "metadata": {"document_type": "runbook"}},
>         ]),
>     )
> ```
>
> ```typescript TypeScript SDK theme={"dark"}
> const result = await client.context.ingest({
>   type: "knowledge",
>   database: "acme_corp",
>   documents: [
>     { path: "/path/to/policy.pdf", filename: "policy.pdf", contentType: "application/pdf" },
>     { path: "/path/to/runbook.pdf", filename: "runbook.pdf", contentType: "application/pdf" },
>   ],
>   documentMetadata: JSON.stringify([
>     { id: "policy_v2", metadata: { document_type: "policy", status: "approved" } },
>     { id: "runbook_deploy", metadata: { document_type: "runbook" } },
>   ]),
> });
> ```
>
> ```bash cURL theme={"dark"}
> curl -X POST 'https://api.hydradb.com/context/ingest' \
>   -H "Authorization: Bearer $HYDRADB_API_KEY" \
>   -H "API-Version: 2" \
>   -F "type=knowledge" \
>   -F "database=acme_corp" \
>   -F "documents=@/path/to/policy.pdf" \
>   -F "documents=@/path/to/runbook.pdf" \
>   -F 'document_metadata=[
>     { "id": "policy_v2", "metadata": { "document_type": "policy", "status": "approved" } },
>     { "id": "runbook_deploy", "metadata": { "document_type": "runbook" } }
>   ]'
> ```
>
> The response lists each file in `data.results` with its `id` and a `status` of `queued`. The payload is wrapped in the standard envelope, so read it from `response.data`:
>
> ```json theme={"dark"}
> {
>   "success": true,
>   "data": {
>     "success": true,
>     "message": "Knowledge uploaded successfully",
>     "results": [
>       { "id": "policy_v2", "filename": "policy.pdf", "status": "queued", "error": null },
>       { "id": "runbook_deploy", "filename": "runbook.pdf", "status": "queued", "error": null }
>     ],
>     "success_count": 2,
>     "failed_count": 0
>   },
>   "error": null,
>   "meta": {
>     "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
>     "latency_ms": 12.3
>   }
> }
> ```

Each `document_metadata` item takes these properties:

| Property | What it is |
| - | - |
| `id` | Your stable ID for the file, and the upsert key. Send the same `id` later to replace the file, or use it to check status and delete. Leave it out to have one generated; the response returns it either way. |
| `metadata` | Values you filter on at query time, such as `document_type`. Declare the keys in the database's [metadata schema](https://docs.hydradb.com/essentials/v2/metadata.md). Up to 16 KiB as compact JSON. |
| `additional_metadata` | Free-form values for display and bookkeeping. Filterable only when nested under `additional_metadata` in `metadata_filters`. Up to 1 KiB as compact JSON. |
| `relations` | Other sources this file belongs with, as `{ "ids": [...] }`. See [Link documents that belong together](#6-link-sources-that-belong-together). |

The key list is closed. Any other key, including `title`, `type`, `url`, and `timestamp`, returns `400`. [Ingest Context](https://docs.hydradb.com/api-reference/v2/endpoint/ingest-context.md#supported-file-formats) lists the supported file formats.

### Send app sources

When your application already holds the text and fields of a Slack message, a Jira ticket, a Notion page, or a CRM record, send it as an **app source** instead of a file. HydraDB keeps the app's structure: the author, the thread, the ticket's status, and the links between records.

The request goes to the same endpoint, with `app_knowledge` in place of `documents`. One request can carry both. `kind` says what the record is (`email`, `message`, `ticket`, `knowledge_base`, `comment`, `meeting`, or `custom`), and `fields` carries its content. An app source can also carry an `acl`, the list of principals allowed to retrieve it. The [field reference](#field-reference) lists the keys each kind takes in `fields`.

> [accordion] Ingest a Slack message: Python, TypeScript, or cURL
>
> ```python Python SDK theme={"dark"}
> import json
>
> result = client.context.ingest(
>     type="knowledge",
>     database="acme_corp",
>     app_knowledge=json.dumps([
>         {
>             "id": "slack_C01_1716213600_000100",
>             "title": "Pricing discussion - Slack #product",
>             "kind": "message",
>             "provider": "slack",
>             "external_id": "1716213600.000100",
>             "fields": {
>                 "kind": "message",
>                 "body": "We agreed on three tiers: Starter at $29, Pro at $79, Enterprise at $199.",
>                 "author": "alice",
>                 "thread_id": "1716213600.000100",
>                 "created_at": "2026-05-20T10:00:00Z",
>             },
>             "metadata": {"channel": "product", "status": "finalized"},
>             "additional_metadata": {"slack_ts": "1716213600.000100"},
>         }
>     ]),
> )
> ```
>
> ```typescript TypeScript SDK theme={"dark"}
> const result = await client.context.ingest({
>   type: "knowledge",
>   database: "acme_corp",
>   appKnowledge: JSON.stringify([
>     {
>       id: "slack_C01_1716213600_000100",
>       title: "Pricing discussion - Slack #product",
>       kind: "message",
>       provider: "slack",
>       external_id: "1716213600.000100",
>       fields: {
>         kind: "message",
>         body: "We agreed on three tiers: Starter at $29, Pro at $79, Enterprise at $199.",
>         author: "alice",
>         thread_id: "1716213600.000100",
>         created_at: "2026-05-20T10:00:00Z",
>       },
>       metadata: { channel: "product", status: "finalized" },
>       additional_metadata: { slack_ts: "1716213600.000100" },
>     },
>   ]),
> });
> ```
>
> ```bash cURL theme={"dark"}
> curl -X POST 'https://api.hydradb.com/context/ingest' \
>   -H "Authorization: Bearer $HYDRADB_API_KEY" \
>   -H "API-Version: 2" \
>   -F "type=knowledge" \
>   -F "database=acme_corp" \
>   -F 'app_knowledge=[
>     {
>       "id": "slack_C01_1716213600_000100",
>       "title": "Pricing discussion - Slack #product",
>       "kind": "message",
>       "provider": "slack",
>       "external_id": "1716213600.000100",
>       "fields": {
>         "kind": "message",
>         "body": "We agreed on three tiers: Starter at $29, Pro at $79, Enterprise at $199.",
>         "author": "alice",
>         "thread_id": "1716213600.000100",
>         "created_at": "2026-05-20T10:00:00Z"
>       },
>       "metadata": { "channel": "product", "status": "finalized" },
>       "additional_metadata": { "slack_ts": "1716213600.000100" }
>     }
>   ]'
> ```
>
> The response has the same shape as a file upload, with one entry per app source in `data.results`.

## 2. Save memories

HydraDB does not capture memories on its own: your application saves one when a user states a preference, makes a decision, finishes a task, or corrects the agent. Give each user their own collection, such as `user_john_123`, and send it on every ingest, status check, and query. Your backend picks it from the signed-in session; the name itself does not authenticate anyone.

The request is the same as for knowledge, with `type=memory` and a `memories` list in place of files:

| Form field | What to send |
| - | - |
| `type` | `memory` |
| `database` | An existing [database](https://docs.hydradb.com/api-reference/v2/endpoint/create-database.md) |
| `collection` | The user's own collection, such as `user_john_123`. |
| `memories` | One item or a list of items, as a JSON string |
| `upsert` | Leave it out. It defaults to `true`, so sending an item again with the same `id` replaces the saved memory. |

> [accordion] Save a memory: Python, TypeScript, or cURL
>
> Set up your [SDK client](https://docs.hydradb.com/api-reference/v2/sdks.md#client-setup) before running the Python or TypeScript example.
>
> ```python Python SDK theme={"dark"}
> import json
> import os
> from hydra_db import HydraDB
>
> client = HydraDB(token=os.environ["HYDRADB_API_KEY"])
>
> result = client.context.ingest(
>     type="memory",
>     database="acme_corp",
>     collection="user_john_123",
>     memories=json.dumps([
>         {
>             "text": "Prefers short, direct answers with no preamble.",
>             "infer": True,
>             "user_name": "John",
>         }
>     ]),
> )
> ```
>
> ```typescript TypeScript SDK theme={"dark"}
> import { HydraDBClient } from "@hydradb/sdk";
>
> const client = new HydraDBClient({
>   token: process.env.HYDRADB_API_KEY,
> });
>
> const result = await client.context.ingest({
>   type: "memory",
>   database: "acme_corp",
>   collection: "user_john_123",
>   memories: JSON.stringify([
>     {
>       text: "Prefers short, direct answers with no preamble.",
>       infer: true,
>       user_name: "John",
>     },
>   ]),
> });
> ```
>
> ```bash cURL theme={"dark"}
> curl -X POST 'https://api.hydradb.com/context/ingest' \
>   -H "Authorization: Bearer $HYDRADB_API_KEY" \
>   -H "API-Version: 2" \
>   -F "type=memory" \
>   -F "database=acme_corp" \
>   -F "collection=user_john_123" \
>   -F 'memories=[
>     {
>       "text": "Prefers short, direct answers with no preamble.",
>       "infer": true,
>       "user_name": "John"
>     }
>   ]'
> ```
>
> The response lists each memory in `data.results` with its `id` and a `status` of `queued`. The payload is wrapped in the standard envelope, so read it from `response.data`:
>
> ```json theme={"dark"}
> {
>   "success": true,
>   "data": {
>     "success": true,
>     "message": "Memories queued for ingestion successfully",
>     "results": [
>       { "id": "mem_abc123", "status": "queued", "infer": true }
>     ],
>     "success_count": 1,
>     "failed_count": 0
>   },
>   "error": null,
>   "meta": {
>     "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
>     "latency_ms": 12.3
>   }
> }
> ```

### Let HydraDB infer the memory

`infer` controls whether HydraDB extracts the memory from what you send or stores it as written.

| | `infer: true` | `infer: false` (the default) |
| - | - | - |
| What HydraDB does | Extracts the useful preference, trait, or fact from messy or indirect evidence. | Stores and indexes exactly what you send. There is no extraction step, so it is faster and deterministic. |
| Use it for | Dialogue where the preference is implicit, behavior logs and event streams, and feedback like "the last summary was too long." | Facts you already captured, such as `"User's plan tier is Pro"`, pre-structured notes, and text you want to retrieve exactly as written. |

`custom_instructions` takes effect only when `infer: true`. Use it to guide extraction, for example: `"Focus on UI and notification preferences only"`.

> [accordion] Infer a preference from a behavior log: Python, TypeScript, or cURL
>
> A user has been toggling dark mode in your application. Instead of extracting the preference yourself, send the raw behavior log and let HydraDB infer the memory.
>
> ```python Python SDK theme={"dark"}
> import json
>
> client.context.ingest(
>     type="memory",
>     database="acme_corp",
>     collection="user_john_123",
>     memories=json.dumps([
>         {
>             "text": (
>                 "User opened the app 14 times in the last week. "
>                 "Switched to dark mode on first session. "
>                 "Toggled back to light once on 2026-04-12 at 3pm, "
>                 "then switched back to dark within 4 minutes. "
>                 "Has not changed theme since."
>             ),
>             "infer": True,
>             "user_name": "John",
>             "custom_instructions": "Focus on display and theme preferences.",
>         }
>     ]),
> )
> ```
>
> ```typescript TypeScript SDK theme={"dark"}
> const result = await client.context.ingest({
>   type: "memory",
>   database: "acme_corp",
>   collection: "user_john_123",
>   memories: JSON.stringify([
>     {
>       text:
>         "User opened the app 14 times in the last week. " +
>         "Switched to dark mode on first session. " +
>         "Toggled back to light once on 2026-04-12 at 3pm, " +
>         "then switched back to dark within 4 minutes. " +
>         "Has not changed theme since.",
>       infer: true,
>       user_name: "John",
>       custom_instructions: "Focus on display and theme preferences.",
>     },
>   ]),
> });
> ```
>
> ```bash cURL theme={"dark"}
> curl -X POST 'https://api.hydradb.com/context/ingest' \
>   -H "Authorization: Bearer $HYDRADB_API_KEY" \
>   -H "API-Version: 2" \
>   -F "type=memory" \
>   -F "database=acme_corp" \
>   -F "collection=user_john_123" \
>   -F 'memories=[
>     {
>       "text": "User opened the app 14 times in the last week. Switched to dark mode on first session. Toggled back to light once on 2026-04-12 at 3pm, then switched back to dark within 4 minutes. Has not changed theme since.",
>       "infer": true,
>       "user_name": "John",
>       "custom_instructions": "Focus on display and theme preferences."
>     }
>   ]'
> ```
>
> Later, when [`POST /query`](https://docs.hydradb.com/api-reference/v2/endpoint/query) runs with `type: "memory"` and a query like `"what UI settings does the user prefer?"`, HydraDB can return the inferred preference (for example, `"prefers dark mode"`) rather than the raw event log.
>
> If you sent the same input with `infer: false`, HydraDB would index the event log verbatim. That is useful only when you want to retrieve the log itself.

### Send text or a conversation

Each memory item takes either `text` or `user_assistant_pairs`, **not both** (sending both returns `400`).

* **`text`:** prose of any kind, such as observations, captured facts, meeting notes, or semi-structured records. Set `is_markdown: true` when the content uses Markdown (headings, lists, code blocks) so HydraDB keeps that structure during chunking and embedding.
* **`user_assistant_pairs`:** dialogue, especially when the preference is implied rather than stated. Each pair is `{ "user": "...", "assistant": "..." }`.

> [accordion] Input shapes: text, Markdown, and user-assistant pairs
>
> Markdown notes, stored as written:
>
> ```json theme={"dark"}
> {
>   "text": "# Meeting Notes\n\n## Key Points\n- Budget approved",
>   "is_markdown": true,
>   "infer": false
> }
> ```
>
> A plain statement, with the preference extracted:
>
> ```json theme={"dark"}
> {
>   "text": "Prefers detailed technical explanations and works in PST.",
>   "infer": true
> }
> ```
>
> A conversation, with the preference extracted:
>
> ```json theme={"dark"}
> {
>   "user_assistant_pairs": [
>     { "user": "Remember I like dark mode", "assistant": "Noted." }
>   ],
>   "infer": true
> }
> ```

## 3. Wait for indexing

Knowledge and memories are indexed the same way, in the background: HydraDB parses each source, splits it into chunks, infers the memory if you asked it to, finds the people, teams, and topics it mentions and how they relate, and builds the search indexes. The ingest request returns `202 Accepted` with each source's `id`.

Poll [`GET /context/status`](https://docs.hydradb.com/api-reference/v2/endpoint/source-status) with those ids and the same `collection` every few seconds. A source's `indexing_status` moves forward through `queued`, `processing`, `graph_creation`, and `completed`, or lands in `errored` on failure:

* At `graph_creation` the source becomes searchable within a few seconds, while its graph is still building. A query sent the moment this status appears can miss the source, and a collection that is new can still be reported as not existing.
* At `completed` the source and its graph are ready. Wait for `completed` when you query straight after ingesting. Most documents complete in 1 to 5 minutes.

[Ingestion Status](https://docs.hydradb.com/api-reference/v2/endpoint/source-status.md) has the full status table. To skip polling, register a [webhook](https://docs.hydradb.com/api-reference/v2/endpoint/webhooks-overview.md).

> [accordion] Poll until indexed: Python, TypeScript, or cURL
>
> ```python Python SDK theme={"dark"}
> import time
>
> ids = ["policy_v2", "runbook_deploy"]
> terminal = {"completed", "errored"}
>
> while True:
>     status = client.context.status(
>         database="acme_corp",
>         collection="default",
>         ids=ids,
>     )
>     if all(s.indexing_status in terminal for s in status.data.statuses):
>         break
>     time.sleep(5)
> ```
>
> ```typescript TypeScript SDK theme={"dark"}
> const ids = ["policy_v2", "runbook_deploy"];
> const terminal = new Set(["completed", "errored"]);
>
> let status;
> while (true) {
>   status = await client.context.status({
>     database: "acme_corp",
>     collection: "default",
>     ids: ids,
>   });
>
>   const allDone = (status.data?.statuses ?? []).every((s) => terminal.has(s.indexingStatus ?? ""));
>   if (allDone) break;
>
>   await new Promise((r) => setTimeout(r, 5_000));
> }
> ```
>
> ```bash cURL theme={"dark"}
> while true; do
>   response=$(curl -s -G 'https://api.hydradb.com/context/status' \
>     -H "Authorization: Bearer $HYDRADB_API_KEY" \
>     -H "API-Version: 2" \
>     --data-urlencode "database=acme_corp" \
>     --data-urlencode "collection=default" \
>     --data-urlencode "ids=policy_v2" \
>     --data-urlencode "ids=runbook_deploy")
>
>   statuses=$(echo "$response" | jq -r '.data.statuses[].indexing_status')
>   if echo "$statuses" | grep -qv '^\(completed\|errored\)$'; then
>     sleep 5
>     continue
>   fi
>   echo "$response" | jq
>   break
> done
> ```
>
> Each status in the `data` payload looks like this:
>
> ```json theme={"dark"}
> {
>   "statuses": [
>     {
>       "id": "policy_v2",
>       "indexing_status": "completed",
>       "success": true
>     }
>   ]
> }
> ```

## 4. Query knowledge and memories together

[`POST /query`](https://docs.hydradb.com/api-reference/v2/endpoint/query) reaches each type through the `type` parameter:

* `type: "knowledge"` (the default) searches files and app sources.
* `type: "memory"` searches memories.
* `type: "all"` searches both and returns one merged, ranked list.

Both types are read from the collections you name. Shared knowledge usually lives in one collection and each user's memories in their own, so list both in `collections`. If some sources carry an `acl`, also send the caller's identity as `acl`. [Query](https://docs.hydradb.com/essentials/v2/query.md) covers the other parameters and how to pass the result to a model.

> [accordion] Search shared documents and a user's memories together: Python, TypeScript, or cURL
>
> ```python Python SDK theme={"dark"}
> result = client.query(
>     database="acme_corp",
>     collections=["company_docs", "user_john_123"],
>     query="How do I reset my password?",
>     type="all",
>     mode="thinking",
>     max_results=8,
> )
> ```
>
> ```typescript TypeScript SDK theme={"dark"}
> const result = await client.query({
>   database: "acme_corp",
>   collections: ["company_docs", "user_john_123"],
>   query: "How do I reset my password?",
>   type: "all",
>   mode: "thinking",
>   maxResults: 8,
> });
> ```
>
> ```bash cURL theme={"dark"}
> curl -X POST 'https://api.hydradb.com/query' \
>   -H "Authorization: Bearer $HYDRADB_API_KEY" \
>   -H "API-Version: 2" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "database": "acme_corp",
>     "collections": ["company_docs", "user_john_123"],
>     "query": "How do I reset my password?",
>     "type": "all",
>     "mode": "thinking",
>     "max_results": 8
>   }'
> ```
>
> Knowledge and memories come back together in `data.chunks`, ranked as one list. To let the user's memories outrank the shared documents, send weights instead of a list:
>
> ```json theme={"dark"}
> {
>   "collections": {
>     "user_john_123": 2,
>     "company_docs": 1
>   }
> }
> ```

## 5. Update or delete context

To change a source or a memory, send it again with the same `id`. `upsert` defaults to `true`, so the saved one is replaced; include everything you want to keep. Wait for the previous ingestion to reach `completed` or `errored` before sending the next update for the same `id`.

To remove one, call [`DELETE /context`](https://docs.hydradb.com/api-reference/v2/endpoint/delete-source) with its `id` and the same `collection`.

## 6. Link sources that belong together

Forceful relations are links you declare between knowledge sources when you ingest them. When a query matches a source that has forceful relations, HydraDB returns the linked sources in the `additional_context` field of the response, alongside the primary chunks.

This is separate from the context graph HydraDB builds on its own. Use forceful relations for documents that belong together when the text does not say so: a contract and its addendum, or a runbook and its troubleshooting guide. They link whole **sources**. To declare the entity and relation graph within one document, replacing extraction for it, use [Bring Your Own Graph](https://docs.hydradb.com/essentials/v2/bring-your-own-graph.md#using-byog-without-cypher).

On a file, set `relations.ids` on its `document_metadata` item. On an app source, set `relations.ids` the same way, and name the relationship in `app_relations` so the link also carries a predicate. `relations.ids` is what makes the linked text come back with query results; a named relationship alone connects the sources without requesting their text.

> [accordion] Link a runbook to its troubleshooting guide: Python, TypeScript, or cURL
>
> This Notion guide links to a troubleshooting page already ingested with the HydraDB `id` `auth-troubleshooting`. Ingest that page first and wait for `completed`.
>
> ```python Python SDK theme={"dark"}
> client.context.ingest(
>     type="knowledge",
>     database="acme_corp",
>     app_knowledge=json.dumps([
>         {
>             "id": "auth-guide",
>             "title": "Authentication Guide",
>             "kind": "knowledge_base",
>             "provider": "notion",
>             "external_id": "AUTH-GUIDE",
>             "fields": {"kind": "knowledge_base", "title": "Authentication Guide", "body": "..."},
>             "relations": {"ids": ["auth-troubleshooting"]},
>             "app_relations": [
>                 {"predicate": "linked_to", "target": {"source_id": "auth-troubleshooting"}},
>             ],
>         }
>     ]),
> )
> ```
>
> ```typescript TypeScript SDK theme={"dark"}
> await client.context.ingest({
>   type: "knowledge",
>   database: "acme_corp",
>   appKnowledge: JSON.stringify([
>     {
>       id: "auth-guide",
>       title: "Authentication Guide",
>       kind: "knowledge_base",
>       provider: "notion",
>       external_id: "AUTH-GUIDE",
>       fields: { kind: "knowledge_base", title: "Authentication Guide", body: "..." },
>       relations: { ids: ["auth-troubleshooting"] },
>       app_relations: [
>         { predicate: "linked_to", target: { source_id: "auth-troubleshooting" } },
>       ],
>     },
>   ]),
> });
> ```
>
> ```bash cURL theme={"dark"}
> curl -X POST 'https://api.hydradb.com/context/ingest' \
>   -H "Authorization: Bearer $HYDRADB_API_KEY" \
>   -H "API-Version: 2" \
>   -F "type=knowledge" \
>   -F "database=acme_corp" \
>   -F 'app_knowledge=[
>     {
>       "id": "auth-guide",
>       "title": "Authentication Guide",
>       "kind": "knowledge_base",
>       "provider": "notion",
>       "external_id": "AUTH-GUIDE",
>       "fields": { "kind": "knowledge_base", "title": "Authentication Guide", "body": "..." },
>       "relations": { "ids": ["auth-troubleshooting"] },
>       "app_relations": [
>         { "predicate": "linked_to", "target": { "source_id": "auth-troubleshooting" } }
>       ]
>     }
>   ]'
> ```

`query_forceful_relations` defaults to `true`; set it to `false` to skip the linked sources.

> [!NOTE]
>
> **Knowledge sources, `thinking` mode only:** Forceful relations stay within one store, so pointing a knowledge source at a memory ID silently returns nothing. Linked context is fetched only when `mode: "thinking"` is set on the query, or when `mode: "auto"` routes to thinking. In `fast` mode, `query_forceful_relations` is ignored even if set to `true`.

## Field reference

> [accordion] Ingest request fields
>
> | Field | Default | Description |
> | - | - | - |
> | `type`&#xA;\* knowledge or memory required | None | Selects the type of context. |
> | `database`&#xA;\* string required | None | Target database. |
> | `collection` string | default collection | The collection to save into. If omitted, context goes to the database's default collection, which a query naming another `collection` does not read. |
> | `documents` file\[] | None | Knowledge only. Files for HydraDB to parse. |
> | `document_metadata` string (JSON array) | None | Knowledge only. One item per file in `documents`, in the same order. |
> | `app_knowledge` string (JSON array) | None | Knowledge only. App sources. |
> | `memories` string (JSON array) | None | Memories only. Memory items. |
> | `upsert` boolean | `true` | Replace existing context with the same `id`. |

> [accordion] App source fields by kind
>
> Set `kind` on the app source and repeat it as `fields.kind`. Every other key in `fields` is optional.
>
> | Kind | Use it for | Keys in `fields` |
> | - | - | - |
> | `email` | An email | `subject`, `body`, `from`, `to`, `cc`, `bcc`, `thread_id`, `reply_to_id`, `forwarded_from_id`, `created_at`, `url` |
> | `message` | A chat message | `body`, `author`, `thread_id`, `parent_id`, `mentions`, `created_at`, `edited_at`, `url` |
> | `ticket` | A ticket or issue | `title`, `description`, `status`, `priority`, `assignee`, `reporter`, `parent_id`, `linked_issue_ids`, `created_at`, `updated_at`, `url` |
> | `knowledge_base` | A wiki page or article | `title`, `body`, `parent_id`, `created_by`, `updated_by`, `created_at`, `updated_at`, `url` |
> | `comment` | A comment on another record | `body`, `author`, `parent_id`, `thread_id`, `created_at`, `updated_at`, `url`, `properties` |
> | `meeting` | A meeting or call | `title`, `body`, `host`, `host_email`, `participants`, `started_at`, `updated_at`, `labels`, `url` |
> | `custom` | Any other record, such as a CRM deal or a database row | `body`, `parent_id`, `thread_id`, `data` |

> [accordion] Memory item fields
>
> | Field | Description |
> | - | - |
> | `id` string | Optional stable ID. Acts as the upsert key. |
> | `title` string | Short label for display in `/context/list` and query results. |
> | `text` ● string recommended | Raw text or Markdown content. Required unless `user_assistant_pairs` is provided. |
> | `user_assistant_pairs` ● array recommended | Conversation pairs in the shape `{ "user": "...", "assistant": "..." }`. Required unless `text` is provided. |
> | `is_markdown` boolean | Treat `text` as Markdown for chunking and indexing. |
> | `infer` boolean | When `true`, HydraDB extracts the underlying preference, trait, or fact. When `false` (the default), it stores the input as written. |
> | `custom_instructions` string | Guides extraction when `infer: true`. Ignored when `infer: false`. |
> | `user_name` string | User name used during inference. |
> | `metadata` object | Metadata schema fields for filtering. Send this as an object inside each `memories[]` item, for example `"metadata": { "department": "support" }`. Up to 16 KiB as compact JSON. |
> | `additional_metadata` object | Free-form per-memory fields for display or bookkeeping. Send this as an object. Up to 1 KiB as compact JSON. |
>
> `memories` is a JSON string. Inside each item, `metadata` and `additional_metadata` are plain objects; do not stringify them again.
>
> ```json theme={"dark"}
> {
>   "id": "pref_dark_mode",
>   "text": "User prefers dark mode and concise answers.",
>   "infer": true,
>   "metadata": { "department": "support", "workspace": "docs" },
>   "additional_metadata": { "source": "onboarding" }
> }
> ```

## Common mistakes

| Mistake | What goes wrong | Fix |
| - | - | - |
| Storing shared documents as memories | They do not surface when querying `type: "knowledge"` | Ingest them with `type=knowledge`. |
| Querying with the wrong `type` | Nothing returned: the other type was searched | Use the matching `type`, or `"all"`. |
| Omitting `collection` for per-user memories | The memory lands in the default collection and may surface in the wrong scope | Use the user's ID as `collection` to keep each user separate. |
| Querying before indexing finishes | Results are empty, or a new collection returns `400` as not existing | Wait for `indexing_status` to reach `completed`. |
| Sending `document_metadata`, `app_knowledge`, or `memories` as a JSON object | `multipart/form-data` validation fails | Send each as a JSON string. |
| Sending pre-extracted facts with `infer: true` | HydraDB may re-derive the memory and change the original phrasing | Use `infer: false` for already-structured facts. |
| Extracting preferences yourself instead of sending the raw logs | You duplicate work HydraDB can do for you | Send the raw signal with `infer: true`. |
| Pointing a forceful relation from knowledge to a memory | Nothing is returned: relations stay within one type | Link knowledge to knowledge. |

## Related

* [Query](https://docs.hydradb.com/essentials/v2/query.md): search context and pass the result to a model
* [Databases and Collections](https://docs.hydradb.com/essentials/v2/databases-and-collections.md): where context is saved, and how to keep users apart
* [Metadata Filters](https://docs.hydradb.com/essentials/v2/metadata.md): fields to filter on
* [Connectors](https://docs.hydradb.com/essentials/v2/connectors.md): keep an app's knowledge in sync
* [Ingest Context: API Reference](https://docs.hydradb.com/api-reference/v2/endpoint/ingest-context.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.