NewRelease 0.12.3 is out: Engine v0.12.3, Dashboard 0.5.2, Helm 0.12.3, the four SDKs, CLI 0.8.2 and MCP 0.12.3Release 0.12.3 is outSee what's new →
Intermediate Architecture

RAG-Augmented Memory

⏱ ~35 min to implement 📦 Requires: Dakera v0.11+

Pure RAG knows your documents but forgets your users. Pure memory knows your users but not your documents. Combine both in a single retrieval pipeline so your agent is simultaneously accurate and personal.

Get Started Free →
Prerequisites
  • Running Dakera server (see Quickstart guide)
  • Document chunking pipeline or existing vector store with content to index
  • LLM API access (Anthropic Claude, OpenAI GPT-4, etc.) for response generation
  • Dakera Python, TypeScript, Rust, or Go SDK installed

The Problem with Choosing Between RAG and Memory

Traditional RAG retrieves document chunks on every query — great for factual accuracy over a static knowledge base, but it has no awareness of who is asking, what they asked before, or what they already know. Conversely, a pure persistent memory system is personal and accumulates context over time, but has no access to your internal documentation or knowledge base.

Enterprise knowledge base assistants need both. A support agent should know that this particular user has a Pro subscription, already tried rebooting, and prefers concise answers — while simultaneously retrieving the correct troubleshooting steps from official documentation.

Dakera's approach: one store, two retrieval paths

Rather than maintaining a separate vector store for documents and a memory system for users, Dakera indexes both in the same store. Document chunks are stored as memories with a doc tag and metadata marking their source. One recall() without a tag filter returns agent memories and document chunks ranked together. This pattern runs two tag-filtered recalls instead, so each source gets its own top_k and importance floor, and merges them in a few lines of your code.

Architecture: Hybrid Retrieval Pipeline

On every query, two retrieval paths run in parallel and your code merges their results before injecting them into the LLM prompt. This diagram shows the full pipeline from user query to LLM response:

Workflow of the hybrid retrieval pipeline. A user query goes to two recall() calls on the same Dakera store, run in parallel: one for user memories with the user tag, top_k=4 and min_importance 0.7, one for doc chunks with the doc tag, top_k=6 and min_importance 0.6. Your agent merges the results, ranks them by score with user memories winning ties, trims to a 2,000-token budget, and builds a prompt with USER CONTEXT and DOCUMENTATION sections. The LLM answers, and after the reply new facts are crystallized back with store_memory().Workflow of the hybrid retrieval pipeline. A user query goes to two recall() calls on the same Dakera store, run in parallel: one for user memories with the user tag, top_k=4 and min_importance 0.7, one for doc chunks with the doc tag, top_k=6 and min_importance 0.6. Your agent merges the results, ranks them by score with user memories winning ties, trims to a 2,000-token budget, and builds a prompt with USER CONTEXT and DOCUMENTATION sections. The LLM answers, and after the reply new facts are crystallized back with store_memory().
Memory recall and doc recall run side by side against one store, so the added latency is the slower of the two, not the sum.Full size ↗Full size ↗

What the Two Paths Cost

Each path is one recall. Sent in parallel, the added latency is the slower of the two, not the sum. Each recall also takes one of the server's admission slots (two per CPU core, at least four), so a busy agent sends twice the recall load; the single unfiltered recall is the cheaper option when one ranking is enough.

Memory Update Loop: Learning from RAG Results

When the RAG pipeline surfaces a fact new to the agent's memory — a policy change, a product update, a user correction — that fact should be stored back into agent memory so future queries answer faster without re-retrieval. This "memory crystallization" loop is the key differentiator of RAG-augmented memory over plain RAG.

Workflow of the crystallization loop, run after the reply. A doc chunk the answer relied on is checked with recall(): your own check decides whether one of the results already states the fact. If one does, nothing is written. If the fact is new it is stored with store_memory() at importance 0.75 with the learned and doc-derived tags. A similar question later is answered from memory through recall(), with no doc retrieval. When the document changes, the stale fact expires at its TTL or is demoted with update_importance().Workflow of the crystallization loop, run after the reply. A doc chunk the answer relied on is checked with recall(): your own check decides whether one of the results already states the fact. If one does, nothing is written. If the fact is new it is stored with store_memory() at importance 0.75 with the learned and doc-derived tags. A similar question later is answered from memory through recall(), with no doc retrieval. When the document changes, the stale fact expires at its TTL or is demoted with update_importance().
A new fact from a doc chunk becomes agent memory once, after your duplicate check, and ages out with its TTL.Full size ↗Full size ↗
Tip: check for a known fact before crystallizing

