# Knowledge-grounded agents

Combine authoritative knowledge retrieval with experiential memory.

A knowledge-grounded agent answers from authoritative sources before consulting conversational memory. This pattern uses SurrealDB Agent Memory's authoritative knowledge to store canonical knowledge - product data, policies, technical documentation - and relies on the authority hierarchy to prevent conversational drift from corrupting those facts.

> [!NOTE]
> `Spectron` was the project name for SurrealDB Agent Memory. These type names
> will be renamed in a future release.

## Why ground agents in authoritative knowledge

Without a knowledge layer, agents hallucinate or rely on stale training data for domain-specific questions. The common workaround - embedding documents in a vector store and retrieving chunks - improves recall but loses structure, provenance, and the ability to enforce authority.

authoritative knowledge gives you:

- **Structured knowledge nodes** with typed attributes, not just text chunks.
- **Authority enforcement** - authoritative knowledge wins when a user asserts something conflicting.
- **`resolves_to` links** - conversational references to products or policies resolve to authoritative nodes.
- **Content addressing** - uploading the same document twice is idempotent.

## Step 1 - Load authoritative knowledge

Documents are ingested through the knowledge API. SurrealDB Agent Memory processes them into knowledge nodes (typed entities with attributes) and keyword-indexed chunks.

```python
import os
import httpx

client = httpx.Client(
    base_url="https://spectron.surrealdb.com/api/v1/my-context",
    headers={"Authorization": f"Bearer {os.environ['SPECTRON_API_KEY']}"},
)

# Upload a product specification as a JSON document
with open("product-specs.json", "rb") as f:
    client.post(
        "/documents",
        files={"file": ("product-specs.json", f, "application/json")},
        data={"title": "Product specifications"},
    )

# Upload a policy document as Markdown
with open("returns-policy.md", "rb") as f:
    client.post(
        "/documents",
        files={"file": ("returns-policy.md", f, "text/markdown")},
        data={"title": "Returns policy"},
    )
```

```typescript
const formData = new FormData();
formData.append(
    "file",
    new Blob([productSpecsJson], { type: "application/json" }),
    "product-specs.json",
);
formData.append("title", "Product specifications");
formData.append("content_type", "product_data");

await fetch("https://spectron.surrealdb.com/api/v1/my-context/documents", {
    method: "POST",
    headers: { "Authorization": `Bearer ${process.env.SPECTRON_API_KEY}` },
    body: formData,
});
```

## Step 2 - Query authoritative knowledge in the agent loop

Before generating a response, search authoritative knowledge for relevant passages. Use `documents.query` for natural-language search over document chunks, or an entity read for exact lookups.

```python
from surrealdb import Spectron

memory = Spectron(context="my-context", api_key="sk-...")

async def grounded_response(session, user_message: str) -> str:
    # Search authoritative knowledge for relevant authoritative knowledge
    knowledge = await memory.documents.query(
        query=user_message,
        mode="hybrid",
        k=4,
    )

    # Retrieve experiential memory user context for personalisation
    ctx = await session.context(query=user_message)

    # Assemble the prompt with authoritative facts taking precedence
    system = "You are a product assistant. Answer from the knowledge provided."

    # documents.query returns {results, queryMs}; each hit is {chunk, document, score}
    if knowledge["results"]:
        system += "\n\n## Authoritative knowledge\n"
        for hit in knowledge["results"]:
            system += f"\n{hit['chunk']['text']}  (source: {hit['document']['title']})"

    if ctx.items:
        system += f"\n\n## User context\n{ctx.formatted}"

    response = your_llm(system=system, user=user_message)

    await memory.remember(user_message, session_id=session.id, role="user")
    await memory.remember(response, session_id=session.id, role="assistant")

    return response
```

```typescript
async function groundedResponse(session: Session, userMessage: string): Promise<string> {
    const [knowledge, ctx] = await Promise.all([
        memory.documents.query({ query: userMessage, mode: "hybrid", k: 4 }),
        session.context({ query: userMessage }),
    ]);

    let system = "You are a product assistant. Answer from the knowledge provided.";

    if (knowledge.results.length > 0) {
        system += "\n\n## Authoritative knowledge\n";
        for (const hit of knowledge.results) {
            system += `\n${hit.chunk.text}  (source: ${hit.document.title})`;
        }
    }

    if (ctx.items.length > 0) {
        system += `\n\n## User context\n${ctx.formatted}`;
    }

    const response = await yourLlm({ system, user: userMessage });

    await memory.remember(userMessage, { sessionId: session.id, role: "user" });
    await memory.remember(response, { sessionId: session.id, role: "assistant" });

    return response;
}
```

## Step 3 - The `resolves_to` mechanism

When a user mentions a product or policy, SurrealDB Agent Memory creates an experiential memory entity and automatically creates a `resolves_to` relation pointing to the matching authoritative knowledge node. The context retrieval traverses this relation so the agent sees both layers together.

```json
// User says "I bought the AirPods Pro" - experiential memory entity created
{
  "id": "entity:[\"Product\", \"airpods_pro\"]",
  "layer": 1,
  "scope": ["org/acme/user/alice"]
}

// authoritative knowledge node - loaded from product catalogue
{
  "id": "knowledge:[\"Product\", \"airpods_pro\"]",
  "layer": 0,
  "name": "AirPods Pro (2nd generation)",
  "price": 279,
  "return_window_days": 30
}

// resolves_to - Spectron creates this automatically
{
  "in": "entity:[\"Product\", \"airpods_pro\"]",
  "out": "knowledge:[\"Product\", \"airpods_pro\"]"
}
```

At retrieval time, a query about the user's AirPods returns both the experiential-memory entity (the user owns them) and the authoritative knowledge node (authoritative specs). The agent receives a complete picture in a single context call.

## Step 4 - Handling authoritative and experiential streams conflicts

When a user asserts something that contradicts authoritative knowledge, SurrealDB Agent Memory records the conflict rather than silently overwriting the authoritative fact.

Example: the return policy is 30 days (authoritative knowledge), and a user says "I thought it was 60 days".

The extraction pipeline:

1. Creates an experiential memory attribute: `return_window_days: 60` on the policy entity.
2. Detects the conflict with the authoritative knowledge node (which says 30).
3. Surfaces the clash in **`uncertainties`** and state/profile responses without modifying the curated record.

The agent is informed via the context retrieval:

```json
{
  "type": "conflict",
  "l0_fact": { "key": "return_window_days", "value": 30 },
  "l1_belief": { "key": "return_window_days", "value": 60, "source": "user assertion" },
  "recommendation": "Inform the user of the authoritative value."
}
```

The agent can then politely correct the user without any custom conflict-detection code.

## Exact entity retrieval

For lookups where you know the entity type and name (from a structured UI, a product SKU field, etc.), read the entity directly instead of searching. This hits **`GET /entities/{entity_type}/{entity_name}`**, not the document store - `documents.get` takes a document id.

```python
entity = await memory.entities.get("product", "airpods_pro")
```

```typescript
const entity = await memory.entities.get("product", "airpods_pro");
```

This is faster than semantic search and appropriate when the reference is unambiguous.
