同步完整源码 - 2026-05-24

This commit is contained in:
xiaoxue 2026-05-24 17:22:17 +08:00
commit f74585aa9d
863 changed files with 224495 additions and 0 deletions

View File

@ -0,0 +1,117 @@
---
name: honcho-cli
description: Inspect and debug Honcho workspaces via the `honcho` CLI. Use when investigating peer representations, memory state, session context, queue status, or dialectic quality — any task that requires introspection of a Honcho deployment.
allowed-tools: Bash(honcho:*), Bash(jq:*), Read, Grep
---
# Honcho CLI
`honcho` wraps the Honcho Python SDK with agent-friendly defaults: JSON output, structured errors, input validation. Use it to inspect workspace state, debug peer memory, and diagnose the dialectic.
## Output & config
- **TTY**: human-readable tables (default when interactive)
- **Piped / `--json`**: JSON — collection commands emit arrays, single-resource commands emit objects
- **Exit codes**: `0` success · `1` client error (bad input, not found) · `2` server error · `3` auth error
- **Config**: `~/.honcho/config.json` (shared with other Honcho tools). The CLI owns `apiKey` and `environmentUrl` at the top level; run `honcho init` to confirm or set them. Per-command scope (workspace / peer / session) is via `-w` / `-p` / `-s` flags or `HONCHO_*` env vars.
## Command groups
- `honcho config` — CLI configuration
- `honcho workspace` — inspect, delete, search
- `honcho peer` — inspect, card, chat, search
- `honcho session` — inspect, messages, context, summaries
- `honcho message` — list and get
- `honcho conclusion` — list, search, create, delete
## Rules
- Always pass `--json` when processing output programmatically.
- Run `honcho peer inspect` before `honcho peer chat` to understand context.
- Use `honcho session context` to see exactly what an agent receives.
- Never run `honcho workspace delete` without `honcho workspace inspect` first.
- Check queue status when derivation seems stalled.
- Compare peer card with conclusions to understand memory state.
## Inspection tour
When orienting to a Honcho deployment, walk outside-in:
### 1. Understand the workspace
```bash
honcho workspace inspect --json
```
### 2. Find the peer
```bash
honcho peer list --json
honcho peer inspect <peer_id> --json
```
### 3. Check peer's memory
```bash
honcho peer card <peer_id> --json
honcho conclusion list --observer <peer_id> --json
honcho conclusion search "topic" --observer <peer_id> --json
```
### 4. Debug a session
```bash
honcho session inspect <session_id> --json
honcho message list <session_id> --last 20 --json
honcho session context <session_id> --json
honcho session summaries <session_id> --json
```
### 5. Search across workspace
```bash
honcho workspace search "query" --json
honcho peer search <peer_id> "query" --json
```
## Debugging playbook
### Peer not learning?
```bash
# Is observation enabled?
honcho peer inspect <peer_id> --json | jq '.configuration'
# Is the deriver queue processing messages?
honcho workspace queue-status --json
# What conclusions exist?
honcho conclusion list --observer <peer_id> --json
honcho conclusion search "expected topic" --observer <peer_id> --json
```
### Session context looks wrong?
```bash
# Raw context an agent would receive
honcho session context <session_id> --json
# Summaries feeding the context
honcho session summaries <session_id> --json
# Recent message history
honcho message list <session_id> --last 50 --json
```
### Dialectic giving bad answers?
```bash
# What the peer card says
honcho peer card <peer_id> --json
# Conclusions on the specific topic
honcho conclusion search "topic" --observer <peer_id> --json
# Exercise the dialectic directly
honcho peer chat <peer_id> "what do you know about X?" --json
```

View File

