From 933f3bbfeed41ce0228feeadaeafdb132520fbda Mon Sep 17 00:00:00 2001 From: ajspig Date: Thu, 22 Jan 2026 15:52:33 -0500 Subject: [PATCH] fix: honcho chat queries the user peer, uses agno architecture --- examples/agno/python/README.md | 128 ++++++++------- .../python/examples/multi_peer_example.py | 154 ------------------ .../agno/python/examples/simple_example.py | 37 +++-- examples/agno/python/src/honcho_agno/tools.py | 113 +++++++------ 4 files changed, 160 insertions(+), 272 deletions(-) delete mode 100644 examples/agno/python/examples/multi_peer_example.py diff --git a/examples/agno/python/README.md b/examples/agno/python/README.md index d98b23ee..a19497d3 100644 --- a/examples/agno/python/README.md +++ b/examples/agno/python/README.md @@ -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 diff --git a/examples/agno/python/examples/multi_peer_example.py b/examples/agno/python/examples/multi_peer_example.py deleted file mode 100644 index ff6529e4..00000000 --- a/examples/agno/python/examples/multi_peer_example.py +++ /dev/null @@ -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() diff --git a/examples/agno/python/examples/simple_example.py b/examples/agno/python/examples/simple_example.py index ec23930b..c6aeedc2 100644 --- a/examples/agno/python/examples/simple_example.py +++ b/examples/agno/python/examples/simple_example.py @@ -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__": diff --git a/examples/agno/python/src/honcho_agno/tools.py b/examples/agno/python/src/honcho_agno/tools.py index d2732e4f..b1b4e3ab 100644 --- a/examples/agno/python/src/honcho_agno/tools.py +++ b/examples/agno/python/src/honcho_agno/tools.py @@ -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}"