Before storing a learned fact, recall the agent's memories for it and let your own rule (or an LLM) decide whether one of them already states it. Recall scores rank results within a query; they are not a cosine similarity, so do not use a fixed score cut-off for this. Repeats of the exact same text are merged later by Dakera's deduplication (AutoPilot, every 6 hours by default); paraphrases are not, which is why the check is yours.

Real-World Scenario: Enterprise Knowledge Base Assistant

An internal knowledge base assistant at a SaaS company handles HR policy questions, IT support, and product documentation queries from employees. The critical requirement: every answer must be both factually accurate (from official docs) and personally aware (remembering this employee's role, past questions, and preferences).

  1. Index company documentation at startup
    Chunk all policy documents, runbooks, and product guides into segments of about 450 tokens with 50-token overlaps (the chunker under Advanced Configuration). Store each chunk as a memory with memory_type="semantic", the doc tag, a 90-day TTL and metadata marking the source document, version, and department. Dakera indexes them in the same store as user memories.
  2. Store user context on first interaction
    When an employee first uses the assistant, store their role, department, technical level, and communication preferences as high-importance semantic memories scoped to their agent ID. These persist across all future sessions.
  3. Run parallel retrieval on every query
    Execute two concurrent recall() calls: one filtered by the user's tag to user memories (preferences, past questions, their specific environment), and one filtered by the doc tag to documentation chunks. Run them in parallel: total added latency is the slower of the two, not the sum.
  4. Merge, rank, and trim to token budget
    Sort all retrieved results by score and trim them to a token budget (2,000 tokens of context in the examples). Scores rank results within one recall, so across the two the order is approximate: put user memories first on a tie, since personalization wins ties.
  5. Crystallize new facts learned from successful answers
    After the reply, take the doc chunks the answer relied on (ask the model to cite them), check that the fact is not already known, and store the condensed fact back with the learned and doc-derived tags, importance 0.75 and a TTL matching the document's expected update frequency (30 days in the examples). Future identical or similar questions skip doc retrieval entirely.

Ship a RAG+Memory pipeline on one store

Dakera handles chunked doc indexing, semantic recall, and memory persistence in one self-hosted API.

Get Started →

Implementation

# 1. Index a document chunk as a memory (tagged "doc", re-indexed within its 90-day TTL)
curl -X POST http://localhost:3000/v1/memory/store \
  -H "Authorization: Bearer $DAKERA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "kb-assistant",
    "content": "SSO is available on Pro and Enterprise plans. To enable, go to Settings > Security > Single Sign-On and upload your IdP metadata XML.",
    "importance": 0.82,
    "memory_type": "semantic",
    "tags": ["doc", "sso", "security", "settings"],
    "ttl_seconds": 7776000,
    "metadata": {"source": "doc", "doc_id": "help-sso-setup", "version": "2024-Q4", "department": "IT"}
  }'

# 2. Store user context
curl -X POST http://localhost:3000/v1/memory/store \
  -H "Authorization: Bearer $DAKERA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "kb-assistant",
    "content": "User is a Systems Administrator on the Pro plan. Prefers step-by-step instructions with CLI examples where available.",
    "importance": 0.95,
    "memory_type": "semantic",
    "tags": ["user-profile", "user:alice"]
  }'

# 3. Two recalls, sent in parallel: the user's memories...
curl -X POST http://localhost:3000/v1/memory/recall \
  -H "Authorization: Bearer $DAKERA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "kb-assistant", "query": "how do I enable SSO", "top_k": 4, "min_importance": 0.7, "tags": ["user:alice"]}'

# ...and the documentation chunks
curl -X POST http://localhost:3000/v1/memory/recall \
  -H "Authorization: Bearer $DAKERA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "kb-assistant", "query": "how do I enable SSO", "top_k": 6, "min_importance": 0.6, "tags": ["doc"]}'

