DatabricksAgent BricksMCPOctober 21, 2026

Databricks Runs Your Agents. Caura Gives Them One Memory.

We plugged Caura into an Agent Bricks agent over MCP and put a Claude agent on the same tenant. We told the Databricks agent about a trip. The Claude agent planned dinner around it, then corrected the date, and a brand-new Databricks session already knew. Here’s the run, how it fits together, and the code it took.

Caura.AI · 10 min read

Databricks agent
Agent Bricks · Qwen3 Next 80B
Saved
“allergic to peanuts”
Caura
one tenant · shared, governed memory
caura.ai/mcp
Claude agent
Claude Agent SDK · Sonnet
Corrected
“flight moved to July 2”

Databricks is a leading platform for enterprise AI agents, with Agent Bricks, Model Serving and Unity Catalog built in. But as agents multiply across teams, frameworks and models, the question changes. It’s no longer whether one agent can remember. It’s how all of them share what they learn, safely.

Caura answers that with one memory every agent can reach over MCP, whatever its harness or model, under one set of rules. This post walks through a real run with two agents on different stacks, shows how the pieces fit, and lists what the integration took.

01A Session Ends. What the Agent Learned Shouldn’t.

A Databricks agent already remembers the conversation in progress. Agent Bricks keeps each session’s transcript, in Lakebase once you bind it. What the agent doesn’t get on its own is memory that outlives the session, finds facts by meaning, and is shared with agents that don’t run on Databricks at all.

That’s the layer Caura adds. Any agent that speaks MCP reads and writes the same tenant. Every memory is attributed to the agent that wrote it and enriched in the background. All of it is bound by keystones: rules every agent loads on every turn.

Databricks
Session store

The transcript of one conversation, keyed by session. The client sends only the new message, and the server rebuilds the earlier turns.

Caura
Long-term memory

Facts that outlive the session: preferences, plans, decisions. Recalled by meaning, and shared with every agent on the tenant.

Caura
Keystones

Mandatory rules, fetched in code at the start of every turn. They hold even when the user asks otherwise.

Databricks also offers managed agent memory, in Beta, which keeps long-term facts per user in Lakebase. In this build Caura fills that slot, and adds sharing across agents, harnesses and models, with every memory attributed and governed.

02One Agent Learns What Another Was Told

The demo’s two-agent page puts the agents side by side. On the left is a Databricks agent: the OpenAI Agents SDK on Agent Bricks, with Qwen3 Next 80B from Databricks Model Serving. On the right is a Claude agent: the Claude Agent SDK with Claude Sonnet. They share nothing but a Caura tenant.

This is a real run from October 21, 2026, with both agents running locally against a Databricks workspace on AWS and caura.ai, each writing under its own agent ID: databricks-blog and claude-blog. Four messages:

  1. 1Databricks agent“I’m allergic to peanuts and I’m flying to Lisbon on June 30.”
  2. 2Claude agentnew chat“Plan dinner for the night I land.”
  3. 3Claude agent“Actually, the flight moved to July 2.”
  4. 4Databricks agentnew chat“When do I fly?”
The two-agent page after step 1. The Databricks agent shows two Saved to memory cards, and the shared memory column lists both facts under databricks-blog.
Fig 1Step 1, learn. The Databricks agent saves both facts. The middle column is the shared memory, listed straight from Caura.
The Claude agent's reply in step 2: two Searched memory cards with memories written on the Databricks side, then a dinner plan for the first night in Lisbon with a section on the peanut allergy.
Fig 2Step 2, recall elsewhere. The Claude agent was never told about the trip. Both of its searches came back with memories written on the Databricks side, and the plan accounts for the allergy.
The shared memory column after step 3: the July 2 memory from claude-blog at the top, the June 30 memory from databricks-blog struck through as replaced, and the peanut allergy still live.
Fig 3Step 3, correct. The June 30 memory belongs to databricks-blog. claude-blog replaced it, and the allergy saved beside it was left alone.
The two-agent page after step 4. In a new chat, the Databricks agent searched memory, found the memory written by claude-blog, and answered that the flight is on July 2.
Fig 4Step 4, new session. In a new chat, the Databricks agent searches memory, finds the memory claude-blog wrote, and answers that the flight is on July 2.

03How It Fits Together

Everything inside the Databricks App is ordinary Python. The Agents SDK loop runs inside Agent Bricks’ DurableAgentServer, and Caura is one more MCP server in the agent’s tool list. The only path out of the workspace is the MCP connection to caura.ai.

