fix: adding tools, changing init, general cleanup

This commit is contained in:
ajspig 2026-01-15 13:14:22 -05:00
parent 74c199aae5
commit c3dd0baabb
4 changed files with 190 additions and 287 deletions

View File

@ -1,14 +1,16 @@
"""
Multi-Peer Honcho + Agno Example
A realistic multi-agent scenario using Agno's patterns:
- A coordinator agent routes questions to specialists
- Each specialist has its own HonchoTools (identity)
- All share the same session for conversation continuity
- The coordinator uses specialists as tools
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:
OPENAI_API_KEY or LLM_OPENAI_API_KEY: OpenAI API key
LLM_OPENAI_API_KEY: OpenAI API key (matches honcho .env)
HONCHO_API_KEY: Required for Honcho API access
"""
@ -19,155 +21,110 @@ from dotenv import load_dotenv
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools import tool
from honcho import Honcho
from honcho.session import SessionPeerConfig
from honcho_agno import HonchoTools
load_dotenv()
if not os.getenv("OPENAI_API_KEY") and (llm_key := os.getenv("LLM_OPENAI_API_KEY")):
# 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_advisor_system(session_id: str):
def create_advisory_session(session_id: str):
"""
Creates a multi-agent advisory system where:
- Each specialist agent has its own identity (HonchoTools)
- A coordinator routes to specialists and synthesizes responses
- All agents share the same conversation session
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 and session
honcho = Honcho(workspace_id="advisory-system")
session = honcho.session(session_id)
# Shared Honcho client
honcho = Honcho(workspace_id="advisory-trio")
# === TECH BRO ADVISOR ===
tech_bro_tools = HonchoTools(
app_id="advisory-trio",
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 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(
app_id="advisory-trio",
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 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
# === SPECIALIST AGENTS ===
# Each has its own identity via HonchoTools
# Add all peers to session and configure observation
session.add_peers([user_peer, tech_bro_tools.peer, guru_tools.peer])
tech_tools = HonchoTools(
app_id="advisory-system",
peer_id="tech-specialist",
session_id=session_id,
honcho_client=honcho,
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)
tech_agent = Agent(
name="Tech Specialist",
model=OpenAIChat(id=model_id),
tools=[tech_tools],
description="Technical advisor for architecture, implementation, and technology choices.",
instructions=[
"Focus on technical feasibility and implementation details.",
"Use get_context to understand what's been discussed.",
"Save key technical recommendations with add_message.",
"Be concise - you're part of a team.",
],
)
business_tools = HonchoTools(
app_id="advisory-system",
peer_id="business-specialist",
session_id=session_id,
honcho_client=honcho,
)
business_agent = Agent(
name="Business Specialist",
model=OpenAIChat(id=model_id),
tools=[business_tools],
description="Business advisor for strategy, market fit, and ROI.",
instructions=[
"Focus on business viability and market considerations.",
"Use get_context to understand what's been discussed.",
"Save key business insights with add_message.",
"Be concise - you're part of a team.",
],
)
# === COORDINATOR TOOLS ===
# Wrap specialists as tools the coordinator can invoke
@tool
def consult_tech_specialist(question: str) -> str:
"""
Consult the technical specialist for architecture, implementation,
or technology-related questions.
Args:
question: The technical question to ask.
Returns:
Technical specialist's response.
"""
response = tech_agent.run(question)
return str(response.content) if response.content else ""
@tool
def consult_business_specialist(question: str) -> str:
"""
Consult the business specialist for strategy, market fit,
or ROI-related questions.
Args:
question: The business question to ask.
Returns:
Business specialist's response.
"""
response = business_agent.run(question)
return str(response.content) if response.content else ""
# Coordinator has its own identity too
coordinator_tools = HonchoTools(
app_id="advisory-system",
peer_id="coordinator",
session_id=session_id,
honcho_client=honcho,
)
coordinator = Agent(
name="Advisory Coordinator",
model=OpenAIChat(id=model_id),
tools=[coordinator_tools, consult_tech_specialist, consult_business_specialist],
description="Coordinates between specialists to provide comprehensive advice.",
instructions=[
"Use get_context to understand the full conversation history.",
"Route technical questions to the tech specialist.",
"Route business questions to the business specialist.",
"Synthesize specialist inputs into actionable recommendations.",
"Save your final synthesis with add_message.",
],
)
return coordinator, session, user_peer
return session, user_peer, tech_bro_tools, guru_tools, tech_bro_agent, guru_agent
def main(test_mode: bool = False):
session_id = f"advisory-{uuid.uuid4().hex[:8]}"
def main():
session_id = f"trio-{uuid.uuid4().hex[:8]}"
print(f"Session: {session_id}")
print("=" * 60)
coordinator, session, user_peer = create_advisor_system(session_id)
session, user_peer, tech_bro_tools, guru_tools, tech_bro_agent, guru_agent = (
create_advisory_session(session_id)
)
if test_mode:
# Non-interactive test
test_question = "I want to build a SaaS product for small businesses. What should I consider?"
print(f"\n[TEST MODE] User: {test_question}\n")
session.add_messages([user_peer.message(test_question)])
response = coordinator.run(test_question)
print(f"Advisor: {response.content}\n")
print("=" * 60)
print("Test completed successfully!")
return
# Interactive chat loop
print("\nAdvisory System Ready")
print("Ask questions about building a product. Type 'quit' to exit.\n")
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()
@ -176,15 +133,24 @@ def main(test_mode: bool = False):
if user_input.lower() in ("quit", "exit", "q"):
break
# Save user message to session
# Save user message
session.add_messages([user_peer.message(user_input)])
# Coordinator handles routing and synthesis
response = coordinator.run(user_input)
print(f"\nAdvisor: {response.content}\n")
# 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__":
import sys
test_mode = "--test" in sys.argv
main(test_mode=test_mode)
main()

View File

@ -1,20 +1,17 @@
"""
Honcho Multi-Tool Example
Demonstrates using all Honcho tools with an Agno agent:
- add_message: Store agent responses (attributed to the toolkit's peer)
- get_context: Retrieve session context
- search_messages: Semantic search
- query_peer: Dialectic API queries about any peer
Demonstrates using Honcho tools with an Agno agent:
- chat: Ask questions about the conversation (recommended)
- get_context: Retrieve raw session context
- search_messages: Semantic search through messages
Pattern: toolkit = agent identity
- HonchoTools represents the assistant's identity
- User messages are added via Honcho directly
- The agent uses tools to query context and save its responses
The chat tool is the recommended way to understand users
It reasons over conversation context and provides synthesized insights.
Environment Variables:
OPENAI_API_KEY or LLM_OPENAI_API_KEY: OpenAI API key
OPENAI_MODEL: Model to use (default: gpt-4o)
LLM_OPENAI_API_KEY: OpenAI API key (matches honcho .env)
OPENAI_MODEL: Model to use
HONCHO_API_KEY: Required for Honcho API access
"""
@ -30,8 +27,8 @@ from honcho_agno import HonchoTools
load_dotenv()
# Support both OPENAI_API_KEY and LLM_OPENAI_API_KEY
if not os.getenv("OPENAI_API_KEY") and (llm_key := os.getenv("LLM_OPENAI_API_KEY")):
# Use LLM_OPENAI_API_KEY from honcho .env
if llm_key := os.getenv("LLM_OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = llm_key
@ -40,20 +37,21 @@ def main():
print("HONCHO TOOLS + AGNO EXAMPLE")
print("=" * 70 + "\n")
# Initialize Honcho for managing the session and user peer
# Initialize Honcho client
honcho = Honcho(workspace_id="travel-app")
session = honcho.session("trip-planning-session")
user_peer = honcho.peer("traveler-42")
# Setup Honcho tools - this IS the assistant's identity
# Setup Honcho tools - creates peer and session internally
honcho_tools = HonchoTools(
app_id="travel-app",
peer_id="travel-assistant", # The toolkit speaks as "travel-assistant"
peer_id="travel-assistant",
session_id="trip-planning-session",
honcho_client=honcho,
)
# Pre-populate with user's travel preferences (via Honcho directly)
# Create user peer (the toolkit's peer is "travel-assistant")
user_peer = honcho.peer("traveler-42")
# Pre-populate with user's travel preferences
print("Adding user's travel preferences to memory...")
messages = [
"I'm planning a trip to Japan in March",
@ -64,7 +62,7 @@ def main():
]
for msg in messages:
session.add_messages([user_peer.message(msg)])
honcho_tools.session.add_messages([user_peer.message(msg)])
print(f" [traveler-42]: {msg[:50]}...")
print("\n" + "-" * 70 + "\n")
@ -75,42 +73,54 @@ def main():
model=OpenAIChat(id=os.getenv("OPENAI_MODEL", "gpt-4o")),
tools=[honcho_tools],
description=(
"A travel planning expert with access to memory tools. "
"Use get_context for recent conversation, search_messages to find "
"specific preferences, and query_peer to understand the traveler."
"A travel planning expert with access to Honcho memory tools. "
"Use chat to understand the traveler's preferences and travel style."
),
instructions=[
"Always retrieve relevant context before making recommendations",
"Use search to find specific preferences mentioned",
"Use query_peer with target_peer_id='traveler-42' to understand their travel style",
"Use the chat tool to understand the user's preferences and travel style",
"Ask both broad and specific questions like 'What is their travel style?' or 'What is their budget?'",
"Only use get_context or search_messages if you need raw message history",
"Be specific and actionable in your recommendations",
"Use add_message to save your recommendations to the conversation",
],
)
# Run the agent with a planning request
print("Asking agent to create a personalized itinerary...\n")
response = agent.run(
"Create a 3-day Tokyo itinerary for me. First, use the memory tools to "
"understand my preferences (budget, accommodation style, interests), "
"then create a personalized plan that matches what I've told you."
"Create a 3-day Tokyo itinerary for me. Use the chat tool to ask about "
"my budget, accommodation preferences, and interests, then create "
"a personalized plan that matches my travel style."
)
# Save the assistant's response to Honcho (using toolkit's peer and session)
assistant_response = str(response.content) if response.content else ""
if assistant_response:
honcho_tools.session.add_messages([honcho_tools.peer.message(assistant_response)])
print("=" * 70)
print("RESPONSE")
print("=" * 70)
print(response.content)
# Demonstrate chat (recommended)
print("\n" + "=" * 70)
print("DIRECT TOOL USAGE: chat (recommended)")
print("=" * 70)
chat_result = honcho_tools.chat(
"What are the traveler's key preferences and constraints?"
)
print(chat_result)
# Demonstrate search capability
print("\n" + "=" * 70)
print("DIRECT TOOL USAGE: Searching for budget info...")
print("DIRECT TOOL USAGE: search_messages")
print("=" * 70)
search_result = honcho_tools.search_messages("budget money cost", limit=5)
print(search_result)
# Show full conversation context
print("\n" + "=" * 70)
print("FULL SESSION CONTEXT")
print("DIRECT TOOL USAGE: get_context")
print("=" * 70)
print(honcho_tools.get_context())

View File

@ -2,7 +2,7 @@
Simple Honcho + Agno Example
Environment Variables:
OPENAI_API_KEY or LLM_OPENAI_API_KEY: OpenAI API key
LLM_OPENAI_API_KEY: OpenAI API key (matches honcho .env)
HONCHO_API_KEY: Required for Honcho API access
"""
@ -19,28 +19,28 @@ from honcho_agno import HonchoTools
load_dotenv()
# Support both OPENAI_API_KEY and LLM_OPENAI_API_KEY
if not os.getenv("OPENAI_API_KEY") and (llm_key := os.getenv("LLM_OPENAI_API_KEY")):
# 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 main():
# Create shared session
session_id = f"simple-{uuid.uuid4().hex[:8]}"
# Initialize Honcho directly for managing user messages
# Initialize Honcho client
honcho = Honcho(workspace_id="agno-demo")
session = honcho.session(session_id)
user_peer = honcho.peer("user")
# Initialize HonchoTools - this IS the assistant's identity
# Initialize HonchoTools - creates peer and session internally
honcho_tools = HonchoTools(
app_id="agno-demo",
peer_id="assistant", # The toolkit speaks as "assistant"
session_id=session_id, # Same session as user
honcho_client=honcho, # Reuse client
peer_id="assistant",
session_id=session_id,
honcho_client=honcho,
)
# Create user peer (toolkit's peer is "assistant")
user_peer = honcho.peer("user")
# Create an agent with memory tools
agent = Agent(
name="Programming Mentor",
@ -48,15 +48,14 @@ def main():
tools=[honcho_tools],
description="A programming mentor that remembers user interests and progress.",
instructions=[
"Use get_context to understand the conversation history",
"Use query_peer to ask about the user's preferences",
"Use add_message to save your responses to the conversation",
"Use the chat tool to understand the user's preferences and interests",
"Use get_context if you need raw conversation history",
],
)
# Add user messages via Honcho directly
# Add user messages
print("Adding user messages to conversation...")
session.add_messages([
honcho_tools.session.add_messages([
user_peer.message("I'm learning Python programming"),
user_peer.message("I'm also interested in web development with FastAPI"),
])
@ -65,9 +64,14 @@ def main():
print("\nAsking the agent for recommendations...")
response = agent.run(
"Based on what you know about the user, what should they learn next? "
"Use get_context to see the conversation history first."
"Use the chat tool to understand their interests first."
)
# Save the assistant's response to Honcho
assistant_response = str(response.content) if response.content else ""
if assistant_response:
honcho_tools.session.add_messages([honcho_tools.peer.message(assistant_response)])
print("\n" + "=" * 60)
print("RESPONSE")
print("=" * 60)

View File

@ -2,17 +2,16 @@
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 dialectic API.
memory system, including session context, semantic search, and chat.
Each HonchoTools instance represents ONE agent identity (peer). The toolkit
speaks as that peer when adding messages or querying the dialectic. For
multi-peer conversations, create separate toolkit instances or use Honcho
directly to manage other peers.
provides read access to Honcho for querying conversation context.
Orchestration code will handle saving messages to avoid duplicates.
"""
import logging
import uuid
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING
from agno.tools import Toolkit
from honcho import Honcho
@ -28,14 +27,12 @@ class HonchoTools(Toolkit):
"""
Honcho toolkit for Agno agents.
Each toolkit instance represents ONE agent identity. The peer_id parameter
defines who this toolkit "speaks as" - all messages added through this
toolkit are attributed to that peer.
Each toolkit instance is for ONE agent identity.
For multi-peer conversations:
- Create one HonchoTools per agent, each with a different peer_id
- Share the same session_id across toolkits
- Use Honcho directly for peers not represented by an agent
- Messages are saved to Honcho by the orchestration code, not the toolkit
Example:
```python
@ -43,7 +40,6 @@ class HonchoTools(Toolkit):
from agno.models.openai import OpenAIChat
from honcho_agno import HonchoTools
# This toolkit IS the assistant - it speaks as "assistant"
honcho_tools = HonchoTools(
app_id="my-app",
peer_id="assistant",
@ -62,8 +58,6 @@ class HonchoTools(Toolkit):
app_id: str = "default",
peer_id: str = "assistant",
session_id: str | None = None,
api_key: str | None = None,
base_url: str | None = None,
honcho_client: Honcho | None = None,
) -> None:
"""
@ -72,31 +66,21 @@ class HonchoTools(Toolkit):
Args:
app_id: Application/workspace ID for scoping operations.
Maps to Honcho's workspace_id.
peer_id: The identity this toolkit represents. All messages
added through this toolkit are attributed to this peer.
This is who the agent "is" in the conversation.
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.
api_key: Optional API key for Honcho. If not provided, will
attempt to read from HONCHO_API_KEY environment variable.
base_url: Optional base URL for the Honcho API.
honcho_client: Optional pre-configured Honcho client instance.
If provided, other connection parameters are ignored.
If provided, app_id is ignored.
"""
super().__init__(name="honcho")
# Initialize Honcho client
self.honcho: Honcho
if honcho_client is not None:
self.honcho = honcho_client
else:
client_kwargs: dict[str, Any] = {"workspace_id": app_id}
if api_key is not None:
client_kwargs["api_key"] = api_key
if base_url is not None:
client_kwargs["base_url"] = base_url
self.honcho = Honcho(**client_kwargs)
self.honcho = Honcho(workspace_id=app_id)
# Store identifiers
self.app_id: str = app_id
@ -104,38 +88,15 @@ class HonchoTools(Toolkit):
self.session_id: str = session_id or str(uuid.uuid4())
# Create the peer this toolkit represents
# This is THE identity of this toolkit - one toolkit = one voice
self.peer: Peer = self.honcho.peer(peer_id)
# Create or get session
self.session: Session = self.honcho.session(self.session_id)
# Register tools
self.register(self.add_message)
self.register(self.get_context)
self.register(self.search_messages)
self.register(self.query_peer)
def add_message(self, content: str) -> str:
"""
Store a message in the current session as this agent.
Use this tool to save your responses or important information
to the conversation history. The message is attributed to this
toolkit's peer identity.
Args:
content: The message content to store.
Returns:
Confirmation message indicating the memory was saved.
"""
try:
self.session.add_messages([self.peer.message(content)])
return f"Message saved as '{self.peer_id}' to session {self.session_id}"
except Exception as e:
logger.exception("Error saving message")
return f"Error saving message: {e!s}"
self.register(self.chat)
def get_context(
self,
@ -145,9 +106,6 @@ class HonchoTools(Toolkit):
"""
Retrieve recent conversation context within token limits.
Use this tool to get optimized context from the current session,
including messages and optional summary, that fits within token budgets.
Args:
tokens: Maximum number of tokens to include. If not specified,
returns all available context.
@ -161,35 +119,7 @@ class HonchoTools(Toolkit):
summary=include_summary,
tokens=tokens,
)
result: list[str] = []
# Add summary if present
if context.summary:
result.append("=== Session Summary ===")
result.append(context.summary.content)
result.append("")
# Add peer representation if present
if context.peer_representation:
result.append("=== Peer Representation ===")
result.append(context.peer_representation)
result.append("")
# Add peer card if present
if context.peer_card:
result.append("=== Peer Card ===")
result.extend(context.peer_card)
result.append("")
# Add messages
if context.messages:
result.append(f"=== Messages ({len(context.messages)}) ===")
for msg in context.messages:
result.append(f"{msg.peer_id}: {msg.content}")
return "\n".join(result) if result else "No context available"
return str(context)
except Exception as e:
logger.exception("Error retrieving context")
return f"Error retrieving context: {e!s}"
@ -230,31 +160,24 @@ class HonchoTools(Toolkit):
logger.exception("Error searching messages")
return f"Error searching messages: {e!s}"
def query_peer(self, query: str, target_peer_id: str | None = None) -> str:
def chat(self, query: str) -> str:
"""
Query the system's knowledge about a peer in the conversation.
Ask a question about what was discussed in this conversation.
Use this tool to ask questions about any participant's preferences,
interests, or past interactions. The system uses dialectic reasoning
to provide insights based on the peer's long-term representation.
Use this tool to query session-specific context and facts.
The system uses Honcho reasoning to provide synthesized
insights based on the conversation history.
Args:
query: Natural language question about the peer.
Examples: "What does the user like?", "What are their preferences?"
target_peer_id: Optional peer ID to query about. If not provided,
queries about this toolkit's own peer identity.
query: Natural language question about the conversation.
Examples: "What did we discuss?", "What preferences should I be aware of?",
"What topics came up?"
Returns:
Response from the dialectic API with insights about the peer.
Synthesized response based on the session context.
"""
try:
# Query about a specific peer, or self if not specified
if target_peer_id:
target = self.honcho.peer(target_peer_id)
else:
target = self.peer
response = target.chat(
response = self.peer.chat(
query=query,
stream=False,
session=self.session_id,
@ -263,8 +186,8 @@ class HonchoTools(Toolkit):
return str(response) if response else "No relevant information found."
except Exception as e:
logger.exception("Error querying peer knowledge")
return f"Error querying peer knowledge: {e!s}"
logger.exception("Error querying conversation")
return f"Error querying conversation: {e!s}"
def reset_session(self) -> str:
"""