# Or one recall without a tag filter: memories and chunks ranked together
curl -X POST http://localhost:3000/v1/memory/recall \
  -H "Authorization: Bearer $DAKERA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "kb-assistant", "query": "how do I enable SSO", "top_k": 8}'
import asyncio
from dakera import DakeraClient

client = DakeraClient(base_url="http://localhost:3000", api_key="dk-...")
AGENT = "kb-assistant"

# -- Step 1: index documents at startup --
def index_document(doc_id: str, chunks: list[str], department: str, version: str):
    """Index a document as a series of memory chunks."""
    for chunk in chunks:
        client.store_memory(
            agent_id=AGENT,
            content=chunk,
            importance=0.82,
            memory_type="semantic",
            tags=["doc", department.lower()],
            metadata={"source": "doc", "doc_id": doc_id, "version": version, "department": department},
            ttl_seconds=90 * 24 * 3600,  # expires after 90 days; re-index before then
        )

index_document("help-sso-setup", [
    "SSO (Single Sign-On) is available on Pro and Enterprise plans only. Starter plan users cannot enable SSO.",
    "To enable SSO, navigate to Settings > Security > Single Sign-On. Upload your IdP metadata XML. Supported IdPs: Okta, Azure AD, Google Workspace, OneLogin.",
    "After uploading IdP metadata, test the SSO flow with a sandbox account before enabling it for your entire organization.",
], "IT", "2024-Q4")

# -- Step 2: store user context on first interaction --
client.store_memory(
    agent_id=AGENT,
    content="Alice Chen is a Systems Administrator on the Pro plan. Prefers step-by-step instructions. Uses Okta as IdP.",
    importance=0.95,
    memory_type="semantic",
    tags=["user-profile", "user:alice"],
)

# -- Step 3: two recalls in parallel --
async def retrieve_context(query: str, user_tag: str):
    mem_task = asyncio.to_thread(client.recall, agent_id=AGENT, query=query,
                                 top_k=4, min_importance=0.7, tags=[user_tag])
    doc_task = asyncio.to_thread(client.recall, agent_id=AGENT, query=query,
                                 top_k=6, min_importance=0.6, tags=["doc"])
    mem, docs = await asyncio.gather(mem_task, doc_task)
    return mem.memories, docs.memories

# -- Step 4: merge, rank and trim to the token budget --
def merge_context(memories, docs, token_budget: int = 2000) -> str:
    """Scores rank results within one recall; across the two recalls the order is
    approximate, so user memories win ties."""
    items = [(m.score, 1, "[USER CONTEXT]", m.content) for m in memories] + \
            [(d.score, 0, "[DOCUMENTATION]", d.content) for d in docs]
    items.sort(reverse=True)
    parts, used = [], 0.0
    for _, _, prefix, content in items:
        tokens = len(content.split()) * 1.3  # rough estimate
        if used + tokens > token_budget:
            break
        parts.append(f"{prefix}\n{content}")
        used += tokens
    return "\n\n".join(parts)

# -- Step 5: crystallize facts the answer relied on --
def same_fact(a: str, b: str) -> bool:
    """Your rule or an LLM call. Recall scores rank results; they are not a similarity cut-off."""
    ...

def crystallize_fact(fact: str, user_tag: str, ttl_days: int = 30):
    known = client.recall(agent_id=AGENT, query=fact, top_k=3)
    if any(same_fact(m.content, fact) for m in known.memories):
        return  # already known
    client.store_memory(
        agent_id=AGENT,
        content=fact,
        importance=0.75,
        memory_type="semantic",
        tags=["learned", "doc-derived", user_tag],
        ttl_seconds=ttl_days * 24 * 3600,
    )

async def answer_question(user_query: str):
    memories, docs = await retrieve_context(user_query, "user:alice")
    context = merge_context(memories, docs)
    system_prompt = f"You are a knowledge base assistant. Use the context below.\n\n{context}"
    answer, cited_ids = call_your_llm(system_prompt, user_query)  # ask the model to cite chunk ids
    # After replying (in the background), crystallize the chunks the answer used
    for doc in docs:
        if doc.id in cited_ids:
            crystallize_fact(doc.content, "user:alice")
    return answer
import { DakeraClient } from '@dakera-ai/dakera';
import Anthropic from '@anthropic-ai/sdk';