@ -0,0 +1,554 @@
---
name: honcho-integration
description: Integrate Honcho memory and social cognition into existing Python or TypeScript codebases. Use when adding Honcho SDK, setting up peers, configuring sessions, implementing the dialectic chat endpoint for AI agents, or wiring Honcho into bot frameworks (nanobot, openclaw, picoclaw, etc).
allowed-tools: Read, Glob, Grep, Bash(uv:*), Bash(bun:*), Bash(npm:*), Edit, Write, WebFetch, AskUserQuestion
---
# 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:
- `**/*.py` or `**/*.ts` files 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.md` for 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 doctor` to confirm connectivity before testing the integration code
- Use `honcho peer list` and `honcho peer chat` to 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:
```bash
honcho init # persist API key + URL to ~/.honcho/config.json
honcho doctor # verify connectivity, config, workspace health
honcho peer chat # test the dialectic endpoint interactively
```
This is the fastest way to confirm the API key and URL are correct before debugging SDK code.
## Installation
### Python (use uv)
```bash
uv add honcho-ai
```
### TypeScript (use bun)
```bash
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);
}
```
## Integration Checklist
When integrating Honcho into an existing codebase:
- [ ] Install SDK with `uv add honcho-ai` (Python) or `bun add @honcho-ai/sdk` (TypeScript)
- [ ] Set up `HONCHO_API_KEY` environment variable
- [ ] Initialize Honcho client with a single workspace ID
- [ ] Create peers for all entities (users AND AI assistants)
- [ ] Set `observe_me=False` for 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 doctor` to verify connectivity before testing integration code
- [ ] (Optional) Use `honcho peer chat` to 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>

View File

@ -0,0 +1,205 @@
# 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](https://github.com/HKUDS/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 (default `true`)?
## 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:
```toml
[project.optional-dependencies]
honcho = ["honcho-ai>=2.0.1"]
```
**TypeScript:** Add `@honcho-ai/sdk`:
```bash
bun add @honcho-ai/sdk
# or npm install @honcho-ai/sdk
```
If the framework supports optional peer dependencies:
```json
{
"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:**
```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:**
```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 via `peer.chat()`
Key patterns (Python):
- `from __future__ import annotations` + `TYPE_CHECKING` for all honcho imports
- Runtime imports inside functions (never top-level) so the bot doesn't crash without `honcho-ai`
- Wrap in `try/except ImportError` for graceful degradation
Key patterns (TypeScript):
- Use dynamic `import()` for honcho SDK (never top-level `import ... 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/catch` for 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:**
```python
session.add_messages([
user_peer.message(user_input),
assistant_peer.message(assistant_response),
])
```
**TypeScript:**
```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) or `session.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_CHECKING` for type hints, runtime imports inside functions
- TypeScript: `import type { ... }` for type-only imports, dynamic `import()` for runtime access
- **Feature flag gating**: Always check `config.enabled` AND `HONCHO_API_KEY` / `process.env.HONCHO_API_KEY` before touching Honcho
- **Graceful degradation**:
- Python: `try/except ImportError` and generic `Exception` catches with logger warnings, never crash the bot
- TypeScript: `try/catch` around dynamic `import()` 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

View File

@ -0,0 +1,85 @@
"""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

View File

@ -0,0 +1,86 @@
"""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)}"

View File

@ -0,0 +1,576 @@
"""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()
]

View File

@ -0,0 +1,607 @@
# Detailed API Changes
## 1. Async Client Architecture (Major Change)
The separate `AsyncHoncho`, `AsyncPeer`, and `AsyncSession` classes have been removed. Use the `.aio` accessor instead.
### Before (v1.6.0)
```python
from honcho import Honcho, AsyncHoncho, AsyncPeer, AsyncSession
# Sync client
client = Honcho()
# Async client - separate class
async_client = AsyncHoncho()
peer = await async_client.peer("user-123")
response = await peer.chat("query")
```
### After (v2.0.0)
```python
from honcho import Honcho
# Single client with .aio accessor for async operations
client = Honcho()
# Sync operations
peer = client.peer("user-123")
response = peer.chat("query")
# Async operations via .aio accessor
peer = await client.aio.peer("user-123")
response = await peer.aio.chat("query")
# Async iteration
async for p in client.aio.peers():
print(p.id)
```
**Migration steps:**
1. Remove all `AsyncHoncho`, `AsyncPeer`, `AsyncSession` imports
2. Replace `AsyncHoncho()` with `Honcho()` and use `.aio` accessor
3. Replace `AsyncPeer` type hints with `Peer`
4. Replace `AsyncSession` type hints with `Session`
5. Access async methods via `.aio` property on instances
---
## 2. Observations → Conclusions (Terminology Change)
### Before (v1.6.0)
```python
from honcho import Observation, ObservationScope, AsyncObservationScope
# Access observations
scope = peer.observations
scope = peer.observations_of("other-peer")
# List observations
obs_list = scope.list()
# Query observations
results = scope.query("preferences")
# Create observations
scope.create([{"content": "User likes dark mode", "session_id": "sess-1"}])
# Get representation from observations
rep = scope.get_representation()
```
### After (v2.0.0)
```python
from honcho import Conclusion, ConclusionScope, ConclusionScopeAio
# Access conclusions
scope = peer.conclusions
scope = peer.conclusions_of("other-peer")
# List conclusions (now returns SyncPage, not list)
conclusions_page = scope.list()
for conclusion in conclusions_page:
print(conclusion.content)
# Query conclusions
results = scope.query("preferences")
# Create conclusions
scope.create([{"content": "User likes dark mode", "session_id": "sess-1"}])
# Get representation from conclusions
rep = scope.representation() # Returns str, not Representation object
```
---
## 3. Representation Type Change (Major Change)
The `Representation` class has been removed. Representations are now simple strings.
### Before (v1.6.0)
```python
from honcho import Representation, ExplicitObservation, DeductiveObservation
# Get working representation
rep: Representation = peer.working_rep()
# Access explicit and deductive observations
for obs in rep.explicit:
print(obs.content, obs.created_at)
for obs in rep.deductive:
print(obs.conclusion, obs.premises)
# Check if empty
if rep.is_empty():
print("No observations")
# Merge representations
rep.merge_representation(other_rep)
# Diff representations
diff = rep.diff_representation(other_rep)
# String formatting
print(str(rep))
print(rep.str_no_timestamps())
print(rep.format_as_markdown())
```
### After (v2.0.0)
```python
# Get representation - now returns str directly
rep: str = peer.representation()
# It's just a string now
print(rep)
# Check if empty
if not rep:
print("No conclusions")
```
**Removed methods:**
- `.explicit` property
- `.deductive` property
- `.is_empty()`
- `.merge_representation()`
- `.diff_representation()`
- `.str_no_timestamps()`
- `.format_as_markdown()`
---
## 4. Configuration Parameter Rename
All `config` parameters have been renamed to `configuration`, and configuration types are now strongly typed.
### Before (v1.6.0)
```python
# Creating resources with config
peer = client.peer("user-1", config={"observe_me": True})
session = client.session("sess-1", config={"some_setting": True})
# Getting/setting config
config = peer.get_config()
peer.set_config({"observe_me": False})
config = session.get_config()
session.set_config({"some_setting": False})
config = client.get_config()
client.set_config({"workspace_setting": True})
# Message config parameter
msg = peer.message("Hello", config={"reasoning": {"enabled": True}})
```
### After (v2.0.0)
```python
from honcho.api_types import PeerConfig, SessionConfiguration, WorkspaceConfiguration
# Creating resources with configuration (typed)
peer = client.peer("user-1", configuration=PeerConfig(observe_me=True))
session = client.session("sess-1", configuration=SessionConfiguration())
# Getting/setting configuration (returns typed objects)
config: PeerConfig = peer.get_configuration()
peer.set_configuration(PeerConfig(observe_me=False))
config: SessionConfiguration = session.get_configuration()
session.set_configuration(SessionConfiguration())
config: WorkspaceConfiguration = client.get_configuration()
client.set_configuration(WorkspaceConfiguration())
# Message configuration parameter
msg = peer.message("Hello", configuration={"reasoning": {"enabled": True}})
```
---
## 5. Streaming Chat API Change
### Before (v1.6.0)
```python
# Streaming via parameter
response = peer.chat("query", stream=True)
for chunk in response:
print(chunk, end="")
final = response.get_final_response()
```
### After (v2.0.0)
```python
# Streaming via separate method
stream = peer.chat_stream("query")
for chunk in stream:
print(chunk, end="")
final = stream.get_final_response()
# Non-streaming (no stream parameter needed)
response = peer.chat("query")
```
---
## 6. Deriver Status → Queue Status
### Before (v1.6.0)
```python
from honcho_core.types import DeriverStatus
# Get status
status: DeriverStatus = client.get_deriver_status()
status = session.get_deriver_status()
# Poll until complete
status = client.poll_deriver_status(timeout=300.0)
status = session.poll_deriver_status(timeout=300.0)
# Access fields
print(status.pending_work_units)
print(status.in_progress_work_units)
```
### After (v2.0.0)
```python
from honcho.api_types import QueueStatusResponse
# Get status
status: QueueStatusResponse = client.queue_status()
status = session.queue_status()
# Access fields (same as before)
print(status.pending_work_units)
print(status.in_progress_work_units)
# poll_deriver_status has been removed - implement polling manually if needed:
import time
def poll_until_complete(client, timeout=300.0):
start = time.time()
while time.time() - start < timeout:
status = client.queue_status()
if status.pending_work_units == 0 and status.in_progress_work_units == 0:
return status
time.sleep(1)
raise TimeoutError("Queue processing did not complete in time")
```
---
## 7. PeerContext Changes
### Before (v1.6.0)
```python
from honcho import PeerContext
context: PeerContext = peer.get_context()
# Access representation (was Representation object)
rep: Representation = context.representation
if rep:
print(rep.explicit)
print(rep.deductive)
```
### After (v2.0.0)
```python
from honcho.api_types import PeerContextResponse
context: PeerContextResponse = peer.context()
# Access representation (now str)
rep: str | None = context.representation
if rep:
print(rep)
```
---
## 8. Card Method Return Type Change
### Before (v1.6.0)
```python
# card() returned str (joined with newlines)
card: str = peer.card()
print(card) # "line1\nline2\nline3"
```
### After (v2.0.0)
```python
# card() returns list[str] | None
card: list[str] | None = peer.card()
if card:
print("\n".join(card)) # Join manually if needed
```
---
## 9. Message Update Location Change
### Before (v1.6.0)
```python
# Update message via client
updated = client.update_message(
message=msg,
metadata={"key": "value"},
session="session-id" # Required if message is string ID
)
```
### After (v2.0.0)
```python
# Update message via session
updated = session.update_message(
message=msg,
metadata={"key": "value"}
)
```
---
## 10. Removed: `core` Property
### Before (v1.6.0)
```python
# Access underlying Stainless-generated client
core_client = client.core
workspace = client.core.workspaces.get_or_create(id="custom-workspace")
```
### After (v2.0.0)
```python
# The `core` property has been removed
# The SDK no longer uses a Stainless-generated client internally
# Use the SDK's public API directly
```
---
## 11. Environment Changes
### Before (v1.6.0)
```python
# Three environments available
client = Honcho(environment="local")
client = Honcho(environment="production")
client = Honcho(environment="demo")
```
### After (v2.0.0)
```python
# Only two environments
client = Honcho(environment="local")
client = Honcho(environment="production")
# "demo" environment has been removed
```
---
## 12. Reasoning Level Parameter (New Feature)
The chat method now supports a `reasoning_level` parameter:
```python
# New in v2.0.0
response = peer.chat(
"complex query",
reasoning_level="high" # "minimal", "low", "medium", "high", "max"
)
stream = peer.chat_stream(
"complex query",
reasoning_level="max"
)
```
---
## 13. Import Changes Summary
### Removed Imports
```python
# These no longer exist in v2.0.0
from honcho import AsyncHoncho # Use Honcho with .aio accessor
from honcho import AsyncPeer # Use Peer with .aio accessor
from honcho import AsyncSession # Use Session with .aio accessor
from honcho import Observation # Renamed to Conclusion
from honcho import ObservationScope # Renamed to ConclusionScope
from honcho import AsyncObservationScope # Renamed to ConclusionScopeAio
from honcho import Representation # Removed (now str)
from honcho import ExplicitObservation # Removed
from honcho import DeductiveObservation # Removed
from honcho import PeerContext # Use PeerContextResponse from api_types
```
### New Imports
```python
from honcho import Conclusion, ConclusionScope
from honcho import ConclusionScopeAio
from honcho import HonchoAio, PeerAio, SessionAio # For type hints
from honcho import MessageCreateParams, Message
# Typed configuration classes
from honcho.api_types import (
PeerConfig,
SessionConfiguration,
WorkspaceConfiguration,
SessionPeerConfig,
QueueStatusResponse,
PeerContextResponse,
)
```
### Message Type Import Changes
```python
# Before
from honcho_core.types.workspaces.sessions import MessageCreateParam
from honcho_core.types.workspaces.sessions.message import Message
from honcho.session import SessionPeerConfig
# After
from honcho import Message, MessageCreateParams # Note: plural "Params"
from honcho.api_types import SessionPeerConfig
```
**Note:** `MessageCreateParam` (singular) is now `MessageCreateParams` (plural).
---
## 14. Card Method Deprecation and set_card (v2.0.1)
### Before (v2.0.0)
```python
card: list[str] | None = peer.card()
```
### After (v2.0.1+)
```python
# get_card() is the preferred method
card: list[str] | None = peer.get_card()
# card() still works but emits a deprecation warning
card = peer.card() # Deprecated
# New: set_card()
updated = peer.set_card(["Fact 1", "Fact 2"])
updated = peer.set_card(["Fact 1"], target="other-peer")
# Async variants
card = await peer.aio.get_card()
await peer.aio.set_card(["Fact 1"])
```
---
## 15. Strict Input Validation (v2.0.2)
All Pydantic input models now use `extra="forbid"`, raising `ValidationError` for unknown fields.
```python
from honcho.api_types import PeerConfig
# This now raises ValidationError instead of silently ignoring the typo
PeerConfig(observe_mee=True) # ValidationError: extra fields not permitted
```
---
## 16. peer() and session() Always Make API Calls (v2.1.0)
### Before (v2.0.x)
```python
# Without options: lazy object, no API call
peer = client.peer("user-123")
# peer.created_at was None
# With options: made API call
peer = client.peer("user-123", metadata={"key": "value"})
```
### After (v2.1.0+)
```python
# Always makes a get-or-create API call
peer = client.peer("user-123")
# peer.created_at is now always populated
# Async
peer = await client.aio.peer("user-123")
```
All Peer/Session objects now have `created_at` populated immediately after construction.
---
## 17. New Properties: created_at, is_active (v2.1.0)
```python
# Peer
peer = client.peer("user-123")
print(peer.created_at) # datetime | None
# Session
session = client.session("sess-1")
print(session.created_at) # datetime | None
print(session.is_active) # bool | None
# These are refreshed by get_metadata(), get_configuration(), and refresh()
peer.refresh()
session.refresh()
```
---
## 18. get_message() on Session (v2.1.0)
```python
# Fetch a single message by ID
msg = session.get_message("msg-abc123")
print(msg.content, msg.created_at)
# Async
msg = await session.aio.get_message("msg-abc123")
```
---
## 19. Pagination Parameters (v2.1.0)
All list methods now accept `page`, `size`, and `reverse`:
```python
# Defaults: page=1, size=50, reverse=False
peers_page = client.peers(page=2, size=25, reverse=True)
# Returns SyncPage / AsyncPage with:
print(peers_page.total) # Total items
print(peers_page.pages) # Total pages
print(peers_page.has_next_page())
# Works on:
# client.peers(), client.sessions()
# peer.sessions()
# session.messages()
# scope.list()
```
---
## 20. Broader HTTP Retry Logic (v2.1.1)
The SDK now catches `httpx.NetworkError` and `httpx.RemoteProtocolError` for retry in addition to `httpx.TimeoutException` and `httpx.ConnectError`. This is transparent — no code changes needed.

View File

@ -0,0 +1,155 @@
# Migration Checklist
Use this checklist to track migration progress. Copy into your working notes and check off items as completed.
## Dependencies
- [ ] Update `honcho` package to v2.1.1
- [ ] Remove any `honcho-core` imports
## Async Architecture Changes
- [ ] Remove `AsyncHoncho` imports → use `Honcho` with `.aio` accessor
- [ ] Remove `AsyncPeer` imports → use `Peer` with `.aio` accessor
- [ ] Remove `AsyncSession` imports → use `Session` with `.aio` accessor
- [ ] Update all async client usage to use `.aio` accessor pattern
- [ ] Update type hints: `AsyncPeer``Peer`, `AsyncSession``Session`
## Terminology: Observations → Conclusions
- [ ] Replace `Observation` import with `Conclusion`
- [ ] Replace `ObservationScope` import with `ConclusionScope`
- [ ] Replace `AsyncObservationScope` import with `ConclusionScopeAio`
- [ ] Replace `.observations` property with `.conclusions`
- [ ] Replace `.observations_of()` method with `.conclusions_of()`
- [ ] Replace `.get_representation()` with `.representation()`
## Representation Changes
- [ ] Remove `Representation` import (now returns `str`)
- [ ] Remove `ExplicitObservation` import
- [ ] Remove `DeductiveObservation` import
- [ ] Replace `working_rep()` with `representation()`
- [ ] Update type hints from `Representation` to `str`
- [ ] Remove `.explicit` property access
- [ ] Remove `.deductive` property access
- [ ] Replace `.is_empty()` checks with `not rep`
- [ ] Remove `.merge_representation()` calls
- [ ] Remove `.diff_representation()` calls
- [ ] Remove `.str_no_timestamps()` calls
- [ ] Remove `.format_as_markdown()` calls
## Configuration Changes
- [ ] Replace all `config=` parameters with `configuration=`
- [ ] Replace `.get_config()` with `.get_configuration()`
- [ ] Replace `.set_config()` with `.set_configuration()`
- [ ] Rename `.get_peer_config()``.get_peer_configuration()`
- [ ] Rename `.set_peer_config()``.set_peer_configuration()`
- [ ] Import typed config classes from `honcho.api_types` if needed:
- [ ] `PeerConfig`
- [ ] `SessionConfiguration`
- [ ] `WorkspaceConfiguration`
## Method Renames
### Peer Methods
- [ ] `peer.working_rep()``peer.representation()`
- [ ] `peer.get_context()``peer.context()`
- [ ] `peer.get_sessions()``peer.sessions()`
- [ ] `peer.chat(stream=True)``peer.chat_stream()`
### Session Methods
- [ ] `session.get_context()``session.context()`
- [ ] `session.get_summaries()``session.summaries()`
- [ ] `session.get_messages()``session.messages()`
- [ ] `session.get_peers()``session.peers()`
- [ ] `session.get_peer_config()``session.get_peer_configuration()`
- [ ] `session.set_peer_config()``session.set_peer_configuration()`
- [ ] `session.working_rep()``session.representation()`
- [ ] `session.get_deriver_status()``session.queue_status()`
- [ ] Remove `session.poll_deriver_status()` calls
### Client Methods
- [ ] `client.get_peers()``client.peers()`
- [ ] `client.get_sessions()``client.sessions()`
- [ ] `client.get_workspaces()``client.workspaces()`
- [ ] `client.get_deriver_status()``client.queue_status()`
- [ ] Remove `client.poll_deriver_status()` calls
- [ ] Move `client.update_message()``session.update_message()`
## Parameter Renames
- [ ] `include_most_derived=``include_most_frequent=`
- [ ] `max_observations=``max_conclusions=`
- [ ] `last_user_message=``search_query=`
## Return Type Changes
- [ ] Handle `card()` returning `list[str] | None` instead of `str`
- [ ] Handle `.list()` on conclusions returning `SyncPage` instead of `list`
## Removed Features
- [ ] Remove any usage of `client.core` property
- [ ] Remove usage of `"demo"` environment (only `"local"` and `"production"` remain)
- [ ] Implement custom polling if you were using `poll_deriver_status()`
## Type Import Updates
- [ ] Replace `PeerContext` import with `PeerContextResponse` from `honcho.api_types`
- [ ] Replace `DeriverStatus` import with `QueueStatusResponse` from `honcho.api_types`
- [ ] Replace `MessageCreateParam` with `MessageCreateParams` (plural)
- [ ] Move `SessionPeerConfig` import from `honcho.session` to `honcho.api_types`
## Exception Handling (Optional)
- [ ] Update exception handling to use new exception types if needed:
- `HonchoError`, `APIError`, `BadRequestError`, `AuthenticationError`
- `PermissionDeniedError`, `NotFoundError`, `ConflictError`
- `UnprocessableEntityError`, `RateLimitError`, `ServerError`
- `TimeoutError`, `ConnectionError`
## Card Method Updates (v2.0.1)
- [ ] Replace `peer.card()` with `peer.get_card()` (card() is deprecated)
- [ ] Use `peer.set_card(list[str])` if setting peer cards
## Strict Validation (v2.0.2)
- [ ] Verify no input models pass unknown/misspelled fields (now raises `ValidationError`)
- [ ] Check for typos in `PeerConfig`, `SessionConfiguration`, `WorkspaceConfiguration` fields
## peer() / session() API Call Change (v2.1.0)
- [ ] Update code that relied on lazy `peer()` / `session()` — they now always make API calls
- [ ] Add `await` if using async and previously didn't need it for lazy construction
## New Properties (v2.1.0)
- [ ] Use `peer.created_at` / `session.created_at` where creation time is needed
- [ ] Use `session.is_active` where session active status is needed
## New Methods (v2.1.0)
- [ ] Use `session.get_message(message_id)` to fetch single messages by ID
## Pagination Parameters (v2.1.0)
- [ ] Add `page`, `size`, `reverse` parameters to list calls where needed:
- [ ] `client.peers()`
- [ ] `client.sessions()`
- [ ] `peer.sessions()`
- [ ] `session.messages()`
- [ ] `scope.list()`
## Final Verification
- [ ] Run type checker (mypy/pyright) with no errors
- [ ] Run tests
- [ ] Verify async operations work with `.aio` accessor
- [ ] Verify streaming functionality works with `chat_stream()`
- [ ] Verify configuration changes take effect

View File

@ -0,0 +1,358 @@
---
name: migrate-honcho
description: Migrates Honcho Python SDK code from v1.6.0 to v2.1.1. Use when upgrading honcho package, fixing breaking changes after upgrade, or when errors mention AsyncHoncho, observations, Representation class, .core property, or get_config methods.
---
# Honcho Python SDK Migration (v1.6.0 → v2.1.1)
## Overview
This skill migrates code from `honcho` Python SDK v1.6.0 to v2.1.1 (required for Honcho 3.0.0+).
**Key breaking changes:**
- `AsyncHoncho`/`AsyncPeer`/`AsyncSession` removed → use `.aio` accessor
- "Observation" → "Conclusion" terminology
- `Representation` class removed (returns `str` now)
- `get_config`/`set_config` → `get_configuration`/`set_configuration`
- Streaming via `chat_stream()` instead of `chat(stream=True)`
- `poll_deriver_status()` removed
- `.core` property removed
## Quick Migration
### 1. Update async architecture
```python
# Before
from honcho import AsyncHoncho, AsyncPeer, AsyncSession
async_client = AsyncHoncho()
peer = await async_client.peer("user-123")
response = await peer.chat("query")
# After
from honcho import Honcho
client = Honcho()
peer = await client.aio.peer("user-123")
response = await peer.aio.chat("query")
# Async iteration
async for p in client.aio.peers():
print(p.id)
```
### 2. Replace observations with conclusions
```python
# Before
from honcho import Observation, ObservationScope, AsyncObservationScope
scope = peer.observations
scope = peer.observations_of("other-peer")
rep = scope.get_representation()
# After
from honcho import Conclusion, ConclusionScope, ConclusionScopeAio
scope = peer.conclusions
scope = peer.conclusions_of("other-peer")
rep = scope.representation() # Returns str
```
### 3. Update representation handling
```python
# Before
from honcho import Representation, ExplicitObservation, DeductiveObservation
rep: Representation = peer.working_rep()
print(rep.explicit)
print(rep.deductive)
if rep.is_empty():
print("No observations")
# After
rep: str = peer.representation()
print(rep) # Just a string now
if not rep:
print("No conclusions")
```
### 4. Rename configuration methods
```python
# Before
config = peer.get_config()
peer.set_config({"observe_me": False})
session.get_config()
client.get_config()
# After
from honcho.api_types import PeerConfig, SessionConfiguration, WorkspaceConfiguration
config = peer.get_configuration()
peer.set_configuration(PeerConfig(observe_me=False))
session.get_configuration()
client.get_configuration()
```
### 5. Update method names
```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()
# 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.
## Quick Reference Table
| v1.6.0 | v2.0.0 |
|--------|--------|
| `AsyncHoncho()` | `Honcho()` + `.aio` accessor |
| `AsyncPeer` | `Peer` + `.aio` accessor |
| `AsyncSession` | `Session` + `.aio` accessor |
| `Observation` | `Conclusion` |
| `ObservationScope` | `ConclusionScope` |
| `AsyncObservationScope` | `ConclusionScopeAio` |
| `Representation` | `str` |
| `.observations` | `.conclusions` |
| `.observations_of()` | `.conclusions_of()` |
| `.get_config()` | `.get_configuration()` |
| `.set_config()` | `.set_configuration()` |
| `.working_rep()` | `.representation()` |
| `.get_context()` | `.context()` |
| `.get_sessions()` | `.sessions()` |
| `.get_peers()` | `.peers()` |
| `.get_messages()` | `.messages()` |
| `.get_summaries()` | `.summaries()` |
| `.get_deriver_status()` | `.queue_status()` |
| `.poll_deriver_status()` | *(removed)* |
| `.get_peer_config()` | `.get_peer_configuration()` |
| `.set_peer_config()` | `.set_peer_configuration()` |
| `client.update_message()` | `session.update_message()` |
| `peer.card()` | `peer.get_card()` *(card() deprecated)* |
| *(new)* | `peer.set_card(list[str])` |
| `chat(stream=True)` | `chat_stream()` |
| `include_most_derived=` | `include_most_frequent=` |
| `max_observations=` | `max_conclusions=` |
| `last_user_message=` | `search_query=` |
| `config=` | `configuration=` |
| `PeerContext` | `PeerContextResponse` |
| `DeriverStatus` | `QueueStatusResponse` |
| `client.core` | *(removed)* |
| *(new v2.1.0)* | `peer.created_at` / `session.created_at` |
| *(new v2.1.0)* | `session.is_active` |
| *(new v2.1.0)* | `session.get_message(id)` |
| *(new v2.1.0)* | `page=`, `size=`, `reverse=` on list methods |
## Detailed Reference
For comprehensive details on each change, see:
- [DETAILED-CHANGES.md](DETAILED-CHANGES.md) - Full API change documentation
- [MIGRATION-CHECKLIST.md](MIGRATION-CHECKLIST.md) - Step-by-step checklist
## New Exception Types
```python
from honcho import (
HonchoError,
APIError,
BadRequestError,
AuthenticationError,
PermissionDeniedError,
NotFoundError,
ConflictError,
UnprocessableEntityError,
RateLimitError,
ServerError,
TimeoutError,
ConnectionError,
)
```
## New Import Locations
```python
# Configuration types
from honcho.api_types import (
PeerConfig,
SessionConfiguration,
WorkspaceConfiguration,
SessionPeerConfig,
QueueStatusResponse,
PeerContextResponse,
)
# Async type hints
from honcho import HonchoAio, PeerAio, SessionAio
# Message types (note: Params is plural now)
from honcho import Message, MessageCreateParams
```

View File

@ -0,0 +1,583 @@
# Detailed API Changes
## Client Changes
### `.core` Property Removed
The `.core` property (which exposed the raw `@honcho-ai/core` client) has been removed. Use `.http` for advanced HTTP access.
```typescript
// Before
const workspace = await client.core.workspaces.getOrCreate({ id: 'my-workspace' })
// After - SDK handles workspace creation automatically
// For advanced usage:
const response = await client.http.post('/v3/workspaces', { body: { id: 'my-workspace' } })
```
### Listing Methods Return Type Changes
- `workspaces()` now returns `Page<string>` instead of `string[]`
- `session.peers()` now returns `Peer[]` instead of `Page<Peer>`
```typescript
const workspacePage = await honcho.workspaces()
for (const id of workspacePage.items) {
console.log(id)
}
```
### `updateMessage()` Moved to Session
```typescript
// Before
await honcho.updateMessage(message, { key: 'value' }, session)
// After
await session.updateMessage(message, { key: 'value' })
```
### `config` Option Renamed to `configuration`
```typescript
// Before
const peer = await honcho.peer('user-id', { config: { observe_me: true } })
const session = await honcho.session('session-id', { config: { ... } })
// After
const peer = await honcho.peer('user-id', { configuration: { observeMe: true } })
const session = await honcho.session('session-id', { configuration: { reasoning: { enabled: true } } })
```
---
## Peer Changes
### Streaming API
The `stream` option on `chat()` has been removed. Use `chatStream()` instead.
```typescript
// Before
const stream = await peer.chat('Hello', { stream: true })
for await (const chunk of stream) {
process.stdout.write(chunk)
}
// After
const stream = await peer.chatStream('Hello')
for await (const chunk of stream) {
process.stdout.write(chunk)
}
```
Non-streaming `chat()` now only returns `string | null`:
```typescript
const response = await peer.chat('Hello') // Returns string | null
```
### New `reasoningLevel` Option
```typescript
const response = await peer.chat('Complex question', {
reasoningLevel: 'high' // 'minimal' | 'low' | 'medium' | 'high' | 'max'
})
```
### `workingRep()` Renamed to `representation()`
```typescript
// Before
const rep = await peer.workingRep(session, target, options)
console.log(rep.toString())
console.log(rep.explicit)
console.log(rep.deductive)
// After
const rep = await peer.representation({
session,
target,
searchQuery: options?.searchQuery,
maxConclusions: options?.maxObservations,
includeMostFrequent: options?.includeMostDerived,
})
console.log(rep) // Returns string directly
```
### `getContext()` Renamed to `context()`
Options are now passed as a single object:
```typescript
// Before
const ctx = await peer.getContext(target, options)
// After
const ctx = await peer.context({ target, ...options })
```
### `card()` Return Type Changed
```typescript
// Before
const card = await peer.card(target) // Returns string
// After
const card = await peer.card(target) // Returns string[] | null
```
### `message()` Options Changed
```typescript
// Before
const msg = peer.message('Hello', {
metadata: { key: 'value' },
configuration: { deriver: { enabled: true } },
created_at: '2024-01-01T00:00:00Z'
})
// Returns ValidatedMessageCreate with peer_id, created_at
// After
const msg = peer.message('Hello', {
metadata: { key: 'value' },
configuration: { reasoning: { enabled: true } },
createdAt: '2024-01-01T00:00:00Z'
})
// Returns MessageInput with peerId, createdAt
```
### `PeerContext.representation` Type Changed
```typescript
// Before
const ctx = await peer.getContext()
if (ctx.representation) {
console.log(ctx.representation.explicit) // Representation object
console.log(ctx.representation.deductive)
}
// After
const ctx = await peer.context()
if (ctx.representation) {
console.log(ctx.representation) // Now a string
}
```
---
## Session Changes
### `getPeers()` Return Type Changed
```typescript
// Before
const peers = await session.getPeers() // Returns Page<Peer>
// After
const peers = await session.peers() // Returns Peer[]
```
### `getContext()` Renamed to `context()`
```typescript
// Before
const ctx = await session.getContext({
summary: true,
peerTarget: user,
peerPerspective: assistant,
lastUserMessage: "What are my preferences?",
representationOptions: {
maxObservations: 50,
includeMostDerived: true
}
})
// After
const ctx = await session.context({
summary: true,
peerTarget: user,
peerPerspective: assistant,
searchQuery: "What are my preferences?",
representationOptions: {
maxConclusions: 50,
includeMostFrequent: true
}
})
```
### `SessionPeerConfig` Uses camelCase and Methods Renamed
```typescript
// Before
await session.setPeerConfig(peer, {
observe_me: true,
observe_others: false
})
const config = await session.peerConfig(peer)
// After
await session.setPeerConfiguration(peer, {
observeMe: true,
observeOthers: false
})
const config = await session.getPeerConfiguration(peer)
```
---
## Message Changes
### Message Properties Use camelCase
```typescript
// Before (from @honcho-ai/core)
message.peer_id
message.session_id
message.workspace_id
message.created_at
message.token_count
// After
message.peerId
message.sessionId
message.workspaceId
message.createdAt
message.tokenCount
```
### MessageInput Type
```typescript
// Before
interface ValidatedMessageCreate {
peer_id: string
content: string
metadata?: Record<string, unknown>
configuration?: Record<string, unknown>
created_at?: string
}
// After
interface MessageInput {
peerId: string
content: string
metadata?: Record<string, unknown>
configuration?: MessageConfiguration
createdAt?: string
}
```
---
## Streaming Changes
### `DialecticStreamDelta` Removed
```typescript
// Before
import { DialecticStreamDelta, DialecticStreamChunk } from '@honcho-ai/sdk'
// After
import { DialecticStreamChunk, DialecticStreamResponse } from '@honcho-ai/sdk'
```
---
## Configuration Changes
### Workspace Configuration
Configurations are now strongly typed objects instead of `Record<string, unknown>`.
```typescript
// Before
await honcho.setConfig({
deriver: { enabled: true },
some_custom_key: 'value'
})
// After
await honcho.setConfiguration({
reasoning: {
enabled: true,
customInstructions: 'Be concise'
},
peerCard: {
use: true,
create: true
},
summary: {
enabled: true,
messagesPerShortSummary: 20,
messagesPerLongSummary: 60
},
dream: {
enabled: true
}
})
```
### Peer Configuration
```typescript
// Before
await peer.setConfig({ observe_me: false })
// After
await peer.setConfiguration({ observeMe: false })
```
### Message Configuration
```typescript
// Before
peer.message('Hello', {
configuration: {
deriver: { enabled: true }
}
})
// After
peer.message('Hello', {
configuration: {
reasoning: {
enabled: true,
customInstructions: 'Focus on emotions'
}
}
})
```
---
## Type Changes
### Removed Exports
- `Observation` (use `Conclusion`)
- `ObservationScope` (use `ConclusionScope`)
- `ObservationData`, `ObservationCreateParam`, `ObservationQueryParams`
- `Representation`, `RepresentationData`, `RepresentationOptions` (class removed)
- `ExplicitObservation`, `DeductiveObservation`
- `DialecticStreamDelta`
- `DeriverStatusOptions` (use `QueueStatusOptions`)
- `MessageCreate` (use `MessageInput`)
- `WorkingRepParams`
### New Exports
```typescript
import {
// Domain classes
Conclusion,
ConclusionScope,
ConclusionCreateParams,
// Error types
HonchoError,
AuthenticationError,
BadRequestError,
NotFoundError,
PermissionDeniedError,
RateLimitError,
ConflictError,
UnprocessableEntityError,
ServerError,
ConnectionError,
TimeoutError,
// Message types
Message,
MessageInput,
// Configuration types
WorkspaceConfig,
SessionConfig,
PeerConfig,
SessionPeerConfig,
MessageConfiguration,
ReasoningConfig,
PeerCardConfig,
SummaryConfig,
DreamConfig,
// API response types
QueueStatus,
QueueStatusOptions,
RepresentationOptions,
ConclusionQueryParams,
ConclusionResponse,
} from '@honcho-ai/sdk'
```
### SummaryData Type Changed
```typescript
// Before
interface SummaryData {
content: string
message_id: string
summary_type: string
created_at: string
token_count: number
}
// After
interface SummaryData {
content: string
messageId: string
summaryType: string
createdAt: string
tokenCount: number
}
```
---
## Post-v2.0.0 Changes
---
## Card Method Deprecation and setCard (v2.0.1)
### Before (v2.0.0)
```typescript
const card = await peer.card(target) // string[] | null
```
### After (v2.0.1+)
```typescript
// getCard() is the preferred method
const card = await peer.getCard(target) // string[] | null
// card() still works but is deprecated
const card = await peer.card(target) // Deprecated
// New: setCard()
const updated = await peer.setCard(['Fact 1', 'Fact 2'])
const updated = await peer.setCard(['Fact 1'], targetPeer)
```
---
## Strict Input Validation (v2.0.2)
Client constructor and all input schemas now use `.strict()` Zod validation.
```typescript
// Before (v2.0.1) — silently ignored
const honcho = new Honcho({ baseUrl: 'http://...' }) // typo fell back to default
// After (v2.0.2+) — ZodError thrown
const honcho = new Honcho({ baseUrl: 'http://...' }) // ZodError: Unrecognized key "baseUrl"
```
---
## peer() and session() Always Make API Calls (v2.1.0)
### Before (v2.0.x)
```typescript
// Without options: lazy object, no API call
const peer = honcho.peer('user-123')
// With options: made API call
const peer = await honcho.peer('user-123', { metadata: { key: 'value' } })
```
### After (v2.1.0+)
```typescript
// Always makes a get-or-create API call
const peer = await honcho.peer('user-123')
// peer.createdAt is now always populated
```
---
## New Properties: createdAt, isActive (v2.1.0)
```typescript
// Peer
const peer = await honcho.peer('user-123')
console.log(peer.createdAt) // string | undefined
// Session
const session = await honcho.session('sess-1')
console.log(session.createdAt) // string | undefined
console.log(session.isActive) // boolean | undefined
// Refreshed by getMetadata(), getConfiguration(), and refresh()
await session.refresh()
```
---
## getMessage() on Session (v2.1.0)
```typescript
// Fetch a single message by ID
const msg = await session.getMessage('msg-abc123')
console.log(msg.content, msg.createdAt)
```
---
## Pagination Parameters (v2.1.0)
All list methods now accept `page`, `size`, and `reverse`:
```typescript
// Defaults: page=1, size=50, reverse=false
const peersPage = await honcho.peers({
filters: { metadata: { role: 'admin' } },
page: 2,
size: 25,
reverse: true
})
// Page<T> properties:
console.log(peersPage.total) // Total items
console.log(peersPage.pages) // Total pages
console.log(peersPage.hasNextPage) // boolean
// Works on:
// honcho.peers(), honcho.sessions(), honcho.workspaces()
// peer.sessions()
// session.messages()
// scope.list()
```
---
## searchQuery Moved in context() (v2.1.0)
### Before (v2.0.x)
```typescript
const ctx = await session.context({
searchQuery: 'What are my preferences?',
representationOptions: { maxConclusions: 50 }
})
```
### After (v2.1.0+)
```typescript
const ctx = await session.context({
representationOptions: {
searchQuery: 'What are my preferences?',
maxConclusions: 50
}
})
```
---
## Broader Fetch Retry Logic (v2.1.1)
The SDK now retries on all `TypeError` network failures (connection resets, DNS errors, etc.) instead of only those containing `'fetch'` in the error message. This is transparent — no code changes needed.

View File

@ -0,0 +1,147 @@
# Migration Checklist
Use this checklist to track migration progress. Copy into your working notes and check off items as completed.
## Dependencies
- [ ] Remove `@honcho-ai/core` from dependencies
- [ ] Update `@honcho-ai/sdk` to v2.1.1
## Client-Level Changes
- [ ] Replace all `.core` usages with `.http` or remove
- [ ] Rename `getConfig()``getConfiguration()`
- [ ] Rename `setConfig()``setConfiguration()`
- [ ] Rename `getPeers()``peers()`
- [ ] Rename `getSessions()``sessions()`
- [ ] Rename `getWorkspaces()``workspaces()` (returns `Page<string>` now)
- [ ] Rename `getDeriverStatus()``queueStatus()`
- [ ] Remove `pollDeriverStatus()` calls entirely (no replacement—do not rely on queue being empty)
- [ ] Move `updateMessage()` calls from client to session
## Peer-Level Changes
- [ ] Replace `peer.chat(q, { stream: true })` with `peer.chatStream(q)`
- [ ] Rename `getSessions()``sessions()`
- [ ] Rename `getConfig()``getConfiguration()`
- [ ] Rename `setConfig()``setConfiguration()`
- [ ] Rename `peerConfig()``getPeerConfiguration()`
- [ ] Rename `setPeerConfig()``setPeerConfiguration()`
- [ ] Rename `workingRep()``representation()` (returns string now)
- [ ] Rename `getContext()``context()`
- [ ] Replace `observations``conclusions`
- [ ] Replace `observationsOf()``conclusionsOf()`
- [ ] Handle `card()` returning `string[] | null` instead of `string`
## Session-Level Changes
- [ ] Rename `getPeers()``peers()` (returns `Peer[]` now, not `Page<Peer>`)
- [ ] Rename `getMessages()``messages()`
- [ ] Rename `getConfig()``getConfiguration()`
- [ ] Rename `setConfig()``setConfiguration()`
- [ ] Rename `getContext()``context()`
- [ ] Rename `getSummaries()``summaries()`
- [ ] Rename `getDeriverStatus()``queueStatus()`
- [ ] Remove `pollDeriverStatus()` calls entirely (no replacement—do not rely on queue being empty)
- [ ] Rename `workingRep()``representation()` (returns string now)
## Terminology Changes
- [ ] Rename `maxObservations``maxConclusions`
- [ ] Rename `includeMostDerived``includeMostFrequent`
- [ ] Rename `lastUserMessage``searchQuery`
- [ ] Rename `Observation` type → `Conclusion`
- [ ] Rename `ObservationScope` type → `ConclusionScope`
## snake_case → camelCase
- [ ] Update all `{ config: ... }` to `{ configuration: ... }`
- [ ] Update `observe_me``observeMe`
- [ ] Update `observe_others``observeOthers`
- [ ] Update `created_at``createdAt`
- [ ] Update message property access:
- [ ] `peer_id``peerId`
- [ ] `session_id``sessionId`
- [ ] `workspace_id``workspaceId`
- [ ] `created_at``createdAt`
- [ ] `token_count``tokenCount`
- [ ] Update summary property access:
- [ ] `message_id``messageId`
- [ ] `summary_type``summaryType`
## Configuration Objects
- [ ] Update workspace configuration to typed structure
- [ ] Update session configuration to typed structure
- [ ] Update peer configuration to typed structure
- [ ] Replace `deriver` config with `reasoning` config
## Error Handling
- [ ] Update error handling to use new error types if needed
## Type Imports
- [ ] Remove imports of deleted types:
- `Observation`, `ObservationScope`, `ObservationData`
- `Representation`, `RepresentationData`
- `ExplicitObservation`, `DeductiveObservation`
- `DialecticStreamDelta`
- `DeriverStatusOptions`
- `MessageCreate`, `ValidatedMessageCreate`
- `WorkingRepParams`
- [ ] Add imports of new types as needed:
- `Conclusion`, `ConclusionScope`
- `MessageInput`
- `QueueStatusOptions`
- Error types
## Representation Handling
- [ ] Remove usage of `Representation` class methods (`.explicit`, `.deductive`, `.isEmpty()`, `.diff()`)
- [ ] Handle representation as plain string
## Card Method Updates (v2.0.1)
- [ ] Replace `peer.card()` with `peer.getCard()` (card() is deprecated)
- [ ] Use `peer.setCard(string[])` if setting peer cards
## Strict Validation (v2.0.2)
- [ ] Verify no constructor options or input schemas pass unknown/misspelled fields (now throws `ZodError`)
- [ ] Check for `baseUrl` vs `baseURL` typo in Honcho constructor
## peer() / session() API Call Change (v2.1.0)
- [ ] Update code that relied on lazy `peer()` / `session()` — they now always make API calls
- [ ] Ensure all `peer()` and `session()` calls are `await`ed
## New Properties (v2.1.0)
- [ ] Use `peer.createdAt` / `session.createdAt` where creation time is needed
- [ ] Use `session.isActive` where session active status is needed
## New Methods (v2.1.0)
- [ ] Use `session.getMessage(messageId)` to fetch single messages by ID
## Pagination Parameters (v2.1.0)
- [ ] Add `page`, `size`, `reverse` parameters to list calls where needed:
- [ ] `honcho.peers()`
- [ ] `honcho.sessions()`
- [ ] `honcho.workspaces()`
- [ ] `peer.sessions()`
- [ ] `session.messages()`
- [ ] `scope.list()`
## searchQuery Location Change (v2.1.0)
- [ ] Move `searchQuery` from top-level `context()` options to `representationOptions.searchQuery`
## Final Verification
- [ ] Run TypeScript compiler with no errors
- [ ] Run tests
- [ ] Verify streaming functionality works
- [ ] Verify configuration changes take effect

View File

@ -0,0 +1,330 @@
---
name: migrate-honcho-ts
description: Migrates Honcho TypeScript SDK code from v1.6.0 to v2.1.1. Use when upgrading @honcho-ai/sdk, fixing breaking changes after upgrade, or when errors mention removed APIs like .core, getConfig, observations, or snake_case properties.
---
# Honcho TypeScript SDK Migration (v1.6.0 → v2.1.1)
## Overview
This skill migrates code from `@honcho-ai/sdk` v1.6.0 to v2.1.1 (required for Honcho 3.0.0+).
**Key breaking changes:**
- `@honcho-ai/core` dependency removed
- "Observation" → "Conclusion" terminology
- "Deriver" → "Queue" terminology
- `getConfig`/`setConfig` → `getConfiguration`/`setConfiguration`
- `snake_case``camelCase` throughout
- Streaming via `chatStream()` instead of `chat({ stream: true })`
- `Representation` class removed (returns string now)
## Quick Migration
### 1. Update dependencies
Remove `@honcho-ai/core` from package.json. The SDK now has its own HTTP client.
### 2. Replace `.core` with `.http`
```typescript
// Before
const workspace = await client.core.workspaces.getOrCreate({ id: 'my-workspace' })
// After
const response = await client.http.post('/v3/workspaces', { body: { id: 'my-workspace' } })
```
### 3. Rename configuration methods
```typescript
// Before
await honcho.getConfig()
await honcho.setConfig({ key: 'value' })
await peer.getConfig()
await session.getConfig()
// After
await honcho.getConfiguration()
await honcho.setConfiguration({ reasoning: { enabled: true } })
await peer.getConfiguration()
await session.getConfiguration()
```
### 4. Rename listing methods
```typescript
// Before
const peers = await honcho.getPeers()
const sessions = await honcho.getSessions()
const workspaces = await honcho.getWorkspaces() // string[]
// After
const peers = await honcho.peers()
const sessions = await honcho.sessions()
const workspaces = await honcho.workspaces() // Page<string>
```
### 5. Update streaming
```typescript
// Before
const stream = await peer.chat('Hello', { stream: true })
// After
const stream = await peer.chatStream('Hello')
```
### 6. Update observations → conclusions
```typescript
// Before
peer.observations
peer.observationsOf('bob')
maxObservations: 50
includeMostDerived: true
// After
peer.conclusions
peer.conclusionsOf('bob')
maxConclusions: 50
includeMostFrequent: true
```
### 7. Update queue status methods
```typescript
// Before
await honcho.getDeriverStatus({ observer: peer })
await honcho.pollDeriverStatus({ timeoutMs: 60000 }) // REMOVE - see note below
// After
await honcho.queueStatus({ observer: peer })
// pollDeriverStatus() has no replacement - see note below
```
**Important:** `pollDeriverStatus()` and its polling pattern have been removed entirely. Do not rely on the queue ever being empty. The queue is a continuous processing system—new messages may arrive at any time, and waiting for "completion" is not a valid pattern. If your code previously polled for queue completion, redesign it to work without that assumption.
### 8. Convert snake_case to camelCase
```typescript
// Before
message.peer_id
message.session_id
message.created_at
message.token_count
{ observe_me: true, observe_others: false }
{ created_at: '2024-01-01' }
// After
message.peerId
message.sessionId
message.createdAt
message.tokenCount
{ observeMe: true, observeOthers: false }
{ createdAt: '2024-01-01' }
```
### 9. Update representation calls
```typescript
// Before
const rep = await peer.workingRep(session, target, options)
console.log(rep.explicit) // ExplicitObservation[]
console.log(rep.deductive) // DeductiveObservation[]
// After
const rep = await peer.representation({ session, target, ...options })
console.log(rep) // string
```
### 10. Move updateMessage to session
```typescript
// Before
await honcho.updateMessage(message, metadata, session)
// After
await session.updateMessage(message, metadata)
```
### 11. Update card() to getCard() (v2.0.1+)
```typescript
// Before
const card = await peer.card(target)
// After (v2.0.1+)
const card = await peer.getCard(target) // Returns string[] | null
// peer.card() still works but is deprecated — use getCard()
// New: setPeerCard / setCard
await peer.setCard(['Prefers dark mode', 'Located in US'])
```
### 12. Strict input validation (v2.0.2+)
Client constructor and all input schemas now reject unknown options via `.strict()` Zod validation.
```typescript
// Before (v2.0.1 and earlier) — silently ignored
const honcho = new Honcho({ baseUrl: 'http://...' }) // typo: baseUrl vs baseURL — silently fell back to default
// After (v2.0.2+) — throws ZodError
const honcho = new Honcho({ baseUrl: 'http://...' }) // ZodError! Use baseURL
```
### 13. 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.
```typescript
// Before (v2.0.x) — no API call without options
const session = honcho.session('my-session') // Lazy, no network request
// After (v2.1.0+) — always hits the API
const session = await honcho.session('my-session') // Makes POST to /sessions (get-or-create)
```
### 14. New properties and methods (v2.1.0+)
```typescript
// createdAt on Peer and Session
const peer = await honcho.peer('user-123')
console.log(peer.createdAt) // string | undefined
const session = await honcho.session('sess-1')
console.log(session.createdAt) // string | undefined
// isActive on Session
console.log(session.isActive) // boolean | undefined
// getMessage() on Session
const msg = await session.getMessage('msg-id')
```
### 15. Pagination parameters on list methods (v2.1.0+)
All list methods now accept `page`, `size`, and `reverse` parameters:
```typescript
// Before (v2.0.x) — only filters
const peers = await honcho.peers({ metadata: { role: 'admin' } })
// After (v2.1.0+) — pagination controls via options object
const peers = await honcho.peers({
filters: { metadata: { role: 'admin' } },
page: 2,
size: 25,
reverse: true
})
// Legacy raw-filter form still works:
const peers = await honcho.peers({ metadata: { role: 'admin' } })
// Works on: honcho.peers(), honcho.sessions(), honcho.workspaces(),
// peer.sessions(), session.messages(), scope.list()
```
### 16. searchQuery moved in context() (v2.1.0+)
**Breaking**: `searchQuery` removed from top-level `context()` options. Use `representationOptions.searchQuery` instead.
```typescript
// Before (v2.0.x)
await session.context({ searchQuery: '...' })
// After (v2.1.0+)
await session.context({ representationOptions: { searchQuery: '...' } })
```
### 17. Broader fetch retry logic (v2.1.1+)
The SDK now retries on all `TypeError` network failures (connection resets, DNS errors, etc.) instead of only those with `'fetch'` in the message. No code changes needed — this is transparent.
## Quick Reference Table
| v1.6.0 | v2.0.0 |
|--------|--------|
| `client.core` | `client.http` |
| `getConfig()` | `getConfiguration()` |
| `setConfig()` | `setConfiguration()` |
| `getPeers()` | `peers()` |
| `getSessions()` | `sessions()` |
| `getWorkspaces()` | `workspaces()` |
| `getDeriverStatus()` | `queueStatus()` |
| `pollDeriverStatus()` | *Removed - do not poll* |
| `peer.chat(q, { stream: true })` | `peer.chatStream(q)` |
| `peer.workingRep()` | `peer.representation()` |
| `peer.getContext()` | `peer.context()` |
| `peer.observations` | `peer.conclusions` |
| `peer.observationsOf()` | `peer.conclusionsOf()` |
| `session.getPeers()` | `session.peers()` |
| `session.getMessages()` | `session.messages()` |
| `session.getSummaries()` | `session.summaries()` |
| `session.getContext()` | `session.context()` |
| `session.workingRep()` | `session.representation()` |
| `session.peerConfig()` | `session.getPeerConfiguration()` |
| `session.setPeerConfig()` | `session.setPeerConfiguration()` |
| `{ timeoutMs: 60000 }` | `{ timeout: 60000 }` |
| `{ maxObservations: 50 }` | `{ maxConclusions: 50 }` |
| `{ includeMostDerived }` | `{ includeMostFrequent }` |
| `{ lastUserMessage }` | `{ searchQuery }` |
| `{ config: ... }` | `{ configuration: ... }` |
| `message.peer_id` | `message.peerId` |
| `message.created_at` | `message.createdAt` |
| `peer.card()` | `peer.getCard()` *(card() deprecated)* |
| *(new)* | `peer.setCard(string[])` |
| `Observation` | `Conclusion` |
| `ObservationScope` | `ConclusionScope` |
| *(new v2.1.0)* | `peer.createdAt` / `session.createdAt` |
| *(new v2.1.0)* | `session.isActive` |
| *(new v2.1.0)* | `session.getMessage(id)` |
| *(new v2.1.0)* | `page`, `size`, `reverse` on list methods |
| `context({ searchQuery })` | `context({ representationOptions: { searchQuery } })` |
## Detailed Reference
For comprehensive details on each change, see:
- [DETAILED-CHANGES.md](DETAILED-CHANGES.md) - Full API change documentation
- [MIGRATION-CHECKLIST.md](MIGRATION-CHECKLIST.md) - Step-by-step checklist
## New Error Types
```typescript
import {
HonchoError,
AuthenticationError,
BadRequestError,
NotFoundError,
PermissionDeniedError,
RateLimitError,
ConflictError,
UnprocessableEntityError,
ServerError,
ConnectionError,
TimeoutError
} from '@honcho-ai/sdk'
```
## New Configuration Types
Configurations are now strongly typed:
```typescript
await honcho.setConfiguration({
reasoning: {
enabled: true,
customInstructions: 'Be concise'
},
peerCard: { use: true, create: true },
summary: {
enabled: true,
messagesPerShortSummary: 20,
messagesPerLongSummary: 60
},
dream: { enabled: true }
})
```

14
.dockerignore Normal file
View File

@ -0,0 +1,14 @@
fly.toml
.env
.env.template
*.md
docs/**
.DS_Store
supabase/**
LICENSE
__pycache__
docker-compose.yml.example
.github/**
.vscode/**
data/**
.venv

293
.env.template Normal file
View File

@ -0,0 +1,293 @@
# Honcho Environment Variables Template
# Copy this file to .env and fill in the appropriate values
#
# Required variables are marked with (REQUIRED)
# Optional variables have default values and can be left commented out
# =============================================================================
# Application Settings
# =============================================================================
LOG_LEVEL=INFO
# SESSION_OBSERVERS_LIMIT=10
# GET_CONTEXT_MAX_TOKENS=100000
# MAX_FILE_SIZE=5242880 # Bytes
# MAX_MESSAGE_SIZE=25000 # Characters
# Embedding settings
# EMBED_MESSAGES=true
# EMBEDDING_VECTOR_DIMENSIONS=1536
# EMBEDDING_MAX_INPUT_TOKENS=8192
# EMBEDDING_MAX_TOKENS_PER_REQUEST=300000
# EMBEDDING_MODEL_CONFIG__TRANSPORT=openai
# EMBEDDING_MODEL_CONFIG__MODEL=text-embedding-3-small
# EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL=
# EMBEDDING_MODEL_CONFIG__OVERRIDES__API_KEY_ENV=
# LANGFUSE_HOST=
# LANGFUSE_PUBLIC_KEY=
# COLLECT_METRICS_LOCAL=false
# LOCAL_METRICS_FILE=metrics.jsonl
# REASONING_TRACES_FILE=traces.jsonl # Path to JSONL file for reasoning traces
# NAMESPACE="honcho"
# =============================================================================
# Database Settings (REQUIRED)
# =============================================================================
# Connection URI for PostgreSQL database with pgvector support
# Must use postgresql+psycopg prefix for SQLAlchemy compatibility
DB_CONNECTION_URI=postgresql+psycopg://postgres:postgres@localhost:5432/postgres
# Optional database settings
# DB_SCHEMA=public
# DB_POOL_CLASS=default
# DB_POOL_SIZE=10
# DB_MAX_OVERFLOW=20
# DB_POOL_TIMEOUT=30
# DB_POOL_RECYCLE=300
# DB_POOL_PRE_PING=true
# DB_POOL_USE_LIFO=true
# DB_SQL_DEBUG=false
# DB_TRACING=false
# =============================================================================
# Authentication Settings
# =============================================================================
# Whether to enable authentication (set to true for production)
AUTH_USE_AUTH=false
# JWT secret key (REQUIRED if AUTH_USE_AUTH=true)
# Generate with: python scripts/generate_jwt_secret.py
# AUTH_JWT_SECRET=your-secret-key-here
# =============================================================================
# LLM Provider (REQUIRED)
# =============================================================================
# Honcho uses LLMs for memory extraction, summarization, dialectic chat, and
# dream consolidation. The server will fail to start without a provider configured.
#
# Quick start: set LLM_OPENAI_API_KEY below to use the built-in defaults.
# Text-generation features default to transport = "openai" and
# model = "gpt-5.4-mini". Embeddings default to transport = "openai" and
# model = "text-embedding-3-small". For OpenAI-compatible proxies
# (OpenRouter, Together, Fireworks, vLLM, Ollama, LiteLLM), override
# MODEL_CONFIG__MODEL and MODEL_CONFIG__OVERRIDES__BASE_URL on each feature
# section you want to route through that endpoint.
# Models must support tool calling (function calling).
#
# Supported transports: openai, anthropic, gemini
# Each transport picks up its API key from the corresponding LLM_*_API_KEY.
# Base URLs are set per-module via MODEL_CONFIG__OVERRIDES__BASE_URL.
#
LLM_OPENAI_API_KEY=your-api-key-here
# LLM_ANTHROPIC_API_KEY=
# LLM_GEMINI_API_KEY=
# =============================================================================
# LLM Configuration
# =============================================================================
# Global LLM settings
# LLM_DEFAULT_MAX_TOKENS=2500
# LLM_MAX_TOOL_OUTPUT_CHARS=10000 # Max chars for tool output (~2500 tokens)
# LLM_MAX_MESSAGE_CONTENT_CHARS=2000 # Max chars per message in tool results
# =============================================================================
# Deriver (Background Worker)
# =============================================================================
# DERIVER_ENABLED=true
# Defaults:
# DERIVER_MODEL_CONFIG__TRANSPORT=openai
# DERIVER_MODEL_CONFIG__MODEL=gpt-5.4-mini
# Optional overrides:
# DERIVER_MODEL_CONFIG__MODEL=your-model-here
# DERIVER_MODEL_CONFIG__OVERRIDES__BASE_URL=https://openrouter.ai/api/v1
# DERIVER_WORKERS=1
# DERIVER_POLLING_SLEEP_INTERVAL_SECONDS=1.0
# DERIVER_STALE_SESSION_TIMEOUT_MINUTES=5
# DERIVER_QUEUE_ERROR_RETENTION_SECONDS=2592000 # 30 days
# DERIVER_MODEL_CONFIG__TEMPERATURE=
# DERIVER_MODEL_CONFIG__THINKING_EFFORT=minimal
# DERIVER_MODEL_CONFIG__THINKING_BUDGET_TOKENS=1024 # Gemini/Anthropic only
# DERIVER_DEDUPLICATE=true
# DERIVER_MODEL_CONFIG__MAX_OUTPUT_TOKENS=4096
# DERIVER_LOG_OBSERVATIONS=false
# DERIVER_MAX_INPUT_TOKENS=25000
# DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000
# DERIVER_WORKING_REPRESENTATION_MAX_OBSERVATIONS=100
# DERIVER_REPRESENTATION_BATCH_MAX_TOKENS=1024
# DERIVER_FLUSH_ENABLED=false # Bypass batch token threshold, process work immediately
# DERIVER_MODEL_CONFIG__FALLBACK__MODEL=
# DERIVER_MODEL_CONFIG__FALLBACK__TRANSPORT=
# DERIVER_MODEL_CONFIG__OVERRIDES__BASE_URL=
# DERIVER_MODEL_CONFIG__OVERRIDES__API_KEY_ENV=
# =============================================================================
# Peer Card
# =============================================================================
# PEER_CARD_ENABLED=true
# =============================================================================
# Dialectic
# =============================================================================
# DIALECTIC_MAX_OUTPUT_TOKENS=8192
# DIALECTIC_MAX_INPUT_TOKENS=100000
# DIALECTIC_HISTORY_TOKEN_LIMIT=8192
# DIALECTIC_SESSION_HISTORY_MAX_TOKENS=4096
#
# Per-level settings (reasoning_level parameter in API)
# Each level has its own nested MODEL_CONFIG, tool iterations, and max output tokens.
# MAX_OUTPUT_TOKENS is optional per level; if not set, uses global DIALECTIC_MAX_OUTPUT_TOKENS.
# Defaults:
# DIALECTIC_LEVELS__minimal__MODEL_CONFIG__TRANSPORT=openai
# DIALECTIC_LEVELS__minimal__MODEL_CONFIG__MODEL=gpt-5.4-mini
# DIALECTIC_LEVELS__minimal__MAX_TOOL_ITERATIONS=1
# DIALECTIC_LEVELS__minimal__MAX_OUTPUT_TOKENS=250
# DIALECTIC_LEVELS__minimal__TOOL_CHOICE=auto
# DIALECTIC_LEVELS__low__MODEL_CONFIG__TRANSPORT=openai
# DIALECTIC_LEVELS__low__MODEL_CONFIG__MODEL=gpt-5.4-mini
# DIALECTIC_LEVELS__low__MAX_TOOL_ITERATIONS=5
# DIALECTIC_LEVELS__low__TOOL_CHOICE=auto
# DIALECTIC_LEVELS__medium__MODEL_CONFIG__TRANSPORT=openai
# DIALECTIC_LEVELS__medium__MODEL_CONFIG__MODEL=gpt-5.4-mini
# DIALECTIC_LEVELS__medium__MAX_TOOL_ITERATIONS=2
# DIALECTIC_LEVELS__high__MODEL_CONFIG__TRANSPORT=openai
# DIALECTIC_LEVELS__high__MODEL_CONFIG__MODEL=gpt-5.4-mini
# DIALECTIC_LEVELS__high__MAX_TOOL_ITERATIONS=4
# DIALECTIC_LEVELS__max__MODEL_CONFIG__TRANSPORT=openai
# DIALECTIC_LEVELS__max__MODEL_CONFIG__MODEL=gpt-5.4-mini
# DIALECTIC_LEVELS__max__MAX_TOOL_ITERATIONS=10
# Optional overrides:
# DIALECTIC_LEVELS__minimal__MODEL_CONFIG__MODEL=your-model-here
# DIALECTIC_LEVELS__low__MODEL_CONFIG__MODEL=your-model-here
# DIALECTIC_LEVELS__medium__MODEL_CONFIG__MODEL=your-model-here
# DIALECTIC_LEVELS__high__MODEL_CONFIG__MODEL=your-model-here
# DIALECTIC_LEVELS__max__MODEL_CONFIG__MODEL=your-model-here
# DIALECTIC_LEVELS__max__MODEL_CONFIG__THINKING_EFFORT=medium
# DIALECTIC_LEVELS__max__MODEL_CONFIG__THINKING_BUDGET_TOKENS=1024
# Optional backup per level (must set both or neither):
# DIALECTIC_LEVELS__max__MODEL_CONFIG__FALLBACK__MODEL=gemini-2.5-pro
# DIALECTIC_LEVELS__max__MODEL_CONFIG__FALLBACK__TRANSPORT=gemini
# =============================================================================
# Summary
# =============================================================================
# SUMMARY_ENABLED=true
# Defaults:
# SUMMARY_MODEL_CONFIG__TRANSPORT=openai
# SUMMARY_MODEL_CONFIG__MODEL=gpt-5.4-mini
# Optional overrides:
# SUMMARY_MODEL_CONFIG__MODEL=your-model-here
# SUMMARY_MODEL_CONFIG__OVERRIDES__BASE_URL=https://openrouter.ai/api/v1
# SUMMARY_MODEL_CONFIG__THINKING_EFFORT=minimal
# SUMMARY_MODEL_CONFIG__THINKING_BUDGET_TOKENS=1024 # Gemini/Anthropic only
# SUMMARY_MESSAGES_PER_SHORT_SUMMARY=20
# SUMMARY_MESSAGES_PER_LONG_SUMMARY=60
# SUMMARY_MAX_TOKENS_SHORT=1000
# SUMMARY_MAX_TOKENS_LONG=4000
# SUMMARY_MODEL_CONFIG__FALLBACK__MODEL=
# =============================================================================
# Dream
# =============================================================================
# DREAM_ENABLED=true
# Defaults:
# DREAM_DEDUCTION_MODEL_CONFIG__TRANSPORT=openai
# DREAM_DEDUCTION_MODEL_CONFIG__MODEL=gpt-5.4-mini
# DREAM_INDUCTION_MODEL_CONFIG__TRANSPORT=openai
# DREAM_INDUCTION_MODEL_CONFIG__MODEL=gpt-5.4-mini
# Optional overrides:
# DREAM_DEDUCTION_MODEL_CONFIG__MODEL=your-model-here
# DREAM_DEDUCTION_MODEL_CONFIG__OVERRIDES__BASE_URL=https://openrouter.ai/api/v1
# DREAM_INDUCTION_MODEL_CONFIG__MODEL=your-model-here
# DREAM_INDUCTION_MODEL_CONFIG__OVERRIDES__BASE_URL=https://openrouter.ai/api/v1
# DREAM_DOCUMENT_THRESHOLD=50
# DREAM_IDLE_TIMEOUT_MINUTES=60
# DREAM_MIN_HOURS_BETWEEN_DREAMS=8
# DREAM_ENABLED_TYPES=["omni"]
# DREAM_MAX_TOOL_ITERATIONS=20
# DREAM_HISTORY_TOKEN_LIMIT=16384
# Surprisal sampling (advanced):
# DREAM_SURPRISAL__ENABLED=false
# DREAM_SURPRISAL__TREE_TYPE=kdtree
# DREAM_SURPRISAL__TREE_K=5
# DREAM_SURPRISAL__SAMPLING_STRATEGY=recent
# DREAM_SURPRISAL__SAMPLE_SIZE=200
# DREAM_SURPRISAL__TOP_PERCENT_SURPRISAL=0.10
# DREAM_SURPRISAL__MIN_HIGH_SURPRISAL_FOR_REPLACE=10
# DREAM_SURPRISAL__INCLUDE_LEVELS=["explicit","deductive"]
# =============================================================================
# Webhook Settings
# =============================================================================
# WEBHOOK_SECRET=
# WEBHOOK_MAX_WORKSPACE_LIMIT=10
# =============================================================================
# Monitoring and Observability (Optional)
# =============================================================================
# Sentry error tracking
# SENTRY_ENABLED=false
# SENTRY_DSN=your-sentry-dsn-here
# SENTRY_RELEASE=your-release-semver
# SENTRY_ENVIRONMENT=development
# SENTRY_TRACES_SAMPLE_RATE=0.1
# SENTRY_PROFILES_SAMPLE_RATE=0.1
# =============================================================================
# Prometheus Metrics Settings (Pull-based metrics)
# =============================================================================
# METRICS_ENABLED=false
# METRICS_NAMESPACE=honcho # Inherits from NAMESPACE if not set
# =============================================================================
# CloudEvents Telemetry Settings (Analytics events)
# =============================================================================
# TELEMETRY_ENABLED=false
# TELEMETRY_ENDPOINT=https://telemetry.honcho.dev/v1/events
# TELEMETRY_HEADERS={"Authorization": "Bearer your-token"} # JSON string for auth headers
# TELEMETRY_BATCH_SIZE=100
# TELEMETRY_FLUSH_INTERVAL_SECONDS=1.0
# TELEMETRY_FLUSH_THRESHOLD=50
# TELEMETRY_MAX_RETRIES=3
# TELEMETRY_MAX_BUFFER_SIZE=10000
# TELEMETRY_NAMESPACE=honcho # Inherits from NAMESPACE if not set
# =============================================================================
# Cache
# =============================================================================
# CACHE_ENABLED=false
# CACHE_URL="redis://localhost:6379/0?suppress=true"
# CACHE_NAMESPACE="honcho" # Inherits from NAMESPACE if not set
# CACHE_DEFAULT_TTL_SECONDS=300
# CACHE_DEFAULT_LOCK_TTL_SECONDS=5
# =============================================================================
# Vector Store Settings
# =============================================================================
# Vector store type: "pgvector", "turbopuffer", or "lancedb"
VECTOR_STORE_TYPE=pgvector
# Migration flag: set to true when migration from pgvector is complete
VECTOR_STORE_MIGRATED=false
# Global namespace prefix for all vector namespaces
# Namespaces follow the pattern: {NAMESPACE}.{type}.{hash}
# where hash is a base64url-encoded SHA-256 of the workspace/peer names
# - Documents: {NAMESPACE}.doc.{hash(workspace, observer, observed)}
# - Messages: {NAMESPACE}.msg.{hash(workspace)}
# VECTOR_STORE_NAMESPACE=honcho # Inherits from NAMESPACE if not set
# Embedding dimensions are configured via EMBEDDING_VECTOR_DIMENSIONS (see top
# of this file). VECTOR_STORE_DIMENSIONS is deprecated and ignored.
# Turbopuffer-specific settings (required if TYPE is "turbopuffer")
# VECTOR_STORE_TURBOPUFFER_API_KEY=your-turbopuffer-api-key
# VECTOR_STORE_TURBOPUFFER_REGION=gcp-us-east4
# LanceDB-specific settings (local embedded mode)
# VECTOR_STORE_LANCEDB_PATH=./lancedb_data
# Reconciliation interval for background sync (default: 5 minutes)
# VECTOR_STORE_RECONCILIATION_INTERVAL_SECONDS=300

76
.github/ISSUE_TEMPLATE/1-bug-report.md vendored Normal file
View File

@ -0,0 +1,76 @@
---
name: "🐞 Bug Report"
about: "Report an issue to help the project improve."
title: "[Bug] "
labels: "bug"
assignees: ""
---
# **🐞 Bug Report**
## **Describe the bug**
<!-- A clear and concise description of what the bug is. -->
*
---
### **Is this a regression?**
<!-- Did this behaviour used to work in the previous version? -->
<!-- Yes, the last version in which this bug was not present was: ... -->
---
### **To Reproduce**
<!-- Steps to reproduce the error:
(e.g.:)
1. Use x argument / navigate to
2. Fill this information
3. Go to...
4. See error -->
<!-- Write the steps here (add or remove as many steps as needed)-->
1.
2.
3.
4.
---
### **Expected behaviour**
<!-- A clear and concise description of what you expected to happen. -->
*
---
### **Media prove**
<!-- If applicable, add screenshots or videos to help explain your problem. -->
---
### **Your environment**
<!-- use all the applicable bulleted list elements for this specific issue,
and remove all the bulleted list elements that are not relevant for this issue. -->
* OS: <!--[e.g. Ubuntu 5.4.0-26-generic x86_64 / Windows 1904 ...]-->
* Browser name and version:
* Honcho Server Version: <!-- e.g. v0.0.8 -->
* Honcho Client Version: <!-- e.g. Python v0.0.8 -->
---
### **Additional context**
<!-- Add any other context or additional information about the problem here.-->
*
<!--📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛
To expedite issue processing, please search open and closed issues before submitting a new one.
📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛-->

View File

@ -0,0 +1,38 @@
---
name: "💉 Failing Test"
about: "Report failing tests or CI jobs."
title: "[Test] "
labels: "Type: Test"
assignees: ""
---
# **💉 Failing Test**
## **Which jobs/test(s) are failing**
<!-- The CI jobs or tests that are failing -->
*
---
## **Reason for failure/description**
<!-- Try to describe why the test is failing or what we are missing to make it pass. -->
---
### **Media prove**
<!-- If applicable, add screenshots or videos to help explain your problem. -->
---
### **Additional context**
<!-- Add any other context or additional information about the problem here. -->
*
<!--📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛
To expedite issue processing, please search open and closed issues before submitting a new one.
📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛-->

57
.github/ISSUE_TEMPLATE/3-docs-bug.md vendored Normal file
View File

@ -0,0 +1,57 @@
---
name: "📚 Documentation or README.md issue report"
about: "Report an issue in the project's documentation or README.md file."
title: ""
labels: "documentation"
assignees: ""
---
# **📚 Documentation Issue Report**
## **Describe the bug**
<!-- A clear and concise description of what the bug is. -->
*
---
### **To Reproduce**
<!-- Steps to reproduce the error:
(e.g.:)
1. Use x argument / navigate to
2. Fill this information
3. Go to...
4. See error -->
<!-- Write the steps here (add or remove as many steps as needed)-->
1.
2.
3.
4.
---
### **Media prove**
<!-- If applicable, add screenshots or videos to help explain your problem. -->
---
## **Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
*
---
### **Additional context**
<!-- Add any other context or additional information about the problem here.-->
*
<!--📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛
To expedite issue processing, please search open and closed issues before submitting a new one.
📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛-->

View File

@ -0,0 +1,42 @@
---
name: "🚀🆕 Feature Request"
about: "Suggest an idea or possible new feature for this project."
title: ""
labels: 'feature'
assignees: ''
---
# **🚀 Feature Request**
## **Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
*
---
## **Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
*
---
## **Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
*
---
### **Additional context**
<!-- Add any other context or additional information about the problem here.-->
*
<!--📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛
To expedite issue processing, please search open and closed issues before submitting a new one.
📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛-->

View File

@ -0,0 +1,42 @@
---
name: "🚀➕ Enhancement Request"
about: "Suggest an enhancement for this project. Improve an existing feature"
title: ""
labels: "Type: Enhancement"
assignees: ""
---
# **🚀 Enhancement Request**
## **Is your enhancement request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
*
---
## **Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
*
---
## **Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
*
---
### **Additional context**
<!-- Add any other context or additional information about the problem here.-->
*
<!--📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛
To expedite issue processing, please search open and closed issues before submitting a new one.
📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛-->

View File

@ -0,0 +1,93 @@
---
name: "⚠️ Security Report"
about: "Report an issue to help the project improve."
title: ""
labels: "security"
assignees: ""
---
<!--📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛
READ CAREFULLY IF YOUR ISSUE REPORT CONTAINS SENSIBLE OR PRIVATE DATA:
(data that might be leaked or subtracted from our servers due to this
security issue).
If this security report (or the guide on how to "identify the security bug") includes
certain personal information or involves personal identifiable data, or you believe
that the data that you might leak by exposing the way on how to attack the project
could be considered as a data leak or could violate the privacy of any kind of
data or sensible data, please do not post it here and directly email the developer:
(hello@plasticlabs.ai). You should post the issue with the least amount of
sensible or private data as possible to help us manage the security issue, and
with the extra data sent from your email to the developer (if any), we will deeply
analyze and try to fix it as fast as possible.
If you are in doubt about the data that you might post here (screenshots or media
also, count as data), please directly email us.
The data that must NOT be posted here:
* Legal and/or full names
* Names or usernames combined with other identifiers like phone numbers or email addresses
* Health or financial information (including insurance information, social security numbers, etc.)
* Information about political or religious affiliations
* Information about race, ethnicity, sexual orientation, gender, or other identifying information that could be used for discriminatory purposes
📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛-->
# **⚠️ Security Report**
## **Describe the security issue**
<!-- A clear and concise description of what the bug is. -->
*
---
### **To Reproduce**
<!-- Steps to reproduce the error:
(e.g.:)
1. Use x argument / navigate to
2. Fill this information
3. Go to...
4. See error -->
<!-- Write the steps here (add or remove as many steps as needed)-->
1.
2.
3.
4.
---
### **Expected behaviour**
<!-- A clear and concise description of what you expected to happen. -->
*
---
### **Media prove**
<!-- If applicable, add screenshots or videos to help explain your problem. -->
---
### **Your environment**
<!-- use all the applicable bulleted list elements for this specific issue,
and remove all the bulleted list elements that are not relevant for this issue. -->
* OS: <!--[e.g. Ubuntu 5.4.0-26-generic x86_64 / Windows 1904 ...]-->
* Browser name and version:
* Honcho Server Version: <!--[e.g. v0.0.1]-->
* Honcho Client Version: <!--[e.g. Python v0.0.1]-->
---
### **Additional context**
<!-- Add any other context or additional information about the problem here.-->
*

View File

@ -0,0 +1,25 @@
---
name: "❓ Question or Support Request"
about: "Questions and requests for support."
title: ""
labels: "question"
assignees: ""
---
# **❓ Question or Support Request**
## **Describe your question or ask for support.**
<!-- A clear and concise description of what your doubt is. -->
*
<!--📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛
Before posting any questions or asking for support, first read the project's README.md file and
(if there is any) the WIKI pages or any other additional documentation that might be listed
in the project's README.md file.
To expedite issue processing, please search open and closed issues before submitting a new one.
📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛📛-->

View File

@ -0,0 +1,47 @@
# **Release v[X.X.X]**
<!-- This PR fixes #NUMBER_OF_THE_ISSUE, and fixes #NUMBER_OF_THE_ISSUE -->
## **Description**
<!-- 📛📛
Please include a summary of the change and/or which issue is fixed.
List any dependencies required for this change, if there are any.
📛📛 -->
*
---
### **Additional context**
<!-- Add any other context or additional information about the pull request.-->
*
<!-- 📛📛📛📛
If it fixes any current issue please let us know this way:
Uncomment the comment above "description", then add your number of issues after the "#".
Example: # **This pull request fixes #NUMBER_OF_THE_ISSUE issue**
If there are multiple issues to be closed with the merge of this pull request
please do it like so: **This pull request fixes #NUMBER_OF_THE_ISSUE, fixes #NUMBER_OF_THE_ISSUE and fixes #NUMBER_OF_THE_ISSUE issue**.
For more information on closing issues using keywords, please check https://docs.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywords#closing-multiple-issues
📛📛📛📛 -->
## **Changelog**
<!-- 📛📛📛📛
Log of changes introduced in this release in the style of https://keepachangelog.com/en/1.1.0/
📛📛📛📛 -->
### **Added**
### **Changed**
### **Deprecated**
### **Removed**
### **Fixed**
### **Security**

58
.github/workflows/docker-build.yml vendored Normal file
View File

@ -0,0 +1,58 @@
name: Build and Push Docker Image
on:
push:
branches:
- main
tags:
- v*
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write
packages: write
contents: read
attestations: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.3.0
with:
platforms: linux/amd64, linux/arm64
- name: Docker meta
id: meta
uses: docker/metadata-action@v5.5.1
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
id: push
uses: docker/build-push-action@v5.3.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1.1.2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

61
.github/workflows/fly-deploy-prod.yml vendored Normal file
View File

@ -0,0 +1,61 @@
# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/
name: Fly Deploy (Production Environment)
permissions:
contents: read
on:
push:
tags:
- v*
workflow_dispatch:
inputs:
version:
description: "Version to deploy (without v prefix)"
required: true
type: string
default: 'manual'
jobs:
deploy-honcho-prod-image:
name: Deploy Honcho Image (Production Environment)
runs-on: ubuntu-latest
concurrency:
group: deploy-prod-group
cancel-in-progress: true
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@1.5
- run: |
# Determine the image label based on trigger type
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
IMAGE_LABEL="deployment-${{ github.event.inputs.version }}"
else
IMAGE_LABEL="deployment-${{ github.ref_name }}"
fi
flyctl deploy -a honcho-prod-image --remote-only --build-only --push --no-cache --image-label "$IMAGE_LABEL"
env:
FLY_API_TOKEN: ${{ secrets.FLY_PROD_API_TOKEN }}
prompt-service:
name: Push to Service (Production Environment)
needs: deploy-honcho-prod-image
runs-on: ubuntu-latest
steps:
- name: Send POST request
env:
GITHUB_REF_NAME: ${{ github.ref_name }}
run: |
# Determine version and image label based on trigger type
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
TAG="${{ github.event.inputs.version }}"
IMAGE_LABEL="honcho-prod-image:deployment-${{ github.event.inputs.version }}"
else
TAG=${GITHUB_REF_NAME#v}
IMAGE_LABEL="honcho-prod-image:deployment-${GITHUB_REF_NAME}"
fi
curl --fail -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${{ secrets.PROD_ENV_WEBHOOK_SECRET }}" \
-d "{\"version\":\"$TAG\",\"image_label\":\"$IMAGE_LABEL\"}" \
"${{ secrets.PROD_ENV_URL }}/webhooks/v1/add_honcho_version"

61
.github/workflows/fly-deploy.yml vendored Normal file
View File

@ -0,0 +1,61 @@
# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/
name: Fly Deploy (Test Environment)
permissions:
contents: read
on:
push:
tags:
- v*
workflow_dispatch:
inputs:
version:
description: "Version to deploy (without v prefix)"
required: true
type: string
default: 'manual'
jobs:
deploy-honcho-image:
name: Deploy Honcho Image (Test Environment)
runs-on: ubuntu-latest
concurrency:
group: deploy-test-group
cancel-in-progress: true
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@1.5
- run: |
# Determine the image label based on trigger type
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
IMAGE_LABEL="deployment-${{ github.event.inputs.version }}"
else
IMAGE_LABEL="deployment-${{ github.ref_name }}"
fi
flyctl deploy -a honcho-image --remote-only --build-only --push --no-cache --image-label "$IMAGE_LABEL"
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
prompt-service:
name: Push to Service (Test Environment)
runs-on: ubuntu-latest
needs: deploy-honcho-image
steps:
- name: Send POST request
env:
GITHUB_REF_NAME: ${{ github.ref_name }}
run: |
# Determine version and image label based on trigger type
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
TAG="${{ github.event.inputs.version }}"
IMAGE_LABEL="honcho-image:deployment-${{ github.event.inputs.version }}"
else
TAG=${GITHUB_REF_NAME#v}
IMAGE_LABEL="honcho-image:deployment-${GITHUB_REF_NAME}"
fi
curl --fail -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${{ secrets.TEST_ENV_WEBHOOK_SECRET }}" \
-d "{\"version\":\"$TAG\",\"image_label\":\"$IMAGE_LABEL\"}" \
"${{ secrets.TEST_ENV_URL }}/webhooks/v1/add_honcho_version"

132
.github/workflows/start-fly-runner.yml vendored Normal file
View File

@ -0,0 +1,132 @@
name: Start Fly Runner
on:
workflow_call:
outputs:
runner-ready:
description: "Whether the runner is ready"
value: ${{ jobs.start-runner.outputs.runner-ready }}
machine-id:
description: "The Fly machine ID that was started"
value: ${{ jobs.start-runner.outputs.machine-id }}
runner-labels:
description: "Labels to target the self-hosted runner"
value: ${{ jobs.start-runner.outputs.runner-labels }}
runner-name:
description: "Resolved GitHub runner name"
value: ${{ jobs.start-runner.outputs.runner-name }}
env:
FLY_RUNNER_APP: ivysaur
FLY_RUNNER_REGION: iad
FLY_RUNNER_IMAGE: registry.fly.io/ivysaur:latest
jobs:
start-runner:
name: Start Fly Runner
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
outputs:
runner-ready: ${{ steps.wait-for-runner.outputs.ready }}
machine-id: ${{ steps.machine-management.outputs.machine-id }}
runner-labels: ${{ steps.generate-labels.outputs.labels }}
runner-name: ${{ steps.wait-for-runner.outputs.runner-name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Generate unique runner labels
id: generate-labels
run: |
UNIQUE_LABELS='"self-hosted","${{ github.run_id }}"'
echo "Generated unique labels: $UNIQUE_LABELS"
echo "labels=$UNIQUE_LABELS" >> "$GITHUB_OUTPUT"
- name: Setup Fly CLI
uses: superfly/flyctl-actions/setup-flyctl@master
- name: Get Fly app info
id: get-app-info
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_TESTING }}
run: |
echo "Getting app info for ${FLY_RUNNER_APP}..."
flyctl status -a "${FLY_RUNNER_APP}"
- name: Set GH_TOKEN in Fly secrets
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_TESTING }}
run: |
echo "Setting GH_TOKEN in Fly secrets..."
flyctl secrets set GH_TOKEN="${{ secrets.GH_TOKEN_ACTIONS }}" -a "${FLY_RUNNER_APP}"
- name: Create Fly machine
id: machine-management
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_TESTING }}
run: |
set -euo pipefail
# Always create a fresh ephemeral machine
FULL_OUTPUT=$(flyctl machines run "${FLY_RUNNER_IMAGE}" \
-a "${FLY_RUNNER_APP}" \
--region "${FLY_RUNNER_REGION}" \
--env RUN_ID=${{ github.run_id }} \
--env TEST_TYPE="honcho-unified-runner" \
--vm-size shared-cpu-8x \
--vm-memory 8192 )
MACHINE_ID=$(echo "$FULL_OUTPUT" | grep "Machine ID:" | awk '{print $3}')
echo "Created machine: $MACHINE_ID"
echo "machine-id=$MACHINE_ID" >> "$GITHUB_OUTPUT"
- name: Wait for runner to be online
id: wait-for-runner
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_ACTIONS }}
MAX_WAIT: 420
run: |
set -euo pipefail
if [ -z "${GITHUB_TOKEN}" ]; then
echo "GH_TOKEN secret is required to poll the Actions runner API."
exit 1
fi
EXPECTED_RUNNER_NAME="honcho-unified-runner-${{ github.run_id }}"
echo "Waiting for runner named ${EXPECTED_RUNNER_NAME} to come online..."
WAITED=0
RUNNER_NAME=""
while [ $WAITED -lt $MAX_WAIT ]; do
RESPONSE=$(curl -s \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${{ github.repository }}/actions/runners")
if echo "$RESPONSE" | grep -q '"message"'; then
echo "API Error: $(echo "$RESPONSE" | jq -r '.message')"
exit 1
fi
# Find runner with exact name and is online and not busy
RUNNER_LINE=$(echo "$RESPONSE" | jq -r --arg runner_name "$EXPECTED_RUNNER_NAME" '.runners[]? | select(.name == $runner_name) | select(.status == "online") | select(.busy == false) | "\(.name)|\(.id)"' | head -n 1)
if [ -n "$RUNNER_LINE" ]; then
RUNNER_NAME=$(echo "$RUNNER_LINE" | cut -d'|' -f1)
echo "✅ Found runner: ${RUNNER_NAME}"
echo "ready=true" >> "$GITHUB_OUTPUT"
echo "runner-name=${RUNNER_NAME}" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "⏳ Waiting for runner... (${WAITED}s elapsed)"
sleep 15
WAITED=$((WAITED + 15))
done
echo "Runner failed to come online within ${MAX_WAIT} seconds"
echo "ready=false" >> "$GITHUB_OUTPUT"
exit 1

24
.github/workflows/staticanalysis.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Static Analysis
on: [push]
permissions:
contents: read
jobs:
basedpyright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: "Set up Python"
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- name: Install uv
uses: astral-sh/setup-uv@v2
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Install the project
run: uv sync --all-extras --dev
- name: run basedpyright
run: uv run basedpyright

139
.github/workflows/unified-tests.yml vendored Normal file
View File

@ -0,0 +1,139 @@
name: Unified Tests (Fly Runner)
on:
push:
branches: [main]
paths:
- 'src/**'
- 'tests/**'
permissions:
contents: read
actions: read
jobs:
start-runner:
name: Start Fly Runner
uses: ./.github/workflows/start-fly-runner.yml
secrets: inherit
unified-tests:
name: Run Unified Tests
runs-on: ${{ fromJSON(format('[{0}]', needs.start-runner.outputs.runner-labels)) }}
needs: start-runner
if: needs.start-runner.outputs.runner-ready == 'true'
timeout-minutes: 90
environment: unified-tests
permissions:
id-token: write # Required for OIDC authentication with AWS
contents: read
env:
PYTHONUNBUFFERED: "1"
TEST_DISCORD_WEBHOOK_URL: ${{ secrets.TEST_DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::444554165670:role/GitHubActionsS3Role
aws-region: us-east-1
role-duration-seconds: 43200 # 12 hours
- name: Fetch secrets from AWS Secrets Manager
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
,testing/unified/tests
parse-json-secrets: true
- name: Verify Docker is available
run: docker info
- name: Verify uv and Python
run: |
uv --version
python3.12 --version
which python3.12
- name: Install the project
run: uv sync --all-extras
- name: Run unified tests
run: uv run python -m tests.unified.run
cleanup-machine:
name: Cleanup Fly Machine and Runner
runs-on: ubuntu-latest
needs: [start-runner, unified-tests]
if: always() && needs.start-runner.outputs.machine-id != ''
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_TESTING }}
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_ACTIONS }}
FLY_RUNNER_APP: ivysaur
steps:
- name: Setup Fly CLI
uses: superfly/flyctl-actions/setup-flyctl@1.5
- name: Cleanup fly machine
run: |
set -euo pipefail
MACHINE_ID="${{ needs.start-runner.outputs.machine-id }}"
if [ -z "$MACHINE_ID" ]; then
echo "No machine ID provided, skipping Fly cleanup."
exit 0
fi
echo "🧹 Cleaning up machine: $MACHINE_ID"
flyctl machines stop "$MACHINE_ID" -a "$FLY_RUNNER_APP" || echo "Machine may already be stopped"
flyctl machines destroy "$MACHINE_ID" -a "$FLY_RUNNER_APP" --force || echo "Failed to destroy machine"
- name: Cleanup GitHub runner
run: |
set -euo pipefail
RUNNER_NAME="${{ needs.start-runner.outputs.runner-name }}"
FALLBACK_LABEL="${{ github.run_id }}"
echo "🗑️ Cleaning up GitHub runner (name: ${RUNNER_NAME:-unknown}, label: ${FALLBACK_LABEL})"
RUNNERS_RESPONSE=$(curl -s \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/actions/runners")
if echo "$RUNNERS_RESPONSE" | grep -q '"message"'; then
echo "⚠️ Failed to fetch runners: $(echo "$RUNNERS_RESPONSE" | jq -r '.message')"
exit 0
fi
RUNNER_ID="" 
if [ -n "$RUNNER_NAME" ]; then
RUNNER_ID=$(echo "$RUNNERS_RESPONSE" | jq -r --arg name "$RUNNER_NAME" '.runners[]? | select(.name == $name) | .id')
fi
if [ -z "$RUNNER_ID" ]; then
RUNNER_ID=$(echo "$RUNNERS_RESPONSE" | jq -r --arg label "$FALLBACK_LABEL" '.runners[]? | select([.labels[].name] | index($label)) | .id' | head -n 1)
fi
if [ -z "$RUNNER_ID" ] || [ "$RUNNER_ID" = "null" ]; then
echo "⚠️ Runner not found, nothing to delete."
exit 0
fi
DELETE_RESPONSE=$(curl -s -w "%{http_code}" \
-X DELETE \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/actions/runners/$RUNNER_ID")
HTTP_CODE="${DELETE_RESPONSE: -3}"
if [ "$HTTP_CODE" = "204" ]; then
echo "✅ Successfully deleted runner."
else
echo "⚠️ Failed to delete runner. HTTP code: $HTTP_CODE"
echo "Response: ${DELETE_RESPONSE%???}"
fi

150
.github/workflows/unittest.yml vendored Normal file
View File

@ -0,0 +1,150 @@
name: FastAPI Tests with PostgreSQL and uv
on:
push:
branches: [main]
paths:
- '**.py'
- '**.ts'
- '**.js'
- '**.tsx'
- '**.jsx'
- 'pyproject.toml'
- 'uv.lock'
- 'sdks/typescript/package.json'
- 'sdks/typescript/bun.lock'
- '.github/workflows/unittest.yml'
pull_request:
branches: [main]
paths:
- '**.py'
- '**.ts'
- '**.js'
- '**.tsx'
- '**.jsx'
- 'pyproject.toml'
- 'uv.lock'
- 'sdks/typescript/package.json'
- 'sdks/typescript/bun.lock'
- '.github/workflows/unittest.yml'
permissions:
contents: read
pull-requests: read
jobs:
# Determine which tests to run based on changed files
changes:
runs-on: ubuntu-latest
outputs:
python: ${{ steps.filter.outputs.python }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
python:
- '**.py'
- 'pyproject.toml'
- 'uv.lock'
- 'migrations/**'
- 'sdks/typescript/**'
- '.github/workflows/unittest.yml'
test-python:
needs: changes
if: ${{ needs.changes.outputs.python == 'true' }}
runs-on: ubuntu-latest
services:
database:
image: pgvector/pgvector:pg15
env:
POSTGRES_DB: test_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v2
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: "Set up Python"
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- name: Install bun
uses: oven-sh/setup-bun@v2
- name: Install TypeScript SDK dependencies
run: bun install
working-directory: sdks/typescript
- name: Install the project
run: uv sync --all-extras --dev
- name: Run Tests
run: uv run pytest -x
env:
DB_CONNECTION_URI: postgresql+psycopg://postgres:postgres@localhost:5432/test_db
AUTH_USE_AUTH: false
SENTRY_ENABLED: false
LLM_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
LLM_ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
LLM_OPENAI_COMPATIBLE_API_KEY: test-key
LLM_OPENAI_COMPATIBLE_BASE_URL: http://localhost:8000
DERIVER_PROVIDER: openai
DERIVER_MODEL: test
DIALECTIC_LEVELS__minimal__PROVIDER: openai
DIALECTIC_LEVELS__minimal__MODEL: test
DIALECTIC_LEVELS__minimal__THINKING_BUDGET_TOKENS: 0
DIALECTIC_LEVELS__minimal__MAX_TOOL_ITERATIONS: 2
DIALECTIC_LEVELS__low__PROVIDER: openai
DIALECTIC_LEVELS__low__MODEL: test
DIALECTIC_LEVELS__low__THINKING_BUDGET_TOKENS: 0
DIALECTIC_LEVELS__low__MAX_TOOL_ITERATIONS: 5
DIALECTIC_LEVELS__medium__PROVIDER: openai
DIALECTIC_LEVELS__medium__MODEL: test
DIALECTIC_LEVELS__medium__THINKING_BUDGET_TOKENS: 0
DIALECTIC_LEVELS__medium__MAX_TOOL_ITERATIONS: 4
DIALECTIC_LEVELS__high__PROVIDER: openai
DIALECTIC_LEVELS__high__MODEL: test
DIALECTIC_LEVELS__high__THINKING_BUDGET_TOKENS: 0
DIALECTIC_LEVELS__high__MAX_TOOL_ITERATIONS: 4
DIALECTIC_LEVELS__max__PROVIDER: openai
DIALECTIC_LEVELS__max__MODEL: test
DIALECTIC_LEVELS__max__THINKING_BUDGET_TOKENS: 0
DIALECTIC_LEVELS__max__MAX_TOOL_ITERATIONS: 10
DIALECTIC_QUERY_GENERATION_PROVIDER: openai
DIALECTIC_QUERY_GENERATION_MODEL: test
SUMMARY_PROVIDER: openai
SUMMARY_MODEL: test
# Status check for branch protection rules
# This job always runs and reports success only if all required jobs pass
test-status:
runs-on: ubuntu-latest
needs: [changes, test-python]
if: always()
steps:
- name: Check test results
run: |
if [[ "${{ needs.changes.outputs.python }}" == "true" && "${{ needs.test-python.result }}" != "success" && "${{ needs.test-python.result }}" != "skipped" ]]; then
echo "Python tests failed or were cancelled"
exit 1
fi
echo "All required tests passed!"

195
.gitignore vendored Normal file
View File

@ -0,0 +1,195 @@
.worktrees/
api/**/*.db
api/data
api/docker-compose.yml
*.db
data
redis-data
docker-compose.yml
compose.yml
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
*.sqlite
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
**/docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.env.backup*
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.DS_Store
supabase/
docs/node_modules
timing_logs.csv
config.json
config.toml
.aider*
CRUSH.md
.crush/
metrics.jsonl
AGENTS.md
lancedb_data/
grafana-data/

15
.markdownlint.json Normal file
View File

@ -0,0 +1,15 @@
{
"default": true,
"MD013": false,
"MD024": false,
"MD025": false,
"MD029": false,
"MD040": false,
"MD041": false,
"line-length": false,
"no-duplicate-heading": false,
"single-h1": false,
"ol-prefix": false,
"fenced-code-language": false,
"first-line-h1": false
}

132
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,132 @@
# .pre-commit-config.yaml
repos:
# Basic file checks (run on all files)
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-added-large-files
args: ['--maxkb=1000']
- id: check-merge-conflict
- id: debug-statements
files: \.(py|js|ts)$
- id: mixed-line-ending
args: ['--fix=lf']
# Additional checks from suggestions
- id: check-docstring-first
files: \.py$
- id: check-executables-have-shebangs
- id: check-case-conflict
# Python code formatting and linting with ruff
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks:
# Linter - only on Python directories
- id: ruff
args: [--fix]
files: ^(src/|tests/|scripts/|migrations/|sdks/python/).*\.py$
# Formatter - only on Python directories
- id: ruff-format
files: ^(src/|tests/|scripts/|migrations/|sdks/python/).*\.py$
# Security checks - only on main src code (not tests/scripts)
- repo: https://github.com/PyCQA/bandit
rev: 1.7.10
hooks:
- id: bandit
args: ['-r']
files: ^(src/|sdks/python/src/).*\.py$
# Local hooks using your uv environment
- repo: local
hooks:
# TypeScript linting with biome
- id: biome-check
name: biome check and format
entry: bash -c 'cd sdks/typescript && bun run lint:fix'
language: system
files: ^sdks/typescript/.*\.(js|ts|jsx|tsx|json|jsonc)$
pass_filenames: false
# Type checking with basedpyright - only on main Python code
- id: basedpyright
name: basedpyright
entry: uv run basedpyright
language: system
files: ^(src/|tests/|sdks/python/|scripts/).*\.py$
require_serial: true
pass_filenames: true
# Run main application tests
- id: pytest-main
name: pytest (main app)
entry: uv run pytest -x tests/ --ignore=tests/alembic/
language: system
files: ^(src/|tests/).*\.py$
stages: [pre-push]
pass_filenames: false
# Run Alembic tests only when migrations change
- id: pytest-alembic
name: pytest (alembic migrations)
entry: uv run python scripts/run_alembic_tests.py
language: system
files: ^(migrations/versions/.*\.py|tests/alembic/.*\.py)$
stages: [pre-push]
pass_filenames: true
require_serial: true
# Ensure each alembic migration revision has a corresponding test file
- id: ensure-alembic-coverage
name: ensure alembic migration test coverage
entry: uv run python scripts/ensure_alembic_tests.py
language: system
files: ^(migrations/versions/.*\.py|tests/alembic/revisions/.*\.py)$
stages: [pre-push]
pass_filenames: false
# Run Python SDK tests (if they exist)
- id: pytest-python-sdk
name: pytest (Python SDK)
entry: bash -c 'if [ -d "sdks/python/tests" ]; then cd sdks/python && uv run pytest; fi'
language: system
files: ^sdks/python/.*\.py$
stages: [pre-push]
pass_filenames: false
# TypeScript build with bun (tests run via pytest)
- id: typescript-check
name: TypeScript build
entry: bash -c 'if [ -f "sdks/typescript/package.json" ]; then cd sdks/typescript && bun run build; fi'
language: system
files: ^sdks/typescript/.*\.(js|ts|jsx|tsx|json)$
stages: [pre-push]
pass_filenames: false
# TypeScript type checking with bun
- id: typescript-typecheck
name: TypeScript type check
entry: bash -c 'if [ -f "sdks/typescript/package.json" ]; then cd sdks/typescript && bun run typecheck; fi'
language: system
files: ^sdks/typescript/.*\.(ts|tsx)$
pass_filenames: false
# Documentation linting
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.37.0
hooks:
- id: markdownlint
args: ['--fix']
files: \.(md|mdx)$
# Commit message linting
- repo: https://github.com/commitizen-tools/commitizen
rev: v3.13.0
hooks:
- id: commitizen
stages: [commit-msg]

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.11

9
.vscode/honcho.code-workspace vendored Normal file
View File

@ -0,0 +1,9 @@
{
"folders": [
{
"path": ".."
}
],
"settings": {
}
}

9
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"python.analysis.typeCheckingMode": "basic",
"files.exclude": {},
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}

19
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "sync",
"type": "shell",
"command": "uv sync",
"group": "none",
"presentation": {
"reveal": "always",
"panel": "shared"
},
"runOptions": {
"runOn": "folderOpen"
},
"problemMatcher": []
}
]
}

