
Honcho Integration
- 725 installs
- 6.4k repo stars
- Updated August 4, 2026
- plastic-labs/honcho
Honcho Integration is a plastic-labs agent skill that connects Honcho memory, sessions, and tool calling into bot frameworks such as nanobot with reference implementations and adapter patterns.
About
Honcho Integration is a plastic-labs/honcho skill for embedding Honcho persistent memory and session semantics into agent-based bot frameworks. It extends the main honcho-integration guidance for applications built around an agent loop, session manager, tool registry, and message bus. Concrete reference implementations ship for nanobot under references/bot-frameworks/nanobot/, while openclaw and picoclaw entries are marked planned. When an unknown framework appears, the skill instructs agents to adapt the general integration pattern—session hooks, memory read/write, and tool-call bridging—to that architecture. Developers reach for Honcho Integration when bots need cross-session user modeling without bespoke vector stores. Outputs include wired session providers, Honcho client calls in the agent loop, and framework-specific reference diffs.
- Detects nanobot, openclaw, picoclaw and other bot frameworks automatically
- Maps agent loop, session manager, tool registry, message bus and config system
- Pulls concrete reference implementations from bot-frameworks/nanobot/ directory
- Provides framework-specific integration patterns instead of generic advice
- Works for both known frameworks and unknown custom agent loops
Honcho Integration by the numbers
- 725 all-time installs (skills.sh)
- +27 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,401 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/plastic-labs/honcho --skill honcho-integrationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 725 |
|---|---|
| repo stars | ★ 6.4k |
| Security audit | 2 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | plastic-labs/honcho ↗ |
How do you add Honcho memory to agents?
Correctly wire Honcho memory, sessions, and tool calling into agent-based bot frameworks such as nanobot.
Who is it for?
Agent developers integrating Honcho into nanobot or similar bot frameworks with session managers and tool registries.
Skip if: Single-shot CLI scripts with no persistent sessions or bot frameworks that do not expose an agent loop hook surface.
When should I use this skill?
A developer builds a bot with nanobot, openclaw, or picoclaw and needs Honcho memory, sessions, or tool-calling integration.
What you get
Integrated Honcho session providers, tool-call bridges, and framework-specific reference wiring for bot agents.
- Session integration code
- Memory hook wiring
- Framework reference patches
By the numbers
- Ships 1 concrete bot framework reference implementation for nanobot
- Lists 2 additional frameworks as planned: openclaw and picoclaw
Files
Honcho Integration Guide
What is Honcho
Honcho is an open source memory library for building stateful agents. It works with any model, framework, or architecture. You send Honcho the messages from your conversations, and custom reasoning models process them in the background — extracting premises, drawing conclusions, and building rich representations of each participant over time. Your agent can then query those representations on-demand ("What does this user care about?", "How technical is this person?") and get grounded, reasoned answers.
The key mental model: Peers are any participant — human or AI. Both are represented the same way. Observation settings (observe_me, observe_others) control which peers Honcho reasons about. Typically you want Honcho to model your users (observe_me=True) but not your AI assistant (observe_me=False). Sessions scope conversations between peers. Messages are the raw data you feed in — Honcho reasons about them asynchronously and stores the results as the peer's representation. No messages means no reasoning means no memory.
Your agent accesses this memory through peer.chat(query) (ask a natural language question, get a reasoned answer), session.context() (get formatted conversation history + representations), or both.
Integration Workflow
Follow these phases in order:
Phase 1: Codebase Exploration
Before asking the user anything, explore the codebase to understand:
1. Language & Framework: Is this Python or TypeScript? What frameworks are used (FastAPI, Express, Next.js, etc.)? 2. Existing AI/LLM code: Search for existing LLM integrations (OpenAI, Anthropic, LangChain, etc.) 3. Entity structure: Identify users, agents, bots, or other entities that interact 4. Session/conversation handling: How does the app currently manage conversations? 5. Message flow: Where are messages sent/received? What's the request/response cycle?
Use Glob and Grep to find:
**/*.pyor**/*.tsfiles with "openai", "anthropic", "llm", "chat", "message"- User/session models or types
- API routes handling chat or conversation endpoints
Bot framework detected? If the codebase is built around an agent loop, tool registry, session manager, and message bus (e.g., nanobot, openclaw, picoclaw), read{baseDir}/references/bot-frameworks.mdfor framework-specific integration guidance and check{baseDir}/references/bot-frameworks/<framework>/for concrete reference implementations.
Phase 2: Interview (REQUIRED)
After exploring the codebase, use the AskUserQuestion tool to clarify integration requirements. Ask these questions (adapt based on what you learned in Phase 1):
Question Set 1 - Entities & Peers
Ask about which entities should be Honcho peers:
- header: "Peers"
- question: "Which entities should Honcho track and build representations for?"
- options based on what you found (e.g., "End users only", "Users + AI assistant", "Users + multiple AI agents", "All participants including third-party services")
- Include a follow-up if they have multiple AI agents: should any AI peers be observed?
Question Set 2 - Integration Pattern
Ask how they want to use Honcho context:
- header: "Pattern"
- question: "How should your AI access Honcho's user context?"
- options:
- "Tool call (Recommended)" - "Agent queries Honcho on-demand via function calling"
- "Pre-fetch" - "Fetch user context before each LLM call with predefined queries"
- "context()" - "Include conversation history and representations in prompt"
- "Multiple patterns" - "Combine approaches for different use cases"
Question Set 3 - Session Structure
Ask about conversation structure:
- header: "Sessions"
- question: "How should conversations map to Honcho sessions?"
- options based on their app (e.g., "One session per chat thread", "One session per user", "Multiple users per session (group chat)", "Custom session logic")
Question Set 4 - Specific Queries (if using pre-fetch pattern)
If they chose pre-fetch, ask what context matters:
- header: "Context"
- question: "What user context should be fetched for the AI?"
- multiSelect: true
- options: "Communication style", "Expertise level", "Goals/priorities", "Preferences", "Recent activity summary", "Custom queries"
Phase 3: Implementation
Based on interview responses, implement the integration:
1. Install the SDK 2. Create Honcho client initialization 3. Set up peer creation for identified entities 4. Implement the chosen integration pattern(s) 5. Add message storage after exchanges 6. Update any existing conversation handlers
Phase 4: Verification
- If the Honcho CLI is available, run
honcho doctorto confirm connectivity before testing the integration code - Use
honcho peer listandhoncho peer chatto verify peers exist and the dialectic endpoint works independently of the integration - Ensure all message exchanges are stored to Honcho
- Verify AI peers have
observe_me=False(unless user specifically wants AI observation) - Check that the workspace ID is consistent across the codebase
- Confirm environment variable for API key is documented
---
Before You Start
1. Check the latest SDK versions at <https://honcho.dev/docs/changelog/introduction>
- Python SDK:
honcho-ai - TypeScript SDK:
@honcho-ai/sdk
2. Get an API key ask the user to get a Honcho API key from <https://app.honcho.dev> and add it to the environment.
3. Verify with the CLI (optional but recommended). If the user has the Honcho CLI installed (pip install honcho-cli), they can validate their setup before writing any integration code:
honcho init # persist API key + URL to ~/.honcho/config.json
honcho doctor # verify connectivity, config, workspace health
honcho peer chat # test the dialectic endpoint interactivelyThis is the fastest way to confirm the API key and URL are correct before debugging SDK code.
Installation
Python (use uv)
uv add honcho-aiTypeScript (use bun)
bun add @honcho-ai/sdkSync vs Async
TypeScript — The SDK is async by default. All methods return promises. No separate sync API.
Python — The SDK provides both sync and async interfaces:
- Sync (default):
from honcho import Honcho— use in sync frameworks (Flask, Django, CLI scripts) - Async:
from honcho import Honchowith.aionamespace — use in async frameworks (FastAPI, Starlette, async workers)
# Sync usage (Flask, Django, scripts)
from honcho import Honcho
honcho = Honcho(workspace_id="my-app", api_key=os.environ["HONCHO_API_KEY"])
peer = honcho.peer("user-123")
response = peer.chat("What does this user prefer?")
# Async usage (FastAPI, Starlette)
from honcho import Honcho
honcho = Honcho(workspace_id="my-app", api_key=os.environ["HONCHO_API_KEY"])
peer = await honcho.aio.peer("user-123")
response = await peer.aio.chat("What does this user prefer?")Match the client to the framework — check whether the codebase uses async def handlers or sync def handlers and choose accordingly. The rest of this skill shows sync Python examples; swap to .aio equivalents for async codebases.
Core Integration Patterns
1. Initialize with a Single Workspace
Use ONE workspace for your entire application. The workspace name should reflect your app/product.
Python:
from honcho import Honcho
import os
# Sync client (Flask, Django, scripts)
honcho = Honcho(
workspace_id="your-app-name",
api_key=os.environ["HONCHO_API_KEY"],
environment="production"
)
# Async client (FastAPI, Starlette) — use honcho.aio for all operations
# honcho.aio.peer(), honcho.aio.session(), etc.TypeScript:
import { Honcho } from '@honcho-ai/sdk';
// All methods are async by default
const honcho = new Honcho({
workspaceId: "your-app-name",
apiKey: process.env.HONCHO_API_KEY,
environment: "production"
});2. Create Peers for ALL Entities
Create peers for every entity in your business logic - users AND AI assistants.
Python:
from honcho.api_types import PeerConfig
# Human users
user = honcho.peer("user-123")
# AI assistants - set observe_me=False so Honcho doesn't model the AI
assistant = honcho.peer("assistant", configuration=PeerConfig(observe_me=False))
support_bot = honcho.peer("support-bot", configuration=PeerConfig(observe_me=False))TypeScript:
// Human users
const user = await honcho.peer("user-123");
// AI assistants - set observeMe=false so Honcho doesn't model the AI
const assistant = await honcho.peer("assistant", { configuration: { observeMe: false } });
const supportBot = await honcho.peer("support-bot", { configuration: { observeMe: false } });3. Multi-Peer Sessions
Sessions can have multiple participants. Configure observation settings per-peer.
Python:
from honcho.api_types import SessionPeerConfig
session = honcho.session("conversation-123")
# User is observed (Honcho builds a model of them)
user_config = SessionPeerConfig(observe_me=True, observe_others=True)
# AI is NOT observed (no model built of the AI)
ai_config = SessionPeerConfig(observe_me=False, observe_others=True)
session.add_peers([
(user, user_config),
(assistant, ai_config)
])TypeScript:
const session = await honcho.session("conversation-123");
await session.addPeers([
[user, { observeMe: true, observeOthers: true }],
[assistant, { observeMe: false, observeOthers: true }]
]);4. Add Messages to Sessions
Python:
session.add_messages([
user.message("I'm having trouble with my account"),
assistant.message("I'd be happy to help. What seems to be the issue?"),
user.message("I can't reset my password")
])TypeScript:
await session.addMessages([
user.message("I'm having trouble with my account"),
assistant.message("I'd be happy to help. What seems to be the issue?"),
user.message("I can't reset my password")
]);Using Honcho for AI Agents
Pattern A: Dialectic Chat as a Tool Call (Recommended for Agents)
Make Honcho's chat endpoint available as a tool for your AI agent. This lets the agent query user context on-demand.
Python (OpenAI function calling):
import openai
from honcho import Honcho
honcho = Honcho(workspace_id="my-app", api_key=os.environ["HONCHO_API_KEY"])
# Define the tool for your agent
honcho_tool = {
"type": "function",
"function": {
"name": "query_user_context",
"description": "Query Honcho to retrieve relevant context about the user based on their history and preferences. Use this when you need to understand the user's background, preferences, past interactions, or goals.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "A natural language question about the user, e.g. 'What are this user's main goals?' or 'What communication style does this user prefer?'"
}
},
"required": ["query"]
}
}
}
def handle_honcho_tool_call(user_id: str, query: str) -> str:
"""Execute the Honcho chat tool call."""
peer = honcho.peer(user_id)
return peer.chat(query)
# Use in your agent loop
def run_agent(user_id: str, user_message: str):
messages = [{"role": "user", "content": user_message}]
response = openai.chat.completions.create(
model="gpt-4",
messages=messages,
tools=[honcho_tool]
)
# Handle tool calls
if response.choices[0].message.tool_calls:
for tool_call in response.choices[0].message.tool_calls:
if tool_call.function.name == "query_user_context":
import json
args = json.loads(tool_call.function.arguments)
result = handle_honcho_tool_call(user_id, args["query"])
# Continue conversation with tool result...TypeScript (OpenAI function calling):
import OpenAI from 'openai';
import { Honcho } from '@honcho-ai/sdk';
const honcho = new Honcho({
workspaceId: "my-app",
apiKey: process.env.HONCHO_API_KEY
});
const honchoTool: OpenAI.ChatCompletionTool = {
type: "function",
function: {
name: "query_user_context",
description: "Query Honcho to retrieve relevant context about the user based on their history and preferences.",
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "A natural language question about the user"
}
},
required: ["query"]
}
}
};
async function handleHonchoToolCall(userId: string, query: string): Promise<string> {
const peer = await honcho.peer(userId);
return await peer.chat(query);
}Pattern B: Pre-fetch Context with Targeted Queries
For simpler integrations, fetch user context before the LLM call using pre-defined queries.
Python:
def get_user_context_for_prompt(user_id: str) -> dict:
"""Fetch key user attributes via targeted Honcho queries."""
peer = honcho.peer(user_id)
return {
"communication_style": peer.chat("What communication style does this user prefer? Be concise."),
"expertise_level": peer.chat("What is this user's technical expertise level? Be concise."),
"current_goals": peer.chat("What are this user's current goals or priorities? Be concise."),
"preferences": peer.chat("What key preferences should I know about this user? Be concise.")
}
def build_system_prompt(user_context: dict) -> str:
return f"""You are a helpful assistant. Here's what you know about this user:
Communication style: {user_context['communication_style']}
Expertise level: {user_context['expertise_level']}
Current goals: {user_context['current_goals']}
Key preferences: {user_context['preferences']}
Tailor your responses accordingly."""TypeScript:
async function getUserContextForPrompt(userId: string): Promise<Record<string, string>> {
const peer = await honcho.peer(userId);
const [style, expertise, goals, preferences] = await Promise.all([
peer.chat("What communication style does this user prefer? Be concise."),
peer.chat("What is this user's technical expertise level? Be concise."),
peer.chat("What are this user's current goals or priorities? Be concise."),
peer.chat("What key preferences should I know about this user? Be concise.")
]);
return {
communicationStyle: style,
expertiseLevel: expertise,
currentGoals: goals,
preferences: preferences
};
}Pattern C: Get Context for LLM Integration
Use context() for conversation history with built-in LLM formatting.
Python:
import openai
session = honcho.session("conversation-123")
user = honcho.peer("user-123")
assistant = honcho.peer("assistant", configuration=PeerConfig(observe_me=False))
# Get context formatted for your LLM
context = session.context(
tokens=2000,
peer_target=user.id, # Include representation of this user
summary=True # Include conversation summaries
)
# Convert to OpenAI format
messages = context.to_openai(assistant=assistant)
# Or Anthropic format
# messages = context.to_anthropic(assistant=assistant)
# Add the new user message
messages.append({"role": "user", "content": "What should I focus on today?"})
response = openai.chat.completions.create(
model="gpt-4",
messages=messages
)
# Store the exchange
session.add_messages([
user.message("What should I focus on today?"),
assistant.message(response.choices[0].message.content)
])TypeScript:
import OpenAI from 'openai';
const session = await honcho.session("conversation-123");
const user = await honcho.peer("user-123");
const assistant = await honcho.peer("assistant", { configuration: { observeMe: false } });
// Get context formatted for your LLM
const context = await session.context({
tokens: 2000,
peerTarget: user.id, // Include representation of this user
summary: true // Include conversation summaries
});
// Convert to OpenAI format
const messages = context.toOpenAI(assistant);
// Or Anthropic format
// const messages = context.toAnthropic(assistant);
// Add the new user message
messages.push({ role: "user", content: "What should I focus on today?" });
const openai = new OpenAI();
const response = await openai.chat.completions.create({
model: "gpt-4",
messages
});
// Store the exchange
await session.addMessages([
user.message("What should I focus on today?"),
assistant.message(response.choices[0].message.content!)
]);Streaming Responses
Python:
stream = peer.chat_stream("What do we know about this user?")
for chunk in stream:
print(chunk, end="", flush=True)TypeScript:
const stream = await peer.chatStream("What do we know about this user?");
for await (const chunk of stream) {
process.stdout.write(chunk);
}Integration Checklist
When integrating Honcho into an existing codebase:
- [ ] Install SDK with
uv add honcho-ai(Python) orbun add @honcho-ai/sdk(TypeScript) - [ ] Set up
HONCHO_API_KEYenvironment variable - [ ] Initialize Honcho client with a single workspace ID
- [ ] Create peers for all entities (users AND AI assistants)
- [ ] Set
observe_me=Falsefor AI peers - [ ] Configure sessions with appropriate peer observation settings
- [ ] Choose integration pattern:
- [ ] Tool call pattern for agentic systems
- [ ] Pre-fetch pattern for simpler integrations
- [ ] context() for conversation history
- [ ] Store messages after each exchange to build user models
- [ ] (Optional) Run
honcho doctorto verify connectivity before testing integration code - [ ] (Optional) Use
honcho peer chatto test dialectic queries independently
Common Mistakes to Avoid
1. Multiple workspaces: Use ONE workspace per application 2. Forgetting AI peers: Create peers for AI assistants, not just users 3. Observing AI peers: Set observe_me=False for AI peers unless you specifically want Honcho to model your AI's behavior 4. Not storing messages: Always call add_messages() to feed Honcho's reasoning engine 5. Blocking on processing: Messages are processed asynchronously — don't poll or wait for reasoning to complete before continuing
Resources
- Documentation: <https://honcho.dev/docs>
- Latest SDK versions: <https://honcho.dev/docs/changelog/introduction>
- API Reference: <https://honcho.dev/docs/v3/api-reference/introduction>
Honcho Integration for Bot Frameworks
This reference extends the main honcho-integration skill for bot frameworks — applications built around an agent loop, session manager, tool registry, and message bus (e.g., nanobot, openclaw, picoclaw).
Supported Frameworks
When a known framework is detected, use concrete reference implementations from {baseDir}/references/bot-frameworks/<framework>/.
| Framework | Status | Reference Dir |
|---|---|---|
| nanobot | concrete references | bot-frameworks/nanobot/ |
| openclaw | planned | -- |
| picoclaw | planned | -- |
For unknown frameworks, adapt the general pattern below to the codebase's architecture.
Phase 1: Explore (bot-specific)
In addition to the main skill's Phase 1, identify these bot-specific components:
1. Agent loop: Where messages are processed (look for while loops calling an LLM) 2. Session manager: How conversation history is stored (JSONL files, database, in-memory) 3. Tool registry: How tools/functions are registered for the LLM to call 4. Message bus: How inbound/outbound messages are routed between channels and the agent 5. Config system: How the bot loads configuration (JSON, YAML, env vars, pydantic models, zod schemas) 6. CLI entry points: How the bot is started (commands, gateway, agent modes)
If the framework matches a known one (e.g., nanobot), pull the concrete references from {baseDir}/references/bot-frameworks/<framework>/ and use them as the implementation target.
Phase 2: Interview (bot-specific)
In addition to the main skill's interview questions, ask about:
- Peer model: Who are the participants? (typically: one user peer per channel:chat_id, one shared assistant peer)
- Session granularity: One session per chat? Per user? Per channel?
- Workspace ID: What namespace for this bot's Honcho data?
- Feature flag: Should Honcho be opt-in (default
false) or opt-out (defaulttrue)?
Phase 3: Implement (bot-specific)
Step 1: Add dependency
Python: Add honcho-ai>=2.0.1. If the framework supports optional dependencies, make it optional:
[project.optional-dependencies]
honcho = ["honcho-ai>=2.0.1"]TypeScript: Add @honcho-ai/sdk:
bun add @honcho-ai/sdk
# or npm install @honcho-ai/sdkIf the framework supports optional peer dependencies:
{
"peerDependencies": {
"@honcho-ai/sdk": ">=2.0.1"
},
"peerDependenciesMeta": {
"@honcho-ai/sdk": { "optional": true }
}
}Step 2: Add config schema
Add a Honcho config section to the bot's configuration system:
Python:
class HonchoConfig(BaseModel):
"""Honcho AI-native memory integration (optional feature flag)."""
enabled: bool = False # or True for Honcho-first deployments
workspace_id: str = "default"
prefetch: bool = True # inject user context into system prompts
context_tokens: int | None = None
environment: str = "production"TypeScript:
interface HonchoConfig {
/** Honcho AI-native memory integration (optional feature flag). */
enabled: boolean; // default: false, or true for Honcho-first deployments
workspaceId: string; // default: "default"
prefetch: boolean; // default: true — inject user context into system prompts
contextTokens?: number;
environment: string; // default: "production"
}
const defaultHonchoConfig: HonchoConfig = {
enabled: false,
workspaceId: "default",
prefetch: true,
environment: "production",
};Step 3: Create the honcho package
Create a honcho integration package with:
- Client singleton (
client.py/client.ts): Lazy initialization, deferred imports,getHonchoClient()factory - Session manager (
session.py/session.ts): Maps bot sessions to Honcho sessions with peer configuration - Agent tool (
honcho_tool.py/honchoTool.ts): Tool the agent can call to query user context viapeer.chat()
Key patterns (Python):
from __future__ import annotations+TYPE_CHECKINGfor all honcho imports- Runtime imports inside functions (never top-level) so the bot doesn't crash without
honcho-ai - Wrap in
try/except ImportErrorfor graceful degradation
Key patterns (TypeScript):
- Use dynamic
import()for honcho SDK (never top-levelimport ... from) so the bot doesn't crash without@honcho-ai/sdk - Use
import type { ... }for type-only imports that are erased at runtime - Wrap in
try/catchfor graceful degradation when the SDK is missing
Key patterns (shared):
- IDs sanitized to
^[a-zA-Z0-9_-]+(Honcho requirement) - User peer:
observe_me=True, observe_others=True - Assistant peer:
observe_me=False, observe_others=True
If references exist for this framework, use them directly from {baseDir}/references/bot-frameworks/<framework>/.
Step 4: Wire into the agent loop
Add these integration points to the agent loop:
1. Tool registration (at startup): If honcho.enabled and HONCHO_API_KEY set, initialize client + register Honcho tools.
Python: Wrap in try/except ImportError for graceful degradation. TypeScript: Use dynamic import() inside a try/catch block.
2. Context setup (per message): Set session context on Honcho tools, ensure Honcho session exists.
3. Prefetch (per message): Call session.context() to get user representation and inject into system prompt before the LLM call.
4. Sync (after response): After saving to local session, sync the user+assistant message pair to Honcho.
Python:
session.add_messages([
user_peer.message(user_input),
assistant_peer.message(assistant_response),
])TypeScript:
await session.addMessages([
userPeer.message(userInput),
assistantPeer.message(assistantResponse),
]);5. Migration (on first activation): If Honcho session is empty but local session has history, upload prior messages as a file via session.upload_file() (Python) or session.uploadFile() (TypeScript). Also upload MEMORY.md and HISTORY.md if they exist (from frameworks with local memory consolidation). Archive originals after successful upload.
Step 5: Pass config through CLI
Pass honcho_config to every agent loop instantiation in the CLI commands.
Step 6: Migration support
When Honcho activates on an instance with existing local data, migrate automatically:
- Session messages (JSONL files): Format as XML transcript, upload via
session.upload_file()(Python) orsession.uploadFile()(TypeScript) - Consolidated memory (MEMORY.md, HISTORY.md): Upload as tagged files with context annotations
- Archive originals: Move to
migrated/subdirectory after successful upload - Idempotent: Skip if Honcho session already has messages
Phase 4: Verify (bot-specific)
After integration, verify:
- [ ] Bot starts normally without the Honcho SDK installed (no import errors)
- [ ] Bot starts normally with the SDK but without
HONCHO_API_KEY(graceful skip) - [ ] With both present and
enabled=true, logs show "Honcho tools registered" - [ ] User context is prefetched and visible in system prompts
- [ ] Messages sync to Honcho after each exchange
- [ ] Local session migration works on first Honcho activation
- [ ] Memory file migration works for MEMORY.md/HISTORY.md (if applicable)
Bot-Specific Patterns
- Lazy imports everywhere:
- Python:
from __future__ import annotations+TYPE_CHECKINGfor type hints, runtime imports inside functions - TypeScript:
import type { ... }for type-only imports, dynamicimport()for runtime access - Feature flag gating: Always check
config.enabledANDHONCHO_API_KEY/process.env.HONCHO_API_KEYbefore touching Honcho - Graceful degradation:
- Python:
try/except ImportErrorand genericExceptioncatches with logger warnings, never crash the bot - TypeScript:
try/catcharound dynamicimport()with logger warnings, never crash the bot - Sanitize IDs: Honcho requires
^[a-zA-Z0-9_-]+— replace colons, dots, spaces with dashes - Sync after success: Only mark messages as synced after the API call succeeds, not before
- Cache consistency: When creating aliased sessions, store under both original and derived keys
"""Honcho client initialization and configuration."""
from __future__ import annotations
import os
from dataclasses import dataclass
from typing import TYPE_CHECKING
from loguru import logger
if TYPE_CHECKING:
from honcho import Honcho
@dataclass
class HonchoConfig:
"""Configuration for Honcho client."""
workspace_id: str = "nanobot"
api_key: str | None = None
environment: str = "production"
@classmethod
def from_env(cls, workspace_id: str = "nanobot") -> HonchoConfig:
"""Create config from environment variables."""
return cls(
workspace_id=workspace_id,
api_key=os.environ.get("HONCHO_API_KEY"),
environment=os.environ.get("HONCHO_ENVIRONMENT", "production"),
)
_honcho_client: Honcho | None = None
def get_honcho_client(config: HonchoConfig | None = None) -> Honcho:
"""
Get or create the Honcho client singleton.
Args:
config: Optional config. If not provided, uses environment variables.
Returns:
Configured Honcho client.
Raises:
ValueError: If HONCHO_API_KEY is not set.
"""
global _honcho_client
if _honcho_client is not None:
return _honcho_client
if config is None:
config = HonchoConfig.from_env()
if not config.api_key:
raise ValueError(
"HONCHO_API_KEY environment variable is required. "
"Get an API key from https://app.honcho.dev"
)
try:
from honcho import Honcho
except ImportError:
raise ImportError(
"honcho-ai is required for Honcho integration. "
"Install it with: nanobot honcho enable --api-key YOUR_KEY"
)
logger.info(f"Initializing Honcho client (workspace: {config.workspace_id})")
_honcho_client = Honcho(
workspace_id=config.workspace_id,
api_key=config.api_key,
environment=config.environment,
)
return _honcho_client
def reset_honcho_client() -> None:
"""Reset the Honcho client singleton (useful for testing)."""
global _honcho_client
_honcho_client = None
"""Honcho tool for querying user context."""
from typing import Any
from nanobot.agent.tools.base import Tool
class HonchoTool(Tool):
"""
Tool for querying Honcho's AI-native memory.
Allows the agent to retrieve relevant context about users
based on their history and learned preferences.
"""
def __init__(self, session_manager: "HonchoSessionManager"):
"""
Initialize the Honcho tool.
Args:
session_manager: The HonchoSessionManager instance.
"""
self._session_manager = session_manager
self._current_session_key: str | None = None
@property
def name(self) -> str:
return "query_user_context"
@property
def description(self) -> str:
return (
"Query Honcho to retrieve relevant context about the user based on their "
"history and preferences. Use this when you need to understand the user's "
"background, preferences, past interactions, or goals. This helps you "
"personalize your responses and provide more relevant assistance."
)
@property
def parameters(self) -> dict[str, Any]:
return {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": (
"A natural language question about the user. Examples: "
"'What are this user's main goals?', "
"'What communication style does this user prefer?', "
"'What topics has this user discussed recently?', "
"'What is this user's technical expertise level?'"
),
}
},
"required": ["query"],
}
def set_context(self, session_key: str) -> None:
"""
Set the current session context.
Args:
session_key: The session key (channel:chat_id).
"""
self._current_session_key = session_key
async def execute(self, query: str) -> str:
"""
Execute the Honcho context query.
Args:
query: Natural language question about the user.
Returns:
Honcho's response about the user.
"""
if not self._current_session_key:
return "Error: No session context set. Unable to query user information."
try:
result = self._session_manager.get_user_context(
self._current_session_key, query
)
return result
except Exception as e:
return f"Error querying user context: {str(e)}"
"""Honcho-based session management for conversation history."""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, TYPE_CHECKING
from loguru import logger
from nanobot.honcho.client import get_honcho_client
if TYPE_CHECKING:
from honcho import Honcho
from honcho.api_types import SessionPeerConfig
@dataclass
class HonchoSession:
"""
A conversation session backed by Honcho.
Provides the same interface as the original Session class
but stores messages in Honcho for AI-native memory.
"""
key: str # channel:chat_id
user_peer_id: str # Honcho peer ID for the user
assistant_peer_id: str # Honcho peer ID for the assistant
honcho_session_id: str # Honcho session ID
messages: list[dict[str, Any]] = field(default_factory=list)
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
metadata: dict[str, Any] = field(default_factory=dict)
def add_message(self, role: str, content: str, **kwargs: Any) -> None:
"""Add a message to the local cache."""
msg = {
"role": role,
"content": content,
"timestamp": datetime.now().isoformat(),
**kwargs,
}
self.messages.append(msg)
self.updated_at = datetime.now()
def get_history(self, max_messages: int = 50) -> list[dict[str, Any]]:
"""
Get message history for LLM context.
Args:
max_messages: Maximum messages to return.
Returns:
List of messages in LLM format.
"""
recent = (
self.messages[-max_messages:]
if len(self.messages) > max_messages
else self.messages
)
return [{"role": m["role"], "content": m["content"]} for m in recent]
def clear(self) -> None:
"""Clear all messages in the session."""
self.messages = []
self.updated_at = datetime.now()
class HonchoSessionManager:
"""
Manages conversation sessions using Honcho.
Replaces the file-based SessionManager with Honcho's
AI-native memory system for user modeling.
"""
def __init__(self, honcho: Honcho | None = None, context_tokens: int | None = None):
"""
Initialize the session manager.
Args:
honcho: Optional Honcho client. If not provided, uses the singleton.
context_tokens: Max tokens for context() calls (None = Honcho default).
"""
self._honcho = honcho
self._context_tokens = context_tokens
self._cache: dict[str, HonchoSession] = {}
self._peers_cache: dict[str, Any] = {}
self._sessions_cache: dict[str, Any] = {}
@property
def honcho(self) -> Honcho:
"""Get the Honcho client, initializing if needed."""
if self._honcho is None:
self._honcho = get_honcho_client()
return self._honcho
def _get_or_create_peer(self, peer_id: str) -> Any:
"""
Get or create a Honcho peer.
As of v2.1.0, peer() always makes a get-or-create API call.
Observation settings are controlled per-session via SessionPeerConfig.
Args:
peer_id: The peer identifier.
Returns:
The Honcho peer object.
"""
if peer_id in self._peers_cache:
return self._peers_cache[peer_id]
peer = self.honcho.peer(peer_id)
self._peers_cache[peer_id] = peer
return peer
def _get_or_create_honcho_session(
self, session_id: str, user_peer: Any, assistant_peer: Any
) -> Any:
"""
Get or create a Honcho session with peers configured.
Args:
session_id: The session identifier.
user_peer: The user peer object.
assistant_peer: The assistant peer object.
Returns:
The Honcho session object.
"""
if session_id in self._sessions_cache:
logger.debug(f"Honcho session '{session_id}' retrieved from cache")
return self._sessions_cache[session_id], []
session = self.honcho.session(session_id)
# Configure peer observation settings
from honcho.api_types import SessionPeerConfig
user_config = SessionPeerConfig(observe_me=True, observe_others=True)
ai_config = SessionPeerConfig(observe_me=False, observe_others=True)
session.add_peers([(user_peer, user_config), (assistant_peer, ai_config)])
# Load existing messages via context() - single call for messages + metadata
existing_messages = []
try:
ctx = session.context(summary=True, tokens=self._context_tokens)
existing_messages = ctx.messages or []
# Verify chronological ordering
if existing_messages and len(existing_messages) > 1:
timestamps = [m.created_at for m in existing_messages if m.created_at]
if timestamps and timestamps != sorted(timestamps):
logger.warning(
f"Honcho messages not chronologically ordered for session '{session_id}', sorting"
)
existing_messages = sorted(
existing_messages,
key=lambda m: m.created_at or datetime.min,
)
if existing_messages:
logger.info(f"Honcho session '{session_id}' retrieved ({len(existing_messages)} existing messages)")
else:
logger.info(f"Honcho session '{session_id}' created (new)")
except Exception as e:
logger.warning(f"Honcho session '{session_id}' loaded (failed to fetch context: {e})")
self._sessions_cache[session_id] = session
return session, existing_messages
def _sanitize_id(self, id_str: str) -> str:
"""Sanitize an ID to match Honcho's pattern: ^[a-zA-Z0-9_-]+"""
return re.sub(r'[^a-zA-Z0-9_-]', '-', id_str)
def get_or_create(self, key: str) -> HonchoSession:
"""
Get an existing session or create a new one.
Args:
key: Session key (usually channel:chat_id).
Returns:
The session.
"""
if key in self._cache:
logger.debug(f"Local session cache hit: {key}")
return self._cache[key]
# Parse key to extract user identifier
# Format: channel:chat_id (e.g., "telegram:123456789")
parts = key.split(":", 1)
channel = parts[0] if len(parts) > 1 else "default"
chat_id = parts[1] if len(parts) > 1 else key
# Create peer IDs (sanitized for Honcho's ID pattern)
user_peer_id = self._sanitize_id(f"user-{channel}-{chat_id}")
assistant_peer_id = "nanobot-assistant"
# Sanitize session ID for Honcho
honcho_session_id = self._sanitize_id(key)
# Get or create peers
user_peer = self._get_or_create_peer(user_peer_id)
assistant_peer = self._get_or_create_peer(assistant_peer_id)
# Get or create Honcho session
honcho_session, existing_messages = self._get_or_create_honcho_session(
honcho_session_id, user_peer, assistant_peer
)
# Convert Honcho messages to local format
local_messages = []
for msg in existing_messages:
role = "assistant" if msg.peer_id == assistant_peer_id else "user"
local_messages.append({
"role": role,
"content": msg.content,
"timestamp": msg.created_at.isoformat() if msg.created_at else "",
"_synced": True, # Already in Honcho
})
# Create local session wrapper with existing messages
session = HonchoSession(
key=key,
user_peer_id=user_peer_id,
assistant_peer_id=assistant_peer_id,
honcho_session_id=honcho_session_id,
messages=local_messages,
)
self._cache[key] = session
return session
def save(self, session: HonchoSession) -> None:
"""
Save messages to Honcho.
This syncs the local message cache to Honcho's storage.
Args:
session: The session to save.
"""
if not session.messages:
return
# Get the Honcho session and peers
user_peer = self._get_or_create_peer(session.user_peer_id)
assistant_peer = self._get_or_create_peer(session.assistant_peer_id)
honcho_session = self._sessions_cache.get(session.honcho_session_id)
if not honcho_session:
honcho_session, _ = self._get_or_create_honcho_session(
session.honcho_session_id, user_peer, assistant_peer
)
# Convert messages to Honcho format and send
# Only send new messages (those without a 'synced' flag)
new_messages = [m for m in session.messages if not m.get("_synced")]
if not new_messages:
return
honcho_messages = []
for msg in new_messages:
peer = user_peer if msg["role"] == "user" else assistant_peer
honcho_messages.append(peer.message(msg["content"]))
try:
honcho_session.add_messages(honcho_messages)
for msg in new_messages:
msg["_synced"] = True
logger.debug(f"Synced {len(honcho_messages)} messages to Honcho for {session.key}")
except Exception as e:
for msg in new_messages:
msg["_synced"] = False
logger.error(f"Failed to sync messages to Honcho: {e}")
# Update cache
self._cache[session.key] = session
def delete(self, key: str) -> bool:
"""
Delete a session from local cache.
Args:
key: Session key.
Returns:
True if deleted from cache, False if not found.
"""
if key in self._cache:
del self._cache[key]
return True
return False
def new_session(self, key: str) -> HonchoSession:
"""
Create a new session, preserving the old one for user modeling.
This creates a fresh session with a new ID while keeping the old
session's data in Honcho for continued user modeling.
Args:
key: Original session key (e.g., "discord:123456").
Returns:
A fresh HonchoSession with no message history.
"""
import time
# Remove old session from caches (but don't delete from Honcho)
old_session = self._cache.pop(key, None)
if old_session:
self._sessions_cache.pop(old_session.honcho_session_id, None)
# Create new session with timestamp suffix
# This preserves old session in Honcho while starting fresh
timestamp = int(time.time())
new_key = f"{key}:{timestamp}"
# Get or create will create a fresh session
session = self.get_or_create(new_key)
# Cache under both original key (for future lookups) and timestamped
# key (so session.key matches a valid cache entry)
self._cache[key] = session
self._cache[new_key] = session
logger.info(f"Created new session for {key} (honcho: {session.honcho_session_id})")
return session
def get_user_context(self, session_key: str, query: str) -> str:
"""
Query Honcho's dialectic chat for user context.
Args:
session_key: The session key to get context for.
query: Natural language question about the user.
Returns:
Honcho's response about the user.
"""
session = self._cache.get(session_key)
if not session:
return "No session found for this context."
user_peer = self._get_or_create_peer(session.user_peer_id)
try:
return user_peer.chat(query)
except Exception as e:
logger.error(f"Failed to get user context from Honcho: {e}")
return f"Unable to retrieve user context: {e}"
def get_prefetch_context(self, session_key: str, user_message: str | None = None) -> dict[str, str]:
"""
Pre-fetch user context using Honcho's context() method.
This is a single API call that returns the user's representation
and peer card, using semantic search based on the user's message.
Args:
session_key: The session key to get context for.
user_message: The user's message for semantic search.
Returns:
Dictionary with 'representation' and 'card' keys.
"""
session = self._cache.get(session_key)
if not session:
return {}
honcho_session = self._sessions_cache.get(session.honcho_session_id)
if not honcho_session:
return {}
try:
# Single API call to get user representation with semantic search
ctx = honcho_session.context(
summary=False,
tokens=self._context_tokens,
peer_target=session.user_peer_id,
search_query=user_message,
)
# peer_card is list[str] in SDK v2, join for prompt injection
card = ctx.peer_card or []
card_str = "\n".join(card) if isinstance(card, list) else str(card)
return {
"representation": ctx.peer_representation or "",
"card": card_str,
}
except Exception as e:
logger.warning(f"Failed to fetch context from Honcho: {e}")
return {}
def migrate_local_history(self, session_key: str, messages: list[dict[str, Any]]) -> bool:
"""
Upload local session history to Honcho as a file.
Used when Honcho activates mid-conversation to preserve prior context.
Args:
session_key: The session key (e.g., "telegram:123456").
messages: Local messages (dicts with role, content, timestamp).
Returns:
True if upload succeeded, False otherwise.
"""
sanitized = self._sanitize_id(session_key)
honcho_session = self._sessions_cache.get(sanitized)
if not honcho_session:
logger.warning(f"No Honcho session cached for '{session_key}', skipping migration")
return False
# Resolve user peer for attribution
parts = session_key.split(":", 1)
channel = parts[0] if len(parts) > 1 else "default"
chat_id = parts[1] if len(parts) > 1 else session_key
user_peer_id = self._sanitize_id(f"user-{channel}-{chat_id}")
user_peer = self._peers_cache.get(user_peer_id)
if not user_peer:
logger.warning(f"No user peer cached for '{user_peer_id}', skipping migration")
return False
content_bytes = self._format_migration_transcript(session_key, messages)
first_ts = messages[0].get("timestamp") if messages else None
try:
honcho_session.upload_file(
file=("prior_history.txt", content_bytes, "text/plain"),
peer=user_peer,
metadata={"source": "local_jsonl", "count": len(messages)},
created_at=first_ts,
)
logger.info(f"Migrated {len(messages)} local messages to Honcho for {session_key}")
return True
except Exception as e:
logger.error(f"Failed to upload local history to Honcho for {session_key}: {e}")
return False
@staticmethod
def _format_migration_transcript(session_key: str, messages: list[dict[str, Any]]) -> bytes:
"""
Format local messages as an XML transcript for Honcho file upload.
Args:
session_key: The session key for metadata.
messages: Local messages (dicts with role, content, timestamp).
Returns:
UTF-8 encoded transcript bytes.
"""
timestamps = [m.get("timestamp", "") for m in messages]
time_range = f"{timestamps[0]} to {timestamps[-1]}" if timestamps else "unknown"
lines = [
"<prior_conversation_history>",
"<context>",
"This conversation history occurred BEFORE the Honcho memory system was activated.",
"These messages are the preceding elements of this conversation session and should",
"be treated as foundational context for all subsequent interactions. The user and",
"assistant have already established rapport through these exchanges.",
"</context>",
"",
f'<transcript session_key="{session_key}" message_count="{len(messages)}"',
f' time_range="{time_range}">',
"",
]
for msg in messages:
ts = msg.get("timestamp", "?")
role = msg.get("role", "unknown")
content = msg.get("content", "")
lines.append(f"[{ts}] {role}: {content}")
lines.append("")
lines.append("</transcript>")
lines.append("</prior_conversation_history>")
return "\n".join(lines).encode("utf-8")
def migrate_memory_files(self, session_key: str, workspace: Any) -> bool:
"""
Upload workspace/memory/MEMORY.md and HISTORY.md to Honcho as files.
Used when Honcho activates on an instance that already has locally
consolidated memory (from upstream's _consolidate_memory). Backwards
compatible -- skips gracefully if files don't exist.
Args:
session_key: The session key to associate files with.
workspace: Path to the workspace directory.
Returns:
True if at least one file was uploaded, False otherwise.
"""
from pathlib import Path
workspace = Path(workspace)
memory_dir = workspace / "memory"
if not memory_dir.exists():
return False
sanitized = self._sanitize_id(session_key)
honcho_session = self._sessions_cache.get(sanitized)
if not honcho_session:
logger.warning(f"No Honcho session cached for '{session_key}', skipping memory migration")
return False
# Resolve user peer for attribution
parts = session_key.split(":", 1)
channel = parts[0] if len(parts) > 1 else "default"
chat_id = parts[1] if len(parts) > 1 else session_key
user_peer_id = self._sanitize_id(f"user-{channel}-{chat_id}")
user_peer = self._peers_cache.get(user_peer_id)
if not user_peer:
logger.warning(f"No user peer cached for '{user_peer_id}', skipping memory migration")
return False
uploaded = False
files = [
("MEMORY.md", "consolidated_memory.md", "Long-term user facts and preferences"),
("HISTORY.md", "conversation_history.md", "Chronological conversation summaries"),
]
for filename, upload_name, description in files:
filepath = memory_dir / filename
if not filepath.exists():
continue
content = filepath.read_text(encoding="utf-8").strip()
if not content:
continue
wrapped = (
f"<prior_memory_file>\n"
f"<context>\n"
f"This file was consolidated from local conversations BEFORE Honcho was activated.\n"
f"{description}. Treat as foundational context for this user.\n"
f"</context>\n"
f"\n"
f"{content}\n"
f"</prior_memory_file>\n"
)
try:
honcho_session.upload_file(
file=(upload_name, wrapped.encode("utf-8"), "text/plain"),
peer=user_peer,
metadata={"source": "local_memory", "original_file": filename},
)
logger.info(f"Uploaded {filename} to Honcho for {session_key}")
uploaded = True
except Exception as e:
logger.error(f"Failed to upload {filename} to Honcho: {e}")
return uploaded
def list_sessions(self) -> list[dict[str, Any]]:
"""
List all cached sessions.
Returns:
List of session info dicts.
"""
return [
{
"key": s.key,
"created_at": s.created_at.isoformat(),
"updated_at": s.updated_at.isoformat(),
"message_count": len(s.messages),
}
for s in self._cache.values()
]
Related skills
How it compares
Use Honcho Integration for framework-specific Honcho wiring; use raw Honcho SDK docs when building a custom runtime without an existing bot scaffold.
FAQ
Which bot frameworks does Honcho Integration support?
Honcho Integration ships concrete references for nanobot in references/bot-frameworks/nanobot/. Openclaw and picoclaw are listed as planned; unknown frameworks follow the documented general adapter pattern for sessions and memory.
What does Honcho Integration configure?
Honcho Integration configures Honcho memory reads and writes, session lifecycle hooks, and tool-calling bridges inside bot frameworks that expose an agent loop, session manager, tool registry, and message bus.
Is Honcho Integration safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.