const client = new DakeraClient({ baseUrl: 'http://localhost:3000', apiKey: 'dk-...' });
const anthropic = new Anthropic();
const AGENT = 'kb-assistant';

// -- Index documentation chunks --
async function indexDocument(docId: string, chunks: string[], tags: string[]) {
  for (const chunk of chunks) {
    await client.storeMemory(AGENT, {
      content: chunk,
      importance: 0.82,
      memory_type: 'semantic',
      tags: ['doc', ...tags],
      metadata: { source: 'doc', doc_id: docId },
      ttl_seconds: 90 * 24 * 3600,
    });
  }
}

await indexDocument('help-sso-setup', [
  'SSO is available on Pro and Enterprise plans. Go to Settings > Security > SSO to enable.',
  'Supported IdPs: Okta, Azure AD, Google Workspace, OneLogin. Upload your IdP metadata XML.',
], ['it', 'sso', 'security']);

// -- Store user profile --
await client.storeMemory(AGENT, {
  content: 'Alice Chen is a Systems Administrator on Pro plan. Uses Okta IdP. Prefers step-by-step instructions.',
  importance: 0.95,
  memory_type: 'semantic',
  tags: ['user-profile', 'user:alice'],
});

// -- Two recalls in parallel --
async function retrieveContext(query: string, userTag: string) {
  const [mem, docs] = await Promise.all([
    client.recall(AGENT, query, { top_k: 4, min_importance: 0.7, tags: [userTag] }),
    client.recall(AGENT, query, { top_k: 6, min_importance: 0.6, tags: ['doc'] }),
  ]);
  return { memories: mem.memories, docs: docs.memories };
}

// -- Merge: scores rank within one recall, so user memories win ties --
function mergeContext(memories: any[], docs: any[], tokenBudget = 2000): string {
  const items = [
    ...memories.map(m => ({ ...m, prefix: '[USER CONTEXT]', tie: 1 })),
    ...docs.map(d => ({ ...d, prefix: '[DOCUMENTATION]', tie: 0 })),
  ].sort((a, b) => b.score - a.score || b.tie - a.tie);
  const parts: string[] = [];
  let tokens = 0;
  for (const item of items) {
    const est = item.content.split(' ').length * 1.3;
    if (tokens + est > tokenBudget) break;
    parts.push(`${item.prefix}\n${item.content}`);
    tokens += est;
  }
  return parts.join('\n\n');
}

// -- Crystallize a fact unless your check finds it already known --
declare function sameFact(a: string, b: string): Promise<boolean>; // your rule or an LLM call

async function crystallizeFact(fact: string, userTag: string) {
  const { memories } = await client.recall(AGENT, fact, { top_k: 3 });
  for (const m of memories) if (await sameFact(m.content, fact)) return;
  await client.storeMemory(AGENT, {
    content: fact,
    importance: 0.75,
    memory_type: 'semantic',
    tags: ['learned', 'doc-derived', userTag],
    ttl_seconds: 30 * 24 * 3600,
  });
}

// -- Full pipeline --
async function answerQuestion(userQuery: string) {
  const { memories, docs } = await retrieveContext(userQuery, 'user:alice');
  const context = mergeContext(memories, docs);
  const response = await anthropic.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: 1024,
    system: `You are a knowledge base assistant. Use the context below.\n\n${context}`,
    messages: [{ role: 'user', content: userQuery }],
  });
  const answer = response.content[0].type === 'text' ? response.content[0].text : '';
  // After replying (in the background), crystallize the chunks the answer used
  for (const doc of docs.filter(d => answer.includes(d.content))) {
    void crystallizeFact(doc.content, 'user:alice');
  }
  return answer;
}
use dakera_client::{DakeraClient, RecallRequest, StoreMemoryRequest};

let client = DakeraClient::builder("http://localhost:3000").api_key("dk-...").build()?;
let agent = "kb-assistant";

// Index a document chunk and the user's profile in the same agent
client.store_memory(
    StoreMemoryRequest::new(agent, "SSO is available on Pro and Enterprise plans. Settings > Security > SSO.")
        .with_importance(0.82)
        .with_tags(vec!["doc".into(), "sso".into()]),
).await?;
client.store_memory(
    StoreMemoryRequest::new(agent, "Alice is a sysadmin on Pro plan, uses Okta, prefers step-by-step guides.")
        .with_importance(0.95)
        .with_tags(vec!["user-profile".into(), "user:alice".into()]),
).await?;