Databricks workspace
Databricks App · serverless
Agent Bricks runtime
DurableAgentServer streams each reply to the chat UI as server-sent events
OpenAI Agents SDK loop
instructions plus keystones · history from the session store
Local tools
get_current_time
whoami
plain Python function tools
CauraServer
agent/caura.py
fills in agent_id on every call
3 of 12 tools, manage cut down
flags what a write replaces
Model Serving
Qwen3 Next 80B
OpenAI-compatible
Lakebase
session store
run state
Secret scope
caura-demo/api-key
MLflow
tracing
experiment
Runs as the app’s service principal when deployed, and as your CLI profile locally
MCP over HTTPS, through the account’s egress policy
Caura tenant
Memories
recall by meaning
vector · keyword · graph
Enrichment
type · PII · entities
contradiction checks
Keystones
rules for every agent
trust levels
Caura Prism
who wrote what
recall counts
audit trail
caura.ai/mcp · streamable HTTP · X-API-Key · one agent_id per caller
Other agents, same tenant
Claude agent · Claude Agent SDK · SonnetClaude CodeCodexany MCP client, on any model
ArchitectureCaura sits outside the workspace. Any MCP-capable agent on the same tenant reads and writes the same memory, whatever its model or harness. If Caura is unreachable, the turn runs without memory and the UI’s badge says so.

Inside the tenant, recall works by meaning, so the right fact surfaces however it’s asked. Writes are linked through shared entities, so related facts come back together, and outdated facts are retired, so every agent works from the current truth. Every memory is attributed to its author, protected by trust levels, bound by keystones and screened for personal data. In Caura Prism, the team sees who wrote each memory, how often it’s recalled, and an audit trail of every change.

04What the Integration Took

About 530 lines in one file, agent/caura.py, demo helpers included. The Agents SDK speaks MCP’s streamable HTTP natively, so no bridge process is needed. Most of the code is there because something went wrong without it.

Connect over MCP, and keep three tools

Subclass the SDK’s MCP server class, so the wrapper sits between the model and Caura. Caura has twelve tools. An open-weight model does better with three: recall, write, and manage.

agent/caura.pyexcerpt
TOOLS = ["caura_recall", "caura_write", "caura_manage"]

class CauraServer(MCPServerStreamableHttp):
    def __init__(self, api_key: str, agent_id: str, url: str) -> None:
        super().__init__(
            params={"url": url, "headers": {"X-API-Key": api_key}, "timeout": 30},
            name="caura",
            cache_tools_list=True,
            client_session_timeout_seconds=30,
            tool_filter=create_static_tool_filter(allowed_tool_names=TOOLS),
        )
        self.agent_id = agent_id

Give the agent its identity in code

A tenant key carries no agent identity, so Caura rejects calls without an agent_id. Leave it to the model and sooner or later it forgets one or invents one. The wrapper removes agent_id from every schema the model sees and adds it to every call. That’s how every memory in the run ended up attributed to the right agent.

agent/caura.pyexcerpt
async def call_tool(self, tool_name, arguments, meta=None) -> CallToolResult:
    arguments, error = narrow(tool_name, arguments or {})
    if error:
        return _error(error)
    result = await super().call_tool(
        tool_name, {**arguments, "agent_id": self.agent_id}, meta
    )
    ...

Cut down the dangerous tool

caura_manage can bulk-delete. The model gets a smaller schema: archive one memory, or correct it. The SDK’s tool_filter only hides tools from list_tools, and call_tool still forwards whatever the model sends. So narrow() enforces the same limits before anything reaches Caura, and a model that ignores the schema gets an error back.

agent/caura.pyexcerpt
NARROWED = {
    "caura_manage": {
        "description": "Forget or correct one memory, using its id from caura_recall. ...",
        "inputSchema": {"type": "object", "properties": {
            "op": {"type": "string", "enum": ["transition", "update"]},
            "memory_id": {"type": "string"},
            "status": {"type": "string", "enum": ["archived"]},
            "content": {"type": "string"}},
            "required": ["op", "memory_id"]},
    },
}

Catch replaced facts at write time

Caura’s contradiction check runs in the background, after the write, and it missed reworded facts. Asking the model to recall before it writes didn’t work either, because Qwen skipped the recall. So after every successful write, the wrapper recalls the new sentence itself. Anything that scores 0.7 or higher goes back in the tool result, with a next step. Small models follow tool results more reliably than instructions, so the model archives the stale fact in the same turn.

agent/caura.pyexcerpt
REPLACES_SIMILARITY = 0.7

async def after_write(call, content, written) -> dict | None:
    ...
    results = (await call("caura_recall", {"query": content, "top_k": 5}))["results"]
    close = [
        {"id": memory["id"], "content": memory.get("content")}
        for memory in results
        if memory.get("id") != saved_id
        and memory.get("status") not in FORGOTTEN
        and (memory.get("similarity") or 0) >= REPLACES_SIMILARITY
    ]
    # -> {"existing_memories_on_this_topic": [...],
    #     "next_step": "If the new fact replaces one of these,
    #                   archive that one with caura_manage."}
An expanded Saved to memory card. The arguments hold the July 2 sentence. The result shows what was saved, then existing_memories_on_this_topic with the id and text of the June 30 memory.
Fig 5From the run, step 3. The Claude agent's write came back with the June 30 memory attached. Its next tool call archived that memory.
0.50.60.70.80.91.0
unrelated facts about the user, 0.55–0.66a fact that replaces another, 0.72–0.82cutoff 0.7the same sentence again, 1.0
Fig 6Why 0.7: on a real tenant, facts that replace an older one scored 0.72 to 0.82 (Berlin to Lisbon 0.72, teal to green 0.82). Unrelated facts about the same user stayed at 0.55 to 0.66.
The Databricks agent chat. The user says their favorite color is now green. The Saved to memory card's result lists the existing teal memory under existing_memories_on_this_topic with a next step, and the agent's next card archives it.
Fig 7The same check in the single-agent chat. “Actually, my favorite color is green” comes back with the teal memory attached, and the agent archives it in the same turn.