845
CHANGELOG.md Normal file
View File

@ -0,0 +1,845 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [3.0.7] - 2026-05-21
### Added
- New `src/llm/` module as the single owner of provider runtime: clients, backends, history adapters, tool loop, request builder, credentials, and caching policy (#459)
- `AttemptPlan` dataclass captures per-retry provider selection (client, model, reasoning_effort, thinking_budget_tokens, selected_config) and pins it across stream-final retries so streaming doesn't bounce back to primary after the tool loop has settled on fallback (#459)
- Gemini JSON-schema sanitizer for `function_declarations` — strips keywords Gemini's validator rejects (`additionalProperties`, `allOf`, etc.) while preserving semantics for all other backends (#459)
- Dreamer specialists derive `effective_max_tokens` from `model_config.max_output_tokens` with a per-specialist default fallback (#459)
- New cloudevent `LLMCallCompletedEvent` (`llm.call.completed`) fires once per provider hit with full cost-attribution context: transport/provider_label, model, token counts with cache breakdown, finish_reason, outcome, `is_final_attempt`, retry/fallback state, duration, tool-call shape, streaming flag, and agent correlation (`run_id` + iteration). Includes a `CallPurpose` closed enum (`deriver.representation`, `dialectic.answer`, `dream.deduction|induction`, `summary.short|long`) (#637)
- `RepresentationCompletedEvent` now carries `total_input_tokens` for full-trace cost attribution (#637)
- Per-emitter `honcho_version` injection on all CloudEvents plus emitter health metrics (#637)
- `TelemetrySettings.HIGH_VOLUME_SAMPLE_RATE` (default 1.0) — deterministic per-`run_id` sampler so an entire agent trace is kept or dropped together; aggregate envelopes bypass the sampler (#637)
- Deriver custom instructions: per-workspace/peer guidance threaded into the deriver prompt with a `MAX_CUSTOM_INSTRUCTIONS_TOKENS` budget (default 2000); deriver `MAX_INPUT_TOKENS` raised 23000 → 25000 to make room (#609)
- Configurable embedding dimensions: `EMBEDDING_MODEL_CONFIG__DIMENSIONS_MODE` (`auto`/`always`/`never`) controls whether the OpenAI `dimensions=` parameter is forwarded; `auto` (default) sends it when the operator explicitly set `EMBEDDING_VECTOR_DIMENSIONS` and the model is not on the known-rejecting allowlist (#678)
- New `honcho-cli` package — Python CLI for inspecting and managing peers, sessions, and configuration against a Honcho deployment (#424)
- `HONCHO_API_URL` env var support in the MCP Worker, enabling self-hosted Honcho deployments to point the Worker at their own instance instead of `https://api.honcho.dev` (#575)
- API ID `max_length` increased from 100 to 512 across `WorkspaceCreate`, `PeerCreate`, and `SessionCreate` to align the API contract with the underlying DB schema (#684)
- Regression tests covering fallback-config thinking-param reach, provider_params → extra_params boundary, OpenAI reasoning-model parameter routing, Gemini blocked finish_reason handling, and fail-fast `max_tool_iterations` validation (#459)
### Changed
- All LLM orchestration moved out of `src/utils/clients.py` into `src/llm/` with modules split by responsibility (api, executor, tool_loop, runtime, registry, conversation, request_builder, credentials, caching, backends, history_adapters) (#459)
- Default `ModelConfig` factories (deriver, summary, dreamer specialists, dialectic levels) normalized to `openai/gpt-5.4-mini` with no extra parameters set by default; operators add transport/thinking overrides explicitly (#459)
- OpenAI reasoning-model routing widened via `_uses_max_completion_tokens` heuristic covering `gpt-5.x` and `o1/o3/o4` — these models receive `max_completion_tokens` instead of `max_tokens` (#459)
- Override client factories switched from unbounded `@cache` to `@lru_cache(maxsize=128)` for predictable memory growth on long-running processes (#459)
- `get_backend` now delegates to `client_for_model_config`, so the live-test path and production path share one missing-API-key validation (#459)
- Blocked Gemini responses (`SAFETY`, `RECITATION`, `PROHIBITED_CONTENT`, `BLOCKLIST`) raise `LLMError` in the streaming path too (previously only the non-streaming path), ensuring retry/fallback logic fires uniformly (#459)
- Transport-change env overrides now strip transport-specific thinking params (thinking_budget_tokens vs. reasoning_effort) during config merge, including at the dialectic-level merge, so switching from Anthropic → OpenAI doesn't leave orphaned Anthropic-only params that the OpenAI backend would reject (#459)
- `max_tool_iterations` out-of-range inputs now raise `ValidationException` instead of being silently clamped (#459)
- Public API schemas (`WorkspaceCreate`, `PeerCreate`, `SessionCreate`) and SDK validation (`api_types.py`, `validation.ts`) accept IDs up to 512 chars (was 100) (#684)
- Peer card prompts reframed as stable identity markers (replaces the prior "biographical/profile facts" language). Induction specialist is now opted out of peer card writes (`can_update_peer_card = False`) so only deduction touches the card (#686)
- Vector store queries no longer fetch embedding vectors — only document metadata is returned, reducing payload size and DB load (pgvector, lancedb, turbopuffer) (#682)
- Langfuse trace metadata now includes `namespace`, `model`, and `provider` so traces can be filtered by deployment slice (#565)
- Deriver: model-aware tokenizer (replaces the previously hardcoded encoding) and explicit guard on empty message content (#647)
- Dialectic level defaults now merge correctly with per-level overrides in `src/config` (DEV-1733) (#656)
- Default dialectic tool choice switched from forced/required to `auto` (#630)
- Vector sync given a substantial retry budget to tolerate transient embedding provider outages (#604)
- `AgentToolConclusionsDeletedEvent` payload now carries `levels` for parity with the rest of the conclusion event surface (#612)
- Turbopuffer vector store: `InternalServerError` caught and surfaced as a warning rather than a hard failure; unused `upsert_with_retry` and `VectorUpsertResult` removed; explicit silent and explicit-error paths for vector DB server errors (#561)
- Troubleshooting docs updated to reflect nested-env-var form for per-component thinking-budget overrides (#459)
- README refresh (#681)
- CLAUDE.md refreshed against the current `src/` layout (#680)
### Fixed
- Fallback `ModelConfig` temperature and `thinking_budget_tokens` reach the backend on the final retry — previously the primary's values were pre-populated into caller kwargs early and clobbered fallback values via `effective_config_for_call(update=...)` (#459)
- Stream-final retries pin to the `AttemptPlan` that succeeded rather than re-running provider selection through the outer `current_attempt` ContextVar (which could roll streaming back to primary after the tool loop had already switched to fallback) (#459)
- OpenAI structured-output calls continue to use `chat.completions.parse()` with strict schema enforcement, while tool-calling paths use `chat.completions.create()` without `strict:True` for broader proxy compatibility (OpenRouter, vLLM, Ollama) (#459)
- Gemini `cached_content` reuse keys now include `system_instruction` and `tool_config` so cache hits don't cross configurations that differ only in those fields (#459)
- Removed strict parameter validation for thinking params on Anthropic and OpenAI transports — was rejecting valid per-transport configs (#686)
- `reverse` query parameter is now honored on the v3 workspace list (`POST /v3/workspaces/list`), peer list (`POST /v3/workspaces/{workspace_id}/peers/list`), workspace-scoped session list (`POST /v3/workspaces/{workspace_id}/sessions/list`), and peer-scoped session list (`POST /v3/workspaces/{workspace_id}/peers/{peer_id}/sessions`). Honcho SDKs at 2.1.0+ were already sending `reverse=true` for these routes but the server silently ignored it. Ties on `created_at` now fall back to the internal nanoid `id` so ordering remains stable across pages (#685)
- LLM client factories now receive `base_url` from `LLMSettings` for default providers — previously the override path honored `base_url` but the default path didn't, so operators pointing at OpenAI-compatible proxies via `LLM__OPENAI_BASE_URL` were ignored (#643, fixes #641)
- Internal N+1 query in dialectic agent tool execution (DEV-1721) — collapsed per-iteration DB lookups into a single fetch (#652)
- Dreamer threshold and time-guard semantics: `check_and_schedule_dream` count filter now includes only `documents.level == 'explicit'` (dreamer-created levels are output, not input, and were inflating the threshold and creating a feedback loop); `last_dream_at` write relocated from `enqueue_dream` into `process_dream` so duplicate enqueues or failed runs no longer reset the 8-hour time guard (#573)
- Deriver: blank observations are filtered out before embedding (previously triggered noisy embedding calls and persisted empty rows); blank-observation filtering unified across tool paths (#615)
- Surprisal module: filter for level observations changed from `{"level": levels}` to `{"level": {"in": levels}}``apply_filter()` requires operator syntax, so the prior call silently returned 0 results and made the entire Surprisal phase of the Dream cycle a no-op (#581, fixes #559)
- Removed hardcoded `stop_sequences` override from Deriver `ModelConfig` (was clobbering operator-configured stop sequences) (#587)
- Removed stale `stop_sequences` from tests (#607)
- Embedding client: `embed()` now wraps single-string input in an array, restoring compatibility with OpenAI-compatible third-party providers that reject scalar input (#586)
- Docker Compose: deriver service startup gated on the API service healthcheck (prevents races where the deriver starts before the API has run migrations) (#689)
- Docker image: `HEALTHCHECK` directive removed from the shared base image — it probed an HTTP endpoint only the API serves, permanently marking deriver containers as unhealthy. Service-level health checks now belong in each service's own configuration (k8s readiness/liveness probes on the API Deployment only) (#530)
- `tests/unified`: `--test-dir`/`--test-file` arguments now use an argparse mutually-exclusive group instead of manual validation (#650)
- CrewAI example updated for the latest CrewAI protocol (#631)
### Removed
- `src/utils/clients.py` deleted; its responsibilities are split across `src/llm/registry.py`, `src/llm/credentials.py`, and the backend-specific modules (#459)
- `HEALTHCHECK` directive removed from the shared Docker image (#530)
## [3.0.6] - 2026-04-10
### Changed
- Tightened transaction scopes across search, agent tools, queue manager, and webhook delivery to minimize DB connection hold time during external operations (#525)
- Search operations refactored to two-phase pattern — external work (embeddings, LLM calls) completes before opening a transaction (#525)
- Agent tool executor performs external operations before acquiring DB sessions (#525)
- Queue manager transaction scope reduced to only the critical section (#525)
- Webhook delivery no longer holds a DB session parameter (#525)
### Fixed
- Session leakage in non-session-scoped dialectic chat calls (#526)
### Added
- Health check endpoint (`/health`) for container orchestration and load balancer probes (#510)
## [3.0.5] - 2026-04-03
### Fixed
- explicit rollback on all transactions to force connection closed
## [3.0.4] - 2026-04-02
### Added
- JSONB metadata validation enforces 100 key limit and max depth of 5 (#419)
### Changed
- Schemas refactored from single `schemas.py` into `schemas/api.py`, `schemas/configuration.py`, and `schemas/internal.py` with backwards-compatible re-exports (#419)
### Fixed
- Missing `deleted_at` filter on `RepresentationManager._query_documents_recent()` and `._query_documents_most_derived()` allowed soft-deleted documents to leak into the deriver's working representation (#456)
- `CleanupStaleItemsCompletedEvent` emitted spuriously when no queue item was actually deleted (#454)
- Empty JSON file uploads caused unhandled errors; now returns normalized error responses (#434)
- Memory leak: `_observation_locks` switched to `WeakValueDictionary` to prevent unbounded growth (#419)
- SQL injection in `dependencies.py`: parameterized `set_config` calls to prevent injection via request context (#419)
- NUL byte crashes: string inputs (message content, queries, peer cards) now stripped at schema level (#419)
- Filter recursion depth capped at 5 to prevent stack overflow (#419)
- Dedup-skipped observations now correctly reflected in created counts (#477)
- External vector store support for message search — routes queries through configured external vector store with oversampling and
deduplication to handle chunked embeddings (#479)
- Dialectic agent no longer holds a DB connection during LLM calls — embeddings are pre-computed before tool execution, DB sessions isolated in `extract_preferences`, `query_documents` no longer accepts a DB session parameter (#477)
## [3.0.3] - 2026-02-25
### Added
- Consolidated session context into a single DB session with 40/60 token budget allocation between summary and messages
- Observation validation via `ObservationInput` Pydantic schema with partial-success support and batch embedding with per-observation fallback
- Peer card hard cap of 40 facts with case-insensitive deduplication and whitespace normalization
- Safe integer coercion (`_safe_int`) for all LLM tool inputs to handle non-integer values like `"Infinity"`
- Embedding pre-computation and reuse across multiple search calls in dialectic and representation flows
- Peer existence validation in dialectic chat endpoints — raises ResourceNotFoundException instead of silently failing
- Logging filter to suppress noisy `GET /metrics` access logs
- Oolong long-context aggregation benchmark (synth and real variants, 1K4M token context windows)
- MolecularBench fact quality evaluation (ambiguity, decontextuality, minimality scoring)
- CoverageBench information recall evaluation (gold fact extraction, coverage matching, QA verification)
- LoCoMo summary-as-context baseline evaluation
- Webhook delivery tests, dependency lifecycle tests, queue cleanup tests, summarizer fallback tests
- Parallel test execution via pytest-xdist with worker-specific databases
- `test_reasoning_levels.py` script for LOCOM dataset testing across reasoning levels
### Changed
- Workspace deletion is now async — returns 202 Accepted, validates no active sessions (409 Conflict), cascade-deletes in background
- Redis caching layer now stores plain-dict instead of ORM objects, with v2-prefixed keys, storage, resilient `safe_cache_set`/`safe_cache_delete` helpers, and deferred post-commit cache invalidation
- All `get_or_create_*` CRUD operations now use savepoints (`db.begin_nested()`) instead of commit/rollback for race condition prevention
- Reconciler vector sync uses direct ORM mutation instead of batch parameterized UPDATE statements
- Summarizer enforces hard word limit in prompt and creates fallback text for empty summaries with `summary_tokens = 0`
- Blocked Gemini responses (SAFETY, RECITATION, PROHIBITED_CONTENT, BLOCKLIST) now raise `LLMError` to trigger retry/backup-provider logic
- Gemini client explicitly sets `max_output_tokens` from `max_tokens` parameter
- All deriver and metrics collector logging replaced with structured `logging.getLogger(__name__)` calls
- Dreamer specialist prompts updated to enforce durable-facts-only peer cards with max 40 entries and deduplication
- `GetOrCreateResult` changed from `NamedTuple` to `dataclass` with `async post_commit()` method
- FastAPI upgraded from 0.111.0 to 0.131.0; added pyarrow dependency
- Queue status filtering to only show user-facing tasks (representation, summary, dream); excludes internal infrastructure tasks
### Fixed
- JWT timestamp bug — `JWTParams.t` was evaluated once at class definition time instead of per-instance
- Session cache invalidation on deletion was missing
- `get_peer_card()` now properly propagates `ResourceNotFoundException` instead of swallowing it
- `set_peer_card()` ensures peer exists via `get_or_create_peers()` before updating
- Backup provider failover with proper tool input type safety
- Removed `setup_admin_jwt()` from server startup
- Sentry coroutine detection switched from `asyncio.iscoroutinefunction` to `inspect.iscoroutinefunction`
### Removed
- `explicit.py` and `obex.py` benchmarks replaced by coverage.py and molecular.py
- Claude Code review automation workflow (`.github/workflows/claude.yml`)
- Coverage reporting from default pytest configuration
## [3.0.2] - 2026-01-27
### Added
- Documentation for reasoning_level and Claude Code plugin
### Changed
- Gave dreaming sub-agents better prompting around peer card creation, tweaked overall prompts
### Fixed
- Added message-search fallback for memory search tool, necessary in fresh sessions
- Made FLUSH_ENABLED a config value
- Removed N+1 query in search_messages
## [3.0.1] - 2026-01-27
### Fixed
- Token counting in Explicit Agent Loop
- Backwards compatibility of queue items
## [3.0.0] - 2026-01-19
### Added
- Agentic Dreamer for intelligent memory consolidation using LLM agents
- Agentic Dialectic for query answering using LLM agents with tool use
- Reasoning levels configuration for dialectic (`minimal`, `low`, `medium`, `high`, `max`)
- Prometheus token tracking for deriver and dialectic operations
- n8n integration
- Cloud Events for auditable telemetry
- External Vector Store support for turbopuffer and lancedb with reconciliation flow
### Changed
- API route renaming for consistency
- Dreamer and dialectic now respect peer card configuration settings
- Observations renamed to Conclusions across API and SDKs
- Deriver to buffer representation tasks to normalize workloads
- Local Representation tasks to create singular QueueItems
- getContext endpoint to use `search_query` rather than force `last_user_message`
### Fixed
- Dream scheduling bugs
- Summary creation when start_message_id > end_message_id
- Cashews upgrade to prevent NoScriptError
- Memory leak in `accumulate_metric` call
### Removed
- Peer card configuration from message configuration; peer cards no longer created/updated in deriver process
## [2.5.1] - 2025-12-15
### Fixed
- Backwards compatibility for `message_ids` field in documents to handle legacy tuple format
## [2.5.0] - 2025-12-03
### Added
- Message level configurations
- CRUD operations for observations
- Comprehensive test cases for harness
- Peer level get_context
- Set Peer Card Method
- Manual dreaming trigger endpoint
### Changed
- Configurations to support more flags for fine-grained control of the deriver, peer cards, summaries, etc.
- Working Representations to support more fine-grained parameters
### Fixed
- File uploads to match `MessageCreate` structure
- Cache invalidation strategy
## [2.4.3] - 2025-11-20
### Added
- Redis caching to improve DB IO
- Backup LLM provider to avoid failures when a provider is down
### Changed
- QueueItems to use standardized columns
- Improved Deduplication logic for Representation Tasks
- More finegrained metrics for representation, summary, and peer card tasks
- DB constraint to follow standard naming conventions
## [2.4.2] - 2025-11-03
### Fixed
- Langfuse tracing to have readable waterfalls
- Alembic Migrations to match models.py
- message_in_seq correctly included in webhook payload
### Changed
- Alembic to always use a session pooler
- Statement timeout during alembic operations to 5 min
## [2.4.1] - 2025-10-24
### Added
- Alembic migration validation test suite
### Fixed
- Alembic migrations to batch changes
- Batch message creation sequence number
### Changed
- Logging infrastructure to remove noisy messages
- Sentry integration is centralized
## [2.4.0] - 2025-10-09
### Added
- Unified `Representation` class
- vllm client support
- Periodic queue cleanup logic
- WIP Dreaming Feature
- LongMemEval to Test Bench
- Prometheus Client for better Metrics
- Performance metrics instrumentation
- Error reporting to deriver
- Workspace Delete Method
- Multi-db option in test harness
### Changed
- Working Representations are Queried on the fly rather than cached in metadata
- EmbeddingStore to RepresentationFactory
- Summary Response Model to use public_id of message for cutoff
- Semantic across codebase to reference resources based on `observer` and `observed`
- Prompts for Deriver & Dialectic to reference peer_id and add examples
- `Get Context` route returns peer card and representation in addition to messages and summaries
- Refactoring logger.info calls to logger.debug where applicable
### Fixed
- Gemini client to use async methods
## [2.3.3] — 2025-10-01
### Changed
- Deriver Rollup Queue processes interleaved messages for more context
### Fixed
- Dialectic Streaming to follow SSE conventions
- Sentry tracing in the deriver
## [2.3.2] — 2025-09-25
### Added
- Get peer cards endpoint (`GET /v2/peers/{peer_id}/card`) for retrieving targeted peer context information
### Changed
- Replaced Mirascope dependency with small client implementation for better control
- Optimized deriver performance by using joins on messages table instead of storing token count in queue payload
- Database scope optimization for various operations
- Batch representation task processing for ~10x speed improvement in practice
### Fixed
- Separated clean and claim work units in queue manager to prevent race conditions
- Skip locked ActiveQueueSession rows on delete operations
- Langfuse SDK integration updates for compatibility
- Added configurable maximum message size to prevent token overflow in deriver
- Various minor bugfixes
## [2.3.1] - 2025-09-18
### Fixed
- Added max message count to deriver in order to not overflow token limits
## [2.3.0] — 2025-08-14
### Added
- `getSummaries` endpoint to get all available summaries for a session directly
- Peer Card feature to improve context for deriver and dialectic
### Changed
- Session Peer limit to be based on observers instead, renamed config value to
`SESSION_OBSERVERS_LIMIT`
- `Messages` can take a custom timestamp for the `created_at` field, defaulting
to the current time
- `get_context` endpoint returns detailed `Summary` object rather than just
summary content
- Working representations use a FIFO queue structure to maintain facts rather
than a full rewrite
- Optimized deriver enqueue by prefetching message sequence numbers (eliminates N+1 queries)
### Fixed
- Deriver uses `get_context` internally to prevent context window limit errors
- Embedding store will truncate context when querying documents to prevent embedding
token limit errors
- Queue manager to schedule work based on available works rather than total
number of workers
- Queue manager to use atomic db transactions rather than long lived transaction
for the worker lifecycle
- Timestamp formats unified to ISO 8601 across the codebase
- Internal get_context method's cutoff value is exclusive now
## [2.2.0] — 2025-08-07
### Added
- Arbitrary filters now available on all search endpoints
- Search combines full-text and semantic using reciprocal rank fusion
- Webhook support (currently only supports queue_empty and test events, more to come)
- Small test harness and custom test format for evaluating Honcho output quality
- Added MCP server and documentation for it
### Changed
- Search has 10 results by default, max 100 results
- Queue structure generalized to handle more event types
- Summarizer now exhaustive by default and tuned for performance
### Fixed
- Resolve race condition for peers that leave a session while sending messages
- Added explicit rollback to solve integrity error in queue
- Re-introduced Sentry tracing to deriver
- Better integrity logic in get_or_create API methods
## [2.1.2] — 2025-07-30
### Fixed
- Summarizer module to ignore empty summaries and pass appropriate one to get_context
- Structured Outputs calls with OpenAI provider to pass strict=True to Pydantic Schema
## [2.1.1] — 2025-07-23
### Added
- Test harness for custom Honcho evaluations
- Better support for session and peer aware dialectic queries
- Langfuse settings
- Added recent history to dialectic prompt, dynamic based on new context window size setting
### Fixed
- Summary queue logic
- Formatting of logs
- Filtering by session
- Peer targeting in queries
### Changed
- Made query expansion in dialectic off by default
- Overhauled logging
- Refactor summarization for performance and code clarity
- Refactor queue payloads for clarity
## [2.1.0] — 2025-07-17
### Added
- File uploads
- Brand new "ROTE" deriver system
- Updated dialectic system
- Local working representations
- Better logging for deriver/dialectic
- Endpoint for deriver queue status
### Fixed
- Document insertion
- Session-scoped and peer-targeted dialectic queries work now
### Removed
- Peer-level messages
### Changed
- Dialectic chat endpoint takes a single query
- Rearranged configuration values (LLM, Deriver, Dialectic, History->Summary)
## [2.0.5] - 2025-07-11
### Fixed
- Groq API client to use the Async library
## [2.0.4] - 2025-07-02
### Fixed
- Migration/provision scripts did not have correct database connection arguments, causing timeouts
## [2.0.3] - 2025-07-01
### Fixed
- Bug that causes runtime error when Sentry flags are enabled
## [2.0.2] - 2025-06-27
### Fixed
- Database initialization was misconfigured and led to provision_db script failing: switch to consistent working configuration with transaction pooler
## [2.0.1] - 2025-06-26
### Added
- Ergonomic SDKs for Python and TypeScript (uses Stainless underneath)
- Deriver Queue Status endpoint
- Complex arbitrary filters on workspace/session/peer/message
- Message embedding table for full semantic search
### Changed
- Overhauled documentation
- BasedPyright typing for entire project
- Resource filtering expanded to include logical operators
### Fixed
- Various bugs
- Use new config arrangement everywhere
- Remove hardcoded responses
## [2.0.0] - 2025-06-24
### Added
- Ability to get a peer's working representation
- Metadata to all data primitives (Workspaces, Peers, Sessions, Messages)
- Internal metadata to store Honcho's state no longer exposed in API
- Batch message operations and enhanced message querying with token and message count limits
- Search and summary functionalities scoped by workspace, peer, and session
- Session context retrieval with summaries and token allocation
- HNSW Index for Documents Table
- Centralized Configuration via Environment Variables or `config.toml` file
### Changed
- API route is now /v2/
- New architecture centered around the concept of a "peer" replaces the former
"app"/"user"/"session" paradigm
- Workspaces replace "apps" as top-level namespace
- Peers replace "users"
- Sessions no longer nested beneath peers and no longer limited to a single
user-assistant model. A session exists independently of any one peer and
peers can be added to and removed from sessions.
- Dialectic API is now part of the Peer, not the Session
- Dialectic API now allows queries to be scoped to a session or "targeted"
to a fellow peer
- Database schema migrated to adopt workspace/peer/session naming and structure
- Authentication and JWT scopes updated to workspace/peer/session hierarchy
- Queue processing now works on 'work units' instead of sessions
- Message token counting updated with tiktoken integration and fallback heuristic
- Queue and message processing updated to handle sender/target and task types for multi-peer scenarios
### Fixed
- Improved error handling and validation for batch message operations and metadata
- Database Sessions to be more atomic to reduce idle in transaction time
### Removed
- Metamessages removed in favor of metadata
- Collections and Documents no longer exposed in the API, solely internal
- Obsolete tests for apps, users, collections, documents, and metamessages
## [1.1.0] - 2025-05-15
### Added
- Normalize resources to remove joins and increase query performance
- Query tracing for debugging
### Changed
- `/list` endpoints to not require a request body
- `metamessage_type` to `label` with backwards compatibility
- Database Provisioning to rely on alembic
- Database Session Manager to explicitly rollback transactions before closing
the connection
### Fixed
- Alembic Migrations to include initial database migrations
- Sentry Middleware to not report Honcho Exceptions
## [1.0.0] - 2025-04-10
### Added
- JWT based API authentication
- Configurable logging
- Consolidated LLM Inference via `ModelClient` class
- Dynamic logging configurable via environment variables
### Changed
- Deriver & Dialectic API to use Hybrid Memory Architecture
- Metamessages are not strictly tied to a message
- Database provisioning is a separate script instead of happening on startup
- Consolidated `session/chat` and `session/chat/stream` endpoints
## [0.0.16] - 2025-03-05
### Added
- Detailed custom exceptions for better error handling
- CLAUDE.md for claude code
### Changed
- Deriver to use a new cognitive architecture that only updates on user messages
and updates user representation to apply more confidence scores to its known
facts
- Dialectic API token cutoff from 150 tokens to 300
- Dialectic API uses Claude 3.7 Sonnet
- SQLAlchemy echo changed to false by default, can be enabled with SQL_DEBUG
environment flag
### Fixed
- Self-hosting documentation and README to mention `uv` instead of `poetry`
## [0.0.15] - 2025-01-06
### Added
- Alembic for handling database migrations
- Additional indexes for reading Messages and Metamessages
- Langfuse for prompt tracing
### Changed
- API validation using Pydantic
### Fixed
- Dialectic Streaming Endpoint properly sends text in `StreamingResponse`
- Deriver Queue handles graceful shutdown
## [0.0.14] — 2024-11-14
### Changed
- Query Documents endpoint is a POST request for better DX
- `String` columns are now `TEXT` columns to match postgres best practices
- Docstrings to have better stainless generations
### Fixed
- Dialectic API to use most recent user representation
- Prepared Statements Transient Error with `psycopg`
- Queue parallel worker scheduling
## [0.0.13] — 2024-11-07
### Added
- Ability to clone session for a user to achieve more [loom-like](https://github.com/socketteer/loom/) behavior
## [0.0.12] — 2024-10-21
### Added
- GitHub Actions Testing
- Ability to disable derivations on a session using the `deriver_disabled` flag
in a session's metadata
- `/v1/` prefix to all routes
- Environment variable to control deriver workers
### Changed
- public_ids to use [NanoID](https://github.com/ai/nanoid) and internal ID to
use `BigInt`
- Dialectic Endpoint can take a list of queries
- Using `uv` for project management
- User Representations stored in a metamessage rather than using reserved
collection
- Base model for Dialectic API and Deriver is now Claude 3.5 Sonnet
- Paginated GET requests now POST requests for better developer UX
### Removed
- Mirascope Dependency
- Slowapi Dependency
- Opentelemetry Dependencies and Setup
## [0.0.11] — 2024-08-01
### Added
- `session_id` column to `QueueItem` Table
- `ActiveQueueSession` Table to track, which sessions are being actively
processed
- Queue can process multiple sessions at once
### Changed
- Sessions do not require a `location_id`
- Detailed printing using `rich`
## [0.0.10] — 2024-07-23
### Added
- Test cases for Storage API
- Sentry tracing and profiling
- Additional Error handling
### Changed
- Document API uses same embedding endpoint as deriver
- CRUD operations use one less database call by removing extra refresh
- Use database for timestampz rather than API
- Pydantic schemas to use modern syntax
### Fixed
- Deriver queue resolution
## [0.0.9] — 2024-05-16
### Added
- Deriver to docker compose
- Postgres based Queue for background jobs
### Changed
- Deriver to use a queue instead of supabase realtime
- Using mirascope instead of langchain
### Removed
- Legacy SDKs in preference for stainless SDKs
## [0.0.8] — 2024-05-09
### Added
- Documentation to OpenAPI
- Bearer token auth to OpenAPI routes
- Get by ID routes for users and collections
- [NodeJS](https://github.com/plastic-labs/honcho-node) SDK support
### Changed
- Authentication Middleware now implemented using built-in FastAPI Security
module
- Get by name routes for users and collections now include "name" in slug
- Python SDK moved to separate [repository](https://github.com/plastic-labs/honcho-python)
### Fixed
- Error reporting for methods with integrity errors due to unique key
constraints
## [0.0.7] — 2024-04-01
### Added
- Authentication Middleware Interface
## [0.0.6] — 2024-03-21
### Added
- Full docker-compose for API and Database
### Fixed
- API Response schema removed unnecessary fields
- OTEL logging to properly work with async database engine
- `fly.toml` default settings for deriver set `auto_stop=false`
### Changed
- Refactored API server into multiple route files
## [0.0.5] — 2024-03-14
### Added
- Metadata to all data primitives (Users, Sessions, Messages, etc.)
- Ability to filter paginated GET requests by JSON filter based on metadata
- Optional Sentry error monitoring
- Optional Opentelemetry logging
- Dialectic API to interact with honcho agent and get insights about users
- Automatic Fact Derivation Script for automatically generating simple memory
### Changed
- API Server now uses async methods to make use of benefits of FastAPI
## [0.0.4] — 2024-02-22
### Added
- apps table with a relationship to the users table
- users table with a relationship to the collections and sessions tables
- Reverse Pagination support to get recent messages, sessions, etc. more easily
- Linting Rules
### Changed
- Get sessions method returns all sessions including inactive
- using timestampz instead of timestamp
## [0.0.3] — 2024-02-15
### Added
- Collections table to reference a collection of embedding documents
- Documents table to hold vector embeddings for RAG workflows
- Local scripts for running a postgres database with pgvector installed
- OpenAI Dependency for embedding models
- PGvector dependency for vector db support
### Changed
- session_data is now metadata
- session_data is a JSON field used python `dict` for compatibility
## [0.0.2] — 2024-02-01
### Added
- Pagination for requests via `fastapi_pagination`
- Metamessages
- `get_message` routes
- `created_at` field added to each Table
- Message size limits
### Changed
- IDs are now UUIDs
- default rate limit now 100 requests per minute
### Removed
- Removed messages from session response model
## [0.0.1] — 2024-02-01
### Added
- Rate limiting of 10 requests for minute
- Application level scoping

299
CLAUDE.md Normal file
View File

@ -0,0 +1,299 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Honcho Overview
## What is Honcho?
Honcho is an infrastructure layer for building AI agents with memory and social cognition. Its primary purposes include:
- Imbuing agents with a sense of identity
- Personalizing user experiences through understanding user psychology
- Providing a Chat Endpoint (the Dialectic agent) that injects personal context just-in-time
- Supporting development of LLM-powered applications that adapt to end users
- Enabling multi-peer sessions where multiple participants (users or agents) can interact
Honcho leverages the inherent reasoning capabilities of LLMs to build coherent models of user psychology over time, enabling more personalized and effective AI interactions.
## Core Concepts
### Peer Paradigm
Honcho uses a peer-based model where both users and agents are represented as "peers". This unified approach enables:
- Multi-participant sessions with mixed human and AI agents
- Configurable observation settings (which peers observe which others)
- Flexible identity management for all participants
### Key Primitives
- **Workspace** (formerly App): The root organizational unit containing all resources
- **Peer** (formerly User): Any participant in the system (human or AI)
- **Session**: A conversation context that can involve multiple peers
- **Message**: Data units that can represent communication between peers OR arbitrary data ingested by a peer to enhance its global representation
- **Collections & Documents**: Internal vector storage for peer representations. Collections are keyed by `(observer, observed)` peer pairs. Collections/Documents are not directly exposed via API, but the observations stored within them are exposed as **Conclusions** (see `/v3/.../conclusions` endpoints).
## Architecture Overview
### API Structure
All API routes follow the pattern: `/v3/{resource}/{id}/{action}`. Most "list/search" endpoints are `POST` so they can accept rich filter bodies.
- **Workspaces**: Create, list, update, search
- **Peers**: Create, list, update, chat (dialectic), messages, representation
- **Sessions**: Create, list, update, delete, clone, manage peers, get context
- **Messages**: Create (batch up to 100), upload (file), list, get, update
- **Conclusions**: Create, list, query (semantic search), delete — the API-facing name for observations stored in `(observer, observed)` collections
- **Keys**: Create scoped JWTs
- **Webhooks**: Register endpoint, list, delete, test
### Key Features
#### Chat Endpoint (Dialectic agent) (`/peers/{peer_id}/chat`)
- Provides bespoke responses informed by the representation
- Integrates long-term facts from vector storage
- Supports streaming responses
- Configurable LLM providers
#### Message Processing Pipeline
1. Messages created via API (batch or single)
2. Enqueued for background processing:
- `representation`: Update peer's context
- `summary`: Create session summaries
3. Session-based queue processing ensures order
4. Results stored internally in vector DB
### Configuration
- Hierarchical config: config.toml + environment variables
- Database settings with connection pooling
- Multiple LLM provider support
- Background worker (deriver) settings
- Authentication can be toggled on/off
## Development Guide
### Commands
- Setup: `uv sync`
- Run server: `uv run fastapi dev src/main.py`
- Run tests: `uv run pytest tests/`
- Run single test: `uv run pytest tests/path/to/test_file.py::test_function`
- Linting: `uv run ruff check src/`
- Typechecking: `uv run basedpyright`
- Format code: `uv run ruff format src/`
### SDK Testing
#### TypeScript SDK
**🚨 DO NOT RUN `bun test` DIRECTLY. IT WILL NOT WORK. 🚨**
The TypeScript SDK tests require a running Honcho server with database and Redis. Running `bun test` alone will fail immediately because there's no server. The tests are orchestrated via pytest which handles all the infrastructure setup.
**The ONLY way to run TypeScript SDK tests:**
```bash
# From the monorepo root (not from sdks/typescript/)
uv run pytest tests/ -k typescript
```
**To type-check the TypeScript SDK (this is fine to run directly):**
```bash
cd sdks/typescript && bun run tsc --noEmit
```
### Code Style
- Follow isort conventions with absolute imports preferred
- Use explicit type hints with SQLAlchemy mapped_column annotations
- snake_case for variables/functions; PascalCase for classes
- Line length: 88 chars (Black compatible)
- Explicit error handling with appropriate exception types
- Docstrings: Use Google style docstrings
- **Never hold a DB session during external calls** (LLM, embedding, HTTP). If a function needs both a DB session and an external call result, compute the external result first and pass it as a parameter. This avoids tying up DB connections during slow network I/O. Use `tracked_db` for short-lived, DB-only operations; pass a shared session when multiple DB-only calls can reuse one connection.
### Runtime Architecture
Honcho runs as two cooperating processes that share a Postgres database and Redis cache:
- **API server** (`uv run fastapi dev src/main.py`) — handles HTTP, enqueues background work, returns immediately. Hosts the **Dialectic** agent inline (synchronous tool loop during chat requests).
- **Deriver worker** (`uv run python -m src.deriver`) — long-running queue consumer (uvloop). Runs the **Deriver**, **Summarizer**, and **Dreamer** off the queue. Can run multiple instances (`DERIVER_WORKERS`). Also hosts an in-process **Reconciler scheduler** (`src/reconciler/`) that periodically embeds messages with `sync_state='pending'` in `MessageEmbedding` and cleans up stale queue items — embedding generation is decoupled from message creation by design.
### Agent Architecture
Honcho uses several specialized LLM agents. They share tool definitions and the LLM client abstraction in `src/utils/agent_tools.py` + `src/llm/`.
> **Terminology:** what users see as **conclusions** (the public API surface and the term we use in documentation) is called **observations** in code symbols — `create_observations`, `delete_observations`, `get_observation_context`, etc. Doc prose below uses "conclusions"; references to actual code symbols stay as "observations."
#### 1. Deriver (`src/deriver/`)
**Role**: Memory formation through content ingestion.
The Deriver processes batches of incoming messages and extracts conclusions about peers. The current architecture is "minimal deriver" — a **single LLM call** per batch using structured output, not an agentic tool loop. This trades flexibility for cost and predictability.
- **Trigger**: Messages enqueued by `src/deriver/enqueue.py` on message create; consumed by `src/deriver/queue_manager.py``consumer.process_item()``deriver.process_representation_tasks_batch()`.
- **Output**: Explicit conclusions (direct facts) and deductive conclusions (inferences) saved to `(observer, observed)` collections.
- **Entry point**: `src/deriver/__main__.py``queue_manager.main()`.
- **Prompts**: `src/deriver/prompts.py` (`minimal_deriver_prompt`).
- **Custom instructions**: per-workspace/peer guidance can be threaded into the prompt via reasoning configuration; `DERIVER__MAX_CUSTOM_INSTRUCTIONS_TOKENS` caps the addition (default 2000) and `DERIVER__MAX_INPUT_TOKENS` defaults to 25000 to make room.
#### 2. Dialectic (`src/dialectic/`)
**Role**: Analysis and recall for answering queries.
The Dialectic answers questions about peers by strategically gathering context from memory. It is the only tool-using agent on the synchronous request path — it loops over `DIALECTIC_TOOLS` until it has enough context to answer. (The Dreamer specialists also use tools, but run off the queue.)
- **Trigger**: API call to `POST /v3/.../peers/{peer_id}/chat`.
- **Tools** (see `DIALECTIC_TOOLS` in `src/utils/agent_tools.py`): `search_memory`, `search_messages`, `get_observation_context`, `grep_messages`, `get_messages_by_date_range`, `search_messages_temporal`, `get_reasoning_chain`. At the `minimal` reasoning level, a reduced set (`DIALECTIC_TOOLS_MINIMAL`) is used: just `search_memory` + `search_messages`.
- **Reasoning levels**: 5 tiers — `minimal`, `low`, `medium`, `high`, `max` — each with its own model config (see `DialecticLevelSettings` in `src/config.py`).
- **Output**: Natural language response grounded in gathered context. Supports SSE streaming.
- **Entry point**: `src/dialectic/chat.py``agentic_chat()` / `agentic_chat_stream()``DialecticAgent` (in `src/dialectic/core.py`).
#### 3. Dreamer (`src/dreamer/`)
**Role**: Consolidation and self-improvement of memory.
The Dreamer is an orchestrated multi-specialist system that runs during scheduled "dreams" to consolidate conclusions and build reasoning trees.
- **Trigger**: Scheduled via `DreamScheduler` (`src/dreamer/dream_scheduler.py`) or explicit dream task on the queue.
- **Strategy**: Surprisal-based prioritization (`src/dreamer/surprisal.py`) selects which conclusions to expand. The orchestrator (`orchestrator.run_dream`) runs two specialist phases:
1. **DeductionSpecialist** (`specialists.py`) — produces deductive conclusions from explicit conclusions. Tools: `get_recent_observations`, `search_memory`, `search_messages`, `create_observations_deductive`, `delete_observations`, `update_peer_card`.
2. **InductionSpecialist** — produces inductive conclusions from explicit + deductive conclusions. Tools: same discovery set + `create_observations_inductive`, `update_peer_card`.
- **Reasoning trees** (`src/dreamer/trees/`, migration `f1a2b3c4d5e6_add_reasoning_tree_columns`): each conclusion links to its premises and downstream conclusions, enabling `get_reasoning_chain` traversal at recall time.
- **Output**: Deductive/inductive conclusions, consolidated redundancies, updated peer cards.
- **Entry point**: `src/dreamer/orchestrator.py``process_dream()` (the package-level export from `src/dreamer/__init__.py`), which wraps `run_dream()`.
#### 4. Summarizer (`src/utils/summarizer.py`)
**Role**: Two-tier session summarization (direct LLM call — no agentic tools).
- **Trigger**: Runs as part of the queue pipeline alongside representation tasks.
- **Tiers**: short summary every `SUMMARY_MESSAGES_PER_SHORT_SUMMARY` messages (default 20); long summary every `SUMMARY_MESSAGES_PER_LONG_SUMMARY` (default 60). Token caps configurable via `SUMMARY_MAX_TOKENS_SHORT` / `SUMMARY_MAX_TOKENS_LONG`.
#### Shared Agent Infrastructure
- **Tool definitions** (`src/utils/agent_tools.py`): unified `TOOLS` dict; per-agent lists (`DIALECTIC_TOOLS`, `DIALECTIC_TOOLS_MINIMAL`, `DREAMER_TOOLS`, `DEDUCTION_SPECIALIST_TOOLS`, `INDUCTION_SPECIALIST_TOOLS`).
- **LLM subsystem** (`src/llm/`): provider-agnostic `honcho_llm_call()`. Backends in `src/llm/backends/` (`anthropic.py`, `gemini.py`, `openai.py`). Includes prompt caching (`caching.py`), structured output (`structured_output.py`), tool loop (`tool_loop.py`), history adapters for cross-provider message formats, and a model registry. Per-retry provider selection is pinned via an `AttemptPlan` so stream-final retries don't bounce back to primary after the tool loop has settled on fallback.
- **Per-agent model config**: each agent has its own `MODEL_CONFIG` in `src/config.py` with fallback chains (see `ConfiguredModelSettings`, `FallbackModelSettings`).
- **Telemetry**: cloudevents in `src/telemetry/events/` cover API routes, dialectic, dream, deletion, reconciliation, representation, and per-call LLM accounting (`llm.py` — `LLMCallCompletedEvent` fires once per provider hit with full cost-attribution context). High-volume events are sampled deterministically per `run_id` via `TelemetrySettings.HIGH_VOLUME_SAMPLE_RATE`.
### Project Structure
```
src/
├── main.py # FastAPI app: middleware, routers, lifespan, exception handlers
├── models.py # SQLAlchemy ORM models (Workspace/Peer/Session/Message/
│ # MessageEmbedding/Collection/Document/QueueItem/...)
├── config.py # Pydantic-settings configuration (very large; see README)
├── db.py # Engine + session/context management (request_context var)
├── dependencies.py # FastAPI DI (tracked_db, etc.)
├── exceptions.py # Custom exception types (HonchoException + subclasses)
├── security.py # JWT authentication
├── embedding_client.py # Embedding provider client (configurable dimensions
│ # via EMBEDDING_MODEL_CONFIG__DIMENSIONS_MODE)
├── schemas/ # Pydantic schemas
│ ├── api.py # Public API request/response schemas
│ ├── configuration.py # Per-resource configuration schemas
│ └── internal.py # Internal-only schemas (queue payloads, etc.)
├── crud/ # Per-resource DB operations
│ ├── collection.py, deriver.py, document.py, message.py
│ ├── peer.py, peer_card.py, representation.py (RepresentationManager)
│ ├── session.py, webhook.py, workspace.py
├── routers/ # FastAPI route handlers (all under /v3)
│ ├── workspaces.py, peers.py (dialectic /chat lives here), sessions.py
│ ├── messages.py, conclusions.py, keys.py, webhooks.py
├── dialectic/ # Dialectic agent — runs inline per chat request
│ ├── chat.py # agentic_chat() / agentic_chat_stream()
│ ├── core.py # DialecticAgent (the tool-loop driver)
│ └── prompts.py
├── deriver/ # Background queue consumer (separate process)
│ ├── __main__.py # `python -m src.deriver` entry point
│ ├── queue_manager.py # QueueManager + main() loop
│ ├── consumer.py # process_item dispatcher (representation / deletion / reconciler)
│ ├── deriver.py # "minimal deriver" — single-LLM-call batch processor
│ ├── enqueue.py # API → queue producer
│ └── prompts.py
├── dreamer/ # Memory consolidation (runs off the queue)
│ ├── orchestrator.py # run_dream() / process_dream()
│ ├── specialists.py # DeductionSpecialist + InductionSpecialist
│ ├── dream_scheduler.py
│ ├── surprisal.py # Surprisal-based conclusion prioritization
│ └── trees/ # Reasoning-tree primitives
├── reconciler/ # In-process scheduler hosted by the deriver worker
│ ├── scheduler.py # ReconcilerScheduler (started from queue_manager.py)
│ ├── sync_vectors.py # Embeds MessageEmbedding rows with sync_state='pending'
│ └── queue_cleanup.py # Removes stale queue items
├── llm/ # Provider-agnostic LLM client subsystem
│ ├── api.py, backend.py, executor.py, runtime.py, registry.py
│ ├── caching.py, structured_output.py, tool_loop.py, conversation.py
│ ├── history_adapters.py, request_builder.py, credentials.py, types.py
│ └── backends/ # anthropic.py, gemini.py, openai.py
├── cache/ # Redis cache abstraction (cashews-backed)
│ └── client.py
├── vector_store/ # Optional external vector stores (pgvector is default,
│ │ # implemented via MessageEmbedding/Document in models+crud)
│ ├── lancedb.py
│ └── turbopuffer.py
├── telemetry/ # Observability
│ ├── emitter.py # CloudEvents emitter
│ ├── logging.py # Logging helpers + route-template extraction
│ ├── metrics_collector.py, reasoning_traces.py, sentry.py
│ ├── events/ # Event type definitions
│ └── prometheus/ # Prometheus metric definitions
├── utils/ # Cross-cutting utilities
│ ├── agent_tools.py # Tool definitions + per-agent tool lists
│ ├── summarizer.py # Two-tier session summarizer
│ ├── representation.py # Representation formatting (distinct from crud/representation.py)
│ ├── search.py, filter.py, formatting.py
│ ├── tokens.py # tiktoken-based counting
│ ├── work_unit.py, queue_payload.py
│ ├── config_helpers.py, json_parser.py, files.py
│ └── types.py
└── webhooks/ # Webhook delivery
├── events.py
└── webhook_delivery.py
```
- Tests in pytest with fixtures in tests/conftest.py; subdirs mirror src/ (`tests/deriver/`, `tests/dialectic/`, etc.) plus `tests/bench/` (perf benchmarks), `tests/integration/`, `tests/live_llm/` (gated by `--live-llm`), and `tests/unified/` (the unified runner).
- Use environment variables via python-dotenv (.env). Config precedence: env > .env > config.toml > defaults.
### Database Design
- All tables use text IDs (nanoid format) as primary keys
- Composite foreign keys for multi-tenant relationships
- Feature flags on workspace, peer, and session levels
- Token counting on messages for usage tracking
- JSONB metadata fields for extensibility
- HNSW indexes for vector similarity search
### Key Architectural Decisions
1. **Peer Paradigm**: humans and AI agents are unified as "Peers"; many-to-many with Sessions. Internal vector storage (Collections/Documents) is keyed by `(observer, observed)` peer pairs — the same mechanism powers self-representation (`observer == observed`) and cross-peer modeling.
2. **Multi-Peer Sessions**: Sessions can have multiple participants with different observation settings.
3. **API server / worker split**: API enqueues, deriver worker process consumes. Never block HTTP on LLM work. The Reconciler runs as an in-process scheduler inside the deriver, handling async embedding sync and queue cleanup.
4. **"Minimal" deriver**: memory formation is a single structured-output LLM call per batch, not an agentic tool loop. Predictable cost, lower latency. The Dialectic is the one true tool-using agent.
5. **Provider-agnostic LLM layer** (`src/llm/`): all model calls go through `honcho_llm_call()`. Backends (`anthropic`, `gemini`, `openai`) sit behind a registry; per-agent `MODEL_CONFIG` with fallback chains is resolved at call time.
6. **Dialectic reasoning tiers**: 5 levels (`minimal` → `max`); each level has its own model config and tool set (`minimal` uses a reduced toolset).
7. **Hybrid search**: Postgres FTS (GIN index on `to_tsvector('english', content)`) + vector similarity (HNSW on `MessageEmbedding.embedding`). `MessageEmbedding` is a separate table from `Message` with its own `sync_state` so embedding is decoupled from message creation.
8. **Pluggable external vector stores**: defaults to pgvector inline; can swap to turbopuffer or lancedb (`VECTOR_STORE_*` config; `src/vector_store/`).
9. **Composite-FK multi-tenancy**: `workspace_name` participates in nearly every composite FK. Cross-workspace data leakage is structurally impossible at the schema level.
10. **Scoped Authentication**: JWTs can be scoped to workspace, peer, or session level.
11. **Batch Operations**: Bulk message creation up to 100 messages per request.
12. **Session History**: Two-tier summarization — short every `SUMMARY_MESSAGES_PER_SHORT_SUMMARY` (default 20), long every `SUMMARY_MESSAGES_PER_LONG_SUMMARY` (default 60).
### Error Handling
- Custom exceptions defined in src/exceptions.py
- Use specific exception types (ResourceNotFoundException, ValidationException, etc.)
- Proper logging with context instead of print statements
- Global exception handlers defined in main.py
- See docs/contributing/error-handling.mdx for details
### Notes
- Always use `uv run` or `uv` to prefix any commands related to python to ensure you use the virtual environment

215
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,215 @@
# Contributing to Honcho
Thank you for your interest in contributing to Honcho! This guide outlines the process for contributing to the project and our development conventions.
## Getting Started
Before you start contributing, please:
1. **Set up your development environment** - Follow the [Local Development guide](./README.md#local-development) in the README to get Honcho running locally.
2. **Join our community** - Feel free to join us in our [Discord](http://discord.gg/honcho) to discuss your changes, get help, or ask questions.
3. **Review existing issues** - Check the [issues tab](https://github.com/plastic-labs/honcho/issues) to see what's already being worked on or to find something to contribute to.
## Contribution Workflow
### 1. Fork and Clone
1. Fork the repository on GitHub
2. Clone your fork locally:
```bash
git clone https://github.com/YOUR_USERNAME/honcho.git
cd honcho
```
3. Add the upstream repository as a remote:
```bash
git remote add upstream https://github.com/plastic-labs/honcho.git
```
### 2. Create a Branch
Create a new branch for your feature or bug fix:
```bash
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-bug-fix-name
```
**Branch naming conventions:**
- `feature/description` - for new features
- `fix/description` - for bug fixes
- `docs/description` - for documentation updates
- `refactor/description` - for code refactoring
- `test/description` - for adding or updating tests
### 3. Make Your Changes
- Write clean, readable code that follows our coding standards (see below)
- Add tests for new functionality
- Update documentation as needed
- Make sure your changes don't break existing functionality
### 4. Commit Your Changes
We follow conventional commit standards. Format your commit messages as:
```
type(scope): description
[optional body]
[optional footer]
```
**Types:**
- `feat`: A new feature
- `fix`: A bug fix
- `docs`: Documentation only changes
- `style`: Changes that do not affect the meaning of the code
- `refactor`: A code change that neither fixes a bug nor adds a feature
- `test`: Adding missing tests or correcting existing tests
- `chore`: Changes to the build process or auxiliary tools
**Examples:**
```bash
git commit -m "feat(api): add new dialectic endpoint for user insights"
git commit -m "fix(db): resolve connection pool timeout issue"
git commit -m "docs(readme): update installation instructions"
```
### 5. Submit a Pull Request
1. Push your branch to your fork:
```bash
git push origin your-branch-name
```
2. Create a pull request on GitHub from your branch to the `main` branch
3. Fill out the pull request template with:
- A clear description of what changes you've made
- The motivation for the changes
- Any relevant issue numbers (use "Closes #123" to auto-close issues)
- Screenshots or examples if applicable
## Pre-commit Hooks
Honcho uses pre-commit hooks to enforce code quality and consistency. They run linting, formatting, type checking, and security scans before each commit.
### Installation
```bash
uv add --dev pre-commit
uv run pre-commit install \
--hook-type pre-commit \
--hook-type commit-msg \
--hook-type pre-push
```
### What the hooks do
- **Code Quality** — Python linting and formatting (ruff), TypeScript linting (biome)
- **Type Checking** — Static analysis with basedpyright
- **Security** — Vulnerability scanning with bandit
- **Documentation** — Markdown linting and license header checks
- **Testing** — Automated test runs for Python and TypeScript
- **File Hygiene** — Trailing whitespace, line endings, file size checks
- **Commit Standards** — Conventional commit message validation
### Manual execution
Run against all files without committing:
```bash
uv run pre-commit run --all-files
```
Run a specific hook:
```bash
uv run pre-commit run ruff --all-files
uv run pre-commit run basedpyright --all-files
```
## Coding Standards
### Python Code Style
- Follow [PEP 8](https://www.python.org/dev/peps/pep-0008/) style guidelines
- Use [ruff](https://docs.astral.sh/ruff/) for linting and code formatting
- Use type hints where possible
- Write docstrings for functions and classes using Google style docstrings
### Code Organization
- Keep functions focused and single-purpose
- Use meaningful variable and function names
- Add comments for complex logic
- Follow existing patterns in the codebase
### Testing
- Write unit tests for new functionality
- Ensure existing tests pass before submitting
- Use descriptive test names that explain what is being tested
- Mock external dependencies appropriately
### Documentation
- Update relevant documentation for new features
- Include examples in docstrings where helpful
- Keep README and other docs up to date with changes
## Review Process
1. **Automated checks** - Your PR will run through automated checks including tests and linting
2. **Project maintainer review** - A project maintainer will review your code for:
- Code quality and adherence to standards
- Functionality and correctness
- Test coverage
- Documentation completeness
3. **Discussion and iteration** - You may be asked to make changes or clarifications
4. **Approval and merge** - Once approved, your PR will be merged into `main`
## Types of Contributions
We welcome various types of contributions:
- **Bug fixes** - Help us squash bugs and improve stability
- **New features** - Add functionality that benefits the community
- **Documentation** - Improve or expand our documentation
- **Tests** - Increase test coverage and reliability
- **Performance improvements** - Help make Honcho faster and more efficient
- **Examples and tutorials** - Help other developers use Honcho
## Issue Reporting
When reporting bugs or requesting features:
1. Check if the issue already exists
2. Use the appropriate issue template
3. Provide clear reproduction steps for bugs
4. Include relevant environment information
5. Be specific about expected vs actual behavior
## Questions and Support
- **General questions** - Join our [Discord](http://discord.gg/honcho)
- **Bug reports** - Use GitHub issues
- **Feature requests** - Use GitHub issues with the feature request template
- **Security issues** - Please email us privately rather than opening a public issue
## License
By contributing to Honcho, you agree that your contributions will be licensed under the same [AGPL-3.0 License](./LICENSE) that covers the project.
Thank you for helping make Honcho better! 🫡

54
Dockerfile Normal file
View File

@ -0,0 +1,54 @@
# https://pythonspeed.com/articles/base-image-python-docker-images/
# https://testdriven.io/blog/docker-best-practices/
FROM python:3.13-slim-bookworm
COPY --from=ghcr.io/astral-sh/uv:0.9.24 /uv /bin/uv
# Set Working directory
WORKDIR /app
# Enable bytecode compilation
ENV UV_COMPILE_BYTECODE=1
# Copy from the cache instead of linking since it's a mounted volume
ENV UV_LINK_MODE=copy
# Python optimizations
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Install the project's dependencies using the lockfile and settings
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project --no-group dev
# Copy only requirements to cache them in docker layer
COPY uv.lock pyproject.toml /app/
# Sync the project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-group dev
# Place executables in the environment at the front of the path
ENV PATH="/app/.venv/bin:$PATH"
ENV HOME=/app
ENV UV_CACHE_DIR=/tmp/uv-cache
# Create non-root user and set ownership
RUN addgroup --system app && adduser --system --group app && mkdir -p /tmp/uv-cache && chown -R app:app /app /tmp/uv-cache
COPY --chown=app:app src/ /app/src/
COPY --chown=app:app migrations/ /app/migrations/
COPY --chown=app:app scripts/ /app/scripts/
COPY --chown=app:app docker/ /app/docker/
COPY --chown=app:app alembic.ini /app/alembic.ini
# Copy config files - this will copy config.toml if it exists, and config.toml.example
COPY --chown=app:app config.toml* /app/
# Switch to non-root user
USER app
EXPOSE 8000
CMD ["fastapi", "run", "--host", "0.0.0.0", "src/main.py"]

661
LICENSE Normal file
View File

@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

667
README.md Normal file
View File

@ -0,0 +1,667 @@
<!-- markdownlint-disable MD033 -->
<div align="center">
<a href="https://app.honcho.dev" target="_blank">
<img src="assets/honcho.svg" alt="Honcho" width="400">
</a>
</div>
<!-- markdownlint-enable MD033 -->
---
![Static Badge](https://img.shields.io/badge/Server-3.0.7-blue)
[![PyPI version](https://img.shields.io/pypi/v/honcho-ai.svg)](https://pypi.org/project/honcho-ai/)
[![NPM version](https://img.shields.io/npm/v/@honcho-ai/sdk.svg)](https://npmjs.org/package/@honcho-ai/sdk)
[![Discord](https://img.shields.io/discord/1016845111637839922?style=flat&logo=discord&logoColor=23ffffff&label=Plastic%20Labs&labelColor=235865F2)](https://discord.gg/honcho)
**Honcho is memory infrastructure for building stateful agents that understand changing people, agents, groups, projects, and ideas over time.**
Store messages and events, let Honcho reason in the background, then query peer representations, session context, search results, or natural-language insights from any model or framework. Use it managed at [api.honcho.dev](https://api.honcho.dev) or self-host the FastAPI server yourself.
Using Honcho as your memory system will earn your agents higher retention, more trust, and help you build data moats to out-compete incumbents.
> Honcho has defined the Pareto Frontier of Agent Memory. Watch the [video](https://x.com/honchodotdev/status/2002090546521911703?s=20), check out our [evals page](https://honcho.dev/evals/), and read the [blog post](https://blog.plasticlabs.ai/research/Benchmarking-Honcho) for more detail.
## Contents
- [Start Here](#start-here)
- [Why Honcho](#why-honcho)
- [The Honcho Loop](#the-honcho-loop)
- [Quickstart](#quickstart)
- [What Honcho Gives You](#what-honcho-gives-you)
- [Integrations](#integrations)
- [Core Concepts](#core-concepts)
- [Benchmarks & Evals](#benchmarks--evals)
- [Self-hosting](#self-hosting)
- [Configuration](#configuration)
- [Architecture](#architecture)
- [SDKs](#sdks)
- [Learn More](#learn-more)
- [Contributing](#contributing)
- [License](#license)
The Honcho project is split between several repositories, with this one hosting the core service logic — implemented as a FastAPI server. Client SDKs for Python and TypeScript live in the [`sdks/`](./sdks) directory.
## Start Here
| I want to... | Path | Get started |
| -------------------------------------- | ---------------------------------------------------------- | ----------------------------- |
| Give my coding agent persistent memory | Claude Code, OpenCode, OpenClaw, Hermes, or any MCP client | [Integrations](#integrations) |
| Add memory to my product | Python or TypeScript SDK | [Quickstart](#quickstart) |
| Self-host Honcho | Docker / local development | [Self-hosting](#self-hosting) |
## Why Honcho
| Capability | What it means |
| ----------------------- | ------------------------------------------------------------------------------------ |
| Reasoning-first memory | Extracts conclusions from conversations and events, not just matching chunks. |
| Peer-centric model | Tracks users, agents, groups, projects, and ideas as entities that change over time. |
| Multi-peer perspective | Models what one peer knows about another when configured. |
| Managed or self-hosted | Use `api.honcho.dev` or run the FastAPI server yourself. |
| Agent-tool integrations | MCP, Claude Code, OpenCode, OpenClaw, Hermes, Cursor-compatible clients. |
## The Honcho Loop
1. **Store** conversations, events, documents, or tool traces as messages on a session.
2. **Reason** — Honcho processes the queue in the background and updates peer representations.
3. **Query** — ask Honcho for context, search results, peer representations, or a natural-language answer.
4. **Inject** — drop the result into any LLM call or agent framework.
Concretely: workspaces hold peers, peers participate in sessions, messages live on sessions, and Honcho builds a per-peer representation that you query through the [Chat Endpoint](https://honcho.dev/docs/v3/documentation/features/chat) or directly.
## Quickstart
Get an API key at [app.honcho.dev](https://app.honcho.dev) — when you sign up you'll be prompted to join an organization, which gets its own dedicated Honcho instance and $100 free credits. Or [self-host](#self-hosting) and run against `http://localhost:8000`.
### Python
```bash
pip install honcho-ai
# or: uv add honcho-ai
# or: poetry add honcho-ai
```
```python
import os
from honcho import Honcho
# Managed service uses api.honcho.dev by default. For self-hosted, pass
# base_url="http://localhost:8000" or set HONCHO_URL.
honcho = Honcho(
workspace_id="my-app-testing",
api_key=os.environ["HONCHO_API_KEY"],
)
# 1. Store: peers and messages on a session
alice = honcho.peer("alice")
tutor = honcho.peer("tutor")
session = honcho.session("session-1")
session.add_messages([
alice.message("Hey there — can you help me with my math homework?"),
tutor.message("Absolutely. Send me your first problem!"),
])
# 2. Reason: happens asynchronously in the background.
# 3. Query: ask Honcho what it knows, or pull prompt-ready context.
answer = alice.chat("What learning styles does the user respond to best?")
context = session.context(summary=True, tokens=10_000)
# 4. Inject: hand the context to your model of choice.
from openai import OpenAI
client = OpenAI()
completion = client.chat.completions.create(
model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
messages=context.to_openai(assistant=tutor),
)
```
### TypeScript
```bash
npm install @honcho-ai/sdk
# or: bun add @honcho-ai/sdk
```
```typescript
import { Honcho } from "@honcho-ai/sdk";
import OpenAI from "openai";
const honcho = new Honcho({
workspaceId: "my-app-testing",
apiKey: process.env.HONCHO_API_KEY,
});
const alice = await honcho.peer("alice");
const tutor = await honcho.peer("tutor");
const session = await honcho.session("session-1");
await session.addMessages([
alice.message("Hey there — can you help me with my math homework?"),
tutor.message("Absolutely. Send me your first problem!"),
]);
const answer = await alice.chat(
"What learning styles does the user respond to best?",
);
const context = await session.context({ summary: true, tokens: 10_000 });
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
model: process.env.OPENAI_MODEL ?? "gpt-4o-mini",
messages: context.toOpenAI({ assistant: tutor }),
});
```
> **Note:** background reasoning is asynchronous. Newly-added messages may take a moment to be reflected in chat/representation responses; for low-latency reads, use the [`representation`](https://honcho.dev/docs/v3/documentation/features/representation) endpoint.
## What Honcho Gives You
| Need | API |
| ---------------------------------- | --------------------------------------------------------------- |
| Save interaction history | `session.add_messages(...)` |
| Ask what Honcho knows about a peer | `peer.chat(...)` |
| Get prompt-ready context | `session.context(...).to_openai(...)` / `.to_anthropic(...)` |
| Hybrid search (BM25 + vector) | `peer.search(...)`, `session.search(...)`, `honcho.search(...)` |
| Low-latency static representations | `peer.representation(...)`, `session.representation(...)` |
| Import documents | `session.upload_file(...)` |
| Inspect background processing | `honcho.queue_status(...)` |
See the full [SDK Reference](https://honcho.dev/docs/v3/documentation/reference/sdk) and [API Reference](https://honcho.dev/docs/v3/api-reference/introduction).
## Integrations
### Claude Code
Two ways, depending on how deep you want to go:
**Plugin (richer integration — recommended for Claude Code users):**
```text
/plugin marketplace add plastic-labs/claude-honcho
/plugin install honcho@honcho
```
**Raw MCP (works in any MCP client — Cursor, Cline, Windsurf, etc.):**
```bash
claude mcp add honcho \
--transport http \
--url "https://mcp.honcho.dev" \
--header "Authorization: Bearer hch-your-key-here" \
--header "X-Honcho-User-Name: YourName"
```
Details: [Claude Code guide](https://honcho.dev/docs/v3/guides/integrations/claude-code) · [MCP guide](https://honcho.dev/docs/v3/guides/integrations/mcp).
### OpenCode
```bash
opencode plugin "@honcho-ai/opencode-honcho" --global
```
Details: [OpenCode guide](https://honcho.dev/docs/v3/guides/integrations/opencode).
### OpenClaw
```bash
openclaw plugins install @honcho-ai/openclaw-honcho
openclaw honcho setup
openclaw gateway --force
```
`openclaw honcho setup` prompts for your API key, writes the config, and optionally migrates legacy `MEMORY.md` / `USER.md` / `IDENTITY.md` files into Honcho (non-destructive — originals are never deleted). Details: [OpenClaw guide](https://honcho.dev/docs/v3/guides/integrations/openclaw).
### Hermes
```bash
hermes memory setup # select "honcho", point at api.honcho.dev or your local server
```
Details: [Hermes guide](https://honcho.dev/docs/v3/guides/integrations/hermes).
### Add Honcho to your own codebase (agent skill)
For wiring the Honcho SDK into an existing application, install the integration skill — it explores your codebase, asks about integration preferences, generates the SDK setup, and verifies it works:
```bash
npx skills add plastic-labs/honcho
```
Then invoke `/honcho-integration` in Claude Code (or `/honcho-dev:integrate` via the plugin marketplace). Details: [agentic development guide](https://honcho.dev/docs/v3/documentation/introduction/vibecoding).
### Other MCP clients
The same `claude mcp add` form (or its client-specific equivalent) works in any MCP-compatible client. See [MCP guide](https://honcho.dev/docs/v3/guides/integrations/mcp).
## Core Concepts
Honcho organises everything around **peers** — humans and AI agents alike are first-class entities. The peer model enables:
- Multi-participant sessions with mixed human and AI agents
- Configurable observation settings (which peers observe which others)
- Flexible identity management for all participants
- Support for complex multi-agent interactions
Peers exchange messages within sessions; Honcho reasons over those messages to build a representation of each peer that you can query.
- **Workspace** (formerly App): top-level container; isolates data between use cases.
- **Peer** (formerly User): any participant — human user or AI agent.
- **Session**: a conversation context; many-to-many with peers.
- **Message**: an atomic data unit (peer-to-peer communication or ingested document chunk).
What you query out of Honcho:
- **Conclusions** — what Honcho has extracted about a peer (deductive and inductive). Exposed via the [conclusions API](https://honcho.dev/docs/v3/api-reference/introduction).
- **Representations** — static, low-latency snapshots of what Honcho knows about a peer (optionally session-scoped).
- **Peer Cards** — compact identity summaries.
- **Session context / summaries** — prompt-ready bundles for long-running conversations.
<!-- markdownlint-disable MD033 -->
<details>
<summary>Internal storage (Collections &amp; Documents)</summary>
Internally, Honcho stores peer-related observations in **collections** of vector-embedded **documents**. Collections are keyed by `(observer, observed)` peer pairs — the same mechanism powers self-representation (`observer == observed`) and cross-peer modelling (peer X's understanding of peer Y). These primitives are not exposed directly; the Conclusions API is the public surface.
</details>
<!-- markdownlint-enable MD033 -->
<!-- TODO(vineeth/marketing): write the "Honcho vs RAG / vector DB / memory-only" comparison.
Audit recommendation referenced; copy intentionally deferred to avoid inventing
positioning claims unsupported by primary sources. -->
## Benchmarks &amp; Evals
Honcho's evals span LongMemEval, LoCoMo, and other long-conversation benchmarks. See the [evals page](https://honcho.dev/evals/), the [research blog post](https://blog.plasticlabs.ai/research/Benchmarking-Honcho), and the [Pareto-frontier announcement video](https://x.com/honchodotdev/status/2002090546521911703?s=20) for methodology and reproducible results.
## Self-hosting
Honcho is open source under AGPL-3.0. You can run the full server locally with Docker, then point the SDKs at `http://localhost:8000`.
### Quick start (Docker)
```bash
git clone https://github.com/plastic-labs/honcho.git
cd honcho
cp docker-compose.yml.example docker-compose.yml
cp .env.template .env # fill in LLM_GEMINI_API_KEY / LLM_ANTHROPIC_API_KEY / LLM_OPENAI_API_KEY
docker compose up
```
Then point the SDKs at it:
```python
honcho = Honcho(workspace_id="my-app-testing", base_url="http://localhost:8000")
# or: export HONCHO_URL=http://localhost:8000
```
<!-- markdownlint-disable MD033 -->
<details>
<summary>Local development without Docker</summary>
Below is a guide on setting up a local environment for running the Honcho Server without Docker.
#### Prerequisites and Dependencies
Honcho is developed using [python](https://www.python.org/) and [uv](https://docs.astral.sh/uv/).
The minimum python version is `3.10`
The minimum uv version is `0.5.0`
#### Setup
Once the dependencies are installed on the system run the following steps to get
the local project setup.
1. **Clone the repository**
```bash
git clone https://github.com/plastic-labs/honcho.git
```
2. **Enter the repository and install the python dependencies**
We recommend using a virtual environment to isolate the dependencies for Honcho
from other projects on the same system. `uv` will create a virtual environment
when you sync your dependencies in the project.
```bash
cd honcho
uv sync
```
This will create a virtual environment and install the dependencies for Honcho.
The default virtual environment will be located at `honcho/.venv`. Activate the
virtual environment via:
```bash
source honcho/.venv/bin/activate
```
3. **Set up a database**
Honcho utilizes [Postgres](https://www.postgresql.org/) for its database with
pgvector. An easy way to get started with a postgres database is to create a project
with [Supabase](https://supabase.com/)
Alternatively, a `docker-compose` template is available with a sample database configuration.
To use Docker:
```bash
cp docker-compose.yml.example docker-compose.yml
docker compose up -d database
```
4. **Edit the environment variables**
Honcho uses a `.env` file for managing runtime environment variables. A
`.env.template` file is included for convenience. Several of the configurations
are not required and are only necessary for additional logging, monitoring, and
security.
Below are the required configurations:
```env
DB_CONNECTION_URI= # Connection uri for a postgres database (with postgresql+psycopg prefix)
# LLM Provider API Keys
LLM_GEMINI_API_KEY= # API Key for Google Gemini (used for deriver, summary, and dialectic minimal/low by default)
LLM_ANTHROPIC_API_KEY= # API Key for Anthropic (used for dialectic medium/high/max and dream by default)
LLM_OPENAI_API_KEY= # API Key for OpenAI (used for embeddings when EMBED_MESSAGES=true)
```
> Note that the `DB_CONNECTION_URI` must have the prefix `postgresql+psycopg` to
> function properly. This is a requirement brought by `sqlalchemy`
The template has the additional functionality disabled by default. To ensure
that they are disabled you can verify the following environment variables are
set to false:
```env
AUTH_USE_AUTH=false
SENTRY_ENABLED=false
```
If you set `AUTH_USE_AUTH` to true you will need to generate a JWT secret. You can
do this with the following command:
```bash
python scripts/generate_jwt_secret.py
```
This will generate a JWT secret and print it to the console. You can then set
the `AUTH_JWT_SECRET` environment variable. This is required for `AUTH_USE_AUTH`:
```env
AUTH_JWT_SECRET=<generated_secret>
```
5. **Run database migrations**
With the database set up and environment variables configured, run the migrations
to create the necessary tables:
```bash
uv run alembic upgrade head
```
This will create all tables for Honcho including workspaces, peers, sessions,
messages, and the queue system.
6. **Launch Honcho**
With everything set up, you can now launch a local instance of Honcho. In addition to the database, two
components need to be running:
**Start the API server:**
```bash
uv run fastapi dev src/main.py
```
This is a development server that will reload whenever code is changed.
**Start a background worker (deriver):**
In a separate terminal, run:
```bash
uv run python -m src.deriver
```
The deriver generates representations, summaries, peer cards, and manages dreaming tasks. You can increase the number of derivers to improve runtime efficiency.
</details>
<!-- markdownlint-enable MD033 -->
Contributors: see [`CONTRIBUTING.md`](./CONTRIBUTING.md) for pre-commit setup. Deploying to Fly.io: see [Self-hosting docs → Deploying on Fly.io](https://honcho.dev/docs/v3/contributing/self-hosting#deploying-on-fly-io).
## Configuration
Honcho uses a flexible configuration system that supports both TOML files and environment variables. Configuration values are loaded in priority order: **environment variables > `.env` file > `config.toml` > defaults**.
<!-- markdownlint-disable MD033 -->
<details>
<summary>Full configuration reference</summary>
### Using config.toml
Copy the example configuration file to get started:
```bash
cp config.toml.example config.toml
```
Then modify the values as needed. The TOML file is organized into sections:
- `[app]` - Application-level settings (log level, session limits, embedding settings, namespace)
- `[db]` - Database connection and pool settings
- `[auth]` - Authentication configuration
- `[cache]` - Redis cache configuration
- `[llm]` - LLM provider API keys and general settings
- `[deriver]` - Background worker settings and representation configuration
- `[peer_card]` - Peer card generation settings
- `[dialectic]` - Chat Endpoint configuration with per-level reasoning settings
- `[summary]` - Session summarization settings
- `[dream]` - Dream processing configuration (including specialist models and surprisal settings)
- `[webhook]` - Webhook configuration
- `[metrics]` - Prometheus pull-based metrics
- `[telemetry]` - CloudEvents telemetry for analytics
- `[vector_store]` - Vector store configuration (pgvector, turbopuffer, or lancedb)
- `[sentry]` - Error tracking and monitoring settings
### Using Environment Variables
All configuration values can be overridden using environment variables. The environment variable names follow this pattern:
- `{SECTION}_{KEY}` for top-level section settings
- Use `__` inside `{KEY}` for nested settings
- Just `{KEY}` for app-level settings
Examples:
- `DB_CONNECTION_URI` - Database connection string
- `AUTH_JWT_SECRET` - JWT secret key
- `DERIVER_MODEL_CONFIG__TRANSPORT` - Transport for the background deriver
- `SUMMARY_MODEL_CONFIG__MODEL` - Summary model override
- `DIALECTIC_LEVELS__low__MODEL_CONFIG__MODEL` - Model for low reasoning level
- `LOG_LEVEL` - Application log level
- `METRICS_ENABLED` - Enable Prometheus metrics
- `TELEMETRY_ENABLED` - Enable CloudEvents telemetry
### Example
If you have this in `config.toml`:
```toml
[db]
CONNECTION_URI = "postgresql+psycopg://localhost/honcho_dev"
POOL_SIZE = 10
```
You can override just the connection URI in production:
```bash
export DB_CONNECTION_URI="postgresql+psycopg://prod-server/honcho_prod"
```
The application will use the production connection URI while keeping the pool size from config.toml.
</details>
<!-- markdownlint-enable MD033 -->
## Architecture
Honcho splits into two services: **Storage** (workspaces, peers, sessions, messages, internal collections) and **Insights** (reasoning, conclusions, representations, summaries, the chat endpoint). Storage is synchronous via the API; Insights is asynchronous via a background queue consumed by the deriver worker process.
**Key features:**
- **Rich Reasoning System** — multiple implementation methods that extract conclusions from interactions and build comprehensive representations of peers
- **Chat Endpoint** — reasoning-informed responses that integrate conclusions with current context
- **Background Processing** — asynchronous processing pipeline for expensive operations like representation updates and session summarization
- **Multi-Provider Support** — configurable LLM providers for different use cases
<!-- markdownlint-disable MD033 MD001 -->
<details>
<summary>Storage primitives in detail</summary>
Honcho contains several different primitives used for storing application and
peer data. This data is used for managing conversations, modeling peer
identity, building RAG applications, and more.
The philosophy behind Honcho is to provide a platform that is peer-centric and
easily scalable from a single user to a million.
Below is a mapping of the different primitives and their relationships.
```
Workspaces
├── Peers ←──────────────────┐
│ ├── Sessions │
│ └── (internal collections, keyed by observer/observed peer pair)
│ │
│ │
└── Sessions ←───────────────┤ (many-to-many)
├── Peers ───────────────┘
└── Messages (session-level)
```
**Relationship Details:**
- A **Workspace** contains multiple **Peers**.
- **Peers** and **Sessions** have a many-to-many relationship (peers can participate in multiple sessions, sessions can have multiple peers).
- **Messages** belong to a session and are labelled by their source peer.
- **Internal collections** of vector-embedded **documents** are keyed by `(observer, observed)` peer pairs. They are not directly exposed via the API; the observations stored in them are exposed as **Conclusions**.
Users familiar with APIs such as the OpenAI Assistants API will be familiar with
much of the mapping here.
#### Workspaces
This is the top level construct of Honcho. Developers can register different
`Workspaces` for different assistants, agents, AI enabled features, etc. It is a way to
isolate data between use cases and provide multi-tenant capabilities.
#### Peers
Within a `Workspace` everything revolves around a `Peer`. The `Peer` object
represents any participant in the system — whether human users or AI agents.
This unified model enables complex multi-participant interactions.
#### Sessions
The `Session` object represents a set of interactions between `Peers` within a
`Workspace`. Other applications may refer to this as a thread or conversation.
Sessions can involve multiple peers with configurable observation settings.
#### Messages
The `Message` represents an atomic data unit that exists at the session level:
communication between peers within a session context. All messages are labelled
by their source peer and can be processed asynchronously to update their
representations. This flexible design allows for both conversational interactions
and broader data ingestion for personality modelling.
</details>
<!-- markdownlint-enable MD033 MD001 -->
<!-- markdownlint-disable MD033 -->
<details>
<summary>Reasoning pipeline</summary>
The reasoning functionality of Honcho is built on top of the Storage service. As
`Messages` and `Sessions` are created for `Peers`, Honcho will asynchronously
reason about peer psychology to derive facts about them and store them
in reserved internal collections.
A high level summary of the pipeline is as follows:
1. Messages are created via the API.
2. Derivation tasks are enqueued for background processing, including:
- `representation`: update representations of `Peers`.
- `summary`: create summaries of `Sessions`.
3. Session-based queue processing ensures proper ordering.
4. Results are stored internally and surfaced via the Conclusions API, Representations, Peer Cards, and the Chat Endpoint.
</details>
<!-- markdownlint-enable MD033 -->
<!-- markdownlint-disable MD033 MD001 -->
<details>
<summary>Retrieving data and insights</summary>
Honcho exposes several different ways to retrieve data from the system to best
serve the needs of any given application.
#### Get Context
In long-running conversations with an LLM, the context window can fill up
quickly. To address this, Honcho provides a `context`
endpoint that returns a combination of messages, conclusions, summaries from a
session up to a provided token limit.
Use this to keep sessions going indefinitely. If you'd like to see this in action, try out [Honcho Chat](https://honcho.chat).
#### Search
There are several search endpoints that let developers query messages at the
`Workspace`, `Session`, or `Peer` level using a hybrid search strategy.
Requests can include advanced filters to further refine
the results.
#### Chat API
The flagship interface for using these insights is the [Chat Endpoint](https://honcho.dev/docs/v3/documentation/features/chat) (`POST /peers/{peer_id}/chat`). It takes natural-language requests to get data about a peer and returns reasoning-grounded responses. Examples:
- Asking Honcho for a generic or specific insight about the peer.
- Asking Honcho to hydrate a prompt with data about the peer's behaviour.
- Asking Honcho for a second opinion on how to respond.
- Getting personalised responses that incorporate long-term facts and context.
#### Representations
For low-latency use cases, Honcho provides access to a `representation` endpoint that returns a static document with insights about a peer in the context of a particular session. Use this to quickly add context to a prompt without having to wait for an LLM response.
</details>
<!-- markdownlint-enable MD033 MD001 -->
## SDKs
- **Python** — [`honcho-ai`](https://pypi.org/project/honcho-ai/) on PyPI · source in [`sdks/python/`](./sdks/python)
- **TypeScript** — [`@honcho-ai/sdk`](https://www.npmjs.com/package/@honcho-ai/sdk) on npm · source in [`sdks/typescript/`](./sdks/typescript)
SDKs are versioned independently of the server. Current SDK versions track each other; the server badge above reflects the deployed server version.
See the [SDK Reference](https://honcho.dev/docs/v3/documentation/reference/sdk) for full API surface, the [API Reference](https://honcho.dev/docs/v3/api-reference/introduction) for the raw HTTP API, and per-SDK example folders for runnable demos.
## Learn More
- [Developer documentation](https://honcho.dev/docs/) — full API surface, guides, integrations.
- [Plastic Labs blog](https://blog.plasticlabs.ai/) — design philosophy and history of the project.
## Contributing
We welcome contributions to Honcho! Please read our [Contributing Guide](./CONTRIBUTING.md) for details on our development process, coding conventions, and how to submit pull requests.
## License
Honcho is licensed under the AGPL-3.0 License. Learn more at the [License file](./LICENSE).

117
alembic.ini Normal file
View File

@ -0,0 +1,117 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
# Use forward slashes (/) also on windows to provide an os agnostic path
script_location = migrations
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
# for all available tokens
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python>=3.9 or backports.zoneinfo library.
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =
# max length of characters to apply to the "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to migrations/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator" below.
# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
# version path separator; As mentioned above, this is the character used to split
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
# Valid values for version_path_separator are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
# version_path_separator = newline
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = driver://user:pass@localhost/dbname
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
# hooks = ruff
# ruff.type = exec
# ruff.executable = %(here)s/.venv/bin/ruff
# ruff.options = --fix REVISION_SCRIPT_FILENAME
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARNING
handlers = console
qualname =
[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

27
assets/honcho.svg Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 491.02 144">
<defs>
<style>
.cls-1 {
fill: #b5daff;
}
.cls-2 {
fill: #191919;
}
</style>
</defs>
<rect class="cls-1" x="0" y="0" width="491.02" height="144" rx="70.34" ry="70.34"/>
<g>
<path class="cls-2" d="M42.36,73.11h0s0,0,0,0Z"/>
<g>
<path class="cls-2" d="M111.94,38.5v24.08h-16.9v-24.08h-21.06c-11.88,0-22.94,6.36-28.94,16.61-2.66,1.61-4.45,2.74-4.49,2.76-5.17,2.82-7.76,5.51-9.03,7.64-.99.02-1.84.75-1.98,1.75-.16,1.11.62,2.14,1.73,2.3s2.14-.62,2.3-1.73c.08-.57-.09-1.13-.42-1.55,1.87-3.02,5.83-5.51,8.25-6.83.42-.23,23.87-14.43,25.63-14.08.29.06.72.22,1.06.91.93,1.93-7.61,7.63-9.35,8.67-1.67,1-3.13,2.06-4.41,3.2-2.69,2.4-4.49,5.09-5.36,8.05-.53,1.78-2.02,6.91-5.66,8.19-.26-.57-.78-1.02-1.44-1.16-1.1-.23-2.18.48-2.41,1.58-.22,1.04.4,2.06,1.4,2.37,2.51,16.26,16.43,28.31,33.09,28.32h0s21.07.01,21.07.01v-28.16h16.9v28.16h21.07V38.5h-21.07,0ZM55.16,59.86c-.08,1.04-.32,2.24-.91,3.02-.62.82-1.79,1.52-2.84,2.01.83-1.79,2.09-3.47,3.75-5.03h0ZM73.96,83.12h-8.26c-.88,0-1.6.71-1.6,1.6,0,.44.18.84.47,1.13.29.29.69.47,1.13.47h8.26v17.37c-15.77,0-28.94-11.42-31.31-26.81.24-.18.44-.4.59-.67,3.01-1.08,5.29-2.56,7.32-9.03,1.09-.38,3.81-1.46,5.13-3.21,1.32-1.75,1.34-4.5,1.29-5.64.83-.63,1.73-1.24,2.7-1.82,5.56-3.33,11.57-7.86,10.05-11.01-.5-1.04-1.31-1.7-2.33-1.9-1.93-.38-11.56,5.06-18.73,9.32,5.94-7.87,15.32-12.63,25.31-12.63v42.82h0Z"/>
<ellipse class="cls-2" cx="63.3" cy="66.65" rx="3.45" ry="5.35"/>
<path class="cls-2" d="M187.15,40.14c4.44,2.01,7.66,5.5,9.66,10.49,2.01,4.99,3.01,12.11,3.01,21.36s-1,16.38-3.01,21.36c-2.01,4.99-5.23,8.48-9.66,10.49-4.44,2.01-10.64,3.01-18.6,3.01s-14.07-1-18.5-3.01c-4.44-2.01-7.66-5.5-9.66-10.49-2.01-4.99-3.01-12.11-3.01-21.36s1-16.38,3.01-21.36c2.01-4.99,5.23-8.48,9.66-10.49,4.44-2.01,10.6-3.01,18.5-3.01s14.16,1,18.6,3.01h0ZM162.97,54.38c-1.26,1.07-2.15,2.95-2.67,5.63s-.78,6.69-.78,11.99.26,9.31.78,11.99c.52,2.69,1.41,4.56,2.67,5.63s3.12,1.6,5.58,1.6,4.32-.53,5.58-1.6,2.15-2.95,2.67-5.63c.52-2.69.78-6.69.78-11.99s-.26-9.31-.78-11.99c-.52-2.69-1.41-4.56-2.67-5.63s-3.13-1.6-5.58-1.6-4.32.53-5.58,1.6Z"/>
<path class="cls-2" d="M265.77,98.22c0,2.4-.61,4.21-1.84,5.44s-3.04,1.84-5.44,1.84h-8.06c-2.01,0-3.54-.45-4.61-1.36s-2.25-2.49-3.54-4.76l-14.57-23.02c-1.62-2.91-3.11-6.73-4.47-11.46h-.68c.64,4.53.97,8.61.97,12.24v28.36h-19.42v-59.73c0-2.4.61-4.21,1.84-5.44s3.04-1.84,5.44-1.84h8.06c2.01,0,3.53.45,4.56,1.36,1.03.91,2.23,2.49,3.59,4.76l13.99,21.95c1.75,3.11,3.46,6.93,5.15,11.46h.68c-.58-4.92-.88-8.97-.88-12.14l-.1-27.39h19.33v59.73h0Z"/>
<path class="cls-2" d="M312.49,37.91c3.04.39,6.34,1.07,9.91,2.04l-1.55,15.34c-1.49,0-2.62-.03-3.4-.1l-17.09-.1c-2.27,0-3.98.47-5.15,1.41-1.17.94-1.98,2.59-2.43,4.95-.45,2.37-.68,5.87-.68,10.54s.23,8.18.68,10.54c.45,2.37,1.26,4.01,2.43,4.95,1.17.94,2.88,1.41,5.15,1.41,5.31,0,9.53-.05,12.67-.15s6.23-.31,9.27-.63l1.55,15.34c-3.43,1.17-6.9,1.99-10.39,2.48s-7.87.73-13.11.73c-7.51,0-13.47-1.12-17.87-3.35-4.4-2.23-7.59-5.86-9.57-10.88-1.98-5.02-2.96-11.83-2.96-20.44s.99-15.43,2.96-20.44c1.97-5.02,5.16-8.64,9.57-10.88,4.4-2.23,10.36-3.35,17.87-3.35,5.05,0,9.09.19,12.14.58h0Z"/>
<path class="cls-2" d="M387.85,38.49v67.01h-21.07v-28.17h-16.9v28.17h-21.07V38.49h21.07v24.08h16.9v-24.08s21.07,0,21.07,0Z"/>
<path class="cls-2" d="M441.99,40.14c4.44,2.01,7.66,5.5,9.66,10.49,2.01,4.99,3.01,12.11,3.01,21.36s-1,16.38-3.01,21.36c-2.01,4.99-5.23,8.48-9.66,10.49-4.44,2.01-10.64,3.01-18.6,3.01s-14.07-1-18.5-3.01c-4.44-2.01-7.66-5.5-9.66-10.49-2.01-4.99-3.01-12.11-3.01-21.36s1-16.38,3.01-21.36,5.23-8.48,9.66-10.49c4.44-2.01,10.6-3.01,18.5-3.01s14.16,1,18.6,3.01h0ZM417.81,54.38c-1.26,1.07-2.15,2.95-2.67,5.63-.52,2.69-.78,6.69-.78,11.99s.26,9.31.78,11.99c.52,2.69,1.41,4.56,2.67,5.63s3.12,1.6,5.58,1.6,4.32-.53,5.58-1.6,2.15-2.95,2.67-5.63c.52-2.69.78-6.69.78-11.99s-.26-9.31-.78-11.99c-.52-2.69-1.41-4.56-2.67-5.63s-3.13-1.6-5.58-1.6-4.32.53-5.58,1.6Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

262
config.toml.example Normal file
View File

@ -0,0 +1,262 @@
# Honcho Configuration File
# This file demonstrates all available configuration options.
# Copy this to config.toml and modify as needed.
# Environment variables will override these values.
# Application-level settings
[app]
LOG_LEVEL = "INFO"
SESSION_OBSERVERS_LIMIT = 10
GET_CONTEXT_MAX_TOKENS = 100000
MAX_FILE_SIZE = 5242880 # 5MB
MAX_MESSAGE_SIZE = 25000 # Characters
EMBED_MESSAGES = true
# LANGFUSE_HOST = "https://api.langfuse.com"
# LANGFUSE_PUBLIC_KEY = "your-public-key-here"
# COLLECT_METRICS_LOCAL = false
# LOCAL_METRICS_FILE = "metrics.jsonl"
# REASONING_TRACES_FILE = "traces.jsonl" # Path to JSONL file for reasoning traces
NAMESPACE = "honcho"
# Database settings
[db]
CONNECTION_URI = "postgresql+psycopg://postgres:postgres@localhost:5432/postgres"
SCHEMA = "public"
POOL_CLASS = "default"
POOL_PRE_PING = true
POOL_SIZE = 10
MAX_OVERFLOW = 20
POOL_TIMEOUT = 30 # seconds
POOL_RECYCLE = 300 # seconds
POOL_USE_LIFO = true
SQL_DEBUG = false
TRACING = false
# Authentication settings
[auth]
USE_AUTH = false
JWT_SECRET = "your-secret-key-here" # Must be set if USE_AUTH is true
# Sentry settings
[sentry]
ENABLED = false
DSN = ""
RELEASE = ""
ENVIRONMENT = "development"
TRACES_SAMPLE_RATE = 0.1
PROFILES_SAMPLE_RATE = 0.1
# LLM settings
[llm]
DEFAULT_MAX_TOKENS = 2500
MAX_TOOL_OUTPUT_CHARS = 10000 # Max chars for tool output (~2500 tokens)
MAX_MESSAGE_CONTENT_CHARS = 2000 # Max chars per message in tool results
# API Keys for LLM providers (set the ones you need)
# Supported transports: openai, anthropic, gemini
# Base URLs are set per-module via model_config.overrides.base_url
# Built-in text-generation defaults use openai / gpt-5.4-mini.
# Embeddings default to openai / text-embedding-3-small.
OPENAI_API_KEY = "your-api-key-here"
# ANTHROPIC_API_KEY = "your-api-key"
# GEMINI_API_KEY = "your-api-key"
# Embedding settings
[embedding]
VECTOR_DIMENSIONS = 1536
MAX_INPUT_TOKENS = 8192
MAX_TOKENS_PER_REQUEST = 300000
[embedding.model_config]
transport = "openai"
model = "text-embedding-3-small"
# Optional module-level endpoint overrides
# [embedding.model_config.overrides]
# base_url = "https://embedding-proxy.internal.example/v1"
# api_key_env = "EMBEDDING_CUSTOM_API_KEY"
# Deriver settings
[deriver]
ENABLED = true
WORKERS = 1
POLLING_SLEEP_INTERVAL_SECONDS = 1.0
STALE_SESSION_TIMEOUT_MINUTES = 5
# QUEUE_ERROR_RETENTION_SECONDS = 2592000 # 30 days
DEDUPLICATE = true
LOG_OBSERVATIONS = false
MAX_INPUT_TOKENS = 25000
MAX_CUSTOM_INSTRUCTIONS_TOKENS = 2000
WORKING_REPRESENTATION_MAX_OBSERVATIONS = 100
REPRESENTATION_BATCH_MAX_TOKENS = 1024
FLUSH_ENABLED = false # Bypass batch token threshold, process work immediately
[deriver.model_config]
transport = "openai"
model = "gpt-5.4-mini"
# temperature = 0.0
# thinking_effort = "minimal"
# thinking_budget_tokens = 1024
# max_output_tokens = 4096
# Optional module-level endpoint overrides
# transport = "openai"
# model = "my-local-model"
# [deriver.model_config.overrides]
# base_url = "https://llm.internal.example/v1"
# api_key_env = "DERIVER_CUSTOM_API_KEY"
# Optional fallback model
# [deriver.model_config.fallback]
# transport = "anthropic"
# model = "claude-haiku-4-5"
# [deriver.model_config.fallback.overrides]
# base_url = "https://llm-backup.internal.example/v1"
# api_key_env = "DERIVER_CUSTOM_BACKUP_API_KEY"
# [deriver.model_config.overrides.provider_params]
# verbosity = "low"
# Peer card settings
[peer_card]
ENABLED = true
# Dialectic settings
[dialectic]
MAX_OUTPUT_TOKENS = 8192
MAX_INPUT_TOKENS = 100000
HISTORY_TOKEN_LIMIT = 8192
SESSION_HISTORY_MAX_TOKENS = 4096
# Per-level settings for reasoning levels
# MAX_OUTPUT_TOKENS is optional per level; if not set, uses global MAX_OUTPUT_TOKENS
[dialectic.levels.minimal]
MAX_TOOL_ITERATIONS = 1
MAX_OUTPUT_TOKENS = 250
TOOL_CHOICE = "auto"
[dialectic.levels.minimal.model_config]
transport = "openai"
model = "gpt-5.4-mini"
[dialectic.levels.low]
MAX_TOOL_ITERATIONS = 5
TOOL_CHOICE = "auto"
[dialectic.levels.low.model_config]
transport = "openai"
model = "gpt-5.4-mini"
[dialectic.levels.medium]
MAX_TOOL_ITERATIONS = 2
[dialectic.levels.medium.model_config]
transport = "openai"
model = "gpt-5.4-mini"
[dialectic.levels.high]
MAX_TOOL_ITERATIONS = 4
[dialectic.levels.high.model_config]
transport = "openai"
model = "gpt-5.4-mini"
[dialectic.levels.max]
MAX_TOOL_ITERATIONS = 10
[dialectic.levels.max.model_config]
transport = "openai"
model = "gpt-5.4-mini"
# [dialectic.levels.max.model_config.fallback]
# transport = "gemini"
# model = "gemini-2.5-pro"
# Summary settings
[summary]
ENABLED = true
MESSAGES_PER_SHORT_SUMMARY = 20
MESSAGES_PER_LONG_SUMMARY = 60
MAX_TOKENS_SHORT = 1000
MAX_TOKENS_LONG = 4000
[summary.model_config]
transport = "openai"
model = "gpt-5.4-mini"
# thinking_effort = "minimal"
# thinking_budget_tokens = 1024
# [summary.model_config.fallback]
# transport = "anthropic"
# model = "claude-haiku-4-5"
# Dream settings
[dream]
ENABLED = true
DOCUMENT_THRESHOLD = 50
IDLE_TIMEOUT_MINUTES = 60
MIN_HOURS_BETWEEN_DREAMS = 8
ENABLED_TYPES = ["omni"]
MAX_TOOL_ITERATIONS = 20
HISTORY_TOKEN_LIMIT = 16384
[dream.deduction_model_config]
transport = "openai"
model = "gpt-5.4-mini"
[dream.induction_model_config]
transport = "openai"
model = "gpt-5.4-mini"
# Surprisal-based sampling subsystem
[dream.surprisal]
ENABLED = false
TREE_TYPE = "kdtree" # Options: kdtree, balltree, rptree, covertree, lsh, graph, prototype
TREE_K = 5 # k for kNN-based trees
SAMPLING_STRATEGY = "recent" # Options: recent, random, all
SAMPLE_SIZE = 200
TOP_PERCENT_SURPRISAL = 0.10 # Top 10% of observations
MIN_HIGH_SURPRISAL_FOR_REPLACE = 10
INCLUDE_LEVELS = ["explicit", "deductive"]
# Webhook settings
[webhook]
SECRET = ""
MAX_WORKSPACE_LIMIT = 10
# Prometheus metrics settings (pull-based metrics)
[metrics]
ENABLED = false
# NAMESPACE = "honcho" # Inherits from app.NAMESPACE if not set
# CloudEvents telemetry settings (analytics events)
[telemetry]
ENABLED = false
# ENDPOINT = "https://telemetry.honcho.dev/v1/events"
# HEADERS = '{"Authorization": "Bearer your-token"}' # JSON string for auth headers
BATCH_SIZE = 100
FLUSH_INTERVAL_SECONDS = 1.0
FLUSH_THRESHOLD = 50
MAX_RETRIES = 3
MAX_BUFFER_SIZE = 10000
# NAMESPACE = "honcho" # Inherits from app.NAMESPACE if not set
# Cache settings
[cache]
ENABLED = false
URL = "redis://localhost:6379/0?suppress=true"
# NAMESPACE = "honcho" # Inherits from app.NAMESPACE if not set
DEFAULT_TTL_SECONDS = 300
DEFAULT_LOCK_TTL_SECONDS = 5
# Vector store settings
[vector_store]
# Vector store type: "pgvector", "turbopuffer", or "lancedb"
TYPE = "pgvector"
# Migration flag: set to true when migration from pgvector is complete
MIGRATED = false
NAMESPACE = "honcho"
# DIMENSIONS is deprecated; embedding.vector_dimensions is authoritative.
# TURBOPUFFER_API_KEY = "your-turbopuffer-api-key"
# TURBOPUFFER_REGION = "us-east-1"
LANCEDB_PATH = "./lancedb_data"
RECONCILIATION_INTERVAL_SECONDS = 300

1
database/init.sql Normal file
View File

@ -0,0 +1 @@
CREATE EXTENSION IF NOT EXISTS vector;

142
docker-compose.yml.example Normal file
View File

@ -0,0 +1,142 @@
# Honcho Docker Compose
#
# Usage:
# cp docker-compose.yml.example docker-compose.yml
# cp .env.template .env # edit with your provider config
# docker compose up -d --build
#
# By default, ports are bound to 127.0.0.1 (localhost only).
# For development, uncomment the source mounts and monitoring services below.
services:
api:
build:
context: .
dockerfile: Dockerfile
entrypoint: ["sh", "docker/entrypoint.sh"]
depends_on:
database:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "127.0.0.1:8000:8000"
healthcheck:
test:
[
"CMD",
"/app/.venv/bin/python",
"-c",
"import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=2).read()",
]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
# -- Development: mount source for live reload --
# volumes:
# - .:/app
# - venv:/app/.venv
environment:
- DB_CONNECTION_URI=postgresql+psycopg://postgres:postgres@database:5432/postgres
- CACHE_URL=redis://redis:6379/0?suppress=true
- CACHE_ENABLED=true
env_file:
- path: .env
required: false
restart: unless-stopped
deriver:
build:
context: .
dockerfile: Dockerfile
entrypoint: ["/app/.venv/bin/python", "-m", "src.deriver"]
depends_on:
api:
condition: service_healthy
database:
condition: service_healthy
redis:
condition: service_healthy
# -- Development: mount source for live reload --
# volumes:
# - .:/app
# - venv:/app/.venv
environment:
- DB_CONNECTION_URI=postgresql+psycopg://postgres:postgres@database:5432/postgres
- CACHE_URL=redis://redis:6379/0?suppress=true
- CACHE_ENABLED=true
env_file:
- path: .env
required: false
restart: unless-stopped
database:
image: pgvector/pgvector:pg15
restart: unless-stopped
ports:
- "127.0.0.1:5432:5432"
command: ["postgres", "-c", "max_connections=200"]
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
# Allow passwordless connections from the host (port is bound to 127.0.0.1).
# Lets the local test suite and ad-hoc tools connect without supplying a
# password. Do NOT use this in production.
- POSTGRES_HOST_AUTH_METHOD=trust
- PGDATA=/var/lib/postgresql/data/pgdata
volumes:
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
- pgdata:/var/lib/postgresql/data/
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:8.2
restart: unless-stopped
ports:
- "127.0.0.1:6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli ping"]
interval: 5s
timeout: 5s
retries: 5
# -- Development: monitoring stack (uncomment to enable) --
# prometheus:
# image: prom/prometheus:v3.2.1
# ports:
# - "127.0.0.1:9090:9090"
# volumes:
# - ./docker/prometheus.yml:/etc/prometheus/prometheus.yml:ro
# - prometheus-data:/prometheus
# depends_on:
# api:
# condition: service_started
# grafana:
# image: grafana/grafana:11.4.0
# ports:
# - "127.0.0.1:3000:3000"
# environment:
# - GF_SECURITY_ADMIN_USER=admin
# - GF_SECURITY_ADMIN_PASSWORD=admin
# - GF_AUTH_ANONYMOUS_ENABLED=true
# - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
# volumes:
# - ./docker/grafana-datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml:ro
# depends_on:
# prometheus:
# condition: service_started
volumes:
pgdata:
redis-data:
# -- Development: uncomment if using source mounts --
# venv:
# prometheus-data:

8
docker/entrypoint.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/sh
set -e
echo "Running database migrations..."
/app/.venv/bin/python scripts/provision_db.py
echo "Starting API server..."
exec /app/.venv/bin/fastapi run --host 0.0.0.0 src/main.py

View File

@ -0,0 +1,9 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false

10
docker/prometheus.yml Normal file
View File

@ -0,0 +1,10 @@
global:
scrape_interval: 15s
scrape_configs:
- job_name: honcho-api
static_configs:
- targets: ["api:8000"]
- job_name: honcho-deriver
static_configs:
- targets: ["deriver:9090"]

48
docs/README.md Normal file
View File

@ -0,0 +1,48 @@
# Honcho Docs
These docs are built using Next.js via mintlify.
## Setting Up Honcho's Docs Locally
1. Clone the repository:
```
git clone git@github.com:plastic-labs/honcho.git
```
2. Navigate into the `docs` folder:
```
cd honcho/docs/
```
The docs folder contains the markdown files that make up the documentation. The majority of the files are in the pages directory. Some notable files in this folder include:
3. Verify that you have Node.js and npm installed in your system. You can check by running:
```
node --version
npm --version
```
4. If not installed, download Node.js and npm from the respective official websites.
5. Once you have Node.js and npm running, proceed to install `pnpm` - another package manager that helps to manage project dependencies:
```
npm install -g pnpm
```
6. Install the project dependencies using pnpm:
```
pnpm i
```
7. After the successful installation of the project dependencies, start the local server:
```
pnpm dev
```
Now, you should be able to view the docs on your local environment by visiting `http://localhost:3000`. You can explore the different markdown files and make changes as you see fit.

1980
docs/bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
---
title: "SDK and API Compatibility Guide"
description: "Compatibility guide for Honcho's SDKs and API"
icon: "shield-check"
---
This guide helps you match the right SDK version to your Honcho API version. Newer SDK patch versions are always backward-compatible within the same major version — install the latest patch for your range.
## Current Versions
<CardGroup cols={2}>
<Card title="TypeScript SDK" icon="js">
**Latest:** v2.1.2
```bash
npm install @honcho-ai/sdk
```
</Card>
<Card title="Python SDK" icon="python">
**Latest:** v2.1.2
```bash
pip install honcho-ai
```
</Card>
</CardGroup>
## Version Compatibility Table
| Honcho API Version | TypeScript SDK | Python SDK |
|-------------------|---------------|------------|
| v3.0.7 (Current) | v2.1.2 | v2.1.2 |
| v3.0.6 | v2.1.1 | v2.1.1 |
| v3.0.5 | v2.1.0 | v2.1.0 |
| v3.0.4 | v2.1.0 | v2.1.0 |
| v3.0.3 | v2.1.0 | v2.1.0 |
| v3.0.2 | v2.0.0+ | v2.0.0+ |
| v3.0.1 | v2.0.0+ | v2.0.0+ |
| v3.0.0 | v2.0.0+ | v2.0.0+ |
| v2.5.1 | v1.6.0 | v1.6.0 |
| v2.5.0 | v1.6.0 | v1.6.0 |
| v2.4.3 | v1.5.0 | v1.5.0 |
| v2.4.2 | v1.5.0 | v1.5.0 |
| v2.4.1 | v1.5.0 | v1.5.0 |
| v2.4.0 | v1.5.0 | v1.5.0 |
| v2.3.3 | v1.4.1 | v1.4.1 |
| v2.3.2 | v1.4.0 | v1.4.0 |
| v2.3.1 | v1.4.0 | v1.4.0 |
| v2.3.0 | v1.4.0 | v1.4.0 |
| v2.2.0 | v1.3.0 | v1.3.0 |
| v2.1.1 | v1.2.1 | v1.2.2 |
| v2.1.0 | v1.2.1 | v1.2.2 |
| v2.0.5 | v1.1.0 | v1.1.0 |
| v2.0.4 | v1.1.0 | v1.1.0 |

View File

@ -0,0 +1,950 @@
---
title: "Changelog"
icon: "clock-rotate-left"
---
Welcome to the Honcho changelog! This section documents all notable changes to the Honcho API and SDKs.
<Accordion title="How to Read This Changelog">
Each release is documented with:
- **Added**: New features and capabilities
- **Changed**: Modifications to existing functionality
- **Deprecated**: Features that will be removed in future versions
- **Removed**: Features that have been removed
- **Fixed**: Bug fixes and corrections
- **Security**: Security-related improvements
## Version Format
Honcho follows [Semantic Versioning](https://semver.org/):
- **MAJOR** version for incompatible API changes
- **MINOR** version for backwards-compatible functionality additions
- **PATCH** version for backwards-compatible bug fixes
</Accordion>
### Honcho API and SDK Changelogs
<Tabs>
<Tab title="Honcho API">
<Update label="v3.0.7 (Current)">
### Added
- New `src/llm/` package as the single owner of provider runtime: clients, backends, history adapters, tool loop, request builder, credentials, and caching policy (#459)
- New cloudevent `LLMCallCompletedEvent` (`llm.call.completed`) fires once per provider hit with full cost-attribution context: transport/provider_label, model, token counts with cache breakdown, finish_reason, outcome, retry/fallback state, duration, tool-call shape, streaming flag, and agent correlation (`run_id` + iteration) (#637)
- `RepresentationCompletedEvent` now carries `total_input_tokens` for full-trace cost attribution; per-emitter `honcho_version` injection; deterministic per-`run_id` high-volume sampler via `TelemetrySettings.HIGH_VOLUME_SAMPLE_RATE` (#637)
- Deriver custom instructions: per-workspace/peer guidance threaded into the deriver prompt with a `MAX_CUSTOM_INSTRUCTIONS_TOKENS` budget (default 2000); deriver `MAX_INPUT_TOKENS` raised 23000 → 25000 (#609)
- Configurable embedding dimensions: `EMBEDDING_MODEL_CONFIG__DIMENSIONS_MODE` (`auto`/`always`/`never`) controls whether OpenAI `dimensions=` is forwarded (#678)
- New `honcho-cli` package — Python CLI for inspecting and managing peers, sessions, and configuration against a Honcho deployment (#424)
- `HONCHO_API_URL` env var support in the MCP Worker for self-hosted deployments (#575)
- API ID `max_length` increased from 100 to 512 across `WorkspaceCreate`, `PeerCreate`, and `SessionCreate` to align with the DB schema (#684)
- `AttemptPlan` dataclass pins per-retry provider selection across stream-final retries so streaming doesn't bounce back to primary after the tool loop has settled on fallback (#459)
- Gemini JSON-schema sanitizer for `function_declarations` — strips keywords Gemini's validator rejects while preserving semantics for other backends (#459)
### Changed
- All LLM orchestration moved out of `src/utils/clients.py` into `src/llm/` with modules split by responsibility (#459)
- Default `ModelConfig` factories (deriver, summary, dreamer specialists, dialectic levels) normalized with no extra parameters set by default; operators add transport/thinking overrides explicitly (#459)
- OpenAI reasoning-model routing widened to cover `gpt-5.x` and `o1/o3/o4` — these models receive `max_completion_tokens` instead of `max_tokens` (#459)
- Peer card prompts reframed as stable identity markers; induction specialist now opts out of peer card writes so only deduction touches the card (#686)
- Vector store queries no longer fetch embedding vectors — only document metadata is returned, reducing payload size and DB load (pgvector, lancedb, turbopuffer) (#682)
- Langfuse trace metadata now includes `namespace`, `model`, and `provider` so traces can be filtered by deployment slice (#565)
- Deriver: model-aware tokenizer (replaces the previously hardcoded encoding) and explicit guard on empty message content (#647)
- Dialectic level defaults now merge correctly with per-level overrides (#656)
- Default dialectic tool choice switched to `auto` (#630)
- Vector sync given a substantial retry budget to tolerate transient embedding provider outages (#604)
- `AgentToolConclusionsDeletedEvent` payload now carries `levels` (#612)
- Turbopuffer: `InternalServerError` caught and surfaced as a warning rather than a hard failure; vector store sync errors downgraded to warnings (#561)
### Fixed
- `reverse` query parameter is now honored on the v3 workspace list, peer list, workspace-scoped session list, and peer-scoped session list. Honcho SDKs at 2.1.0+ were already sending `reverse=true` for these routes but the server silently ignored it. Ties on `created_at` now fall back to the internal nanoid `id` for stable ordering across pages (#685)
- LLM client factories now receive `base_url` from `LLMSettings` for default providers — operators pointing at OpenAI-compatible proxies via `LLM__OPENAI_BASE_URL` were previously ignored on the default path (#643, fixes #641)
- Internal N+1 query in dialectic agent tool execution — collapsed per-iteration DB lookups into a single fetch (#652)
- Dreamer threshold and time-guard semantics: count filter now includes only `documents.level == 'explicit'` (was inflating threshold via dreamer-created levels and creating a feedback loop); `last_dream_at` write relocated from enqueue to process so duplicate enqueues or failed runs no longer reset the 8-hour time guard (#573)
- Deriver: blank observations are filtered out before embedding (previously triggered noisy embedding calls and persisted empty rows) (#615)
- Surprisal module: filter format corrected from `{"level": levels}` to `{"level": {"in": levels}}` — the prior call silently returned 0 results and made the entire Surprisal phase of the Dream cycle a no-op (#581, fixes #559)
- Removed hardcoded `stop_sequences` override from Deriver `ModelConfig` (was clobbering operator-configured stop sequences) (#587)
- Embedding client: `embed()` now wraps single-string input in an array, restoring compatibility with OpenAI-compatible third-party providers that reject scalar input (#586)
- Docker Compose: deriver service startup gated on the API service healthcheck — prevents races where the deriver starts before the API has run migrations (#689)
- Docker image: `HEALTHCHECK` directive removed from the shared base image; service-level health checks now belong in each service's own configuration (#530)
- Removed strict parameter validation for thinking params on Anthropic and OpenAI transports — was rejecting valid per-transport configs (#686)
- Stream-final retries pin to the `AttemptPlan` that succeeded rather than re-running provider selection through the outer `current_attempt` ContextVar (#459)
- Gemini `cached_content` reuse keys now include `system_instruction` and `tool_config` so cache hits don't cross configurations (#459)
- CrewAI example updated for the latest CrewAI protocol (#631)
### Removed
- `src/utils/clients.py` deleted; its responsibilities are split across `src/llm/registry.py`, `src/llm/credentials.py`, and the backend-specific modules (#459)
- `HEALTHCHECK` directive from the shared Docker image (#530)
</Update>
<Update label="v3.0.6">
### Changed
- Tightened transaction scopes across search, agent tools, queue manager, and webhook delivery to minimize DB connection hold time during external operations (#525)
- Search operations refactored to two-phase pattern — external work (embeddings, LLM calls) completes before opening a transaction (#525)
- Agent tool executor performs external operations before acquiring DB sessions (#525)
- Queue manager transaction scope reduced to only the critical section (#525)
- Webhook delivery no longer holds a DB session parameter (#525)
### Fixed
- Session leakage in non-session-scoped dialectic chat calls (#526)
### Added
- Health check endpoint (`/health`) for container orchestration and load balancer probes (#510)
</Update>
<Update label="v3.0.5">
### Fixed
- explicit rollback on all transactions to force connection closed
</Update>
<Update label="v3.0.4">
### Added
- JSONB metadata validation enforces 100 key limit and max depth of 5 (#419)
### Changed
- Schemas refactored from single `schemas.py` into `schemas/api.py`, `schemas/configuration.py`, and `schemas/internal.py` with backwards-compatible re-exports (#419)
### Fixed
- Missing `deleted_at` filter on `RepresentationManager._query_documents_recent()` and `._query_documents_most_derived()` allowed soft-deleted documents to leak into the deriver's working representation (#456)
- `CleanupStaleItemsCompletedEvent` emitted spuriously when no queue item was actually deleted (#454)
- Empty JSON file uploads caused unhandled errors; now returns normalized error responses (#434)
- Memory leak: `_observation_locks` switched to `WeakValueDictionary` to prevent unbounded growth (#419)
- SQL injection in `dependencies.py`: parameterized `set_config` calls to prevent injection via request context (#419)
- NUL byte crashes: string inputs (message content, queries, peer cards) now stripped at schema level (#419)
- Filter recursion depth capped at 5 to prevent stack overflow (#419)
- Dedup-skipped observations now correctly reflected in created counts (#477)
- External vector store support for message search — routes queries through configured external vector store with oversampling and
deduplication to handle chunked embeddings (#479)
- Dialectic agent no longer holds a DB connection during LLM calls — embeddings are pre-computed before tool execution, DB sessions isolated in `extract_preferences`, `query_documents` no longer accepts a DB session parameter (#477)
</Update>
<Update label="v3.0.3">
### Added
- Consolidated session context into a single DB session with 40/60 token budget allocation between summary and messages
- Observation validation via `ObservationInput` Pydantic schema with partial-success support and batch embedding with per-observation fallback
- Peer card hard cap of 40 facts with case-insensitive deduplication and whitespace normalization
- Safe integer coercion (`_safe_int`) for all LLM tool inputs to handle non-integer values like `"Infinity"`
- Embedding pre-computation and reuse across multiple search calls in dialectic and representation flows
- Peer existence validation in dialectic chat endpoints — raises ResourceNotFoundException instead of silently failing
- Logging filter to suppress noisy `GET /metrics` access logs
- Oolong long-context aggregation benchmark (synth and real variants, 1K4M token context windows)
- MolecularBench fact quality evaluation (ambiguity, decontextuality, minimality scoring)
- CoverageBench information recall evaluation (gold fact extraction, coverage matching, QA verification)
- LoCoMo summary-as-context baseline evaluation
- Webhook delivery tests, dependency lifecycle tests, queue cleanup tests, summarizer fallback tests
- Parallel test execution via pytest-xdist with worker-specific databases
- `test_reasoning_levels.py` script for LOCOM dataset testing across reasoning levels
### Changed
- Workspace deletion is now async — returns 202 Accepted, validates no active sessions (409 Conflict), cascade-deletes in background
- Redis caching layer now stores plain-dict instead of ORM objects, with v2-prefixed keys, storage, resilient `safe_cache_set`/`safe_cache_delete` helpers, and deferred post-commit cache invalidation
- All `get_or_create_*` CRUD operations now use savepoints (`db.begin_nested()`) instead of commit/rollback for race condition prevention
- Reconciler vector sync uses direct ORM mutation instead of batch parameterized UPDATE statements
- Summarizer enforces hard word limit in prompt and creates fallback text for empty summaries with `summary_tokens = 0`
- Blocked Gemini responses (SAFETY, RECITATION, PROHIBITED_CONTENT, BLOCKLIST) now raise `LLMError` to trigger retry/backup-provider logic
- Gemini client explicitly sets `max_output_tokens` from `max_tokens` parameter
- All deriver and metrics collector logging replaced with structured `logging.getLogger(__name__)` calls
- Dreamer specialist prompts updated to enforce durable-facts-only peer cards with max 40 entries and deduplication
- `GetOrCreateResult` changed from `NamedTuple` to `dataclass` with `async post_commit()` method
- FastAPI upgraded from 0.111.0 to 0.131.0; added pyarrow dependency
- Queue status filtering to only show user-facing tasks (representation, summary, dream); excludes internal infrastructure tasks
### Fixed
- JWT timestamp bug — `JWTParams.t` was evaluated once at class definition time instead of per-instance
- Session cache invalidation on deletion was missing
- `get_peer_card()` now properly propagates `ResourceNotFoundException` instead of swallowing it
- `set_peer_card()` ensures peer exists via `get_or_create_peers()` before updating
- Backup provider failover with proper tool input type safety
- Removed `setup_admin_jwt()` from server startup
- Sentry coroutine detection switched from `asyncio.iscoroutinefunction` to `inspect.iscoroutinefunction`
### Removed
- `explicit.py` and `obex.py` benchmarks replaced by coverage.py and molecular.py
- Claude Code review automation workflow (`.github/workflows/claude.yml`)
- Coverage reporting from default pytest configuration
</Update>
<Update label="v3.0.2">
### Added
- Documentation for reasoning_level and Claude Code plugin
### Changed
- Gave dreaming sub-agents better prompting around peer card creation, tweaked overall prompts
### Fixed
- Added message-search fallback for memory search tool, necessary in fresh sessions
- Made FLUSH_ENABLED a config value
- Removed N+1 query in search_messages
</Update>
<Update label="v3.0.1">
### Fixed
- Token counting in Explicit Agent Loop
- Backwards compatibility of queue items
</Update>
<Update label="v3.0.0">
### Added
- Agentic Dreamer for intelligent memory consolidation using LLM agents
- Agentic Dialectic for query answering using LLM agents with tool use
- Reasoning levels configuration for dialectic (`minimal`, `low`, `medium`, `high`, `max`)
- Prometheus token tracking for deriver and dialectic operations
- n8n integration
- Cloud Events for auditable telemetry
- External Vector Store support for turbopuffer and lancedb with reconciliation flow
### Changed
- API route renaming for consistency
- Dreamer and dialectic now respect peer card configuration settings
- Observations renamed to Conclusions across API and SDKs
- Deriver to buffer representation tasks to normalize workloads
- Local Representation tasks to create singular QueueItems
- getContext endpoint to use `search_query` rather than force `last_user_message`
### Fixed
- Dream scheduling bugs
- Summary creation when start_message_id > end_message_id
- Cashews upgrade to prevent NoScriptError
- Memory leak in `accumulate_metric` call
### Removed
- Peer card configuration from message configuration; peer cards no longer created/updated in deriver process
</Update>
<Update label="v2.5.1">
### Fixed
- Backwards compatibility for `message_ids` field in documents to handle legacy tuple format
</Update>
<Update label="v2.5.0">
### Added
- Message level configurations
- CRUD operations for observations
- Comprehensive test cases for harness
- Peer level get_context
- Set Peer Card Method
- Manual dreaming trigger endpoint
### Changed
- Configurations to support more flags for fine-grained control of the deriver, peer cards, summaries, etc.
- Working Representations to support more fine-grained parameters
### Fixed
- File uploads to match `MessageCreate` structure
- Cache invalidation strategy
</Update>
<Update label="v2.4.3">
### Added
- Redis caching to improve DB IO
- Backup LLM provider to avoid failures when a provider is down
### Changed
- QueueItems to use standardized columns
- Improved Deduplication logic for Representation Tasks
- More finegrained metrics for representation, summary, and peer card tasks
- DB constraint to follow standard naming conventions
</Update>
<Update label="v2.4.2">
### Fixed
- Langfuse tracing to have readable waterfalls
- Alembic Migrations to match models.py
- message_in_seq correctly included in webhook payload
### Changed
- Alembic to always use a session pooler
- Statement timeout during alembic operations to 5 min
</Update>
<Update label="v2.4.1">
### Added
- Alembic migration validation test suite
### Fixed
- Alembic migrations to batch changes
- Batch message creation sequence number
### Changed
- Logging infrastructure to remove noisy messages
- Sentry integration is centralized
</Update>
<Update label="v2.4.0">
### Added
- Unified `Representation` class
- vllm client support
- Periodic queue cleanup logic
- WIP Dreaming Feature
- LongMemEval to Test Bench
- Prometheus Client for better Metrics
- Performance metrics instrumentation
- Error reporting to deriver
- Workspace Delete Method
- Multi-db option in test harness
### Changed
- Working Representations are Queried on the fly rather than cached in metadata
- EmbeddingStore to RepresentationFactory
- Summary Response Model to use public_id of message for cutoff
- Semantic across codebase to reference resources based on `observer` and `observed`
- Prompts for Deriver & Dialectic to reference peer_id and add examples
- `Get Context` route returns peer card and representation in addition to messages and summaries
- Refactoring logger.info calls to logger.debug where applicable
### Fixed
- Gemini client to use async methods
</Update>
<Update label="v2.3.3">
### Changed
- Deriver Rollup Queue processes interleaved messages for more context
### Fixed
- Dialectic Streaming to follow SSE conventions
- Sentry tracing in the deriver
</Update>
<Update label="v2.3.2">
### Added
- Get peer cards endpoint (`GET /v2/peers/{peer_id}/card`) for retrieving targeted peer context information
### Changed
- Replaced Mirascope dependency with small client implementation for better control
- Optimized deriver performance by using joins on messages table instead of storing token count in queue payload
- Database scope optimization for various operations
- Batch representation task processing for ~10x speed improvement in practice
### Fixed
- Separated clean and claim work units in queue manager to prevent race conditions
- Skip locked ActiveQueueSession rows on delete operations
- Langfuse SDK integration updates for compatibility
- Added configurable maximum message size to prevent token overflow in deriver
- Various minor bugfixes
</Update>
<Update label="v2.3.1">
### Fixed
- Added max message count to deriver in order to not overflow token limits
</Update>
<Update label="v2.3.0">
### Added
- `getSummaries` endpoint to get all available summaries for a session directly
- Peer Card feature to improve context for deriver and dialectic
### Changed
- Session Peer limit to be based on observers instead, renamed config value to
`SESSION_OBSERVERS_LIMIT`
- `Messages` can take a custom timestamp for the `created_at` field, defaulting
to the current time
- `get_context` endpoint returns detailed `Summary` object rather than just
summary content
- Working representations use a FIFO queue structure to maintain facts rather
than a full rewrite
- Optimized deriver enqueue by prefetching message sequence numbers (eliminates N+1 queries)
### Fixed
- Deriver uses `get_context` internally to prevent context window limit errors
- Embedding store will truncate context when querying documents to prevent embedding
token limit errors
- Queue manager to schedule work based on available works rather than total
number of workers
- Queue manager to use atomic db transactions rather than long lived transaction
for the worker lifecycle
- Timestamp formats unified to ISO 8601 across the codebase
- Internal get_context method's cutoff value is exclusive now
</Update>
<Update label="v2.2.0">
### Added
- Arbitrary filters now available on all search endpoints
- Search combines full-text and semantic using reciprocal rank fusion
- Webhook support (currently only supports queue_empty and test events, more to come)
- Small test harness and custom test format for evaluating Honcho output quality
- Added MCP server and documentation for it
### Changed
- Search has 10 results by default, max 100 results
- Queue structure generalized to handle more event types
- Summarizer now exhaustive by default and tuned for performance
### Fixed
- Resolve race condition for peers that leave a session while sending messages
- Added explicit rollback to solve integrity error in queue
- Re-introduced Sentry tracing to deriver
- Better integrity logic in get_or_create API methods
</Update>
<Update label="v2.1.2">
### Fixed
- Summarizer module to ignore empty summaries and pass appropriate one to get_context
- Structured Outputs calls with OpenAI provider to pass strict=True to Pydantic Schema
</Update>
<Update label="v2.1.1">
### Added
- Test harness for custom Honcho evaluations
- Better support for session and peer aware dialectic queries
- Langfuse settings
- Added recent history to dialectic prompt, dynamic based on new context window size setting
### Fixed
- Summary queue logic
- Formatting of logs
- Filtering by session
- Peer targeting in queries
### Changed
- Made query expansion in dialectic off by default
- Overhauled logging
- Refactor summarization for performance and code clarity
- Refactor queue payloads for clarity
</Update>
<Update label="v2.1.0">
### Added
- File uploads
- Brand new "ROTE" deriver system
- Updated dialectic system
- Local working representations
- Better logging for deriver/dialectic
- Deriver Queue Status no longer has redundant data
### Fixed
- Document insertion
- Session-scoped and peer-targeted dialectic queries work now
- Minor bugs
### Removed
- Peer-level messages
### Changed
- Dialectic chat endpoint takes a single query
- Rearranged configuration values (LLM, Deriver, Dialectic, History->Summary)
</Update>
<Update label="v2.0.5">
### Fixed
- Groq API client to use the Async library
</Update>
<Update label="v2.0.4">
### Fixed
- Migration/provision scripts did not have correct database connection arguments, causing timeouts
</Update>
<Update label="v2.0.3">
### Fixed
- Bug that causes runtime error when Sentry flags are enabled
</Update>
<Update label="v2.0.2">
### Fixed
- Database initialization was misconfigured and led to provision_db script failing: switch to consistent working configuration with transaction pooler
</Update>
<Update label="v2.0.1">
### Added
- Ergonomic SDKs for Python and TypeScript (uses Stainless underneath)
- Deriver Queue Status endpoint
- Complex arbitrary filters on workspace/session/peer/message
- Message embedding table for full semantic search
### Changed
- Overhauled documentation
- BasedPyright typing for entire project
- Resource filtering expanded to include logical operators
### Fixed
- Various bugs
- Use new config arrangement everywhere
- Remove hardcoded responses
</Update>
<Update label="v2.0.0">
### Added
- Ability to get a peer's working representation
- Metadata to all data primitives (Workspaces, Peers, Sessions, Messages)
- Internal metadata to store Honcho's state no longer exposed in API
- Batch message operations and enhanced message querying with token and message count limits
- Search and summary functionalities scoped by workspace, peer, and session
- Session context retrieval with summaries and token allocatio
- HNSW Index for Documents Table
- Centralized Configuration via Environment Variables or config.toml file
### Changed
- New architecture centered around the concept of a "peer" replaces the former
"app"/"user"/"session" paradigm
- Workspaces replace "apps" as top-level namespace
- Peers replace "users"
- Sessions no longer nested beneath peers and no longer limited to a single
user-assistant model. A session exists independently of any one peer and
peers can be added to and removed from sessions.
- Dialectic API is now part of the Peer, not the Session
- Dialectic API now allows queries to be scoped to a session or "targeted"
to a fellow peer
- Database schema migrated to adopt workspace/peer/session naming and structure
- Authentication and JWT scopes updated to workspace/peer/session hierarchy
- Queue processing now works on 'work units' instead of sessions
- Message token counting updated with tiktoken integration and fallback heuristic
- Queue and message processing updated to handle sender/target and task types for multi-peer scenarios
### Fixed
- Improved error handling and validation for batch message operations and metadata
- Database Sessions to be more atomic to reduce idle in transaction time
### Removed
- Metamessages removed in favor of metadata
- Collections and Documents no longer exposed in the API, solely internal
- Obsolete tests for apps, users, collections, documents, and metamessages
---
</Update>
<Update label="v1.1.0">
### Added
- Normalize resources to remove joins and increase query performance
- Query tracing for debugging
### Changed
- `/list` endpoints to not require a request body
- `metamessage_type` to `label` with backwards compatibility
- Database Provisioning to rely on alembic
- Database Session Manager to explicitly rollback transactions before closing
the connection
### Fixed
- Alembic Migrations to include initial database migrations
- Sentry Middleware to not report Honcho Exceptions
</Update>
<Update label="v1.0.0">
### Added
- JWT based API authentication
- Configurable logging
- Consolidated LLM Inference via `ModelClient` class
- Dynamic logging configurable via environment variables
### Changed
- Deriver & Dialectic API to use Hybrid Memory Architecture
- Metamessages are not strictly tied to a message
- Database provisioning is a separate script instead of happening on startup
- Consolidated `session/chat` and `session/chat/stream` endpoints
</Update>
## Previous Releases
For a complete history of all releases, see our [GitHub Releases](https://github.com/plastic-labs/honcho/tags) page.
</Tab>
<Tab title="Python SDK">
[Python SDK](https://pypi.org/project/honcho-ai/)
<Update label="v2.1.2 (Current)">
### Added
- `page`, `size`, and `reverse` pagination parameters on `Honcho.workspaces()` and `HonchoAio.workspaces()`, closing the gap from 2.1.0 which added these to other list methods but not to `workspaces()`. Honoring `reverse` on the workspace/peer/session list routes also requires a Honcho server with the matching API fix; older servers silently ignore the parameter.
- `peers` parameter on `Honcho.session()` and `HonchoAio.session()` — attach peers to a session at creation time instead of needing a follow-up `session.add_peers()` call. Accepts the same shapes as `Session.add_peers` (peer ID string, `Peer` object, list of either, or tuples with `SessionPeerConfig`).
### Changed
- `WorkspaceCreateParams`, `PeerCreateParams`, and `SessionCreateParams` now accept IDs up to 512 characters (was 100), matching the server-side schema change in Honcho v3.0.7.
</Update>
<Update label="v2.1.1">
### Fixed
- Broadened HTTP retry logic to cover `httpx.NetworkError` and `httpx.RemoteProtocolError` in addition to `httpx.TimeoutException` and `httpx.ConnectError`, improving resilience against transient network failures
</Update>
<Update label="v2.1.0">
### Added
- `created_at` property on `Peer` and `Session` objects
- `is_active` property on `Session` objects
- `get_message(message_id)` method on `Session` (sync and async) to fetch a single message by ID
- `page`, `size`, and `reverse` pagination parameters on all list methods
### Changed
- **Breaking**: `peer()` and `session()` now always make a get-or-create API call — no more lazy initialization
- Response configuration models now tolerate unknown fields from newer servers for forward compatibility
### Fixed
- Sync and async `Session.get_metadata()`, `get_configuration()`, and `refresh()` now refresh cached `created_at` and `is_active` values along with metadata and configuration
- `honcho.__version__` now derives from package metadata, with a source-checkout fallback, so it stays aligned with released package versions
</Update>
<Update label="v2.0.2">
### Changed
- All input models now reject unknown fields via strict Pydantic validation (`extra="forbid"`). Previously, misspelled or extraneous fields were silently ignored. Now a `ValidationError` is raised with the unrecognized field name.
</Update>
<Update label="v2.0.1">
### Added
- `set_peer_card` method
### Changed
- `card` is now `get_card` with `card` kept for backwards compatibility and marked as deprecated
</Update>
<Update label="v2.0.0">
### Added
- `ConclusionScope` object for CRUD operations on conclusions (renamed from observations)
- Representation configuration support
### Changed
- Observations renamed to Conclusions across the SDK
- Major SDK refactoring and cleanup
- Simplified method signatures throughout
- Representation endpoints now return `string` instead of old Representation object
### Removed
- Standalone types module (now uses honcho-core types)
- Representation object
</Update>
<Update label="v1.6.0">
### Added
- metadata and configuration fields to Workspace, Peer, Session, and Message objects
- Session Clone methods
- Peer level get_context method
- `ObservationScope` object to perform CRUD operations on observations
- Representation object for WorkingRepresentations
### Changed
- methods that take IDs, can all optionally take an object of the same type
</Update>
<Update label="v1.5.0">
### Added
- Delete workspace method
### Changed
- message_id of `Summary` model is a string nanoid
- Get Context can return Peer Card & Peer Representation
</Update>
<Update label="v1.4.1">
### Added
- Get Peer Card method
- Update Message metadata method
- Session level deriver status methods
- Delete session message
### Fixed
- Dialectic Stream returns Iterators
- Type warnings
### Changed
- Pagination class to match core implementation
</Update>
<Update label="v1.4.0">
### Added
- getSummaries API returning structured summaries
- Webhook support
### Changed
- Messages can take an optional `created_at` value, defaulting to the current
time (UTC ISO 8601)
</Update>
<Update label="v1.2.2">
### Added
- Filter parameter to various endpoints
</Update>
<Update label="v1.2.1">
### Fixed
- Honcho util import paths
</Update>
<Update label="v1.2.0">
### Added
- Get/poll deriver queue status endpoints added to workspace
- Added endpoint to upload files as messages
### Removed
- Removed peer messages in accordance with Honcho 2.1.0
### Changed
- Updated chat endpoint to use singular `query` in accordance with Honcho 2.1.0
</Update>
<Update label="v1.1.0">
### Fixed
- Properly handle AsyncClient
</Update>
</Tab>
<Tab title="TypeScript SDK">
[TypeScript SDK](https://www.npmjs.com/package/@honcho-ai/sdk)
<Update label="v2.1.2 (Current)">
### Added
- `peers` option on `Honcho.session()` — attach peers to a session at creation time instead of needing a follow-up `session.addPeers()` call. Accepts the same `PeerAddition` shape as `session.addPeers()` (peer ID strings, `Peer` objects, arrays of either, or a record with per-peer `observe_me`/`observe_others` config).
### Changed
- ID validation in `validation.ts` now accepts workspace, peer, and session IDs up to 512 characters (was 100), matching the server-side schema change in Honcho v3.0.7.
### Fixed
- `Honcho.workspaces()` now actually forwards the `reverse` option to the server. The 2.1.0 changelog listed `workspaces()` among the list methods that gained `reverse`, but `client.ts` was missing the field on the params type and request builder, so the option was silently dropped. Honoring `reverse` on the workspace/peer/session list routes also requires a Honcho server with the matching API fix; older servers silently ignore the parameter.
</Update>
<Update label="v2.1.1">
### Fixed
- Broadened fetch error retry logic to catch all `TypeError` network failures (connection resets, DNS errors, etc.) instead of only those with `'fetch'` in the message, improving resilience across runtimes (Node, Bun, browsers)
</Update>
<Update label="v2.1.0">
### Added
- `createdAt` property on `Peer` and `Session` wrapper objects
- `isActive` property on `Session` wrapper objects
- `getMessage(messageId)` method on `Session` to fetch a single message by ID
- `Peer.representation()`, `Session.representation()`, and `Session.context()` now accept `Message` objects for `searchQuery`
- `page`, `size`, and `reverse` pagination controls on all list methods
### Changed
- **Breaking**: `searchQuery` removed from top-level `context()` options — use `representationOptions.searchQuery` instead:
```typescript
// Before (v2.0.x)
await session.context({ searchQuery: "..." });
// After (v2.1.0)
await session.context({ representationOptions: { searchQuery: "..." } });
```
- List methods (`peers()`, `sessions()`, `messages()`, `workspaces()`) support both the new options object and the legacy raw-filter form
- Representation search options now accept strings and content-like objects, including `Message` instances, while rejecting whitespace-only or invalid runtime inputs
- **Breaking**: `peer()` and `session()` now always make a get-or-create API call — no more lazy initialization. If you relied on constructing SDK objects without triggering a network request, note that every `peer()` and `session()` call now hits the API:
```typescript
// Before (v2.0.x) — no API call
const session = honcho.session("my-session");
// After (v2.1.0) — makes a get-or-create API call
const session = await honcho.session("my-session");
```
- Response configuration models now tolerate unknown fields from newer servers for forward compatibility
- Moved `@types/node` from `dependencies` to `devDependencies`
### Fixed
- `uploadFile()` now rejects unsupported top-level binary/object inputs and only validates inputs the serializer can actually upload
- `uploadFile()` now serializes message configuration using API field names, matching `addMessages()`
- Session fetch methods now refresh cached `createdAt` and `isActive` values alongside metadata and configuration
</Update>
<Update label="v2.0.2">
### Changed
- Client constructor now rejects unknown options via `.strict()` Zod validation. Previously, misspelled options (e.g., `baseUrl` instead of `baseURL`) were silently ignored, causing the SDK to fall back to defaults. Now a `ZodError` is thrown with the unrecognized key name.
- All input schemas now use `.strict()` validation to reject unknown fields.
- `FileUploadSchema.configuration` now uses `MessageConfigurationSchema` instead of open record type.
### Fixed
- README example used `baseUrl` instead of `baseURL`.
</Update>
<Update label="v2.0.1">
### Added
- `setPeerCard` method
### Changed
- `card` is now `getCard` with `card` kept for backwards compatibility and marked as deprecated
</Update>
<Update label="v2.0.0">
### Added
- `ConclusionScope` object for CRUD operations on conclusions (renamed from observations)
- Representation configuration support
### Changed
- Observations renamed to Conclusions across the SDK
- Major SDK refactoring and cleanup
- Simplified method signatures throughout
- Representation endpoints now return `string` instead of old Representation object
### Fixed
- Pagination `this` binding issue
### Removed
- Representation object
- Stainless "core" SDK -- this SDK is now standalone
</Update>
<Update label="v1.6.0">
### Added
- metadata and configuration fields to Workspace, Peer, Session, and Message objects
- Session Clone methods
- Peer level get_context method
- `ObservationScope` object to perform CRUD operations on observations
- Representation object for WorkingRepresentations
### Changed
- methods that take IDs, can all optionally take an object of the same type
</Update>
<Update label="v1.5.0">
### Added
- Delete workspace method
### Changed
- message_id of `Summary` model is a string nanoid
- Get Context can return Peer Card & Peer Representation
</Update>
<Update label="v1.4.1">
### Added
- Get Peer Card method
- Update Message metadata method
- Session level deriver status methods
- Delete session message
### Fixed
- Dialectic Stream returns Iterators
- Type warnings
### Changed
- Pagination class to match core implementation
</Update>
<Update label="v1.4.0">
### Added
- getSummaries API returning structured summaries
- Webhook support
### Changed
- Messages can take an optional `created_at` value, defaulting to the current
time (UTC ISO 8601)
</Update>
<Update label="v1.2.1">
### Added
- linting via Biome
- Adding filter parameter to various endpoints
### Fixed
- Order of parameters in `getSessions` endpoint
</Update>
<Update label="v1.2.0">
### Added
- Get/poll deriver queue status endpoints added to workspace
- Added endpoint to upload files as messages
### Removed
- Removed peer messages in accordance with Honcho 2.1.0
### Changed
- Updated chat endpoint to use singular `query` in accordance with Honcho 2.1.0
</Update>
<Update label="v1.1.0">
### Fixed
- Create default workspace on Honcho client instantiation
- Simplified Honcho client import path
</Update>
</Tab>
</Tabs>
## Getting Help
If you encounter issues using the Honcho API or its SDKs:
1. Open an issue on [GitHub](https://github.com/plastic-labs/honcho/issues)
2. Join our [Discord community](http://discord.gg/honcho) for support

596
docs/docs.json Normal file
View File

@ -0,0 +1,596 @@
{
"$schema": "https://mintlify.com/docs.json",
"theme": "mint",
"name": "Honcho",
"redirects": [
{
"source": "/",
"destination": "/v3/documentation/introduction/overview"
},
{
"source": "/v3/guides/integrations/claudecode",
"destination": "/v3/guides/integrations/claude-code"
}
],
"colors": {
"primary": "#66AAFF",
"dark": "#151E27",
"light": "#86BCF2"
},
"favicon": "/favicon.svg",
"contextual": {
"options": ["copy", "view", "chatgpt", "claude"]
},
"navigation": {
"versions": [
{
"version": "v3.0.7",
"api": {
"openapi": ["v3/openapi.json"]
},
"tabs": [
{
"tab": "Documentation",
"groups": [
{
"group": "Introduction",
"pages": [
"v3/documentation/introduction/overview",
"v3/documentation/introduction/quickstart",
"v3/documentation/introduction/vibecoding"
]
},
{
"group": "Core Concepts",
"pages": [
"v3/documentation/core-concepts/architecture",
"v3/documentation/core-concepts/reasoning",
"v3/documentation/core-concepts/representation",
"v3/documentation/core-concepts/design-patterns"
]
},
{
"group": "Features",
"pages": [
"v3/documentation/features/storing-data",
"v3/documentation/features/get-context",
"v3/documentation/features/chat",
{
"group": "Advanced",
"pages": [
"v3/documentation/features/advanced/overview",
"v3/documentation/features/advanced/reasoning-configuration",
"v3/documentation/features/advanced/summarizer",
"v3/documentation/features/advanced/peer-card",
"v3/documentation/features/advanced/representation-scopes",
"v3/documentation/features/advanced/dreaming",
"v3/documentation/features/advanced/queue-status",
"v3/documentation/features/advanced/search",
"v3/documentation/features/advanced/using-filters",
"v3/documentation/features/advanced/streaming-response",
"v3/documentation/features/advanced/file-uploads"
]
}
]
},
{
"group": "Reference",
"pages": [
"v3/documentation/reference/platform",
"v3/documentation/reference/sdk",
"v3/documentation/reference/cli"
]
}
]
},
{
"tab": "Guides",
"groups": [
{
"group": "Overview",
"pages": ["v3/guides/overview"]
},
{
"group": "Integrations",
"pages": [
"v3/guides/integrations/claude-code",
"v3/guides/integrations/opencode",
"v3/guides/integrations/vercel-ai-sdk",
"v3/guides/integrations/crewai",
"v3/guides/integrations/langgraph",
"v3/guides/integrations/mcp",
"v3/guides/integrations/n8n",
"v3/guides/integrations/openclaw",
"v3/guides/integrations/hermes",
"v3/guides/integrations/zo-computer",
"v3/guides/integrations/paperclip",
"v3/guides/integrations/sillytavern"
]
},
{
"group": "Tutorials",
"pages": [
"v3/guides/discord",
"v3/guides/granola",
"v3/guides/telegram",
"v3/guides/integrations/reachy-mini",
"v3/guides/gmail"
]
},
{
"group": "Community Integrations",
"pages": [
"v3/guides/community/agent0",
"v3/guides/community/pi-honcho-memory"
]
},
{
"group": "Migrations",
"pages": ["v3/guides/migrations/mem0"]
}
]
},
{
"tab": "Open Source",
"groups": [
{
"group": "Self-Hosting",
"pages": [
"v3/contributing/self-hosting",
"v3/contributing/configuration",
"v3/contributing/changing-embeddings",
"v3/contributing/troubleshooting"
]
},
{
"group": "Contributing",
"pages": [
"v3/contributing/guidelines",
"v3/contributing/license"
]
}
]
},
{
"tab": "API Reference",
"groups": [
{
"group": "API Documentation",
"pages": ["v3/api-reference/introduction"]
},
{
"group": "workspaces",
"pages": [
"v3/api-reference/endpoint/workspaces/get-or-create-workspace",
"v3/api-reference/endpoint/workspaces/get-all-workspaces",
"v3/api-reference/endpoint/workspaces/update-workspace",
"v3/api-reference/endpoint/workspaces/delete-workspace",
"v3/api-reference/endpoint/workspaces/search-workspace",
"v3/api-reference/endpoint/workspaces/get-queue-status",
"v3/api-reference/endpoint/workspaces/schedule-dream"
]
},
{
"group": "peers",
"pages": [
"v3/api-reference/endpoint/peers/get-peers",
"v3/api-reference/endpoint/peers/get-or-create-peer",
"v3/api-reference/endpoint/peers/update-peer",
"v3/api-reference/endpoint/peers/get-sessions-for-peer",
"v3/api-reference/endpoint/peers/chat",
"v3/api-reference/endpoint/peers/get-representation",
"v3/api-reference/endpoint/peers/get-peer-card",
"v3/api-reference/endpoint/peers/set-peer-card",
"v3/api-reference/endpoint/peers/get-peer-context",
"v3/api-reference/endpoint/peers/search-peer"
]
},
{
"group": "sessions",
"pages": [
"v3/api-reference/endpoint/sessions/get-or-create-session",
"v3/api-reference/endpoint/sessions/get-sessions",
"v3/api-reference/endpoint/sessions/update-session",
"v3/api-reference/endpoint/sessions/delete-session",
"v3/api-reference/endpoint/sessions/clone-session",
"v3/api-reference/endpoint/sessions/get-session-peers",
"v3/api-reference/endpoint/sessions/set-session-peers",
"v3/api-reference/endpoint/sessions/add-peers-to-session",
"v3/api-reference/endpoint/sessions/remove-peers-from-session",
"v3/api-reference/endpoint/sessions/get-peer-config",
"v3/api-reference/endpoint/sessions/set-peer-config",
"v3/api-reference/endpoint/sessions/get-session-context",
"v3/api-reference/endpoint/sessions/get-session-summaries",
"v3/api-reference/endpoint/sessions/search-session"
]
},
{
"group": "messages",
"pages": [
"v3/api-reference/endpoint/messages/create-messages-for-session",
"v3/api-reference/endpoint/messages/get-messages",
"v3/api-reference/endpoint/messages/get-message",
"v3/api-reference/endpoint/messages/update-message",
"v3/api-reference/endpoint/messages/create-messages-with-file"
]
},
{
"group": "conclusions",
"pages": [
"v3/api-reference/endpoint/conclusions/create-conclusions",
"v3/api-reference/endpoint/conclusions/list-conclusions",
"v3/api-reference/endpoint/conclusions/query-conclusions",
"v3/api-reference/endpoint/conclusions/delete-conclusion"
]
},
{
"group": "webhooks",
"pages": [
"v3/api-reference/endpoint/webhooks/list-webhook-endpoints",
"v3/api-reference/endpoint/webhooks/get-or-create-webhook-endpoint",
"v3/api-reference/endpoint/webhooks/delete-webhook-endpoint",
"v3/api-reference/endpoint/webhooks/test-emit"
]
},
{
"group": "miscellaneous",
"pages": ["v3/api-reference/endpoint/keys/create-key"]
}
]
},
{
"tab": "Changelog",
"groups": [
{
"group": "Overview",
"pages": [
"changelog/introduction",
"changelog/compatibility-guide"
]
}
]
}
]
},
{
"version": "v2.5.1",
"api": {
"openapi": ["v2/openapi.json"]
},
"tabs": [
{
"tab": "Documentation",
"groups": [
{
"group": "Introduction",
"pages": [
"v2/documentation/introduction/overview",
"v2/documentation/introduction/quickstart",
"v2/documentation/introduction/vibecoding"
]
},
{
"group": "Core Concepts",
"pages": [
"v2/documentation/core-concepts/architecture",
"v2/documentation/core-concepts/features/storing-data",
"v2/documentation/core-concepts/features/dialectic-endpoint",
"v2/documentation/core-concepts/features/get-context",
"v2/documentation/core-concepts/features/search",
"v2/documentation/core-concepts/features/working-rep",
"v2/documentation/core-concepts/features/streaming-response",
"v2/documentation/core-concepts/features/using-filters",
"v2/documentation/core-concepts/features/file-uploads",
"v2/documentation/core-concepts/features/queue-status",
"v2/documentation/core-concepts/features/local-vs-global",
"v2/documentation/core-concepts/configuration",
"v2/documentation/core-concepts/summarizer",
"v2/documentation/core-concepts/glossary"
]
},
{
"group": "Reference",
"pages": [
"v2/documentation/reference/platform",
"v2/documentation/reference/sdk"
]
}
]
},
{
"tab": "Spellbooks",
"groups": [
{
"group": "Getting Started",
"pages": ["v2/guides/overview"]
},
{
"group": "Migrations",
"pages": ["v2/migrations/from-mem0"]
},
{
"group": "Integrations",
"pages": [
"v2/integrations/crewai",
"v2/integrations/langgraph",
"v2/integrations/mcp"
]
},
{
"group": "Application Interfaces",
"pages": [
"v2/guides/discord",
"v2/guides/n8n",
"v2/guides/telegram"
]
}
]
},
{
"tab": "API Reference",
"groups": [
{
"group": "API Documentation",
"pages": ["v2/api-reference/introduction"]
},
{
"group": "workspaces",
"pages": [
"v2/api-reference/endpoint/workspaces/get-or-create-workspace",
"v2/api-reference/endpoint/workspaces/get-all-workspaces",
"v2/api-reference/endpoint/workspaces/update-workspace",
"v2/api-reference/endpoint/workspaces/delete-workspace",
"v2/api-reference/endpoint/workspaces/search-workspace",
"v2/api-reference/endpoint/workspaces/get-deriver-status",
"v2/api-reference/endpoint/workspaces/trigger-dream"
]
},
{
"group": "peers",
"pages": [
"v2/api-reference/endpoint/peers/get-peers",
"v2/api-reference/endpoint/peers/get-or-create-peer",
"v2/api-reference/endpoint/peers/update-peer",
"v2/api-reference/endpoint/peers/get-sessions-for-peer",
"v2/api-reference/endpoint/peers/chat",
"v2/api-reference/endpoint/peers/get-working-representation",
"v2/api-reference/endpoint/peers/get-peer-card",
"v2/api-reference/endpoint/peers/set-peer-card",
"v2/api-reference/endpoint/peers/get-peer-context",
"v2/api-reference/endpoint/peers/search-peer"
]
},
{
"group": "sessions",
"pages": [
"v2/api-reference/endpoint/sessions/get-or-create-session",
"v2/api-reference/endpoint/sessions/get-sessions",
"v2/api-reference/endpoint/sessions/update-session",
"v2/api-reference/endpoint/sessions/delete-session",
"v2/api-reference/endpoint/sessions/clone-session",
"v2/api-reference/endpoint/sessions/get-session-peers",
"v2/api-reference/endpoint/sessions/set-session-peers",
"v2/api-reference/endpoint/sessions/add-peers-to-session",
"v2/api-reference/endpoint/sessions/remove-peers-from-session",
"v2/api-reference/endpoint/sessions/get-peer-config",
"v2/api-reference/endpoint/sessions/set-peer-config",
"v2/api-reference/endpoint/sessions/get-session-context",
"v2/api-reference/endpoint/sessions/get-session-summaries",
"v2/api-reference/endpoint/sessions/search-session"
]
},
{
"group": "messages",
"pages": [
"v2/api-reference/endpoint/messages/create-messages-for-session",
"v2/api-reference/endpoint/messages/get-messages",
"v2/api-reference/endpoint/messages/get-message",
"v2/api-reference/endpoint/messages/update-message",
"v2/api-reference/endpoint/messages/create-messages-with-file"
]
},
{
"group": "observations",
"pages": [
"v2/api-reference/endpoint/observations/create-observations",
"v2/api-reference/endpoint/observations/list-observations",
"v2/api-reference/endpoint/observations/query-observations",
"v2/api-reference/endpoint/observations/delete-observation"
]
},
{
"group": "webhooks",
"pages": [
"v2/api-reference/endpoint/webhooks/list-webhook-endpoints",
"v2/api-reference/endpoint/webhooks/get-or-create-webhook-endpoint",
"v2/api-reference/endpoint/webhooks/delete-webhook-endpoint",
"v2/api-reference/endpoint/webhooks/test-emit"
]
},
{
"group": "miscellaneous",
"pages": [
"v2/api-reference/endpoint/keys/create-key",
"v2/api-reference/endpoint/metrics"
]
}
]
},
{
"tab": "Contributing",
"groups": [
{
"group": "Contributing",
"pages": [
"v2/contributing/guidelines",
"v2/contributing/self-hosting",
"v2/contributing/configuration",
"v2/contributing/license"
]
}
]
}
]
},
{
"version": "v1.1.0",
"api": {
"openapi": ["openapi.json"]
},
"tabs": [
{
"tab": "Documentation",
"groups": [
{
"group": "Get Started",
"pages": [
"v1/getting-started/introduction",
"v1/getting-started/quickstart",
"v1/getting-started/architecture"
]
},
{
"group": "Contributing",
"pages": [
"v1/contributing/guidelines",
"v1/contributing/self-hosting",
"v1/contributing/deploying",
"v1/contributing/license"
]
}
]
},
{
"tab": "Spellbooks and Tutorials",
"groups": [
{
"group": "Getting Started",
"pages": ["v1/guides/overview", "v1/guides/streaming-response"]
},
{
"group": "Application Interfaces",
"pages": ["v1/guides/discord", "v1/guides/honcho-mcp"]
},
{
"group": "Personal Memory",
"pages": ["v1/guides/dialectic-endpoint"]
}
]
},
{
"tab": "API Reference",
"groups": [
{
"group": "API Documentation",
"pages": ["v1/api-reference/introduction"]
},
{
"group": "apps",
"pages": [
"v1/api-reference/endpoint/apps/get-app",
"v1/api-reference/endpoint/apps/get-all-apps",
"v1/api-reference/endpoint/apps/update-app",
"v1/api-reference/endpoint/apps/get-app-by-name",
"v1/api-reference/endpoint/apps/create-app",
"v1/api-reference/endpoint/apps/get-or-create-app"
]
},
{
"group": "sessions",
"pages": [
"v1/api-reference/endpoint/sessions/get-sessions",
"v1/api-reference/endpoint/sessions/create-session",
"v1/api-reference/endpoint/sessions/get-session",
"v1/api-reference/endpoint/sessions/update-session",
"v1/api-reference/endpoint/sessions/delete-session",
"v1/api-reference/endpoint/sessions/chat",
"v1/api-reference/endpoint/sessions/clone-session"
]
},
{
"group": "users",
"pages": [
"v1/api-reference/endpoint/users/get-users",
"v1/api-reference/endpoint/users/create-user",
"v1/api-reference/endpoint/users/get-user-by-name",
"v1/api-reference/endpoint/users/get-user",
"v1/api-reference/endpoint/users/get-or-create-user",
"v1/api-reference/endpoint/users/update-user"
]
},
{
"group": "messages",
"pages": [
"v1/api-reference/endpoint/messages/get-messages",
"v1/api-reference/endpoint/messages/create-message-for-session",
"v1/api-reference/endpoint/messages/create-batch-messages-for-session",
"v1/api-reference/endpoint/messages/get-message",
"v1/api-reference/endpoint/messages/update-message"
]
},
{
"group": "keys",
"pages": ["v1/api-reference/endpoint/keys/create-key"]
},
{
"group": "metamessages",
"pages": [
"v1/api-reference/endpoint/metamessages/create-metamessage",
"v1/api-reference/endpoint/metamessages/get-metamessages",
"v1/api-reference/endpoint/metamessages/get-metamessage",
"v1/api-reference/endpoint/metamessages/update-metamessage"
]
},
{
"group": "collections",
"pages": [
"v1/api-reference/endpoint/collections/get-collections",
"v1/api-reference/endpoint/collections/create-collection",
"v1/api-reference/endpoint/collections/get-collection-by-name",
"v1/api-reference/endpoint/collections/get-collection",
"v1/api-reference/endpoint/collections/update-collection",
"v1/api-reference/endpoint/collections/delete-collection"
]
},
{
"group": "documents",
"pages": [
"v1/api-reference/endpoint/documents/get-documents",
"v1/api-reference/endpoint/documents/create-document",
"v1/api-reference/endpoint/documents/get-document",
"v1/api-reference/endpoint/documents/update-document",
"v1/api-reference/endpoint/documents/delete-document",
"v1/api-reference/endpoint/documents/query-documents"
]
}
]
}
]
}
]
},
"logo": {
"light": "/logo/honcho-dark.svg",
"dark": "/logo/honcho-light.svg"
},
"navbar": {
"primary": {
"type": "github",
"href": "https://github.com/plastic-labs/honcho"
}
},
"footer": {
"socials": {
"twitter": "https://x.com/honchodotdev",
"github": "https://github.com/plastic-labs/honcho",
"discord": "https://discord.gg/honcho",
"linkedin": "https://www.linkedin.com/company/plasticlabs",
"youtube": "https://www.youtube.com/@plasticlabs"
}
},
"integrations": {
"posthog": {
"apiKey": "phc_1yrzzcgywqXGcerkkI4g7C0YfyPMcAKNOOvGcjTCiUk"
}
}
}

28
docs/favicon.svg Normal file
View File

@ -0,0 +1,28 @@
<svg width="650" height="650" viewBox="0 0 650 650" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="650" height="650" fill="#101447"/>
<g filter="url(#filter0_dd_11_8)">
<path d="M325.56 80C238.59 80 157.54 126.59 113.62 201.69C94.1801 213.51 81.0001 221.78 80.7301 221.92C42.8201 242.58 23.9001 262.29 14.6201 277.9C7.41011 278.03 1.1701 283.37 0.150101 290.73C-1.0099 298.88 4.6801 306.44 12.8401 307.58C21.0101 308.74 28.5501 303.05 29.7101 294.88C30.3101 290.68 29.0701 286.63 26.6301 283.55C40.3601 261.4 69.3201 243.18 87.0601 233.52C90.1101 231.86 261.89 127.81 274.74 130.38C276.89 130.81 280.03 131.97 282.5 137.08C289.3 151.18 226.78 192.95 214.03 200.59C201.82 207.9 191.06 215.71 181.75 224.01C162.02 241.59 148.85 261.32 142.45 282.97C138.6 296.02 127.63 333.6 101.02 342.94C99.1301 338.74 95.3301 335.46 90.4701 334.46C82.4001 332.77 74.4901 337.95 72.8201 346.02C71.2201 353.66 75.7801 361.14 83.1201 363.34C101.49 482.47 203.51 570.77 325.56 570.77C460.86 570.77 570.95 460.69 570.95 325.38C570.95 190.07 460.87 80 325.56 80ZM187.7 236.51C187.12 244.15 185.37 252.9 181.07 258.61C176.54 264.61 167.95 269.71 160.23 273.31C166.33 260.21 175.51 247.92 187.7 236.51ZM325.56 557.54C210.02 557.54 113.46 473.91 96.1601 361.12C97.9201 359.84 99.4201 358.2 100.48 356.25C122.53 348.38 139.19 337.49 154.08 290.08C162.07 287.28 182.01 279.35 191.64 266.56C201.32 253.69 201.46 233.61 201.05 225.28C207.1 220.67 213.7 216.22 220.83 211.95C261.59 187.55 305.55 154.35 294.41 131.32C290.74 123.73 284.83 118.92 277.34 117.43C263.23 114.63 192.72 154.44 140.15 185.73C183.7 128.08 252.41 93.24 325.56 93.24C453.57 93.24 557.71 197.39 557.71 325.39C557.71 453.39 453.57 557.55 325.56 557.55V557.54Z" fill="white"/>
<path d="M247.34 325.38C261.296 325.38 272.61 307.843 272.61 286.21C272.61 264.577 261.296 247.04 247.34 247.04C233.384 247.04 222.07 264.577 222.07 286.21C222.07 307.843 233.384 325.38 247.34 325.38Z" fill="white"/>
<path d="M403.78 325.38C417.736 325.38 429.05 307.843 429.05 286.21C429.05 264.577 417.736 247.04 403.78 247.04C389.824 247.04 378.51 264.577 378.51 286.21C378.51 307.843 389.824 325.38 403.78 325.38Z" fill="white"/>
<path d="M386.18 406.91H264.94C258.48 406.91 253.25 412.14 253.25 418.6C253.25 425.06 258.48 430.29 264.94 430.29H386.18C392.64 430.29 397.87 425.06 397.87 418.6C397.87 412.14 392.64 406.91 386.18 406.91Z" fill="white"/>
<path d="M93.9401 333.61L93.9701 333.58L93.9401 333.6V333.62V333.61Z" fill="white"/>
</g>
<defs>
<filter id="filter0_dd_11_8" x="0" y="80" width="594.95" height="514.77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="20" dy="20"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.352941 0 0 0 0 0.494118 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_11_8"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="10" dy="10"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0352941 0 0 0 0 0.996078 0 0 0 0 0.972549 0 0 0 1 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_11_8" result="effect2_dropShadow_11_8"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_11_8" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
docs/images/db_schema.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

161
docs/images/hero-dark.svg Normal file
View File

@ -0,0 +1,161 @@
<svg width="700" height="320" viewBox="0 0 700 320" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2862_30)">
<rect width="700" height="320" rx="16" fill="url(#paint0_linear_2862_30)"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="white"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint1_radial_2862_30)"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint2_linear_2862_30)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M311.72 247.034C283.108 246.887 258.409 231.208 246.538 201.531C234.656 171.825 238.271 134.702 253.583 101.377C282.195 101.524 306.894 117.203 318.765 146.88C330.647 176.586 327.031 213.709 311.72 247.034Z" stroke="url(#paint3_linear_2862_30)" stroke-opacity="0.05" stroke-width="0.530516"/>
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="white"/>
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="url(#paint4_radial_2862_30)"/>
<path d="M393.341 171.537C376.971 210.369 343.89 237.091 305.969 246.867C286.462 212.959 282.476 170.663 298.845 131.831C315.215 92.9978 348.295 66.2765 386.217 56.5004C405.724 90.4077 409.71 132.704 393.341 171.537Z" stroke="url(#paint5_linear_2862_30)" stroke-opacity="0.05" stroke-width="0.530516"/>
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="white"/>
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint6_radial_2862_30)"/>
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
<path d="M305.686 246.995C329.749 266.114 361.965 272.832 393.67 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.045 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint7_linear_2862_30)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M393.586 261.878C362.034 272.529 329.98 265.88 306.002 246.907C317.534 215.919 341.57 190.327 373.13 179.673C404.681 169.023 436.735 175.671 460.714 194.644C449.181 225.632 425.145 251.224 393.586 261.878Z" stroke="url(#paint8_linear_2862_30)" stroke-opacity="0.05" stroke-width="0.530516"/>
<g opacity="0.8" filter="url(#filter0_f_2862_30)">
<circle cx="660" cy="-60" r="160" fill="#18E244" fill-opacity="0.4"/>
</g>
<g opacity="0.8" filter="url(#filter1_f_2862_30)">
<circle cx="20" cy="213" r="160" fill="#18CAE2" fill-opacity="0.33"/>
</g>
<g opacity="0.8" filter="url(#filter2_f_2862_30)">
<circle cx="660" cy="480" r="160" fill="#18E2B2" fill-opacity="0.52"/>
</g>
<g opacity="0.8" filter="url(#filter3_f_2862_30)">
<circle cx="20" cy="413" r="160" fill="#4018E2" fill-opacity="0.22"/>
</g>
<path opacity="0.2" d="M0 50H700" stroke="url(#paint9_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.1" d="M0 82H700" stroke="url(#paint10_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.2" d="M239 0L239 320" stroke="url(#paint11_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.1" d="M271 0L271 320" stroke="url(#paint12_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.2" d="M461 0L461 320" stroke="url(#paint13_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.1" d="M429 0L429 320" stroke="url(#paint14_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.2" d="M0 271H700" stroke="url(#paint15_radial_2862_30)" stroke-dasharray="4 4"/>
<path opacity="0.1" d="M0 239H700" stroke="url(#paint16_radial_2862_30)" stroke-dasharray="4 4"/>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 160H700" stroke="url(#paint17_linear_2862_30)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.2">
<path d="M511 -1L189 321" stroke="url(#paint18_linear_2862_30)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.2">
<path d="M511 321L189 -1" stroke="url(#paint19_linear_2862_30)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<circle cx="350" cy="160" r="111" stroke="white"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<circle cx="350" cy="160" r="79" stroke="white"/>
</g>
</g>
<defs>
<filter id="filter0_f_2862_30" x="260" y="-460" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
</filter>
<filter id="filter1_f_2862_30" x="-380" y="-187" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
</filter>
<filter id="filter2_f_2862_30" x="260" y="80" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
</filter>
<filter id="filter3_f_2862_30" x="-380" y="13" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_30"/>
</filter>
<linearGradient id="paint0_linear_2862_30" x1="1.04308e-05" y1="320" x2="710.784" y2="26.0793" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299" stop-opacity="0.09"/>
<stop offset="0.729167" stop-color="#0D9373" stop-opacity="0.08"/>
</linearGradient>
<radialGradient id="paint1_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(208.697 189.703) rotate(-10.029) scale(169.097 167.466)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint2_linear_2862_30" x1="306.587" y1="93.5598" x2="252.341" y2="224.228" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint3_linear_2862_30" x1="311.84" y1="123.717" x2="253.579" y2="224.761" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint4_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(313.407 243.64) rotate(-75.7542) scale(203.632 223.902)">
<stop stop-color="#00BBBB"/>
<stop offset="0.712616" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint5_linear_2862_30" x1="308.586" y1="102.284" x2="383.487" y2="201.169" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint6_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(311.446 249.925) rotate(-20.3524) scale(174.776 163.096)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint7_linear_2862_30" x1="395.842" y1="169.781" x2="332.121" y2="263.82" gradientUnits="userSpaceOnUse">
<stop stop-color="#00B1BC"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint8_linear_2862_30" x1="395.842" y1="169.781" x2="370.99" y2="271.799" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint9_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 50) scale(398.125 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint10_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 82) scale(398.125 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint11_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(239 160) rotate(90) scale(182 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint12_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(271 160) rotate(90) scale(182 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint13_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(461 160) rotate(90) scale(182 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint14_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(429 160) rotate(90) scale(182 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint15_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 271) scale(398.125 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint16_radial_2862_30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(350 239) scale(398.125 182)">
<stop offset="0.348958" stop-color="#84FFD3"/>
<stop offset="0.880208" stop-color="#18E299" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint17_linear_2862_30" x1="0" y1="160" x2="700" y2="160" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="0.5" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
</linearGradient>
<linearGradient id="paint18_linear_2862_30" x1="511" y1="-1" x2="189" y2="321" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="0.5" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
</linearGradient>
<linearGradient id="paint19_linear_2862_30" x1="511" y1="321" x2="189" y2="-0.999997" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="0.5" stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
</linearGradient>
<clipPath id="clip0_2862_30">
<rect width="700" height="320" rx="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

155
docs/images/hero-light.svg Normal file
View File

@ -0,0 +1,155 @@
<svg width="700" height="320" viewBox="0 0 700 320" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2862_278)">
<rect width="700" height="320" rx="16" fill="url(#paint0_linear_2862_278)"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="white"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint1_radial_2862_278)"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M311.889 247.3C283.097 247.215 258.226 231.466 246.292 201.629C234.357 171.793 238.02 134.523 253.414 101.112C282.206 101.197 307.077 116.945 319.011 146.782C330.946 176.619 327.283 213.888 311.889 247.3Z" fill="url(#paint2_linear_2862_278)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M311.72 247.034C283.108 246.887 258.409 231.208 246.538 201.531C234.656 171.825 238.271 134.702 253.583 101.377C282.195 101.524 306.894 117.203 318.765 146.88C330.647 176.586 327.031 213.709 311.72 247.034Z" stroke="url(#paint3_linear_2862_278)" stroke-opacity="0.05" stroke-width="0.530516"/>
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="white"/>
<path d="M305.839 247.174C343.92 237.419 377.154 210.619 393.585 171.64C410.017 132.661 405.98 90.1988 386.347 56.1934C348.266 65.9477 315.032 92.7486 298.601 131.728C282.169 170.706 286.206 213.168 305.839 247.174Z" fill="url(#paint4_radial_2862_278)"/>
<path d="M393.341 171.537C376.971 210.369 343.89 237.091 305.969 246.867C286.462 212.959 282.476 170.663 298.845 131.831C315.215 92.9978 348.295 66.2765 386.217 56.5004C405.724 90.4077 409.71 132.704 393.341 171.537Z" stroke="url(#paint5_linear_2862_278)" stroke-opacity="0.05" stroke-width="0.530516"/>
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="white"/>
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint6_radial_2862_278)"/>
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
<path d="M305.686 246.995C329.75 266.114 361.965 272.832 393.671 262.129C425.376 251.426 449.499 225.691 461.03 194.556C436.967 175.437 404.751 168.719 373.046 179.422C341.34 190.125 317.217 215.86 305.686 246.995Z" fill="url(#paint7_linear_2862_278)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M393.586 261.878C362.035 272.529 329.981 265.88 306.002 246.907C317.535 215.919 341.571 190.327 373.13 179.673C404.682 169.023 436.736 175.671 460.715 194.644C449.182 225.632 425.146 251.224 393.586 261.878Z" stroke="url(#paint8_linear_2862_278)" stroke-opacity="0.05" stroke-width="0.530516"/>
<g opacity="0.8" filter="url(#filter0_f_2862_278)">
<circle cx="660" cy="-60" r="160" fill="#18E299" fill-opacity="0.4"/>
</g>
<g opacity="0.8" filter="url(#filter1_f_2862_278)">
<circle cx="20" cy="213" r="160" fill="#18E299" fill-opacity="0.33"/>
</g>
<g opacity="0.8" filter="url(#filter2_f_2862_278)">
<circle cx="660" cy="480" r="160" fill="#18E299" fill-opacity="0.52"/>
</g>
<g opacity="0.8" filter="url(#filter3_f_2862_278)">
<circle cx="20" cy="413" r="160" fill="#18E299" fill-opacity="0.22"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 50H700" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 82H700" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M239 0L239 320" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M271 0L271 320" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M461 0L461 320" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M350 0L350 320" stroke="url(#paint9_linear_2862_278)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M429 0L429 320" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 271H700" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 239H700" stroke="black" stroke-dasharray="4 4"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M0 160H700" stroke="url(#paint10_linear_2862_278)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M511 -1L189 321" stroke="url(#paint11_linear_2862_278)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.1">
<path d="M511 321L189 -1" stroke="url(#paint12_linear_2862_278)"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.05">
<circle cx="350" cy="160" r="111" stroke="black"/>
</g>
<g style="mix-blend-mode:overlay" opacity="0.05">
<circle cx="350" cy="160" r="79" stroke="black"/>
</g>
</g>
<defs>
<filter id="filter0_f_2862_278" x="260" y="-460" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
</filter>
<filter id="filter1_f_2862_278" x="-380" y="-187" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
</filter>
<filter id="filter2_f_2862_278" x="260" y="80" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
</filter>
<filter id="filter3_f_2862_278" x="-380" y="13" width="800" height="800" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="120" result="effect1_foregroundBlur_2862_278"/>
</filter>
<linearGradient id="paint0_linear_2862_278" x1="1.04308e-05" y1="320" x2="710.784" y2="26.0793" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299" stop-opacity="0.09"/>
<stop offset="0.729167" stop-color="#0D9373" stop-opacity="0.08"/>
</linearGradient>
<radialGradient id="paint1_radial_2862_278" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(208.697 189.703) rotate(-10.029) scale(169.097 167.466)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint2_linear_2862_278" x1="306.587" y1="93.5598" x2="252.341" y2="224.228" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint3_linear_2862_278" x1="311.84" y1="123.717" x2="253.579" y2="224.761" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint4_radial_2862_278" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(313.407 243.64) rotate(-75.7542) scale(203.632 223.902)">
<stop stop-color="#00BBBB"/>
<stop offset="0.712616" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint5_linear_2862_278" x1="308.586" y1="102.284" x2="383.487" y2="201.169" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint6_radial_2862_278" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(311.447 249.925) rotate(-20.3524) scale(174.776 163.096)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint7_linear_2862_278" x1="395.843" y1="169.781" x2="332.121" y2="263.82" gradientUnits="userSpaceOnUse">
<stop stop-color="#00B1BC"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint8_linear_2862_278" x1="395.843" y1="169.781" x2="370.991" y2="271.799" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint9_linear_2862_278" x1="350" y1="0" x2="350" y2="320" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0"/>
<stop offset="0.0001" stop-opacity="0.3"/>
<stop offset="0.333333"/>
<stop offset="0.666667"/>
<stop offset="1" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="paint10_linear_2862_278" x1="0" y1="160" x2="700" y2="160" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.1"/>
<stop offset="0.5"/>
<stop offset="1" stop-opacity="0.1"/>
</linearGradient>
<linearGradient id="paint11_linear_2862_278" x1="511" y1="-1" x2="189" y2="321" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.1"/>
<stop offset="0.5"/>
<stop offset="1" stop-opacity="0.1"/>
</linearGradient>
<linearGradient id="paint12_linear_2862_278" x1="511" y1="321" x2="189" y2="-0.999997" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.1"/>
<stop offset="0.5"/>
<stop offset="1" stop-opacity="0.1"/>
</linearGradient>
<clipPath id="clip0_2862_278">
<rect width="700" height="320" rx="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
docs/images/reasoning.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

55
docs/logo/dark.svg Normal file
View File

@ -0,0 +1,55 @@
<svg width="160" height="24" viewBox="0 0 160 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="white"/>
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="url(#paint0_radial_115_109)"/>
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="url(#paint1_linear_115_109)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M7.9354 21.1112C4.89702 21.0957 2.27411 19.4306 1.01347 16.279C-0.248375 13.1244 0.135612 9.18218 1.76165 5.64327C4.80004 5.65882 7.42295 7.32385 8.68359 10.4755C9.94543 13.63 9.56144 17.5723 7.9354 21.1112Z" stroke="url(#paint2_linear_115_109)" stroke-opacity="0.05" stroke-width="0.056338"/>
<path d="M7.31038 21.2574C11.3543 20.2215 14.8836 17.3754 16.6285 13.2361C18.3735 9.09671 17.9448 4.58749 15.8598 0.976291C11.8159 2.01214 8.2866 4.85826 6.54167 8.99762C4.79674 13.137 5.2254 17.6462 7.31038 21.2574Z" fill="white"/>
<path d="M7.31038 21.2574C11.3543 20.2215 14.8836 17.3754 16.6285 13.2361C18.3735 9.09671 17.9448 4.58749 15.8598 0.976291C11.8159 2.01214 8.2866 4.85826 6.54167 8.99762C4.79674 13.137 5.2254 17.6462 7.31038 21.2574Z" fill="url(#paint3_radial_115_109)"/>
<path d="M16.6025 13.2251C14.8642 17.349 11.3512 20.1866 7.32411 21.2248C5.25257 17.624 4.82926 13.1324 6.56764 9.00855C8.30603 4.88472 11.819 2.04706 15.8461 1.00889C17.9176 4.60967 18.3409 9.10131 16.6025 13.2251Z" stroke="url(#paint4_linear_115_109)" stroke-opacity="0.05" stroke-width="0.056338"/>
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="white"/>
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="url(#paint5_radial_115_109)"/>
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="url(#paint6_linear_115_109)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M16.5682 22.7874C13.2176 23.9184 9.81361 23.2124 7.2672 21.1975C8.49194 17.9068 11.0444 15.189 14.3959 14.0577C17.7465 12.9266 21.1504 13.6326 23.6968 15.6476C22.4721 18.9383 19.9196 21.656 16.5682 22.7874Z" stroke="url(#paint7_linear_115_109)" stroke-opacity="0.05" stroke-width="0.056338"/>
<path d="M34.2124 19V5.4H39.4924L41.6924 12.2L42.3724 14.74L43.0524 12.2L45.2524 5.4H50.4124V19H46.3324L46.5924 9.98L45.5324 13.68L43.7924 19H40.8324L39.0524 13.6L38.0324 10.02L38.2924 19H34.2124ZM52.4155 7.3V4.6H56.2955V7.3H52.4155ZM52.4155 19V8.14H56.2955V19H52.4155ZM58.1038 19V8.14H61.9838V9.58C62.6638 8.34 63.7438 7.76 65.0038 7.76C66.9638 7.76 68.6238 8.98 68.6238 11.78V19H64.7438V12.56C64.7438 11.34 64.3038 10.86 63.4838 10.86C62.6038 10.86 61.9838 11.58 61.9838 12.88V19H58.1038ZM70.9327 15.22V11.06H69.7327V8.14H70.9327V5.62H74.8127V8.14H76.9327V11.06H74.8127V14.6C74.8127 15.5 75.0327 16.06 76.2127 16.06H76.9327V19C76.4927 19.2 75.6727 19.38 74.6527 19.38C72.1527 19.38 70.9327 17.88 70.9327 15.22Z" fill="url(#paint8_radial_115_109)"/>
<path d="M87.232 10.519C87.232 13.687 94.1125 11.2285 94.1125 15.832C94.1125 17.9935 92.3635 19.198 89.971 19.198C87.562 19.198 85.912 18.0925 85.417 15.832H87.001C87.364 17.1685 88.3705 17.8945 89.9875 17.8945C91.6705 17.8945 92.5615 17.152 92.5615 16.03C92.5615 12.598 85.681 15.1555 85.681 10.618C85.681 9.001 87.034 7.582 89.509 7.582C91.6705 7.582 93.403 8.6215 93.8155 11.014H92.215C91.8685 9.529 90.9115 8.8855 89.476 8.8855C88.057 8.8855 87.232 9.529 87.232 10.519ZM96.2499 16.4755V11.3935H95.0289V10.2385H96.2499V8.2255H97.7019V10.2385H99.6324V11.3935H97.7019V16.4755C97.7019 17.5315 98.0154 18.0265 99.3024 18.0265H99.5994V19.066C99.4344 19.1485 99.0714 19.198 98.6589 19.198C97.0254 19.198 96.2499 18.3235 96.2499 16.4755ZM102.516 13.093H101.064C101.345 11.1625 102.615 10.024 104.76 10.024C107.103 10.024 108.242 11.3935 108.242 13.4395V16.888C108.242 17.8945 108.324 18.5215 108.555 19H107.021C106.856 18.6535 106.806 18.142 106.79 17.614C106.047 18.7195 104.859 19.198 103.803 19.198C101.988 19.198 100.767 18.3565 100.767 16.69C100.767 15.4855 101.427 14.611 102.714 14.182C103.902 13.786 105.107 13.687 106.79 13.6705V13.4725C106.79 12.0535 106.13 11.278 104.628 11.278C103.374 11.278 102.698 11.971 102.516 13.093ZM102.252 16.657C102.252 17.4655 102.929 17.944 103.952 17.944C105.569 17.944 106.79 16.6735 106.79 15.172V14.7595C103.061 14.7925 102.252 15.5845 102.252 16.657ZM110.787 19V10.2385H112.239V11.5915C112.833 10.519 113.774 10.024 114.83 10.024C115.176 10.024 115.49 10.1065 115.655 10.2385V11.542C115.407 11.4595 115.094 11.4265 114.747 11.4265C112.998 11.4265 112.239 12.5155 112.239 14.0995V19H110.787ZM117.305 16.4755V11.3935H116.084V10.2385H117.305V8.2255H118.757V10.2385H120.688V11.3935H118.757V16.4755C118.757 17.5315 119.071 18.0265 120.358 18.0265H120.655V19.066C120.49 19.1485 120.127 19.198 119.714 19.198C118.081 19.198 117.305 18.3235 117.305 16.4755ZM129.809 16.1455C129.33 18.1915 127.862 19.198 125.865 19.198C123.324 19.198 121.79 17.482 121.79 14.6275C121.79 11.6575 123.324 10.024 125.783 10.024C128.258 10.024 129.743 11.7235 129.743 14.512V14.875H123.275C123.357 16.8385 124.281 17.944 125.865 17.944C127.103 17.944 127.977 17.35 128.291 16.1455H129.809ZM125.783 11.278C124.38 11.278 123.539 12.1525 123.324 13.786H128.225C128.027 12.169 127.152 11.278 125.783 11.278ZM131.843 19V10.2385H133.295V11.5915C133.889 10.519 134.829 10.024 135.885 10.024C136.232 10.024 136.545 10.1065 136.71 10.2385V11.542C136.463 11.4595 136.149 11.4265 135.803 11.4265C134.054 11.4265 133.295 12.5155 133.295 14.0995V19H131.843ZM141.763 19V7.78H143.281V13.192L148.413 7.78H150.327L145.047 13.291L150.459 19H148.413L143.281 13.621V19H141.763ZM152.06 9.067V7.12H153.512V9.067H152.06ZM152.06 19V10.2385H153.512V19H152.06ZM156.178 16.4755V11.3935H154.957V10.2385H156.178V8.2255H157.63V10.2385H159.56V11.3935H157.63V16.4755C157.63 17.5315 157.943 18.0265 159.23 18.0265H159.527V19.066C159.362 19.1485 158.999 19.198 158.587 19.198C156.953 19.198 156.178 18.3235 156.178 16.4755Z" fill="white" fill-opacity="0.55"/>
<defs>
<radialGradient id="paint0_radial_115_109" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-3.00503 15.023) rotate(-10.029) scale(17.9572 17.784)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint1_linear_115_109" x1="7.39036" y1="4.81308" x2="1.62975" y2="18.6894" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint2_linear_115_109" x1="7.94816" y1="8.01562" x2="1.7612" y2="18.746" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint3_radial_115_109" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.11404 20.8822) rotate(-75.7542) scale(21.6246 23.7772)">
<stop stop-color="#00BBBB"/>
<stop offset="0.712616" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint4_linear_115_109" x1="7.60205" y1="5.8709" x2="15.5561" y2="16.3719" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint5_radial_115_109" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(7.84537 21.5181) rotate(-20.3525) scale(18.5603 17.32)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint6_linear_115_109" x1="16.8078" y1="13.0071" x2="10.0409" y2="22.9937" gradientUnits="userSpaceOnUse">
<stop stop-color="#00B1BC"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint7_linear_115_109" x1="16.8078" y1="13.0071" x2="14.1687" y2="23.841" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint8_radial_115_109" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(47.2781 7) rotate(19.0047) scale(67.5582 85.7506)">
<stop stop-color="white"/>
<stop offset="1" stop-color="white" stop-opacity="0.5"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

37
docs/logo/honcho-dark.svg Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 995.73 163.32">
<defs>
<style>
.cls-1 {
mask: url(#mask);
}
.cls-2 {
fill: #1A1C1E;
}
.cls-2, .cls-3 {
stroke-width: 0px;
}
.cls-3 {
fill: #fff;
}
</style>
<mask id="mask" x="-10.78" y="-24.14" width="114.88" height="198.1" maskUnits="userSpaceOnUse">
<rect class="cls-3" x="-10.78" y="-24.14" width="114.88" height="198.1"/>
</mask>
</defs>
<g class="cls-1">
<path class="cls-2" d="M30.05,84.26s0,0-.01.01h0s0,0,.01-.01Z"/>
</g>
<g>
<path class="cls-2" d="M193.01,3.19v56.41h-39.58V3.19h-48.82c-.17,0-.34,0-.51,0-27.82,0-53.74,14.9-67.78,38.91-6.22,3.78-10.42,6.42-10.51,6.46-12.12,6.61-18.17,12.91-21.14,17.9-2.31.04-4.3,1.75-4.63,4.11-.37,2.6,1.45,5.02,4.06,5.39,2.61.37,5.02-1.45,5.39-4.06.19-1.34-.21-2.64-.99-3.62,4.39-7.08,13.65-12.91,19.32-16,.98-.53,55.9-33.8,60.02-32.98.69.14,1.69.51,2.48,2.14,2.17,4.51-17.82,17.87-21.9,20.31-3.9,2.34-7.34,4.83-10.32,7.49-6.31,5.62-10.52,11.93-12.56,18.85-1.23,4.18-4.74,16.19-13.25,19.18-.6-1.34-1.82-2.39-3.37-2.71-2.58-.54-5.11,1.12-5.64,3.7-.51,2.44.94,4.83,3.28,5.54,5.87,38.08,38.48,66.31,77.5,66.33v.03h49.36v-65.96h39.58v65.96h49.36V3.19h-49.36ZM60.02,53.22c-.18,2.44-.75,5.24-2.12,7.07-1.45,1.92-4.2,3.55-6.66,4.7,1.95-4.19,4.89-8.12,8.79-11.77ZM104.07,107.71h-19.35c-2.06,0-3.74,1.67-3.74,3.74,0,1.03.42,1.97,1.1,2.64.68.68,1.61,1.09,2.64,1.09h19.35v40.69c-36.93-.01-67.79-26.74-73.33-62.79.57-.41,1.04-.94,1.39-1.57,7.05-2.52,12.38-6,17.14-21.16,2.56-.89,8.93-3.43,12.01-7.52,3.1-4.11,3.14-10.53,3.01-13.2,1.94-1.48,4.05-2.9,6.33-4.26,13.03-7.8,27.09-18.42,23.53-25.78-1.17-2.43-3.06-3.97-5.46-4.44-4.51-.89-27.07,11.84-43.88,21.84,13.92-18.43,35.88-29.57,59.27-29.58v100.3Z"/>
<ellipse class="cls-2" cx="79.09" cy="69.12" rx="8.08" ry="12.53"/>
<path class="cls-2" d="M369.18,7.05c10.39,4.7,17.93,12.89,22.63,24.57,4.7,11.68,7.05,28.36,7.05,50.04s-2.35,38.37-7.05,50.04c-4.7,11.68-12.25,19.87-22.63,24.57-10.39,4.7-24.91,7.05-43.56,7.05s-32.95-2.35-43.33-7.05c-10.39-4.7-17.93-12.89-22.63-24.57-4.7-11.68-7.05-28.36-7.05-50.04s2.35-38.36,7.05-50.04c4.7-11.68,12.24-19.86,22.63-24.57,10.39-4.7,24.83-7.05,43.33-7.05s33.17,2.35,43.56,7.05ZM312.54,40.38c-2.96,2.5-5.04,6.9-6.26,13.19-1.22,6.29-1.82,15.66-1.82,28.09s.6,21.8,1.82,28.09c1.21,6.29,3.3,10.69,6.26,13.19s7.31,3.75,13.08,3.75,10.12-1.25,13.08-3.75,5.04-6.9,6.26-13.19c1.21-6.29,1.82-15.66,1.82-28.09s-.61-21.8-1.82-28.09c-1.22-6.29-3.3-10.69-6.26-13.19s-7.32-3.75-13.08-3.75-10.12,1.25-13.08,3.75Z"/>
<path class="cls-2" d="M553.31,143.08c0,5.61-1.44,9.86-4.32,12.74-2.88,2.88-7.13,4.32-12.74,4.32h-18.88c-4.7,0-8.3-1.06-10.8-3.18-2.5-2.12-5.27-5.84-8.3-11.15l-34.12-53.91c-3.79-6.82-7.28-15.77-10.46-26.84h-1.59c1.51,10.62,2.27,20.17,2.27,28.66v66.42h-45.49V20.24c0-5.61,1.44-9.86,4.32-12.74s7.13-4.32,12.74-4.32h18.88c4.7,0,8.26,1.06,10.69,3.18,2.42,2.13,5.23,5.84,8.42,11.15l32.76,51.41c4.09,7.28,8.11,16.23,12.06,26.84h1.59c-1.36-11.52-2.05-21-2.05-28.43l-.23-64.15h45.27v139.89Z"/>
<path class="cls-2" d="M662.72,1.82c7.13.91,14.86,2.5,23.2,4.78l-3.64,35.94c-3.49,0-6.14-.07-7.96-.23l-40.03-.23c-5.31,0-9.33,1.1-12.06,3.3-2.73,2.2-4.63,6.07-5.69,11.6-1.06,5.54-1.59,13.76-1.59,24.68s.53,19.15,1.59,24.68c1.06,5.54,2.96,9.4,5.69,11.6,2.73,2.2,6.75,3.3,12.06,3.3,12.43,0,22.33-.11,29.68-.34,7.35-.23,14.59-.72,21.72-1.48l3.64,35.94c-8.04,2.73-16.15,4.66-24.34,5.8-8.19,1.14-18.43,1.71-30.71,1.71-17.59,0-31.54-2.62-41.85-7.85-10.31-5.23-17.78-13.72-22.41-25.48-4.63-11.75-6.94-27.71-6.94-47.88s2.31-36.13,6.94-47.88c4.62-11.75,12.09-20.24,22.41-25.48,10.31-5.23,24.26-7.85,41.85-7.85,11.83,0,21.3.45,28.43,1.36Z"/>
<path class="cls-2" d="M839.24,3.18v156.95h-49.36v-65.97h-39.58v65.97h-49.36V3.18h49.36v56.41h39.58V3.18h49.36Z"/>
<path class="cls-2" d="M966.05,7.05c10.39,4.7,17.93,12.89,22.63,24.57,4.7,11.68,7.05,28.36,7.05,50.04s-2.35,38.37-7.05,50.04c-4.7,11.68-12.25,19.87-22.63,24.57-10.39,4.7-24.91,7.05-43.56,7.05s-32.95-2.35-43.33-7.05c-10.39-4.7-17.93-12.89-22.63-24.57-4.7-11.68-7.05-28.36-7.05-50.04s2.35-38.36,7.05-50.04c4.7-11.68,12.24-19.86,22.63-24.57,10.39-4.7,24.83-7.05,43.33-7.05s33.17,2.35,43.56,7.05ZM909.41,40.38c-2.96,2.5-5.04,6.9-6.26,13.19-1.22,6.29-1.82,15.66-1.82,28.09s.6,21.8,1.82,28.09c1.21,6.29,3.3,10.69,6.26,13.19s7.31,3.75,13.08,3.75,10.12-1.25,13.08-3.75,5.04-6.9,6.26-13.19c1.21-6.29,1.82-15.66,1.82-28.09s-.61-21.8-1.82-28.09c-1.22-6.29-3.3-10.69-6.26-13.19s-7.32-3.75-13.08-3.75-10.12,1.25-13.08,3.75Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 995.73 163.32">
<defs>
<style>
.cls-1 {
mask: url(#mask);
}
.cls-2 {
fill: #B5D9FD;
stroke-width: 0px;
}
</style>
<mask id="mask" x="-10.78" y="-24.14" width="114.88" height="198.1" maskUnits="userSpaceOnUse">
<rect class="cls-2" x="-10.78" y="-24.14" width="114.88" height="198.1"/>
</mask>
</defs>
<g class="cls-1">
<path class="cls-2" d="M30.05,84.26s0,0-.01.01h0s0,0,.01-.01Z"/>
</g>
<g>
<path class="cls-2" d="M193.01,3.19v56.41h-39.58V3.19h-48.82c-.17,0-.34,0-.51,0-27.82,0-53.74,14.9-67.78,38.91-6.22,3.78-10.42,6.42-10.51,6.46-12.12,6.61-18.17,12.91-21.14,17.9-2.31.04-4.3,1.75-4.63,4.11-.37,2.6,1.45,5.02,4.06,5.39,2.61.37,5.02-1.45,5.39-4.06.19-1.34-.21-2.64-.99-3.62,4.39-7.08,13.65-12.91,19.32-16,.98-.53,55.9-33.8,60.02-32.98.69.14,1.69.51,2.48,2.14,2.17,4.51-17.82,17.87-21.9,20.31-3.9,2.34-7.34,4.83-10.32,7.49-6.31,5.62-10.52,11.93-12.56,18.85-1.23,4.18-4.74,16.19-13.25,19.18-.6-1.34-1.82-2.39-3.37-2.71-2.58-.54-5.11,1.12-5.64,3.7-.51,2.44.94,4.83,3.28,5.54,5.87,38.08,38.48,66.31,77.5,66.33v.03h49.36v-65.96h39.58v65.96h49.36V3.19h-49.36ZM60.02,53.22c-.18,2.44-.75,5.24-2.12,7.07-1.45,1.92-4.2,3.55-6.66,4.7,1.95-4.19,4.89-8.12,8.79-11.77ZM104.07,107.71h-19.35c-2.06,0-3.74,1.67-3.74,3.74,0,1.03.42,1.97,1.1,2.64.68.68,1.61,1.09,2.64,1.09h19.35v40.69c-36.93-.01-67.79-26.74-73.33-62.79.57-.41,1.04-.94,1.39-1.57,7.05-2.52,12.38-6,17.14-21.16,2.56-.89,8.93-3.43,12.01-7.52,3.1-4.11,3.14-10.53,3.01-13.2,1.94-1.48,4.05-2.9,6.33-4.26,13.03-7.8,27.09-18.42,23.53-25.78-1.17-2.43-3.06-3.97-5.46-4.44-4.51-.89-27.07,11.84-43.88,21.84,13.92-18.43,35.88-29.57,59.27-29.58v100.3Z"/>
<ellipse class="cls-2" cx="79.09" cy="69.12" rx="8.08" ry="12.53"/>
<path class="cls-2" d="M369.18,7.05c10.39,4.7,17.93,12.89,22.63,24.57,4.7,11.68,7.05,28.36,7.05,50.04s-2.35,38.37-7.05,50.04c-4.7,11.68-12.25,19.87-22.63,24.57-10.39,4.7-24.91,7.05-43.56,7.05s-32.95-2.35-43.33-7.05c-10.39-4.7-17.93-12.89-22.63-24.57-4.7-11.68-7.05-28.36-7.05-50.04s2.35-38.36,7.05-50.04c4.7-11.68,12.24-19.86,22.63-24.57,10.39-4.7,24.83-7.05,43.33-7.05s33.17,2.35,43.56,7.05ZM312.54,40.38c-2.96,2.5-5.04,6.9-6.26,13.19-1.22,6.29-1.82,15.66-1.82,28.09s.6,21.8,1.82,28.09c1.21,6.29,3.3,10.69,6.26,13.19s7.31,3.75,13.08,3.75,10.12-1.25,13.08-3.75,5.04-6.9,6.26-13.19c1.21-6.29,1.82-15.66,1.82-28.09s-.61-21.8-1.82-28.09c-1.22-6.29-3.3-10.69-6.26-13.19s-7.32-3.75-13.08-3.75-10.12,1.25-13.08,3.75Z"/>
<path class="cls-2" d="M553.31,143.08c0,5.61-1.44,9.86-4.32,12.74-2.88,2.88-7.13,4.32-12.74,4.32h-18.88c-4.7,0-8.3-1.06-10.8-3.18-2.5-2.12-5.27-5.84-8.3-11.15l-34.12-53.91c-3.79-6.82-7.28-15.77-10.46-26.84h-1.59c1.51,10.62,2.27,20.17,2.27,28.66v66.42h-45.49V20.24c0-5.61,1.44-9.86,4.32-12.74s7.13-4.32,12.74-4.32h18.88c4.7,0,8.26,1.06,10.69,3.18,2.42,2.13,5.23,5.84,8.42,11.15l32.76,51.41c4.09,7.28,8.11,16.23,12.06,26.84h1.59c-1.36-11.52-2.05-21-2.05-28.43l-.23-64.15h45.27v139.89Z"/>
<path class="cls-2" d="M662.72,1.82c7.13.91,14.86,2.5,23.2,4.78l-3.64,35.94c-3.49,0-6.14-.07-7.96-.23l-40.03-.23c-5.31,0-9.33,1.1-12.06,3.3-2.73,2.2-4.63,6.07-5.69,11.6-1.06,5.54-1.59,13.76-1.59,24.68s.53,19.15,1.59,24.68c1.06,5.54,2.96,9.4,5.69,11.6,2.73,2.2,6.75,3.3,12.06,3.3,12.43,0,22.33-.11,29.68-.34,7.35-.23,14.59-.72,21.72-1.48l3.64,35.94c-8.04,2.73-16.15,4.66-24.34,5.8-8.19,1.14-18.43,1.71-30.71,1.71-17.59,0-31.54-2.62-41.85-7.85-10.31-5.23-17.78-13.72-22.41-25.48-4.63-11.75-6.94-27.71-6.94-47.88s2.31-36.13,6.94-47.88c4.62-11.75,12.09-20.24,22.41-25.48,10.31-5.23,24.26-7.85,41.85-7.85,11.83,0,21.3.45,28.43,1.36Z"/>
<path class="cls-2" d="M839.24,3.18v156.95h-49.36v-65.97h-39.58v65.97h-49.36V3.18h49.36v56.41h39.58V3.18h49.36Z"/>
<path class="cls-2" d="M966.05,7.05c10.39,4.7,17.93,12.89,22.63,24.57,4.7,11.68,7.05,28.36,7.05,50.04s-2.35,38.37-7.05,50.04c-4.7,11.68-12.25,19.87-22.63,24.57-10.39,4.7-24.91,7.05-43.56,7.05s-32.95-2.35-43.33-7.05c-10.39-4.7-17.93-12.89-22.63-24.57-4.7-11.68-7.05-28.36-7.05-50.04s2.35-38.36,7.05-50.04c4.7-11.68,12.24-19.86,22.63-24.57,10.39-4.7,24.83-7.05,43.33-7.05s33.17,2.35,43.56,7.05ZM909.41,40.38c-2.96,2.5-5.04,6.9-6.26,13.19-1.22,6.29-1.82,15.66-1.82,28.09s.6,21.8,1.82,28.09c1.21,6.29,3.3,10.69,6.26,13.19s7.31,3.75,13.08,3.75,10.12-1.25,13.08-3.75,5.04-6.9,6.26-13.19c1.21-6.29,1.82-15.66,1.82-28.09s-.61-21.8-1.82-28.09c-1.22-6.29-3.3-10.69-6.26-13.19s-7.32-3.75-13.08-3.75-10.12,1.25-13.08,3.75Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

51
docs/logo/light.svg Normal file
View File

@ -0,0 +1,51 @@
<svg width="160" height="24" viewBox="0 0 160 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="white"/>
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="url(#paint0_radial_115_86)"/>
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="black" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M7.95343 21.1394C4.89586 21.1304 2.25471 19.458 0.987296 16.2895C-0.280118 13.121 0.108924 9.16314 1.74363 5.61504C4.8012 5.62409 7.44235 7.29648 8.70976 10.465C9.97718 13.6335 9.58814 17.5913 7.95343 21.1394Z" fill="url(#paint1_linear_115_86)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M7.9354 21.1112C4.89702 21.0957 2.27411 19.4306 1.01347 16.279C-0.248375 13.1244 0.135612 9.18218 1.76165 5.64327C4.80004 5.65882 7.42295 7.32385 8.68359 10.4755C9.94543 13.63 9.56144 17.5723 7.9354 21.1112Z" stroke="url(#paint2_linear_115_86)" stroke-opacity="0.05" stroke-width="0.056338"/>
<path d="M7.31038 21.2574C11.3543 20.2215 14.8836 17.3754 16.6285 13.2361C18.3735 9.09671 17.9448 4.58749 15.8598 0.976291C11.8159 2.01214 8.2866 4.85826 6.54167 8.99762C4.79674 13.137 5.2254 17.6462 7.31038 21.2574Z" fill="white"/>
<path d="M7.31038 21.2574C11.3543 20.2215 14.8836 17.3754 16.6285 13.2361C18.3735 9.09671 17.9448 4.58749 15.8598 0.976291C11.8159 2.01214 8.2866 4.85826 6.54167 8.99762C4.79674 13.137 5.2254 17.6462 7.31038 21.2574Z" fill="url(#paint3_radial_115_86)"/>
<path d="M16.6025 13.2251C14.8642 17.349 11.3512 20.1866 7.32411 21.2248C5.25257 17.624 4.82926 13.1324 6.56764 9.00855C8.30603 4.88472 11.819 2.04706 15.8461 1.00889C17.9176 4.60967 18.3409 9.10131 16.6025 13.2251Z" stroke="url(#paint4_linear_115_86)" stroke-opacity="0.05" stroke-width="0.056338"/>
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="white"/>
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="url(#paint5_radial_115_86)"/>
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="black" fill-opacity="0.2" style="mix-blend-mode:hard-light"/>
<path d="M7.23368 21.2069C9.78906 23.2373 13.2102 23.9506 16.5772 22.8141C19.9441 21.6775 22.5058 18.9445 23.7304 15.6382C21.175 13.6078 17.7538 12.8944 14.3869 14.031C11.0199 15.1676 8.45822 17.9006 7.23368 21.2069Z" fill="url(#paint6_linear_115_86)" fill-opacity="0.5" style="mix-blend-mode:hard-light"/>
<path d="M16.5682 22.7874C13.2176 23.9184 9.81361 23.2124 7.2672 21.1975C8.49194 17.9068 11.0444 15.189 14.3959 14.0577C17.7465 12.9266 21.1504 13.6326 23.6968 15.6476C22.4721 18.9383 19.9196 21.656 16.5682 22.7874Z" stroke="url(#paint7_linear_115_86)" stroke-opacity="0.05" stroke-width="0.056338"/>
<path d="M34.2124 19V5.4H39.4924L41.6924 12.2L42.3724 14.74L43.0524 12.2L45.2524 5.4H50.4124V19H46.3324L46.5924 9.98L45.5324 13.68L43.7924 19H40.8324L39.0524 13.6L38.0324 10.02L38.2924 19H34.2124ZM52.4155 7.3V4.6H56.2955V7.3H52.4155ZM52.4155 19V8.14H56.2955V19H52.4155ZM58.1038 19V8.14H61.9838V9.58C62.6638 8.34 63.7438 7.76 65.0038 7.76C66.9638 7.76 68.6238 8.98 68.6238 11.78V19H64.7438V12.56C64.7438 11.34 64.3038 10.86 63.4838 10.86C62.6038 10.86 61.9838 11.58 61.9838 12.88V19H58.1038ZM70.9327 15.22V11.06H69.7327V8.14H70.9327V5.62H74.8127V8.14H76.9327V11.06H74.8127V14.6C74.8127 15.5 75.0327 16.06 76.2127 16.06H76.9327V19C76.4927 19.2 75.6727 19.38 74.6527 19.38C72.1527 19.38 70.9327 17.88 70.9327 15.22Z" fill="#001E13"/>
<path d="M87.232 10.519C87.232 13.687 94.1125 11.2285 94.1125 15.832C94.1125 17.9935 92.3635 19.198 89.971 19.198C87.562 19.198 85.912 18.0925 85.417 15.832H87.001C87.364 17.1685 88.3705 17.8945 89.9875 17.8945C91.6705 17.8945 92.5615 17.152 92.5615 16.03C92.5615 12.598 85.681 15.1555 85.681 10.618C85.681 9.001 87.034 7.582 89.509 7.582C91.6705 7.582 93.403 8.6215 93.8155 11.014H92.215C91.8685 9.529 90.9115 8.8855 89.476 8.8855C88.057 8.8855 87.232 9.529 87.232 10.519ZM96.2499 16.4755V11.3935H95.0289V10.2385H96.2499V8.2255H97.7019V10.2385H99.6324V11.3935H97.7019V16.4755C97.7019 17.5315 98.0154 18.0265 99.3024 18.0265H99.5994V19.066C99.4344 19.1485 99.0714 19.198 98.6589 19.198C97.0254 19.198 96.2499 18.3235 96.2499 16.4755ZM102.516 13.093H101.064C101.345 11.1625 102.615 10.024 104.76 10.024C107.103 10.024 108.242 11.3935 108.242 13.4395V16.888C108.242 17.8945 108.324 18.5215 108.555 19H107.021C106.856 18.6535 106.806 18.142 106.79 17.614C106.047 18.7195 104.859 19.198 103.803 19.198C101.988 19.198 100.767 18.3565 100.767 16.69C100.767 15.4855 101.427 14.611 102.714 14.182C103.902 13.786 105.107 13.687 106.79 13.6705V13.4725C106.79 12.0535 106.13 11.278 104.628 11.278C103.374 11.278 102.698 11.971 102.516 13.093ZM102.252 16.657C102.252 17.4655 102.929 17.944 103.952 17.944C105.569 17.944 106.79 16.6735 106.79 15.172V14.7595C103.061 14.7925 102.252 15.5845 102.252 16.657ZM110.787 19V10.2385H112.239V11.5915C112.833 10.519 113.774 10.024 114.83 10.024C115.176 10.024 115.49 10.1065 115.655 10.2385V11.542C115.407 11.4595 115.094 11.4265 114.747 11.4265C112.998 11.4265 112.239 12.5155 112.239 14.0995V19H110.787ZM117.305 16.4755V11.3935H116.084V10.2385H117.305V8.2255H118.757V10.2385H120.688V11.3935H118.757V16.4755C118.757 17.5315 119.071 18.0265 120.358 18.0265H120.655V19.066C120.49 19.1485 120.127 19.198 119.714 19.198C118.081 19.198 117.305 18.3235 117.305 16.4755ZM129.809 16.1455C129.33 18.1915 127.862 19.198 125.865 19.198C123.324 19.198 121.79 17.482 121.79 14.6275C121.79 11.6575 123.324 10.024 125.783 10.024C128.258 10.024 129.743 11.7235 129.743 14.512V14.875H123.275C123.357 16.8385 124.281 17.944 125.865 17.944C127.103 17.944 127.977 17.35 128.291 16.1455H129.809ZM125.783 11.278C124.38 11.278 123.539 12.1525 123.324 13.786H128.225C128.027 12.169 127.152 11.278 125.783 11.278ZM131.843 19V10.2385H133.295V11.5915C133.889 10.519 134.829 10.024 135.885 10.024C136.232 10.024 136.545 10.1065 136.71 10.2385V11.542C136.463 11.4595 136.149 11.4265 135.803 11.4265C134.054 11.4265 133.295 12.5155 133.295 14.0995V19H131.843ZM141.763 19V7.78H143.281V13.192L148.413 7.78H150.327L145.047 13.291L150.459 19H148.413L143.281 13.621V19H141.763ZM152.06 9.067V7.12H153.512V9.067H152.06ZM152.06 19V10.2385H153.512V19H152.06ZM156.178 16.4755V11.3935H154.957V10.2385H156.178V8.2255H157.63V10.2385H159.56V11.3935H157.63V16.4755C157.63 17.5315 157.943 18.0265 159.23 18.0265H159.527V19.066C159.362 19.1485 158.999 19.198 158.587 19.198C156.953 19.198 156.178 18.3235 156.178 16.4755Z" fill="#002719" fill-opacity="0.6"/>
<defs>
<radialGradient id="paint0_radial_115_86" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-3.00503 15.023) rotate(-10.029) scale(17.9572 17.784)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint1_linear_115_86" x1="7.39036" y1="4.81308" x2="1.62975" y2="18.6894" gradientUnits="userSpaceOnUse">
<stop stop-color="#18E299"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint2_linear_115_86" x1="7.94816" y1="8.01562" x2="1.7612" y2="18.746" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint3_radial_115_86" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.11404 20.8822) rotate(-75.7542) scale(21.6246 23.7772)">
<stop stop-color="#00BBBB"/>
<stop offset="0.712616" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint4_linear_115_86" x1="7.60205" y1="5.8709" x2="15.5561" y2="16.3719" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<radialGradient id="paint5_radial_115_86" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(7.84537 21.5181) rotate(-20.3525) scale(18.5603 17.32)">
<stop stop-color="#00B0BB"/>
<stop offset="1" stop-color="#00DB65"/>
</radialGradient>
<linearGradient id="paint6_linear_115_86" x1="16.8078" y1="13.0071" x2="10.0409" y2="22.9937" gradientUnits="userSpaceOnUse">
<stop stop-color="#00B1BC"/>
<stop offset="1"/>
</linearGradient>
<linearGradient id="paint7_linear_115_86" x1="16.8078" y1="13.0071" x2="14.1687" y2="23.841" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

19
docs/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "honcho-docs",
"version": "1.0.0",
"description": "## Setting Up `honcho-docs` Locally",
"main": ".pnp.js",
"scripts": {
"dev": "mint dev",
"openapi": "npx @mintlify/scraping openapi-file v3/openapi.json -o v3/api-reference/endpoint",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@mintlify/scraping": "^4.0.467"
},
"devDependencies": {
"mint": "^4.2.204"
}
}

Some files were not shown because too many files have changed in this diff Show More