Agent Squad Integration
DakeraRetriever plugs Dakera's server-side semantic search into Agent Squad agents as a retriever. Attach it to an agent and every turn is grounded in the most relevant memories from a Dakera namespace — no local embedding model, in Python, TypeScript, or Swift.
agent_squad.retrievers.DakeraRetriever · Source PR →Overview
Agent Squad (formerly the AWS Multi-Agent Orchestrator) routes each user message to the best agent in a squad and streams back its answer. Any agent can carry a retriever — an object the framework calls to fetch supporting context before the LLM generates. DakeraRetriever is that object, backed by a self-hosted Dakera memory server: it queries a Dakera namespace over Dakera's text-query API and hands the agent the most relevant stored documents to answer from.
Embedding happens server-side — the retriever sends raw query text and Dakera embeds, ranks, and returns matches. Nothing about the integration ships a local embedding model, ONNX runtime, or vector index into your process, in any of the three supported languages.
When to use it
- You already run (or will run) a Dakera server and want an Agent Squad agent to answer from what lives there.
- You want persistent, decay-weighted recall across sessions rather than a per-request, in-memory vector store.
- You want the same retrieval behaviour across a Python backend, a TypeScript/Node service, and a Swift app — one API, three ports.
- You want retrieval and generation to stay cleanly separated: Dakera ranks context, your agent's LLM writes the answer.
How a retriever fits the orchestrator
The orchestrator itself doesn't call Dakera. It classifies the incoming message, picks an agent, and invokes it; the agent owns the retriever. When the selected agent has 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. Add a retriever to one agent, several agents, or every agent — each queries whatever namespace (and filter) you configured for it.
Unlike a read-write memory, DakeraRetriever only reads: it ranks and returns the most relevant memories for each turn, and your agent’s own model writes the answer. Populate the namespace with the Dakera SDK, the REST API, or another Dakera integration.
Quick Start
Run Dakera
git clone https://github.com/dakera-ai/dakera-deploy
cd dakera-deploy && docker compose up -d # API on :3000
Install
# Python — the [dakera] extra pulls in the dakera SDK (>= 0.12.8)
pip install "agent-squad[dakera]"
# TypeScript — Dakera SDK is an optional peer dependency
npm install agent-squad @dakera-ai/dakera
# Swift — add the package; no third-party SDK needed
# .package(url: "https://github.com/2FastLabs/agent-squad", ...)
The Python [dakera] extra depends on dakera >= 0.12.8. In TypeScript, @dakera-ai/dakera (^0.11.100) is an optional peer dependency, loaded lazily in the constructor — installing agent-squad never pulls it in unless you actually use this retriever. The Swift port talks to Dakera's REST API over URLSession, so it needs no extra package at all.
Configure
export DAKERA_URL="http://localhost:3000"
export DAKERA_API_KEY="dk-..."
Attach the retriever to an agent
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 questions using context retrieved from Dakera.",
streaming=True,
inference_config={"temperature": 0.1},
retriever=DakeraRetriever(DakeraRetrieverOptions(
namespace="my-docs",
top_k=5,
)),
))
)
TypeScript
The TypeScript port has the same shape — pass a DakeraRetriever as the agent's retriever. Options are camelCased (topK rather than top_k).
import { BedrockLLMAgent, DakeraRetriever } from "agent-squad";
orchestrator.addAgent(
new BedrockLLMAgent({
name: "My personal agent",
description: "Answers questions using context retrieved from Dakera.",
streaming: true,
inferenceConfig: { temperature: 0.1 },
retriever: new DakeraRetriever({ namespace: "my-docs", topK: 5 }),
})
);
Swift
The Swift SDK grounds answers through tools rather than a Retriever base class, so DakeraRetriever is a ToolProvider: hand it to an Agent or GroundedAgent and the model can call a search_memory tool to ground its answers on remembered facts. The same type also exposes a direct retrieve(_:) API for manual RAG. It talks to Dakera's REST API over URLSession — no third-party SDK is pulled into the package.
import AgentSquad
// As a tool provider — the agent can call the search_memory tool
let memory = DakeraRetriever(
namespace: "my-docs",
apiKey: "dk-...", // or the DAKERA_API_KEY env var
url: "http://localhost:3000",
topK: 5
)
// Direct retrieval — 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?")
Standalone retrieval
DakeraRetriever is async and uses Dakera's text-query API (server-side embedding). retrieve returns the raw ranked results; retrieve_and_combine_results joins their text into a single newline-separated context string ready to pass to an LLM (results with no text are skipped).
import asyncio
from agent_squad.retrievers import DakeraRetriever, DakeraRetrieverOptions
retriever = DakeraRetriever(DakeraRetrieverOptions(namespace="my-docs", top_k=5))
# Raw results — each has .id / .score / .text / .metadata
results = asyncio.run(retriever.retrieve("How many languages are spoken worldwide?"))
for r in results:
print(r.id, r.score, r.text)
# Or a single combined context string, ready to hand to an LLM
context = asyncio.run(
retriever.retrieve_and_combine_results("How many languages are spoken worldwide?")
)
Per-call overrides (Python)
In Python, retrieve and retrieve_and_combine_results accept optional top_k and metadata_filter arguments that override the options for a single call — useful when one agent needs a wider or more narrowly filtered pull than its default:
# Override the configured top_k and filter for this query only
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.
Return shape
Each result is one match from Dakera's text query. In Python and TypeScript the results are TextSearchResult objects with id, score, text, and metadata. In Swift each hit is a DakeraDocument with the same data, except the text field is named content:
| Field | Type | Meaning |
|---|---|---|
id | string | The stored document's vector id. |
score | float | Similarity score; higher is more relevant. |
text / content | string | The document text (Swift names it content). Empty when the match stored no text. |
metadata | object | Whatever metadata the document was stored with. |
DakeraRetrieverOptions
The same five options exist in every language (names differ only in casing). Swift adds three more for its tool-provider role.
| Option (py / ts / swift) | Type | Default | Description |
|---|---|---|---|
namespace | string | — | Dakera namespace to query. Required; an empty value raises an error. |
api_key / apiKey | string? | DAKERA_API_KEY env | Dakera API key (a dk-… token). Required — set here or via the env var, else construction fails. |
url | string | DAKERA_URL env · else http://localhost:3000 | Base URL of the Dakera server. |
top_k / topK | int | 10 | Maximum number of results to return per query. |
filter | object? | None / undefined / nil | Optional Dakera metadata filter applied to the query (see Scoping). |
timeout (Swift) | TimeInterval | 30 | Request timeout, in seconds. |
toolName (Swift) | String | "search_memory" | The name the retrieval tool is advertised under to the model. |
toolDescription (Swift) | String | built-in prompt | The description the retrieval tool is advertised with to the model. |
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.
| Purpose | Python | TypeScript | Swift |
|---|---|---|---|
| Fetch ranked results | retrieve(text, top_k=, metadata_filter=) | retrieve(text) | retrieve(_:) |
| Fetch a combined string | retrieve_and_combine_results(text, …) | retrieveAndCombineResults(text) | retrieveAndCombineResults(_:separator:) |
| Generate an answer | retrieve_and_generate → raises | retrieveAndGenerate → throws | n/a — exposes search_memory tool |
Configuring via environment
Rather than hard-coding credentials, set DAKERA_API_KEY (and optionally DAKERA_URL) in the environment and construct the retriever with just a namespace — every language reads those two variables as fallbacks:
export DAKERA_API_KEY="dk-..."
export DAKERA_URL="http://localhost:3000"
# api_key and url now come from the environment
retriever = DakeraRetriever(DakeraRetrieverOptions(namespace="my-docs", top_k=5))
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 document collection its own namespace and their contexts never bleed together. 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. Leave filter unset to search the whole namespace.
# One namespace, two agents, different slices via metadata filter
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 (for example {"lang": {"$eq": "en"}}). In TypeScript the field is typed as a FilterExpression; in Swift it is any JSONValue.
Retrieval-only by design
DakeraRetriever deliberately does not generate answers. In Python and TypeScript it extends the framework's Retriever base class and implements two methods — retrieve and retrieve_and_combine_results — while the third the base class defines, retrieve_and_generate, is overridden to raise (Python raises NotImplementedError; TypeScript throws) with a message directing you to the retrieval methods. The rationale is a clean split of concerns: Dakera's job is to rank and return the most relevant context, and your agent's LLM (Bedrock, OpenAI, or whatever the agent wraps) writes the response from it. That keeps the agent process light — no generation model bundled into the retriever — and lets you swap the answering model freely.
retrieve / retrieve_and_combine_results and raise on retrieve_and_generate. Swift instead surfaces retrieval as a search_memory tool (a ToolProvider) plus a direct retrieve — same server-side ranking, native to how the Swift SDK grounds agents. No generation happens inside the retriever in any language.Populating the namespace
Because DakeraRetriever is retrieval-only, the memories it searches are written separately — the retriever reads whatever already lives in the namespace. Populate it with the Dakera SDK or REST API (its text-upsert endpoint embeds server-side, mirroring the text-query the retriever uses), or point another Dakera integration at the same namespace. Any agent with a DakeraRetriever on that namespace then grounds its answers in that content.
How it works
- Each turn, the agent calls the retriever with the user's query.
DakeraRetrieversends that query to Dakera's text-query API, which embeds it server-side — no embedding model runs inside your agent, in any of the three languages.- Dakera returns the
top_kmost relevant memories from the namespace (optionally narrowed byfilter), each with an id, score, text, and metadata. retrieve_and_combine_resultsflattens those into a single context string; your agent's LLM answers from it. Generation stays entirely on your side.
Under the hood the query is a single call to Dakera's text-query endpoint (POST /v1/namespaces/{namespace}/query-text), authenticated with an X-API-Key header and carrying the query text, top_k, and any filter. The Python and TypeScript ports make that call through the official Dakera SDK; the Swift port issues the request directly over URLSession. The API key travels only as a header — it is never logged, echoed into arguments, or included in the tool trace.
Troubleshooting
| Symptom | Cause & fix |
|---|---|
| ImportError / "requires the optional peer dependency" | The Dakera SDK isn't installed. Run pip install "agent-squad[dakera]" (Python) or npm install @dakera-ai/dakera (TypeScript). Swift needs no extra package. |
| "namespace is required in options" | namespace was empty. Every retriever must target a namespace. |
| "api_key is required" / "apiKey is required" | No key was found. Pass api_key/apiKey in the options or set the DAKERA_API_KEY environment variable. |
| "Input text is required for retrieve" | Python and TypeScript raise on an empty query string. (Swift instead returns an empty result list for a blank/whitespace query without calling the server.) |
| Connection refused / wrong host | The retriever defaults to http://localhost:3000. Point url (or DAKERA_URL) at your server, and confirm the dakera-deploy stack is up. |
| Empty results every time | The namespace has no matching documents, or your filter excludes them all. Confirm the namespace is populated and loosen or drop the filter. |
| Non-2xx from the server (Swift) | Surfaced as DakeraRetrieverError.server(status:message:); a 401 usually means a bad or missing API key. |
Related integrations
Links
- Source PR — 2FastLabs/agent-squad #553
- Dakera deploy — Docker Compose setup
- Dakera quickstart
- RAG-Augmented Memory pattern
- All integrations
Frequently Asked Questions
How do I ground an Agent Squad agent in Dakera?
Install agent-squad[dakera], run a self-hosted Dakera server, then pass a DakeraRetriever (with your namespace) as the retriever on any agent. It fetches the top-k most relevant memories server-side each turn and the agent answers from them.
Which languages does the Dakera retriever support?
Python, TypeScript, and Swift. Python and TypeScript ship a Retriever-based DakeraRetriever with matching options; Swift ships a ToolProvider that exposes a search_memory tool plus a direct retrieve API.
Does the retriever generate answers?
No — it is retrieval-only. It returns ranked context (or a combined context string) for your agent's LLM to answer from. In Python and TypeScript, retrieve_and_generate is intentionally left to raise; grounding is Dakera's job and generation is your agent's.
Do I need a local embedding model?
No. Dakera embeds the query server-side over its text-query API, so nothing about the integration bundles an embedding model, ONNX runtime, or vector index into your process.
How do I put documents into the namespace?
Separately from the retriever, which is read-only. Write to the namespace with the Dakera SDK or REST API (its text-upsert endpoint embeds server-side, mirroring the text-query the retriever uses), or point another Dakera integration at the same namespace.
Does it only work with Bedrock agents?
No. Any Agent Squad agent that accepts a retriever can take a DakeraRetriever; the examples use BedrockLLMAgent because it is the framework's reference agent.
What does the retriever connect to by default?
http://localhost:3000 — the default port of the dakera-deploy stack. Override it with the url option or the DAKERA_URL environment variable.
Ready to ship?
Deploy Dakera in minutes — self-hosted and free. Cloud managed hosting coming soon.
Give your AI agents persistent memory
Self-host free today — or join the Dakera Cloud waitlist for managed hosting, SLA & founder pricing.