fix: splitting context into references & verifying content is consistent.
This commit is contained in:
parent
bcb588f980
commit
a26fafc905
|
|
@ -12,7 +12,17 @@ Honcho is an open source memory library for building stateful agents. It works w
|
|||
|
||||
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.
|
||||
Your agent accesses this memory through `peer.chat(query)` (ask a natural language question, get a reasoned answer — a few seconds of live reasoning) or `session.context()` (near-instant read of formatted history + representation). Prefer `context()` for per-turn grounding; use `chat()` when you need a reasoned answer.
|
||||
|
||||
## Reference map
|
||||
|
||||
Follow the workflow below. Read a reference file only when you reach the step that needs it:
|
||||
|
||||
| When you're… | Read |
|
||||
| --- | --- |
|
||||
| Writing the client/peer/session setup (init, peers, sessions, add messages) | `references/core-patterns.md` |
|
||||
| Wiring how the AI reads context (tool call, pre-fetch, `context()`, streaming) | `references/agent-patterns.md` |
|
||||
| Integrating into a bot framework (nanobot, openclaw, picoclaw, …) | `references/bot-frameworks.md` + `references/bot-frameworks/<framework>/` |
|
||||
|
||||
## Integration Workflow
|
||||
|
||||
|
|
@ -34,7 +44,7 @@ Use Glob and Grep to find:
|
|||
- 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.md` for framework-specific integration guidance and check `{baseDir}/references/bot-frameworks/<framework>/` for concrete reference implementations.
|
||||
> **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 `references/bot-frameworks.md` for framework-specific integration guidance and check `references/bot-frameworks/<framework>/` for concrete reference implementations.
|
||||
|
||||
### Phase 2: Interview (REQUIRED)
|
||||
|
||||
|
|
@ -51,7 +61,7 @@ Ask about which entities should be Honcho peers:
|
|||
|
||||
#### Question Set 2 - Integration Pattern
|
||||
|
||||
Ask how they want to use Honcho context:
|
||||
Ask how they want to use Honcho context (see `references/agent-patterns.md` for the implementation of each):
|
||||
|
||||
- header: "Pattern"
|
||||
- question: "How should your AI access Honcho's user context?"
|
||||
|
|
@ -82,11 +92,11 @@ If they chose pre-fetch, ask what context matters:
|
|||
|
||||
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
|
||||
1. Install the SDK (see [Installation](#installation))
|
||||
2. Create Honcho client initialization — `references/core-patterns.md` §1
|
||||
3. Set up peer creation for identified entities — `references/core-patterns.md` §2–3
|
||||
4. Implement the chosen integration pattern(s) — `references/agent-patterns.md`
|
||||
5. Add message storage after exchanges — `references/core-patterns.md` §4
|
||||
6. Update any existing conversation handlers
|
||||
|
||||
### Phase 4: Verification
|
||||
|
|
@ -132,394 +142,7 @@ uv add honcho-ai
|
|||
bun add @honcho-ai/sdk
|
||||
```
|
||||
|
||||
## Sync 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 Honcho` with `.aio` namespace — use in async frameworks (FastAPI, Starlette, async workers)
|
||||
|
||||
```python
|
||||
# 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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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):**
|
||||
|
||||
```python
|
||||
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):**
|
||||
|
||||
```typescript
|
||||
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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```python
|
||||
stream = peer.chat_stream("What do we know about this user?")
|
||||
|
||||
for chunk in stream:
|
||||
print(chunk, end="", flush=True)
|
||||
```
|
||||
|
||||
**TypeScript:**
|
||||
|
||||
```typescript
|
||||
const stream = await peer.chatStream("What do we know about this user?");
|
||||
|
||||
for await (const chunk of stream) {
|
||||
process.stdout.write(chunk);
|
||||
}
|
||||
```
|
||||
The SDK is sync-by-default in Python (with an `.aio` async namespace) and async-only in TypeScript — match the client to your framework. Full sync/async guidance and the base client/peer/session/message code are in `references/core-patterns.md`.
|
||||
|
||||
## Integration Checklist
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,257 @@
|
|||
# Agent Access Patterns
|
||||
|
||||
How your AI accesses Honcho's user context. Pick based on the interview answer to Question Set 2. These build on the client/peer/session setup in `core-patterns.md`.
|
||||
|
||||
- **Pattern A — Dialectic chat as a tool call** (recommended for agents): the agent decides when to query context on-demand.
|
||||
- **Pattern B — Pre-fetch with targeted queries**: fetch a fixed set of attributes before each LLM call.
|
||||
- **Pattern C — `context()` for LLM integration**: inject conversation history + representation into the prompt.
|
||||
|
||||
> **Speed note.** `chat()` runs live dialectic reasoning (a few seconds) — Patterns A and B call it. `context()` (Pattern C) is a near-instant read. Prefer `context()` for per-turn grounding; reach for `chat()` when you genuinely need a reasoned answer.
|
||||
|
||||
## 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):**
|
||||
|
||||
```python
|
||||
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):**
|
||||
|
||||
```typescript
|
||||
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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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. This is a near-instant read — no dialectic reasoning — so it's the cheapest way to ground each turn.
|
||||
|
||||
**Python:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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!)
|
||||
]);
|
||||
```
|
||||
|
||||
### What `context()` returns
|
||||
|
||||
`session.context()` bundles the session-local view you can drop straight into an LLM call:
|
||||
|
||||
- **Recent messages** from the session, trimmed to the `tokens` budget.
|
||||
- **Conversation summaries** when `summary=True` — the two-tier short/long summaries so older turns still count without spending the full token budget.
|
||||
- **The target peer's representation** when you pass `peer_target` — Honcho's synthesized understanding of that user, folded in. Omit it and you get session-local context only (no cross-session memory).
|
||||
|
||||
The `to_openai()` / `to_anthropic()` helpers format all of that as a `messages` array for the respective provider. Pass your `assistant` peer so its turns are tagged as the assistant role.
|
||||
|
||||
## 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);
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
# Core Integration Patterns
|
||||
|
||||
The base SDK boilerplate for any Honcho integration: choosing sync vs async, initializing the client, creating peers, configuring sessions, and adding messages. Read this once you've chosen your entities and session structure (Phases 1–2). For the agent-facing recall patterns (tool call, pre-fetch, `context()`), see `agent-patterns.md`.
|
||||
|
||||
## Sync 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 Honcho` with `.aio` namespace — use in async frameworks (FastAPI, Starlette, async workers)
|
||||
|
||||
```python
|
||||
# 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 examples below show sync Python; swap to `.aio` equivalents for async codebases.
|
||||
|
||||
## 1. Initialize with a Single Workspace
|
||||
|
||||
Use ONE workspace for your entire application. The workspace name should reflect your app/product.
|
||||
|
||||
**Python:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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:**
|
||||
|
||||
```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")
|
||||
]);
|
||||
```
|
||||
|
|
@ -51,7 +51,7 @@ add_messages_to_session session_id: "<session-id>"
|
|||
## Speed: reads vs. reasoning
|
||||
|
||||
- **`chat` is the slow one** — it runs the dialectic (live reasoning over the user's memory), so it takes a few seconds. Use it when you need a reasoned answer, not for every turn.
|
||||
- **`get_context` / `get_peer_context` / `get_representation` / `search` are reads** — near-instantaneous. Reach for these first when you just need the current representation or history; only call `chat` when you actually need reasoning.
|
||||
- **`get_session_context` / `get_peer_context` / `get_representation` / `search` are reads** — near-instantaneous. Reach for these first when you just need the current representation or history; only call `chat` when you actually need reasoning.
|
||||
|
||||
## Reasoning levels
|
||||
|
||||
|
|
|
|||
|
|
@ -26,11 +26,21 @@ Reasoning happens **asynchronously**. After you record a turn, don't poll or wai
|
|||
Do this every conversation. It's the whole skill.
|
||||
|
||||
1. **Once per conversation** — make sure there's a session with you and the user as peers (observe the user, don't observe yourself).
|
||||
2. **Before responding, when personalization helps** — pull the user's current context (`get_context` / `get_representation`) or search past messages (`search`) — these are fast reads. For a reasoned answer to a specific question, ask the dialectic (`chat`) — that one takes a few seconds, so use it when it earns its keep.
|
||||
2. **Before responding, when personalization helps** — pull the user's current context (`get_session_context` / `get_representation`) or search past messages (`search`) — these are fast reads. For a reasoned answer to a specific question, ask the dialectic (`chat`) — that one takes a few seconds, so use it when it earns its keep.
|
||||
3. **After every exchange** — record both the user's message and your reply. This is what makes Honcho learn. Don't skip it.
|
||||
|
||||
Optionally, when you learn a durable fact you don't want to wait for background reasoning to surface, **store a conclusion** directly.
|
||||
|
||||
## What you get back when you recall
|
||||
|
||||
Three ways to pull memory, cheapest first:
|
||||
|
||||
- **Representation** (`get_representation`) — Honcho's synthesized understanding of the user as text, ready to drop straight into a system prompt. Near-instant read.
|
||||
- **Context** (`get_session_context`) — the fuller session view: a session summary + recent messages covering the conversation, and — *only if you target a peer* — that peer's representation folded in. Without a peer target it's session-local (recent turns + summary) and carries no cross-conversation memory. Near-instant read.
|
||||
- **Dialectic** (`chat`) — a *reasoned* natural-language answer to a specific question ("How does this user like to receive feedback?"). Runs live reasoning, so it takes a few seconds. Use it when a plain read won't answer the question.
|
||||
|
||||
The dialectic (`chat`) also takes a **reasoning level** that trades speed for depth — from `minimal` (fast factual lookup) through `low` (the default balance) to `max` (deep synthesis for the hardest questions). Pick the lowest level that answers the question; higher levels are slower and cost more. The full level-by-level table and model routing live in the path skills (`honcho-mcp`, `honcho-cli`) and the [chat docs](https://honcho.dev/docs/v3/documentation/features/chat.md).
|
||||
|
||||
---
|
||||
|
||||
## Pick your access path
|
||||
|
|
@ -59,7 +69,7 @@ You need a Honcho API key — get one free at <https://app.honcho.dev> (starts w
|
|||
- **One stable peer ID per entity.** Reuse the same `peer_id` for a person across every session and channel; splitting them (`user`, `user-web`, `user-discord`) builds separate representations and fragments memory.
|
||||
- **Scope sessions so reasoning actually fires.** Reasoning is token-batched per peer (~1,000 tokens within a session). Scope a session to one active interaction (per-conversation, per-channel, per-task, per-project); create a new one when context genuinely resets (new topic, new day), reuse it while context should keep accumulating. Many tiny sessions each stall below the threshold and never get reasoned over.
|
||||
- **Don't block on reasoning.** It's asynchronous. Respond now; the representation will be richer next time.
|
||||
- **Reads are cheap; reasoning isn't.** Fetching the representation/context (`get_context`, `get_representation`, `search`) is a near-instant read — use it freely. The dialectic (`chat`) runs live reasoning and takes a few seconds, so save it for when you genuinely need a reasoned answer, not every turn.
|
||||
- **Reads are cheap; reasoning isn't.** Fetching the representation/context (`get_session_context`, `get_representation`, `search`) is a near-instant read — use it freely. The dialectic (`chat`) runs live reasoning and takes a few seconds, so save it for when you genuinely need a reasoned answer, not every turn.
|
||||
- **Check before you store.** Background reasoning derives most conclusions automatically. Store a conclusion manually only for a durable fact you want available immediately; `list`/`query` first to avoid duplicates.
|
||||
- **One workspace per app/user-context.** Don't scatter the same user's memory across multiple workspaces.
|
||||
- **Unify memory across tools with a shared workspace + peer ID.** To give one user continuous memory across several apps or agents (e.g. Claude Code, Cursor, your own app), point them at the same workspace and reuse the same peer ID — that shared ID is what links the representation. See [Unified Memory Setup](https://honcho.dev/docs/v3/guides/recipes/unified-memory-setup.md).
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ This skill migrates code from `honcho` Python SDK v1.6.0 to v2.1.1 (required for
|
|||
|
||||
## Quick Migration
|
||||
|
||||
The four changes below are the structural breaks you hit first — apply them inline. The remaining renames and smaller changes are one-line lookups in the [Quick Reference Table](#quick-reference-table); the full before/after for every change lives in [DETAILED-CHANGES.md](DETAILED-CHANGES.md).
|
||||
|
||||
### 1. Update async architecture
|
||||
|
||||
```python
|
||||
|
|
@ -98,177 +100,21 @@ session.get_configuration()
|
|||
client.get_configuration()
|
||||
```
|
||||
|
||||
### 5. Update method names
|
||||
### Changes 5–15 (at a glance)
|
||||
|
||||
```python
|
||||
# Before
|
||||
peer.working_rep()
|
||||
peer.get_context()
|
||||
peer.get_sessions()
|
||||
session.get_context()
|
||||
session.get_summaries()
|
||||
session.get_messages()
|
||||
session.get_peers()
|
||||
session.get_peer_config()
|
||||
client.get_peers()
|
||||
client.get_sessions()
|
||||
client.get_workspaces()
|
||||
The remaining breaks are one-line mappings in the [Quick Reference Table](#quick-reference-table) below, with full before/after in [DETAILED-CHANGES.md](DETAILED-CHANGES.md):
|
||||
|
||||
# After
|
||||
peer.representation()
|
||||
peer.context()
|
||||
peer.sessions()
|
||||
session.context()
|
||||
session.summaries()
|
||||
session.messages()
|
||||
session.peers()
|
||||
session.get_peer_configuration()
|
||||
client.peers()
|
||||
client.sessions()
|
||||
client.workspaces()
|
||||
```
|
||||
|
||||
### 6. Update streaming
|
||||
|
||||
```python
|
||||
# Before
|
||||
response = peer.chat("query", stream=True)
|
||||
for chunk in response:
|
||||
print(chunk, end="")
|
||||
|
||||
# After
|
||||
stream = peer.chat_stream("query")
|
||||
for chunk in stream:
|
||||
print(chunk, end="")
|
||||
```
|
||||
|
||||
### 7. Update queue status (formerly deriver)
|
||||
|
||||
```python
|
||||
# Before
|
||||
from honcho_core.types import DeriverStatus
|
||||
|
||||
status = client.get_deriver_status()
|
||||
status = client.poll_deriver_status(timeout=300.0) # Removed!
|
||||
|
||||
# After
|
||||
from honcho.api_types import QueueStatusResponse
|
||||
|
||||
status = client.queue_status()
|
||||
# poll_deriver_status removed - implement polling manually if needed
|
||||
```
|
||||
|
||||
### 8. Update representation parameters
|
||||
|
||||
```python
|
||||
# Before
|
||||
rep = peer.working_rep(
|
||||
include_most_derived=True,
|
||||
max_observations=50
|
||||
)
|
||||
|
||||
# After
|
||||
rep = peer.representation(
|
||||
include_most_frequent=True,
|
||||
max_conclusions=50
|
||||
)
|
||||
```
|
||||
|
||||
### 9. Move update_message to session
|
||||
|
||||
```python
|
||||
# Before
|
||||
updated = client.update_message(message=msg, metadata={"key": "value"}, session="sess-id")
|
||||
|
||||
# After
|
||||
updated = session.update_message(message=msg, metadata={"key": "value"})
|
||||
```
|
||||
|
||||
### 10. Update card() return type and method name
|
||||
|
||||
```python
|
||||
# Before
|
||||
card: str = peer.card() # Returns str
|
||||
|
||||
# After (v2.0.0+)
|
||||
card: list[str] | None = peer.get_card() # Returns list[str] | None
|
||||
if card:
|
||||
print("\n".join(card))
|
||||
|
||||
# peer.card() still works but is deprecated — use get_card()
|
||||
|
||||
# New in v2.0.1: set_card()
|
||||
peer.set_card(["Prefers dark mode", "Located in US"])
|
||||
```
|
||||
|
||||
### 11. Strict input validation (v2.0.2+)
|
||||
|
||||
All input models now reject unknown fields via `extra="forbid"` Pydantic validation. Previously, misspelled or extraneous fields were silently ignored.
|
||||
|
||||
```python
|
||||
# Before (v2.0.1 and earlier) — silently ignored
|
||||
peer = client.peer("user-1", configuration=PeerConfig(observe_mee=True)) # typo silently ignored
|
||||
|
||||
# After (v2.0.2+) — raises ValidationError
|
||||
peer = client.peer("user-1", configuration=PeerConfig(observe_mee=True)) # ValidationError!
|
||||
```
|
||||
|
||||
### 12. peer() and session() always make API calls (v2.1.0+)
|
||||
|
||||
**Breaking**: `peer()` and `session()` now always make a get-or-create API call. Previously, calling without metadata/configuration returned a lazy object with no API call.
|
||||
|
||||
```python
|
||||
# Before (v2.0.x) — no API call without options
|
||||
peer = client.peer("user-123") # Lazy, no network request
|
||||
|
||||
# After (v2.1.0+) — always hits the API
|
||||
peer = client.peer("user-123") # Makes POST to /peers (get-or-create)
|
||||
|
||||
# Async
|
||||
peer = await client.aio.peer("user-123") # Also always hits API
|
||||
```
|
||||
|
||||
### 13. New properties and methods (v2.1.0+)
|
||||
|
||||
```python
|
||||
# created_at on Peer and Session
|
||||
peer = client.peer("user-123")
|
||||
print(peer.created_at) # datetime | None
|
||||
|
||||
session = client.session("sess-1")
|
||||
print(session.created_at) # datetime | None
|
||||
|
||||
# is_active on Session
|
||||
print(session.is_active) # bool | None
|
||||
|
||||
# get_message() on Session
|
||||
msg = session.get_message("msg-id")
|
||||
# Async: msg = await session.aio.get_message("msg-id")
|
||||
```
|
||||
|
||||
### 14. Pagination parameters on list methods (v2.1.0+)
|
||||
|
||||
All list methods now accept `page`, `size`, and `reverse` parameters:
|
||||
|
||||
```python
|
||||
# Before (v2.0.x) — only filters
|
||||
peers_page = client.peers(filters={"metadata": {"role": "admin"}})
|
||||
|
||||
# After (v2.1.0+) — pagination controls
|
||||
peers_page = client.peers(
|
||||
filters={"metadata": {"role": "admin"}},
|
||||
page=2,
|
||||
size=25,
|
||||
reverse=True
|
||||
)
|
||||
|
||||
# Works on: client.peers(), client.sessions(), peer.sessions(),
|
||||
# session.messages(), scope.list()
|
||||
```
|
||||
|
||||
### 15. Broader HTTP retry logic (v2.1.1+)
|
||||
|
||||
The SDK now retries on `httpx.TimeoutException`, `httpx.NetworkError`, and `httpx.RemoteProtocolError` (previously only `httpx.TimeoutException` and `httpx.ConnectError`). These are mapped to the SDK's `TimeoutError` and `ConnectionError` respectively. No code changes needed — this is transparent.
|
||||
- **5. Method renames** — `get_context()`→`context()`, `get_sessions()`→`sessions()`, `get_messages()`→`messages()`, etc. (drop the `get_` prefix)
|
||||
- **6. Streaming** — `chat("q", stream=True)` → `chat_stream("q")`
|
||||
- **7. Queue status** — `get_deriver_status()`→`queue_status()`; `poll_deriver_status()` removed (poll manually)
|
||||
- **8. Representation params** — `include_most_derived=`→`include_most_frequent=`, `max_observations=`→`max_conclusions=`
|
||||
- **9. `update_message`** — moved from `client.update_message(..., session=)` to `session.update_message(...)`
|
||||
- **10. Card** — `card(): str` → `get_card(): list[str] | None`; new `set_card(list[str])` (v2.0.1); `card()` deprecated
|
||||
- **11. Strict validation** (v2.0.2) — unknown config fields now raise `ValidationError` instead of being ignored
|
||||
- **12. `peer()`/`session()`** (v2.1.0) — now always make a get-or-create API call (previously lazy)
|
||||
- **13. New properties** (v2.1.0) — `created_at`, `session.is_active`, `session.get_message(id)`
|
||||
- **14. Pagination** (v2.1.0) — `page=`, `size=`, `reverse=` on all list methods
|
||||
- **15. Retries** (v2.1.1) — broader HTTP retry coverage; transparent, no code changes
|
||||
|
||||
## Quick Reference Table
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue