Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions examples/openai-agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Honcho Memory Integration for the OpenAI Agents SDK

Give your [OpenAI Agents SDK](https://github.com/openai/openai-agents-python) agents persistent memory using [Honcho](https://honcho.dev).

## Features

- **Persistent Memory**: Every conversation turn is saved to Honcho and automatically injected into the agent's instructions on the next turn.
- **Natural Language Recall**: The agent can query Honcho's Dialectic API to answer questions like "What are my hobbies?" or "What did we talk about last time?"
- **Context Injection**: Conversation history is retrieved from Honcho and formatted for the LLM before every request via dynamic `instructions`.
- **Zero Boilerplate**: Pass a `HonchoContext` to `Runner.run()` — the tools and instructions handle the rest.

## Installation

```bash
pip install honcho-ai openai-agents python-dotenv
```

Or with uv:

```bash
uv add honcho-ai openai-agents python-dotenv
```

## Environment Variables

Create a `.env` file:

```env
HONCHO_API_KEY=your-honcho-api-key
HONCHO_WORKSPACE_ID=default
OPENAI_API_KEY=your-openai-api-key
```

Get your Honcho API key at [honcho.dev](https://honcho.dev).

## Quick Start

```python
import asyncio
from agents import Agent, RunContextWrapper, Runner
from tools.client import HonchoContext
from tools.get_context import get_context
from tools.query_memory import query_memory
from tools.save_memory import save_memory


def honcho_instructions(ctx: RunContextWrapper[HonchoContext], agent: Agent) -> str:
base = "You are a helpful assistant with persistent memory powered by Honcho."
history = get_context(ctx.context, tokens=2000)
if not history:
return base
formatted = "\n".join(f"{m['role'].title()}: {m['content']}" for m in history)
return f"{base}\n\n## Conversation History\n{formatted}"


agent = Agent[HonchoContext](
name="HonchoMemoryAgent",
instructions=honcho_instructions,
tools=[query_memory],
model="gpt-4.1-mini",
)


async def chat(user_id: str, message: str, session_id: str) -> str:
ctx = HonchoContext(user_id=user_id, session_id=session_id)
save_memory(user_id, message, "user", session_id)
result = await Runner.run(agent, message, context=ctx)
response = str(result.final_output)
save_memory(user_id, response, "assistant", session_id)
return response


# Run a conversation turn
response = asyncio.run(chat("alice", "I love hiking in the mountains", "session-1"))
print(response)
```

Run the interactive demo:

```bash
python main.py
```

## How It Works

### 1. Dynamic Instructions

The agent uses a callable `instructions` function instead of a static string. Before every LLM call, the SDK invokes this function with the current `RunContextWrapper`. The function calls `get_context()` to fetch recent messages from Honcho and injects them into the system prompt:

```
You are a helpful assistant with persistent memory powered by Honcho.

## Conversation History
User: I love hiking
Assistant: That sounds wonderful! Do you have a favorite trail?
```

### 2. Memory Tools

The `query_memory` tool is exposed to the LLM via `@function_tool`. When the user asks "What do you remember about me?", the agent calls this tool to query Honcho's Dialectic API — a semantic memory layer that synthesizes observations about the user into a natural language answer.

### 3. Auto-Save

The `chat()` helper in `main.py` wraps `Runner.run()` to save the user message before the run and the assistant response after. This keeps Honcho in sync with every conversation turn.

## API Reference

### `HonchoContext`

```python
@dataclass
class HonchoContext:
user_id: str # Unique identifier for the human peer
session_id: str # Identifier for the current conversation session
assistant_id: str # Peer ID for the assistant (default: "assistant")
```

Pass this as the `context` argument to `Runner.run()`.

---

### `save_memory(user_id, content, role, session_id, assistant_id="assistant")`

Saves a message to Honcho. Creates the peer and session if they don't exist.

| Param | Type | Description |
|---|---|---|
| `user_id` | `str` | Unique user identifier |
| `content` | `str` | Message text |
| `role` | `str` | `"user"` or `"assistant"` |
| `session_id` | `str` | Session identifier |
| `assistant_id` | `str` | Peer ID for the assistant (default: `"assistant"`) |

---

### `get_context(ctx, tokens=2000)`

Returns recent conversation history from Honcho as OpenAI-format message dicts.

| Param | Type | Description |
|---|---|---|
| `ctx` | `HonchoContext` | Context with user, session, and assistant IDs |
| `tokens` | `int` | Max tokens to include (default: `2000`) |

Returns `list[dict[str, str]]` — suitable for direct use as LLM input.

---

### `query_memory` (agent tool)

A `@function_tool` decorated function the agent calls to query Honcho's Dialectic API.

| Param | Type | Description |
|---|---|---|
| `ctx` | `RunContextWrapper[HonchoContext]` | Injected automatically by the SDK |
| `query` | `str` | Natural language question about the user |

Returns a natural language answer from Honcho's memory.

## Concept Mapping

| OpenAI Agents SDK | Honcho |
|---|---|
| `context.user_id` | Peer (human) |
| `context.assistant_id` | Peer (agent) |
| `context.session_id` | Session |
| `Runner.run()` input | Message |

## Running Tests

```bash
# Structural tests (no API keys required)
pytest tests/test_basic.py -v

# Integration tests (requires HONCHO_API_KEY)
pytest tests/test_integration.py -v
```

## License

AGPL-3.0-or-later
135 changes: 135 additions & 0 deletions examples/openai-agents/python/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"""OpenAI Agents SDK integration with Honcho persistent memory.

Demonstrates a conversational agent that remembers users across sessions.
Honcho stores every message and builds a long-term representation of the user;
the agent injects that context into its instructions on every turn and can
query memory on demand via the ``query_memory`` tool.

Usage:
python main.py

Environment variables:
HONCHO_API_KEY Required. Your Honcho API key from honcho.dev.
HONCHO_WORKSPACE_ID Optional. Workspace ID (default: "default").
OPENAI_API_KEY Required. Your OpenAI API key.
"""

import asyncio
import logging
import uuid

from agents import Agent, RunContextWrapper, Runner
from honcho.http.exceptions import HonchoError

from tools.client import HonchoContext, get_client
from tools.get_context import get_context
from tools.query_memory import query_memory
from tools.save_memory import save_memory

logger = logging.getLogger(__name__)


def setup_session(user_id: str, session_id: str, assistant_id: str = "assistant") -> None:
"""Register peers in the session once at startup.

Should be called once before the conversation loop begins. Calling
``add_peers`` on every turn is redundant — this ensures peers are
registered exactly once.

Args:
user_id: Unique identifier for the user peer.
session_id: Identifier for the conversation session.
assistant_id: Peer ID for the assistant. Defaults to ``"assistant"``.

Raises:
RuntimeError: If the Honcho API call fails.
"""
try:
honcho = get_client()
user_peer = honcho.peer(user_id)
assistant_peer = honcho.peer(assistant_id)
session = honcho.session(session_id)
session.add_peers([user_peer, assistant_peer])
except HonchoError as exc:
raise RuntimeError("Failed to initialize Honcho session peers") from exc


honcho_agent = Agent[HonchoContext](
name="HonchoMemoryAgent",
instructions=(
"You are a helpful assistant with persistent memory powered by Honcho. "
"You remember users across conversations. "
"When a user asks what you remember about them, use the query_memory tool."
),
tools=[query_memory],
model="gpt-4.1-mini",
)


async def chat(
user_id: str,
message: str,
session_id: str,
assistant_id: str = "assistant",
) -> str:
"""Run one conversation turn with persistent Honcho memory.

Fetches prior history first, then saves the user message, runs the agent,
and saves the assistant reply. History is read before persisting the current
turn to avoid duplicating it in the prompt.

Args:
user_id: Unique identifier for the user.
message: The user's input message.
session_id: Identifier for the current conversation session.
assistant_id: Peer ID for the assistant. Defaults to ``"assistant"``.

Returns:
The agent's response as a string.
"""
ctx = HonchoContext(user_id=user_id, session_id=session_id, assistant_id=assistant_id)

# Fetch prior history BEFORE saving the current turn to avoid duplicating it
try:
history = get_context(ctx, tokens=2000)
except HonchoError as exc:
logger.warning("Could not load Honcho context; continuing without history: %s", exc)
history = []
input_messages = [*history, {"role": "user", "content": message}]

# Persist user message before the agent runs
try:
save_memory(user_id, message, "user", session_id, assistant_id=assistant_id)
except HonchoError as exc:
logger.warning("Could not persist user message: %s", exc)

result = await Runner.run(honcho_agent, input_messages, context=ctx)
response = str(result.final_output)

# Persist assistant response after the run
try:
save_memory(user_id, response, "assistant", session_id, assistant_id=assistant_id)
except HonchoError as exc:
logger.warning("Could not persist assistant message: %s", exc)

return response


if __name__ == "__main__":
print("HonchoMemoryAgent — type 'quit' to exit\n")
# Replace "demo-user" with a real user identifier in production.
_user_id = "demo-user"
# A fresh session ID per run prevents history from accumulating across runs.
_session_id = str(uuid.uuid4())

# Register peers once at session start — not on every turn.
setup_session(_user_id, _session_id)

while True:
_user_input = input("You: ").strip()
if not _user_input:
continue
if _user_input.lower() in ("quit", "exit"):
break
_response = asyncio.run(chat(_user_id, _user_input, _session_id))
print(f"Agent: {_response}\n")
26 changes: 26 additions & 0 deletions examples/openai-agents/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[project]
name = "honcho-openai-agents"
version = "0.1.0"
description = "Honcho persistent memory integration for the OpenAI Agents SDK"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"honcho-ai>=2.1.0",
"openai-agents>=0.0.3",
"python-dotenv>=1.0.0",
]

[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["tools"]

[tool.pytest.ini_options]
pythonpath = ["."]
Loading