Ground Agent Squad agents in Dakera with DakeraRetriever

DakeraRetriever plugs Dakera's server-side semantic search into Agent Squad agents as a retriever — every turn grounded in the most relevant memories, in three languages.

Ground Agent Squad agents in Dakera with DakeraRetriever

Retrieval-augmented agents are only as good as their retriever. DakeraRetriever plugs Dakera into Agent Squad so every agent turn is grounded in the top-k most relevant memories from a Dakera namespace — with embedding done server-side, so there's no local model to ship. It lands in three languages at once: Python, TypeScript, and Swift.


Bolted-on RAG vs. server-side ranked retrieval

The usual way a squad of agents gets RAG is bolted on: you stand up a vector store in-process, load an embedding model (often a few hundred megabytes of ONNX weights), chunk and embed your documents, and hand-roll the retrieve-then-stuff-the-prompt glue for every agent. It works, but it drags an embedding model and an index into the same process that runs your agents, ties recall quality to whatever local model you happened to bundle, and leaves each agent with its own copy of "memory" that vanishes when the process restarts.

A retriever flips that around. Agent Squad already has a retriever seam — an object an agent calls to fetch context before the LLM generates. DakeraRetriever fills that seam with a call to a self-hosted Dakera server. Your agent sends raw query text; Dakera embeds it server-side, ranks the namespace, and returns the most relevant documents. No embedding model in your process, no index to warm, and the memory is persistent and shared — every agent pointed at the namespace sees the same, always-current context.

How a retriever fits the orchestrator

Agent Squad — formerly the AWS Multi-Agent Orchestrator — routes each incoming message to the best agent in a squad and streams back its answer. The orchestrator itself never touches Dakera. It classifies the message, picks an agent, and invokes it; the agent owns the retriever. When the selected agent carries a DakeraRetriever, the framework calls the retriever with the user's query, folds the returned text into the prompt as grounding context, and only then runs the model.

That seam is deliberately narrow, which makes it composable: attach a retriever to one agent, several agents, or every agent, and each queries whatever namespace and filter you gave it. A support agent and a billing agent can read the same knowledge base through different metadata filters, and their contexts never bleed together.

Install and configure

First run a Dakera server — the dakera-deploy Docker Compose stack brings the API up on port 3000:

git clone https://github.com/dakera-ai/dakera-deploy
cd dakera-deploy && docker compose up -d   # API on :3000

Then install for your language. The pieces differ because each runtime talks to Dakera differently:

# Python — the [dakera] extra pulls in the dakera SDK (>= 0.12.8)
pip install "agent-squad[dakera]"

# TypeScript — @dakera-ai/dakera is an optional peer dependency
npm install agent-squad @dakera-ai/dakera

# Swift — just add the package; transport is plain URLSession

In TypeScript the SDK is an optional peer dependency loaded lazily in the constructor, so installing agent-squad never pulls it in unless you actually use this retriever. The Swift port speaks to Dakera's REST API over URLSession, so it needs no third-party SDK at all. Point the retriever at your server and key with two environment variables:

export DAKERA_URL="http://localhost:3000"
export DAKERA_API_KEY="dk-..."

Attach it to an agent

Pass a DakeraRetriever as the retriever on any Agent Squad agent. From then on, every turn is grounded in what the retriever pulls back — the orchestrator handles the rest:

from agent_squad.agents import BedrockLLMAgent, BedrockLLMAgentOptions
from agent_squad.retrievers import DakeraRetriever, DakeraRetrieverOptions

orchestrator.add_agent(
    BedrockLLMAgent(BedrockLLMAgentOptions(
        name="My personal agent",
        description="Answers using context retrieved from Dakera.",
        streaming=True,
        inference_config={"temperature": 0.1},
        retriever=DakeraRetriever(DakeraRetrieverOptions(namespace="my-docs", top_k=5)),
    ))
)

The example uses BedrockLLMAgent because it is the framework's reference agent, but nothing here is Bedrock-specific — any Agent Squad agent that accepts a retriever can take a DakeraRetriever.

Or use it standalone

The retriever is async and works on its own, too. retrieve returns ranked results — each with .id, .score, .text, and .metadata — while retrieve_and_combine_results joins their text into a single newline-separated context string ready to drop into an LLM prompt (results with no text are skipped):

results = await retriever.retrieve("How many languages are spoken worldwide?")
for r in results:
    print(r.id, r.score, r.text)

context = await retriever.retrieve_and_combine_results("How many languages are spoken worldwide?")

In Python, both methods accept optional top_k and metadata_filter arguments that override the configured options for a single call — handy when one query needs a wider or more narrowly filtered pull than the agent's default:

results = await retriever.retrieve(
    "quarterly revenue",
    top_k=20,
    metadata_filter={"lang": {"$eq": "en"}},
)

The TypeScript and Swift retrieve methods take the query text only and use the values from the options object.

Options

The same five options exist in every language — names differ only in casing. Swift adds a few more for its tool-provider role (a request timeout, plus toolName / toolDescription for how the tool is advertised to the model).

OptionDefaultDescription
namespaceDakera namespace to query. Required — an empty value raises.
api_keyenvDakera dk-… token. Required here or via DAKERA_API_KEY.
urlenv / :3000Base URL of the server; falls back to DAKERA_URL then http://localhost:3000.
top_k10Max results per query.
filterNoneOptional Dakera metadata filter to narrow the search.