// One recall without a tag filter ranks memories and doc chunks together
// (for the two-path version with tag filters, see the REST or Python tab)
let found = client.recall(RecallRequest::new(agent, "How do I enable SSO?").with_top_k(8)).await?;

let mut context = String::new();
for m in &found.memories {
    context.push_str(&format!("{}\n\n", m.content));
}
println!("Context for LLM:\n{}", context);
ctx := context.Background()
client := dakera.NewClientWithOptions(dakera.ClientOptions{
    BaseURL: "http://localhost:3000",
    APIKey:  os.Getenv("DAKERA_API_KEY"),
})
agent := "kb-assistant"

docImp, userImp := float32(0.82), float32(0.95)

// Index a documentation chunk and the user's profile in the same agent
client.StoreMemory(ctx, agent, dakera.StoreMemoryRequest{
    Content:    "SSO available on Pro and Enterprise. Settings > Security > SSO. Upload IdP metadata XML.",
    Importance: &docImp,
    Tags:       []string{"doc", "sso"},
})
client.StoreMemory(ctx, agent, dakera.StoreMemoryRequest{
    Content:    "Alice Chen: sysadmin, Pro plan, Okta IdP, prefers step-by-step instructions.",
    Importance: &userImp,
    Tags:       []string{"user-profile", "user:alice"},
})

// One recall without a tag filter ranks memories and doc chunks together
// (for the two-path version with tag filters, see the REST or Python tab)
found, err := client.Recall(ctx, agent, dakera.RecallRequest{Query: "How do I enable SSO?", TopK: 8})
if err != nil {
    log.Fatal(err)
}
contextStr := ""
for _, m := range found.Memories {
    contextStr += m.Content + "\n\n"
}
fmt.Println("Context for LLM:", contextStr)

Before / After: Pure RAG vs. RAG + Dakera Memory

Before: Pure RAG only
Query: "How do I enable SSO?"

Retrieved chunks:
- SSO setup guide (generic)
- SSO troubleshooting (generic)
- IdP metadata format spec

Generated answer:
"To enable SSO, go to Settings >
Security > Single Sign-On and
upload your IdP metadata XML.
Supported IdPs: Okta, Azure AD,
Google Workspace, OneLogin."

Problems:
- Doesn't know Alice is already
  on Pro plan (no upsell friction)
- Doesn't know she uses Okta
  (misses Okta-specific steps)
- Same generic answer every time
- No memory of past questions
  (may answer the same thing 5x)
After: RAG + Dakera Memory
Query: "How do I enable SSO?"

Memory recall:
- Alice: Pro plan, Okta IdP,
  prefers step-by-step guides
- Alice asked about SSO last week;
  showed her the Settings page

Doc recall:
- SSO setup guide (generic)
- Okta-specific SAML config steps
- Pro plan feature confirmation

Generated answer:
"Since you're on Pro plan, SSO is
available. For Okta specifically:
1. In Okta Admin, create a new
   SAML 2.0 application
2. Download the metadata XML
3. In Dakera Settings > Security >
   SSO, upload the XML
Last time we spoke about this you
were on the Settings page — the
SSO tab is in the left nav."

Personal, accurate, and contextual.

Edge Cases

1. Conflicting RAG vs. Memory Facts

A policy document might say "SSO requires Enterprise plan" while a crystallized memory from 6 months ago says "SSO is available on Pro plan" (because the policy changed). When both come back and conflict, prefer the document chunk: it reflects the current source of truth. Demote the stale memory with update_importance(), or forget() it.

# Detect conflict: doc says X, memory says Y on same topic
# Demote stale memory importance so doc wins future rankings
client.update_importance(
    agent_id="kb-assistant",
    memory_ids=["mem_stale_sso_policy"],
    importance=0.2,  # demote so doc chunk ranks higher
)

2. Document Staleness After Updates

Documents change. Crystallized memories derived from outdated chunks persist until their TTL expires or you force-update them. Set TTL on all doc-derived memories to match your document update cadence. For fast-changing docs (weekly releases), use 7-day TTLs. For stable policy docs, 90 days is safe.

3. Chunking Strategy Affects Recall Quality

