Metadata-Version: 2.4
Name: memengine-adk
Version: 0.1.0
Summary: Google ADK plugin for MemEngine persistent memory layer
Project-URL: Homepage, https://github.com/ouyeelf/MemEngine
Project-URL: Documentation, https://github.com/ouyeelf/MemEngine/blob/main/sdk/memengine-adk/README.md
License: Apache-2.0
Requires-Python: >=3.10
Requires-Dist: google-adk>=0.1.0
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# memengine-adk

Google ADK plugin and tools for [MemEngine](https://github.com/ouyeelf/MemEngine) — a standalone persistent memory layer for AI agents.

[![PyPI version](https://img.shields.io/pypi/v/memengine-adk)](https://pypi.org/project/memengine-adk/)
[![Python](https://img.shields.io/pypi/pyversions/memengine-adk)](https://pypi.org/project/memengine-adk/)
[![License](https://img.shields.io/pypi/l/memengine-adk)](LICENSE)

---

## What is this?

`memengine-adk` integrates [MemEngine](https://github.com/ouyeelf/MemEngine) into Google ADK agents, giving them persistent long-term memory across sessions without changing your agent logic.

MemEngine runs as a separate HTTP service. This package is the ADK-side adapter.

---

## Requirements

- Python 3.10+
- `google-adk >= 0.1.0`
- A running MemEngine instance

---

## Installation

```bash
pip install memengine-adk
```

Start MemEngine before running your agent:

```bash
uvicorn memengine.main:app --host 0.0.0.0 --port 8001
```

Admin UI: http://localhost:8001/admin

---

## Integration Approaches

| Approach | Memory happens | Agent changes needed | Best for |
|---|---|---|---|
| **Plugin** | Every turn, automatically | None | Production chatbots, assistants |
| **Tools** | When agent decides | Add tool descriptions | Autonomous agents |
| **Callbacks** | You control everything | Full custom integration | Advanced use cases |

---

## Approach 1: Plugin (Recommended)

The plugin hooks into ADK's `Runner` and handles all memory reads and writes automatically.

```python
from google.adk.agents import LlmAgent
from google.adk.runners import InMemoryRunner
from memengine_adk import MemEnginePlugin

plugin = MemEnginePlugin(
    base_url="http://localhost:8001",
    user_id="user_001",       # stable user identifier
    agent_id="my_assistant",  # memory namespace
    top_k=5,                  # memories to inject per turn
)

agent = LlmAgent(
    name="my_agent",
    model="gemini-2.0-flash",
    instruction="You are a helpful personal assistant.",
)

runner = InMemoryRunner(
    agent=agent,
    app_name="my_app",
    plugins=[plugin],
)

async for event in runner.run_async(
    user_id="user_001",
    session_id="session_abc",
    new_message="My name is Alice and I prefer dark mode.",
):
    if event.is_final_response():
        print(event.content.parts[0].text)
```

### How the plugin works

| Callback | When it runs | What it does |
|---|---|---|
| `on_user_message_callback` | User sends a message | Enqueues async long-term memory write evaluation |
| `before_model_callback` | Before each LLM call | Calls `/memory/process`, injects `system_prompt` |
| `after_model_callback` | After each LLM reply | Caches assistant reply for next turn's write evaluation |

### MemEnginePlugin parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `base_url` | str | `http://localhost:8001` | MemEngine service URL |
| `user_id` | str | `default_user` | Stable user identifier |
| `agent_id` | str | `adk_agent` | Memory namespace — different agent_ids are fully isolated |
| `top_k` | int | `5` | Max long-term memories injected per turn |
| `enabled` | bool | `True` | Set `False` to disable without removing the plugin |

---

## Approach 2: Tools (Explicit Agent Control)

Exposes memory as ADK `FunctionTool`s. The agent decides when to save and fetch.

```python
from google.adk.agents import LlmAgent
from google.adk.runners import InMemoryRunner
from memengine_adk import MemEngineSaveTool, MemEngineFetchTool

save_tool = MemEngineSaveTool(
    base_url="http://localhost:8001",
    user_id="user_001",
    agent_id="my_assistant",
)
fetch_tool = MemEngineFetchTool(
    base_url="http://localhost:8001",
    user_id="user_001",
    agent_id="my_assistant",
)

agent = LlmAgent(
    name="my_agent",
    model="gemini-2.0-flash",
    instruction="""You are a helpful personal assistant with persistent memory.

Use fetch_memories at the start of conversations to recall what you know about the user.
Use save_memory when the user shares something important (name, preferences, decisions).""",
    tools=[save_tool, fetch_tool],
)

runner = InMemoryRunner(agent=agent, app_name="my_app")
```

### Available tools

**`save_memory(content, chat_id)`**
Save important information to long-term memory. Use when the user shares something worth remembering across future conversations.

**`fetch_memories(query, limit)`**
Retrieve stored memories. Supports optional keyword filter. Returns up to `limit` records (default 10).

---

## Approach 3: Callbacks (Manual Integration)

Use `MemEngineClient` directly for full control in custom runners or non-standard ADK setups.

```python
from memengine_adk import MemEngineClient

client = MemEngineClient(base_url="http://localhost:8001")

# Before LLM call — get enriched system_prompt
result = await client.process(
    user_id="user_001",
    agent_id="my_assistant",
    chat_id="session_abc",
    user_input=user_message,
    assistant_output=last_reply,
)
system_prompt = result["system_prompt"]

# List memories
memories = await client.list_memories(user_id="user_001", agent_id="my_assistant")
for m in memories:
    print(m.content, m.score)

await client.aclose()
```

### MemEngineClient methods

| Method | Description |
|---|---|
| `process(user_id, agent_id, chat_id, user_input, assistant_output)` | Process a turn, returns `system_prompt` and `long_term_written` |
| `list_memories(user_id, agent_id, limit, q)` | List long-term memories with optional keyword filter |
| `aclose()` | Close the underlying HTTP client |

---

## Environment Variables

All constructor parameters can be set via environment variables for convenience:

```bash
export MEMENGINE_URL=http://localhost:8001
export MEMENGINE_USER_ID=user_001
export MEMENGINE_AGENT_ID=my_assistant
```

```python
import os
plugin = MemEnginePlugin(
    base_url=os.getenv("MEMENGINE_URL", "http://localhost:8001"),
    user_id=os.getenv("MEMENGINE_USER_ID", "default_user"),
    agent_id=os.getenv("MEMENGINE_AGENT_ID", "adk_agent"),
)
```

---

## Running the Examples

```bash
cd sdk/memengine-adk

# Plugin demo (automatic memory)
python -m examples.plugin_demo.agent

# Tools demo (agent-controlled memory)
python -m examples.tools_demo.agent
```

Both examples read `MEMENGINE_URL`, `MEMENGINE_USER_ID`, `MEMENGINE_AGENT_ID` from environment.

---

## Memory Concepts

**agent_id is the namespace.** Memories are scoped by `(user_id, agent_id)`. Two agents with different `agent_id` values have completely separate memory spaces for the same user.

**Long-term writes are async.** When `long_term_written > 0`, it means the turn was enqueued for memory extraction — not instantly persisted. The MemEngine background worker processes it shortly after.

**system_prompt replaces your static instruction.** The value returned by `/memory/process` already includes your agent's configured base instruction plus short-term context and long-term memories. If you use the Plugin, this is injected automatically.

---

## Links

- [MemEngine GitHub](https://github.com/ouyeelf/MemEngine)
- [MemEngine Admin UI](http://localhost:8001/admin)
- [MemEngine API Reference](http://localhost:8001/llms.txt)
- [Interactive API Docs](http://localhost:8001/scalar)