Load keystones every turn

Keystones aren’t offered as a tool, so the model can’t skip them. Code fetches them once the connection is up and appends them to the instructions as mandatory rules. A failed lookup never fails the turn.

agent/caura.pyexcerpt
async def keystones(server: MCPServer) -> list[str]:
    try:
        result = await server.call_tool("caura_keystones", {})
        data = json.loads(result.content[0].text)
    except Exception as exc:  # a keystone lookup must never fail the turn
        logger.warning("Continuing without Caura keystones: %s", exc)
        return []
    return [rule["content"] for rule in data.get("rules", []) if rule.get("content")]

Tolerate failures, and say so

Each request opens its own MCP connection. MCPServerManager collects connection errors instead of raising them, so an unreachable Caura means a turn without memory, not a failed turn. The status goes to the UI as the first event of every turn. Otherwise an offline memory just looks like an agent that forgets.

agent/agent.pyexcerpt
servers = caura.caura_servers()  # [] when CAURA_API_KEY isn't set
async with MCPServerManager(servers) as mcp:
    rules = await caura.keystones(mcp.active_servers[0]) if mcp.active_servers else []
    memory = {
        "status": "online" if mcp.active_servers else "offline" if servers else "disabled",
        "keystones": len(rules),
    }
    agent = create_agent(mcp.active_servers, rules)

05What We Measured

All runs used a real Caura tenant and a Databricks workspace on AWS, with databricks-qwen3-next-80b-a3b-instruct unless noted. The Claude agent ran Claude Sonnet.

3 of 12
Caura tools the model sees
12 of 12
facts saved unprompted, up from 0 of 9
20 of 20
correct archives after rewording
9 of 9
cross-agent checks, 3 of 3 runs
~530
lines, the whole integration

The second number came from the instructions. With an example sentence in the prompt, Qwen echoed the sentence back instead of calling the tool, every time. Describing the shape of a memory (“one self-contained sentence”) rather than giving an example took it to 12 of 12. The archive number came the same way. Telling the model that “a version pin is the version a service is held on” got it to archive memories phrased in other words, 20 of 20 times.

Six things we’d tell the next team

Caura
Tenant keys have no agent identity

Calls fail with MISSING_AGENT_ID unless each one names its agent.

Do this: Inject agent_id in the wrapper, and hide it from the schema.

Caura
Recall returns forgotten memories

Archived memories come back with their status set, so forgetting looked like it had failed.

Do this: Filter on status before the model sees results.

Caura
Recall is tenant-wide

Memories from other agents come back too. That's the point of shared memory, and it also means one agent's test data shows up for another.

Do this: Test on a scratch tenant, and use visibility scopes for private facts.

Model
Small models follow tool results, not instructions

Qwen ignored “recall first, then archive”, but acted every time on a list of related memories inside the caura_write result.

Do this: Put the next step where the model is already looking.

Model
Tool count matters

With four tools, Llama 4 Maverick called the wrong one, or wrote the call out as text, in 2 of 4 runs. Qwen3 Next passed every run.

Do this: Keep the list short, and test the model you'll ship.

Agents SDK
tool_filter only hides tools

It filters list_tools. call_tool still forwards any name and arguments the model sends.

Do this: Enforce the cut-down schema in call_tool too.

06Deploying as a Databricks App

Locally, the key lives in a gitignored .env. On Databricks it goes in a secret scope, attached to the app as a resource and mapped to an environment variable in app.yaml. No secrets end up in code.

terminalDatabricks CLI
databricks secrets create-scope caura-demo
databricks secrets put-secret caura-demo api-key   # paste the mc_ key at the prompt
uv run agentbricks deploy caura-demo
app.yamlexcerpt
command: [uv, run, start-server]
env:
  - name: CAURA_API_KEY
    valueFrom: caura-api-key   # an app resource that points at caura-demo/api-key

Serverless compute, Apps included, reaches the internet through the account’s network policy. If yours is restricted, allow caura.ai as a destination. MCP is ordinary HTTPS, so there’s no separate switch for it.


Give Your Databricks Agents a Memory They Share

Agent Bricks, a Claude agent, Claude Code, Codex: anything that speaks MCP can join the same tenant, under the same rules.

  1. Sign up at caura.ai. Signing up creates your first tenant.
  2. Create an mc_ key in Connect › Keys.
  3. Point your agent at https://caura.ai/mcp, with the key in X-API-Key and an agent_id on every call.

Measurements come from this project’s test runs, on one Caura tenant and two models, so treat thresholds such as 0.7 as starting points.

Databricks runs your agents.
Caura gives every agent one memory.

Shared, governed memory for agents on any harness and any model, over MCP.

Databricks, Agent Bricks and Lakebase are trademarks of Databricks, Inc. Claude is a trademark of Anthropic.