fix: honcho chat queries the user peer, uses agno architecture

This commit is contained in:
ajspig 2026-01-22 15:52:33 -05:00
parent 2d9bf1c779
commit 933f3bbfee
4 changed files with 160 additions and 272 deletions

View File

@ -27,14 +27,10 @@ honcho = Honcho(workspace_id="my-app")
# Create Honcho tools for the agent
honcho_tools = HonchoTools(
peer_id="assistant",
session_id="session-123",
agent_id="assistant",
honcho_client=honcho,
)
# Create user peer for orchestration
user_peer = honcho.peer("user")
# Create an agent with memory tools
agent = Agent(
name="Memory Agent",
@ -43,16 +39,37 @@ agent = Agent(
description="An assistant with persistent memory powered by Honcho.",
)
# Add user message via orchestration
honcho_tools.session.add_messages([user_peer.message("I prefer Python over JavaScript")])
# Create peers and session for orchestration
user_peer = honcho.peer("user-123")
assistant_peer = honcho.peer("assistant")
session = honcho.session("session-123")
# Run the agent
response = agent.run("What programming language does the user prefer?")
# Add user message via orchestration
session.add_messages([user_peer.message("I prefer Python over JavaScript")])
# Run the agent - user_id and session_id flow through RunContext
response = agent.run(
"What programming language does the user prefer?",
user_id="user-123",
session_id="session-123",
)
# Save assistant response via orchestration
honcho_tools.session.add_messages([honcho_tools.peer.message(str(response.content))])
session.add_messages([assistant_peer.message(str(response.content))])
```
## How It Works
HonchoTools maps to Agno's user/assistant architecture:
| Agno Concept | Honcho Concept | Description |
|--------------|----------------|-------------|
| `user_id` (from RunContext) | Peer | The human user being queried about |
| `agent_id` (from init) | Peer | The AI assistant's identity |
| `session_id` (from RunContext) | Session | The conversation context |
**Key insight**: Tools query Honcho about the **USER**, not the agent. When the agent asks "What does this user prefer?", Honcho returns insights about the human user identified by `context.user_id`.
## Features
The `HonchoTools` toolkit provides three memory tools:
@ -61,7 +78,7 @@ The `HonchoTools` toolkit provides three memory tools:
|------|-------------|
| `honcho_get_context` | Retrieve conversation context within token limits |
| `honcho_search_messages` | Semantic search through past messages |
| `honcho_chat` | Query Honcho for synthesized insights about the conversation |
| `honcho_chat` | Query Honcho for synthesized insights about the user |
## Configuration
@ -76,9 +93,8 @@ honcho = Honcho(workspace_id="my-app")
# Create toolkit for an agent
tools = HonchoTools(
peer_id="assistant", # Identity for this agent
session_id="session-456", # Optional: specific session ID
honcho_client=honcho, # Shared Honcho client
agent_id="assistant", # Agent's identity in Honcho
honcho_client=honcho, # Shared Honcho client
)
```
@ -89,9 +105,8 @@ from honcho_agno import HonchoTools
# Creates its own Honcho client internally
tools = HonchoTools(
workspace_id="my-app", # Workspace ID (used to create internal client)
peer_id="assistant", # Identity for this agent
session_id="session-456", # Optional: auto-generated if not provided
workspace_id="my-app", # Workspace ID (used to create internal client)
agent_id="assistant", # Agent's identity
)
```
@ -116,77 +131,81 @@ Configure via `.env` file in the root honcho directory:
### honcho_get_context
Retrieve recent conversation context.
Retrieve recent conversation context. Uses `session_id` from RunContext.
```python
context = honcho_tools.honcho_get_context(
tokens=2000, # Max tokens to include (optional)
include_summary=True, # Include session summary (default: True)
)
# Called by the agent automatically with RunContext
# Or call directly with a mock context for testing
```
### honcho_search_messages
Search through past messages semantically.
Search through past messages semantically. Uses `session_id` from RunContext.
```python
results = honcho_tools.honcho_search_messages(
query="programming preferences",
limit=10, # Max results (default: 10)
)
# Called by the agent automatically with RunContext
# Query example: "programming preferences"
```
### honcho_chat
Ask questions about the conversation using Honcho's reasoning.
Ask questions about the user using Honcho's reasoning. Uses both `user_id` and `session_id` from RunContext.
```python
insights = honcho_tools.honcho_chat(
query="What programming languages does the user prefer?"
)
# Called by the agent automatically with RunContext
# Query example: "What programming languages does the user prefer?"
```
## Multi-Peer Conversations
## Multi-Agent Systems (Teams)
For multi-agent systems, create separate `HonchoTools` instances for each agent, sharing the same session:
Agno Teams share context within a run, but what about across runs? What if Agent A needs to remember what Agent B learned last week? That's where Honcho comes in.
```python
from agno.agent import Agent
from agno.team import Team
from honcho import Honcho
from honcho_agno import HonchoTools
# Shared Honcho client and session
# Shared Honcho client
honcho = Honcho(workspace_id="advisory-app")
session_id = "shared-session-123"
# Tech advisor agent
tech_tools = HonchoTools(
peer_id="tech-advisor",
session_id=session_id,
honcho_client=honcho,
# Tech advisor with Honcho memory
tech_tools = HonchoTools(agent_id="tech-advisor", honcho_client=honcho)
tech_agent = Agent(
name="Tech Advisor",
model=OpenAIChat(id="gpt-4o"),
tools=[tech_tools],
)
# Business advisor agent
biz_tools = HonchoTools(
peer_id="biz-advisor",
session_id=session_id,
honcho_client=honcho,
# Business advisor with Honcho memory
biz_tools = HonchoTools(agent_id="biz-advisor", honcho_client=honcho)
biz_agent = Agent(
name="Business Advisor",
model=OpenAIChat(id="gpt-4o"),
tools=[biz_tools],
)
# User peer for orchestration
user = honcho.peer("user")
# Create team
team = Team(
name="Advisory Team",
agents=[tech_agent, biz_agent],
)
# Add messages via orchestration (not toolkit methods)
tech_tools.session.add_messages([user.message("How should I scale my startup?")])
tech_tools.session.add_messages([tech_tools.peer.message("Consider microservices...")])
biz_tools.session.add_messages([biz_tools.peer.message("Focus on unit economics...")])
# Run with shared user_id and session_id
# Both agents query Honcho about the same user
response = team.run(
"How should I scale my startup?",
user_id="founder-123",
session_id="strategy-session",
)
```
## Architecture Notes
- **Read-only toolkit**: `HonchoTools` provides read access to Honcho (context, search, chat)
- **Orchestration pattern**: Message saving is handled by your orchestration code, not the toolkit
- **One peer per toolkit**: Each `HonchoTools` instance represents one agent identity
- **Shared sessions**: Multiple toolkits can share a session for multi-agent conversations
- **Orchestration pattern**: Message saving is handled by your orchestration code using `honcho.session().add_messages()`
- **RunContext integration**: `user_id` and `session_id` flow through Agno's RunContext automatically
- **Cross-run memory**: Unlike Agno Teams (context within a run), Honcho persists memory across runs
## Examples
@ -194,7 +213,6 @@ See the [examples](./examples) directory for complete working examples:
- `simple_example.py`: Basic usage with HonchoTools
- `multi_tool_example.py`: Using all tools together
- `multi_peer_example.py`: Multi-agent conversation with different perspectives
## Development

View File

@ -1,154 +0,0 @@
"""
Multi-Peer Honcho + Agno Example
A three-way conversation between:
- User: asking questions about life, work, and meaning
- Tech Bro Advisor: startup culture, hustle, optimization mindset
- Philosophy Guru: mindfulness, ancient wisdom, inner peace
All three peers observe each other and build representations on each other,
creating a rich understanding of each participant's perspective over time.
Environment Variables:
LLM_OPENAI_API_KEY: OpenAI API key (matches honcho .env)
HONCHO_API_KEY: Required for Honcho API access
"""
import os
import uuid
from dotenv import load_dotenv
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from honcho import Honcho
from honcho.session import SessionPeerConfig
from honcho_agno import HonchoTools
load_dotenv()
# Use LLM_OPENAI_API_KEY from honcho .env
if llm_key := os.getenv("LLM_OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = llm_key
def create_advisory_session(session_id: str):
"""
Creates a three-peer advisory system where:
- User asks questions
- Tech Bro gives startup/optimization perspective
- Philosophy Guru gives mindfulness/wisdom perspective
- All three observe each other and build representations
"""
model_id = os.getenv("OPENAI_MODEL", "gpt-4o")
# Shared Honcho client
honcho = Honcho(workspace_id="advisory-trio")
# === TECH BRO ADVISOR ===
tech_bro_tools = HonchoTools(
peer_id="tech-bro",
session_id=session_id,
honcho_client=honcho,
)
tech_bro_agent = Agent(
name="Tech Bro Advisor",
model=OpenAIChat(id=model_id),
tools=[tech_bro_tools],
description="Startup founder vibes, optimization mindset, hustle culture perspective.",
instructions=[
"You're a successful tech entrepreneur who's been through YC and raised Series B.",
"Everything is an opportunity to optimize, scale, or disrupt.",
"Use the honcho_chat tool to understand what the user is dealing with and what they care about.",
"Give advice through the lens of productivity, systems thinking, and growth hacking.",
"Reference things like morning routines, cold plunges, biohacking, and 10x thinking.",
"Be enthusiastic but genuine - you really believe this stuff works.",
"Keep responses conversational and punchy.",
],
)
# === PHILOSOPHY MEDITATION GURU ===
guru_tools = HonchoTools(
peer_id="philosophy-guru",
session_id=session_id,
honcho_client=honcho,
)
guru_agent = Agent(
name="Philosophy Guru",
model=OpenAIChat(id=model_id),
tools=[guru_tools],
description="Meditation teacher, draws on Stoicism, Buddhism, and Taoism.",
instructions=[
"You're a calm, wise meditation teacher who's spent years studying ancient philosophy.",
"Draw on Stoicism, Buddhism, Taoism, and other contemplative traditions.",
"Use the honcho_chat tool to understand the user's inner state and what they truly seek.",
"Gently guide toward presence, acceptance, and inner peace.",
"Reference concepts like impermanence, the present moment, letting go, and wu wei.",
"Offer a counterbalance to hustle culture - not everything needs to be optimized.",
"Speak slowly and thoughtfully. Use metaphors from nature.",
],
)
# Create user peer and configure session observation
user_peer = honcho.peer("user")
session = tech_bro_tools.session # Use session from toolkit
# Add all peers to session and configure observation
session.add_peers([user_peer, tech_bro_tools.peer, guru_tools.peer])
full_observation = SessionPeerConfig(
observe_me=True,
observe_others=True
)
session.set_peer_config(user_peer, full_observation)
session.set_peer_config(tech_bro_tools.peer, full_observation)
session.set_peer_config(guru_tools.peer, full_observation)
return session, user_peer, tech_bro_tools, guru_tools, tech_bro_agent, guru_agent
def main():
session_id = f"trio-{uuid.uuid4().hex[:8]}"
print(f"Session: {session_id}")
print("=" * 60)
session, user_peer, tech_bro_tools, guru_tools, tech_bro_agent, guru_agent = (
create_advisory_session(session_id)
)
print("\nAdvisory Trio Ready")
print("Ask about life, work, meaning - get two very different perspectives.")
print("Type 'quit' to exit.\n")
while True:
user_input = input("You: ").strip()
if not user_input:
continue
if user_input.lower() in ("quit", "exit", "q"):
break
# Save user message
session.add_messages([user_peer.message(user_input)])
# Tech Bro responds
print()
print("-" * 40)
tech_response = tech_bro_agent.run(user_input)
tech_content = str(tech_response.content) if tech_response.content else ""
session.add_messages([tech_bro_tools.peer.message(tech_content)])
print(f"Tech Bro: {tech_content}\n")
# Guru responds
print("-" * 40)
guru_response = guru_agent.run(user_input)
guru_content = str(guru_response.content) if guru_response.content else ""
session.add_messages([guru_tools.peer.message(guru_content)])
print(f"Guru: {guru_content}\n")
if __name__ == "__main__":
main()

View File

@ -1,6 +1,11 @@
"""
Simple Honcho + Agno Example
Demonstrates the RunContext integration:
- user_id and session_id are passed to agent.run()
- Tools automatically receive RunContext with these values
- Orchestration uses the honcho client directly
Environment Variables:
LLM_OPENAI_API_KEY: OpenAI API key (matches honcho .env)
HONCHO_API_KEY: Required for Honcho API access
@ -25,20 +30,24 @@ if llm_key := os.getenv("LLM_OPENAI_API_KEY"):
def main():
# Unique IDs for this run
user_id = "user-python-learner"
session_id = f"simple-{uuid.uuid4().hex[:8]}"
# Initialize Honcho client
honcho = Honcho(workspace_id="agno-demo")
# Initialize HonchoTools - creates peer and session internally
# Initialize HonchoTools with agent identity
# user_id and session_id come from RunContext at runtime
honcho_tools = HonchoTools(
peer_id="assistant",
session_id=session_id,
agent_id="assistant",
honcho_client=honcho,
)
# Create user peer (toolkit's peer is "assistant")
user_peer = honcho.peer("user")
# Create peers and session for orchestration
user_peer = honcho.peer(user_id)
assistant_peer = honcho.peer("assistant")
session = honcho.session(session_id)
# Create an agent with memory tools
agent = Agent(
@ -52,35 +61,39 @@ def main():
],
)
# Add user messages
# Add user messages via orchestration (not toolkit)
print("Adding user messages to conversation...")
honcho_tools.session.add_messages([
session.add_messages([
user_peer.message("I'm learning Python programming"),
user_peer.message("I'm also interested in web development with FastAPI"),
])
# The agent can now query memories and provide personalized responses
# user_id and session_id flow through RunContext to the tools
print("\nAsking the agent for recommendations...")
response = agent.run(
"Based on what you know about the user, what should they learn next? "
"Use the honcho_chat tool to understand their interests first."
"Use the honcho_chat tool to understand their interests first.",
user_id=user_id,
session_id=session_id,
)
# Save the assistant's response to Honcho
# Save the assistant's response via orchestration
assistant_response = str(response.content) if response.content else ""
if assistant_response:
honcho_tools.session.add_messages([honcho_tools.peer.message(assistant_response)])
session.add_messages([assistant_peer.message(assistant_response)])
print("\n" + "=" * 60)
print("RESPONSE")
print("=" * 60)
print(response.content)
# Show the full context
# Show the full context using the honcho client directly
print("\n" + "=" * 60)
print("SESSION CONTEXT")
print("=" * 60)
print(honcho_tools.honcho_get_context())
context = session.get_context()
print(context)
if __name__ == "__main__":

View File

@ -4,22 +4,22 @@ Honcho Tools for Agno
This module provides a Toolkit that allows Agno agents to interact with Honcho's
memory system, including session context, semantic search, and chat.
Each HonchoTools instance represents ONE agent identity (peer). The toolkit
provides read access to Honcho for querying conversation context.
Orchestration code will handle saving messages to avoid duplicates.
Designed for Agno's user/assistant architecture:
- user_id from RunContext Honcho peer (the human user)
- agent_id from init Honcho peer (the AI assistant)
- session_id from RunContext Honcho session (shared conversation)
Cross-run memory: Unlike Agno Teams which only share context within a run,
Honcho persists memory across runs. Agent A can remember what Agent B
learned last week.
"""
import logging
import uuid
from typing import TYPE_CHECKING
from agno.run import RunContext
from agno.tools import Toolkit
from honcho import Honcho
if TYPE_CHECKING:
from honcho.peer import Peer
from honcho.session import Session
logger = logging.getLogger(__name__)
@ -27,12 +27,14 @@ class HonchoTools(Toolkit):
"""
Honcho toolkit for Agno agents.
Each toolkit instance is for ONE agent identity.
Maps to Agno's user/assistant model:
- user_id from RunContext Honcho peer (the human being queried about)
- agent_id from init Honcho peer (the AI assistant's identity)
- session_id from RunContext Honcho session (the conversation)
For multi-peer conversations:
- Create one HonchoTools per agent, each with a different peer_id
- Share the same session_id across toolkits
- Messages are saved to Honcho by the orchestration code, not the toolkit
Tools query Honcho about the USER, not the agent. When the agent asks
"What does this user prefer?", Honcho returns insights about the human
user identified by run_context.user_id.
Example:
```python
@ -40,24 +42,26 @@ class HonchoTools(Toolkit):
from agno.models.openai import OpenAIChat
from honcho_agno import HonchoTools
# Initialize toolkit with agent identity
honcho_tools = HonchoTools(
workspace_id="my-app",
peer_id="assistant",
session_id="shared-session",
agent_id="travel-assistant",
)
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[honcho_tools],
)
# At runtime, pass user_id and session_id
agent.run("Plan my trip", user_id="user-123", session_id="conv-456")
```
"""
def __init__(
self,
workspace_id: str = "default",
peer_id: str = "assistant",
session_id: str | None = None,
agent_id: str = "assistant",
honcho_client: Honcho | None = None,
) -> None:
"""
@ -66,11 +70,8 @@ class HonchoTools(Toolkit):
Args:
workspace_id: Workspace ID for creating an internal Honcho client.
Ignored if honcho_client is provided.
peer_id: The identity this toolkit represents. This is who
the agent "is" when querying peer knowledge.
session_id: Optional session ID. If not provided, a new UUID
will be generated. Share this across toolkits for multi-peer
conversations.
agent_id: The agent's identity in Honcho. Used for message attribution
when the orchestration code saves messages.
honcho_client: Optional pre-configured Honcho client instance.
When provided, uses this client directly (workspace_id is ignored).
"""
@ -82,14 +83,7 @@ class HonchoTools(Toolkit):
else:
self.honcho = Honcho(workspace_id=workspace_id)
self.peer_id: str = peer_id
self.session_id: str = session_id or str(uuid.uuid4())
# Create the peer this toolkit represents
self.peer: Peer = self.honcho.peer(peer_id)
# Create or get session
self.session: Session = self.honcho.session(self.session_id)
self.agent_id: str = agent_id
# Register tools with honcho_ prefix to avoid conflicts with other toolkits
self.register(self.honcho_get_context)
@ -98,13 +92,18 @@ class HonchoTools(Toolkit):
def honcho_get_context(
self,
run_context: RunContext,
tokens: int | None = None,
include_summary: bool = True,
) -> str:
"""
Retrieve recent conversation context within token limits.
Uses run_context.session_id to identify which conversation to retrieve
context from.
Args:
run_context: Agno RunContext providing session_id (auto-injected).
tokens: Maximum number of tokens to include. If not specified,
returns all available context.
include_summary: Whether to include session summary in the context.
@ -113,17 +112,19 @@ class HonchoTools(Toolkit):
Formatted string containing conversation context.
"""
try:
context = self.session.get_context(
session = self.honcho.session(run_context.session_id)
result = session.get_context(
summary=include_summary,
tokens=tokens,
)
return str(context)
return str(result)
except Exception as e:
logger.exception("Error retrieving context")
return f"Error retrieving context: {e!s}"
def honcho_search_messages(
self,
run_context: RunContext,
query: str,
limit: int = 10,
) -> str:
@ -134,6 +135,7 @@ class HonchoTools(Toolkit):
history based on semantic meaning rather than exact keyword matching.
Args:
run_context: Agno RunContext providing session_id (auto-injected).
query: Search query for semantic matching.
limit: Number of results to return (1-100).
@ -141,48 +143,57 @@ class HonchoTools(Toolkit):
Formatted string with search results.
"""
try:
messages = self.session.search(query=query, limit=limit)
session = self.honcho.session(run_context.session_id)
messages = session.search(query=query, limit=limit)
if not messages:
return f"No messages found matching '{query}'"
result = [f"=== Search Results for '{query}' ({len(messages)} found) ==="]
results = [f"=== Search Results for '{query}' ({len(messages)} found) ==="]
for i, msg in enumerate(messages, 1):
result.append(f"\n{i}. [{msg.peer_id}] {msg.content}")
results.append(f"\n{i}. [{msg.peer_id}] {msg.content}")
if hasattr(msg, "created_at") and msg.created_at:
result.append(f" Created: {msg.created_at}")
results.append(f" Created: {msg.created_at}")
return "\n".join(result)
return "\n".join(results)
except Exception as e:
logger.exception("Error searching messages")
return f"Error searching messages: {e!s}"
def honcho_chat(self, query: str) -> str:
def honcho_chat(self, run_context: RunContext, query: str) -> str:
"""
Ask a question about what was discussed in this conversation.
Ask Honcho what it knows about the current user.
Use this tool to query session-specific context and facts.
The system uses Honcho reasoning to provide synthesized
insights based on the conversation history.
Queries the USER's peer (run_context.user_id) to get synthesized insights
about the human user based on their conversation history. This is how
the agent learns about user preferences, past discussions, and context.
Args:
query: Natural language question about the conversation.
Examples: "What did we discuss?", "What preferences should I be aware of?",
"What topics came up?"
run_context: Agno RunContext providing user_id and session_id (auto-injected).
query: Natural language question about the user.
Examples: "What are the user's preferences?",
"What topics has the user discussed?",
"What should I know about this user?"
Returns:
Synthesized response based on the session context.
Synthesized response about the user based on Honcho's memory.
"""
try:
response = self.peer.chat(
user_id = run_context.user_id
if not user_id:
return "Error: No user_id provided in RunContext"
# Query the USER's peer - this is who we want to learn about
user_peer = self.honcho.peer(user_id)
response = user_peer.chat(
query=query,
stream=False,
session=self.session_id,
session=run_context.session_id,
)
return str(response) if response else "No relevant information found."
except Exception as e:
logger.exception("Error querying conversation")
return f"Error querying conversation: {e!s}"
logger.exception("Error querying user information")
return f"Error querying user information: {e!s}"