> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Google ADK

> Per-user long-term memory for Google Agent Development Kit agents.

ADK's memory service is an interface; Anona implements it. Wire
`AnonaMemoryService` in as your agent's `memory_service` and it can recall
across sessions, deployments and restarts, scoped to the right user
automatically, because ADK hands you `user_id` on every call.

```bash theme={null}
pip install 'anona[adk]'
```

## Setup

```python theme={null}
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools import load_memory
from anona.integrations import MemoryBridge
from anona.integrations.google_adk import AnonaMemoryService

bridge = MemoryBridge(api_key="anona_live_...", space_id="my-agent")

async def save_to_memory(callback_context):
    await callback_context.add_session_to_memory()

agent = LlmAgent(
    name="assistant",
    model="gemini-2.0-flash",
    tools=[load_memory],                   # lets the model search memory
    after_agent_callback=save_to_memory,   # stores the finished turn
)

runner = Runner(
    agent=agent,
    app_name="my-app",
    session_service=InMemorySessionService(),
    memory_service=AnonaMemoryService(bridge=bridge),
)
```

## Nothing here is automatic

Both directions require you to wire something. This is how ADK's memory
services work in general, not a limitation of this adapter.

**Reading: two options, different cost.** Attach the built-in `load_memory`
tool (above) and the model decides for itself when to call it, with zero calls
if it never asks. Attach `preload_memory` instead (`from google.adk.tools
import preload_memory`) and it runs on its own, with no model decision
involved. But "automatic" here is more expensive than it sounds: it fires
before **every model call**, not once per turn. A single tool-calling turn
(the model calls one tool, then answers) measured **2** separate retrieve
calls, both for the identical query, because ADK re-runs it once per model
step, not once per turn. Unlike this SDK's LangChain adapter, there is
no per-turn cache to collapse that back down. A three-tool-call turn costs
four retrieves for one logical question, all billed. Reach for
`preload_memory` only once that cost is a deliberate choice; `load_memory`
is the lower-cost default for most agents. You can also call
`await tool_context.search_memory(query)` directly from inside your own
tool or callback code if neither built-in shape fits.

**Writing.** A session is never saved to memory just because a run finished.
The supported hook is `Context.add_session_to_memory()`, reachable from any
callback that receives a context. `after_agent_callback`, as above, is the
natural place, since it fires once per `runner.run_async()` call, after any
internal tool-calling loop has finished. Skip this and Anona never receives
a single turn; the agent still runs, silently, with no memory. Note the
callback's parameter must be named exactly `callback_context`, because ADK invokes
it as a keyword argument.

You can also call `memory_service.add_session_to_memory(session)` directly:
`add_session_to_memory` on the callback context is just a shortcut for that,
using the callback's own session.

Calling this again on a session you've already saved, the normal shape of
a multi-turn conversation with one call per turn on the same `session.id`, is
safe: you don't need to track what you've already sent yourself. In the
common case (each call sees the same, growing event history, true of the
`after_agent_callback` idiom above, and of a fresh
`session_service.get_session(...)` fetch) only what's new goes out. If a call instead
receives a *different*, shorter view of the same session, for example a
trimmed fetch via `GetSessionConfig(num_recent_events=N)`, this can't
always tell which of those events it already saw, and resends the whole
view rather than guess and risk dropping something new. So: never a
silently lost turn; occasionally, in that specific case, a duplicate.

## Per-user isolation, at no cost

ADK passes `user_id` and `app_name` on every memory call, and this adapter
maps them onto Anona's scope, asymmetrically between writing and reading:

| ADK          | `add_session_to_memory` (write) | `search_memory` (read)                     |
| ------------ | ------------------------------- | ------------------------------------------ |
| `user_id`    | `user_id`                       | `user_id`                                  |
| `app_name`   | `agent_id`                      | *not forwarded*                            |
| `session.id` | `session_id`                    | *(ADK's `search_memory` takes no session)* |

`app_name` is written as `agent_id` but deliberately not forwarded on a
read. Anona consolidates raw memories into synthesized observations over
time, and an observation is tagged by *user* only, not by the agent that
happened to write the originating turns, so a two-key `user_id` +
`agent_id` search would never match a consolidated observation, only raw,
unconsolidated facts. Since `app_name` is the one scope key ADK supplies
automatically with no way to opt out, forwarding it on every read would have
silently hidden your agent's best answers behind its rawest ones, for every
ADK user, all the time.

Isolation itself is unaffected: `user_id` (the key that actually keeps one
end user's memories from another's) is forwarded on every call, read and
write alike. One space serves every user of your app, and a memory written
under one `user_id` is only ever returned to that same user. You configure
nothing.

## Failure behaviour

If Anona is unreachable, `search_memory` returns no memories and the agent
continues. Failures are logged, never raised.