Chunks that are too small lack enough context for accurate semantic matching; chunks that are too large dilute the embedding and return irrelevant sentences. The examples use about 450 tokens with 50-token overlaps between adjacent chunks, to prevent context loss at boundaries; measure on your own documents before settling on a size.

4. Token Budget Overflow

When both memory recall and doc retrieval return high-scoring results, the merged context can overflow the LLM's context window. Cap the merged context (2,000 tokens in the examples) and leave room in the window for the system prompt and the user message. If one source crowds out the other, give each its own share of the budget, for example a cap on user memories and the rest for doc chunks.

5. Cold Start: No User Memory Yet

On a user's first interaction, memory recall returns nothing. Fall back gracefully to doc-only retrieval with a wider top_k. After the first turn, store a minimal user profile memory to bootstrap personalization for the second turn. Never show degraded behavior to the user — the transition should be seamless.

Performance Considerations

Crystallize asynchronously

Always run the crystallization check and store_memory() call after returning the response to the user, not before: a recall and a write would otherwise add to the user-facing latency. Queue them as a background task; the user should never wait for memory writes.

SDK Reference

MethodSDKPurpose
store_memory(agent_id, content, importance, memory_type, tags, metadata, ttl_seconds)PythonIndex doc chunks or store user memories
storeMemory(agentId, {content, importance, memory_type, tags, metadata, ttl_seconds})TypeScriptIndex doc chunks or store user memories
recall(agent_id, query, top_k, min_importance, tags)PythonOne retrieval path (or both, without a tag filter)
recall(agentId, query, {top_k, min_importance, tags})TypeScriptOne retrieval path (or both, without a tag filter)
update_importance(agent_id, memory_ids, importance)PythonDemote stale crystallized facts when docs are updated
POST /v1/memory/importanceRESTThe same, as a batch importance update
forget(agent_id, memory_id)PythonRemove outdated doc chunks when a document is replaced
batch_forget(BatchForgetRequest(...))PythonRemove every chunk of a replaced document by filter
Advanced Configuration: Chunking, TTL, and Index Tuning
from dakera import DakeraClient
import tiktoken

client = DakeraClient(base_url="http://localhost:3000", api_key="dk-...")
enc = tiktoken.get_encoding("cl100k_base")

def chunk_document(text: str, chunk_tokens: int = 450, overlap_tokens: int = 50):
    """Chunk text with overlap to prevent context boundary loss."""
    tokens = enc.encode(text)
    chunks = []
    start = 0
    while start < len(tokens):
        end = min(start + chunk_tokens, len(tokens))
        chunk_text = enc.decode(tokens[start:end])
        chunks.append(chunk_text)
        start += chunk_tokens - overlap_tokens  # slide with overlap
    return chunks

TTL strategy by document type

# Changelog / release notes: short TTL, changes frequently
client.store_memory(agent_id="kb-assistant", content=chunk, importance=0.82, tags=["doc"], ttl_seconds=7*24*3600)

# Policy documents: medium TTL, quarterly updates
client.store_memory(agent_id="kb-assistant", content=chunk, importance=0.82, tags=["doc"], ttl_seconds=90*24*3600)

# Core product docs: long TTL, stable unless a major version changes
client.store_memory(agent_id="kb-assistant", content=chunk, importance=0.82, tags=["doc"], ttl_seconds=365*24*3600)

# Crystallized facts learned from RAG answers: 30 days
client.store_memory(agent_id="kb-assistant", content=fact, importance=0.75, tags=["learned", "doc-derived"], ttl_seconds=30*24*3600)

Routing and hybrid weighting per recall

# By default the server classifies each query and picks BM25, vector or a blend.
# Override it per request: "routing" is auto, vector, bm25 or hybrid;
# vector_weight sets the blend (0.0 = BM25 only, 1.0 = vector only).
curl -X POST http://localhost:3000/v1/memory/recall \
  -H "Authorization: Bearer $DAKERA_API_KEY" -H "Content-Type: application/json" \
  -d '{"agent_id": "kb-assistant", "query": "ERR_SSO_METADATA", "tags": ["doc"], "routing": "bm25", "top_k": 6}'
Try it live

Run this pattern against a real Dakera.

The Playground executes live API calls in your browser. No account, no install. Then run it on your own server in five minutes.