Return shape

Each result is one match from Dakera's text query. In Python and TypeScript results are TextSearchResult objects; in Swift each hit is a DakeraDocument with the same data, except the text field is named content:

FieldTypeMeaning
idstringThe stored document's vector id.
scorefloatSimilarity score; higher is more relevant.
text / contentstringThe document text (Swift names it content). Empty when the match stored no text.
metadataobjectWhatever metadata the document was stored with.

Three languages, one server

Python and TypeScript share the same DakeraRetriever and options (camelCased in TS), and in TypeScript it's a one-liner on the agent:

import { BedrockLLMAgent, DakeraRetriever } from "agent-squad";

orchestrator.addAgent(new BedrockLLMAgent({
  name: "My personal agent",
  inferenceConfig: { temperature: 0.1 },
  retriever: new DakeraRetriever({ namespace: "my-docs", topK: 5 }),
}));

The Swift SDK grounds answers through tools rather than a Retriever base class, so there DakeraRetriever is a ToolProvider: hand it to an Agent or GroundedAgent and the model can call a search_memory tool to ground its answers. The same type still exposes a direct retrieve(_:) for manual RAG:

import AgentSquad

let memory = DakeraRetriever(
    namespace: "my-docs",
    apiKey: "dk-...",          // or the DAKERA_API_KEY env var
    url: "http://localhost:3000",
    topK: 5
)

// Each hit is a DakeraDocument (.id / .content / .score / .metadata)
let docs = try await memory.retrieve("what does the user prefer?")
let context = try await memory.retrieveAndCombineResults("what does the user prefer?")

Methods by language

The retrieval surface is small and consistent. Python and TypeScript extend the framework Retriever; Swift is a ToolProvider that also offers direct retrieval.

PurposePythonTypeScriptSwift
Fetch ranked resultsretrieve(text, top_k=, metadata_filter=)retrieve(text)retrieve(_:)
Fetch a combined stringretrieve_and_combine_results(text, …)retrieveAndCombineResults(text)retrieveAndCombineResults(_:separator:)
Generate an answerretrieve_and_generate → raisesretrieveAndGenerate → throwsn/a — exposes search_memory

Retrieval-only, by design. DakeraRetriever uses Dakera's text-query API, which embeds server-side — so your agent process stays light and there's no ONNX model to bundle. In Python and TypeScript it implements retrieve and retrieve_and_combine_results and leaves the base class's retrieve_and_generate to raise (Python raises NotImplementedError; TypeScript throws) with a message pointing you at the retrieval methods. In Swift it exposes the search_memory tool plus retrieve. Grounding is Dakera's job; generation is your agent's — which keeps the answering model swappable and out of the retriever entirely.

Scoping what a retriever sees

Two options control the search surface. namespace (required) isolates one set of memories from another — give each agent, tenant, or collection its own namespace and their contexts stay separate. The optional filter narrows within a namespace using a Dakera metadata filter, so one namespace can back several agents that each see only their slice:

support = DakeraRetriever(DakeraRetrieverOptions(
    namespace="kb",
    filter={"team": {"$eq": "support"}},
))
billing = DakeraRetriever(DakeraRetrieverOptions(
    namespace="kb",
    filter={"team": {"$eq": "billing"}},
))

The filter is passed straight through to Dakera's text-query API — the same metadata-filter syntax you use elsewhere in Dakera. Because the retriever is read-only, the memories it searches are written separately: populate the namespace with the Dakera SDK, the REST text-upsert endpoint (which embeds server-side, mirroring the text-query the retriever uses), or another Dakera integration. Any agent with a DakeraRetriever on that namespace then grounds its answers in that content.

Why the retriever, not just the plumbing

Moving retrieval server-side only pays off if the server ranks well. Dakera's recall is decay-weighted — matches are scored by semantic relevance and tempered by recency, so an agent surfaces what's both on-topic and current rather than whatever was embedded first. On the full LoCoMo long-conversation benchmark — 50 sessions, 1,540 questions — Dakera scores 88.2% Recall@20, meaning the right supporting memory reliably lands in the results the agent reads. Because the retriever hands those ranked hits straight to your agent's prompt, better recall on the server translates directly into better-grounded answers — without changing a line of agent code.

And because the namespace is server-side and persistent, the same memory backs every agent in the squad and survives restarts. Populate it once and every DakeraRetriever pointed at that namespace reads the latest content — whether it's called from Python, TypeScript, or Swift.

Ground your squad in Dakera

Self-hosted and free — spin up Dakera with Docker and attach a retriever in minutes. Cloud managed hosting coming soon.

Self-Host Free → Integration docs →

Setup, the full options table, Swift examples, and troubleshooting are on the Agent Squad integration page → The retriever was merged in PR #553. Agent Squad joins Dakera's lineup alongside LangChain, LlamaIndex, CrewAI, AutoGen, Strands, Dify, PraisonAI, and the Vercel AI SDK — see them all on the integrations page →

Build with Dakera

Give your AI agents persistent memory — self-hosted, production-ready, zero dependencies.

Stay sharp on agent memory
Benchmark releases, engineering deep-dives, and product updates. Once a week max, no fluff.
✓ Subscribed. Thanks!