Files
Skald-Circle/docs/plugins/honcho.md
T
2026-07-10 15:02:09 +01:00

9.2 KiB

Honcho Memory Plugin

Plugin: crates/plugin-honcho/src/lib.rs HTTP client: crates/honcho-client/ (separate workspace crate)


Purpose

Streams completed chat turns to a Honcho server so that it can extract long-term conclusions about the user (write path), and reads that context back into every LLM turn via the Memory trait (read path).


Self-hosted Docker package

A ready-to-run Docker Compose setup is in the honcho/ folder at the project root. It starts four services: the Honcho API, the deriver background worker, PostgreSQL + pgvector, and Redis.

Quick start:

cd honcho
cp .env.example .env
# Edit .env — set at least LLM_OPENAI_API_KEY=sk-...
docker compose up -d
# API available at http://localhost:8000

Full instructions, LLM provider options (OpenAI, OpenRouter, Ollama), and troubleshooting are in honcho/README.md.


Setup

  1. Start the Honcho server (see above).
  2. Enable the plugin via the agent or REST API:
    PUT /api/plugins/honcho
    {
      "enabled": true,
      "config": {
        "base_url":     "http://localhost:8000",
        "api_key":      "",
        "workspace_id": "personal-agent"
      }
    }
    
    Or ask the main agent: "enable the honcho plugin".

Configuration

Stored in the plugins SQLite table (config JSON blob). Managed at runtime — no entry in config.yml.

Field Type Default Description
base_url string http://localhost:8000 Honcho server URL
api_key string (empty) API key; leave empty for local/unauthenticated instances
workspace_id string personal-agent Honcho workspace identifier for this agent instance

Honcho Object Model

workspace  (workspace_id from config — one per agent instance)
├── peer  "user"       observe_others=true
├── peer  "assistant"  observe_me=true
└── session            one per local chat_sessions.id
    ├── message  peer_id="user"
    ├── message  peer_id="assistant"
    └── …

Workspace and peers are created (idempotently) each time the plugin starts. If they already exist, the API returns an error which is logged at WARN/DEBUG and ignored.

Sessions are created lazily on the first event for a new chat_sessions.id, then cached in memory for the life of the listener task. The Honcho session UUID is stored in the session cache but not persisted to SQLite — restarting the plugin creates new Honcho sessions for subsequent events.


Event Filtering

An event is forwarded only when all of the following conditions hold:

Condition Reason
is_interactive = true A real user is in the conversation
is_ephemeral = false Not a short-lived automated session (cron, tic)
is_synthetic = false Message content was typed by the user, not injected by the system
role is User or Assistant Sub-agent messages (Agent role) are skipped
content is non-empty Guard against empty strings

Lifecycle

  1. start() — subscribes to skald.event_bus, calls ensure_workspace_ready (best-effort), then spawns the listener task.
  2. Listener tasktokio::select! loop on the bus receiver and a CancellationToken. On RecvError::Lagged, logs a warning and continues (some turns are missed but the task stays alive).
  3. stop() — cancels the token and awaits the task.
  4. reload() — follows the standard plugin pattern: start/stop/restart-on-change.

Error Handling

All Honcho API errors are fire-and-forget: logged as warn! and never propagated to the session handler or the user. A Honcho outage has zero impact on chat functionality.

HonchoError::Request's Display walks the full source() chain, so transport failures surface the real cause in logs (e.g. Request failed: error sending request for url (...): Connection reset by peer) instead of just reqwest's opaque top-line. This makes host↔container issues (e.g. a stale Docker Desktop port-forward after a container recreation) diagnosable from the warn! alone.


Read Path

HonchoMemory implements the Memory trait. Before each LLM turn, query_context is called automatically by ChatSessionHandler::handle_message — for all session types: interactive, cron, and tic.

Flow

  1. Checks is_available() — returns None immediately if the plugin is stopped.
  2. Looks up the Honcho session UUID for the local session_id in the shared session_map.
  3. If a mapping exists (interactive session with at least one turn written):
    • Calls client.session_context(workspace_id, honcho_session_id, tokens=2000, search_query=user_msg).
    • Returns the formatted result on success.
    • On error: logs warn! and falls through to the peer-context fallback without search_query (avoids a second embedding of the same user message — session_context already embedded it before failing).
  4. Fallback — peer_context("user") (no mapping, or session_context error):
    • Cold start / cron / tic (no session_map entry): calls with search_query=user_msg for relevance.
    • After a session_context failure: calls without search_query to avoid double-embedding.
    • Returns global user knowledge derived from all sessions Honcho has observed.
    • On error: logs warn! and returns None.

The formatted context is prepended to extra_system_context and injected into the system prompt. Errors are never propagated — they degrade gracefully to None.

Context format

format_context() extracts, in priority order:

  1. conclusions[].content → "Known facts about the user: …"
  2. summary → "Conversation summary: …"
  3. Fallback: pretty-printed raw JSON

The result is wrapped in --- Honcho memory context --- / --- end --- markers.


LLM-callable Tools

HonchoMemory::tools() returns five tools whenever the plugin is active (is_available() true). They give the LLM direct, on-demand access to every layer of Honcho's API, complementing the automatic pre-turn query_context injection. All operate on the user peer and are inherited by sub-agents via AgentRunConfig::memory_tools.

The official Honcho documentation recommends exposing these as tools so the agent decides on its own when to read or write memory, rather than only relying on automatic injection.

Tool Endpoint Cost What it does
memory_query POST .../peers/user/chat High (LLM synthesis) Natural-language question → synthesized answer (dialectic reasoning, reasoning_level=low)
honcho_search GET .../peers/user/context?search_query=… Low Semantic search over derived facts; returns raw ranked excerpts (with ids when present)
honcho_context GET .../peers/user/context Low Full context snapshot (conclusions + summary), no synthesis; optional focus query
honcho_profile GET/PUT .../peers/user/card Low Read the peer card, or overwrite it with a list of fact strings (card)
honcho_conclude POST .../conclusions / DELETE .../conclusions/{id} Low Write a new fact (conclusion) or delete one by id (delete_id); exactly one required

Peer model — all tools operate on the user peer as both observer and observed. This plugin configures the user peer with observe_me = true, so the user's self-knowledge lives in the observer = user / observed = user slot. Therefore honcho_conclude writes with observer_id = observed_id = user, and honcho_search uses peer_context (not the conclusions/query endpoint, which requires explicit observer/observed filters) — the same proven path as the automatic read-path injection. This differs from setups where the assistant observes the user (observer = assistant); keeping observer = user is what lets the read-path see facts written by honcho_conclude.

When to use vs. the automatic injection:

Mechanism When it fires Best for
query_context (auto) Before every LLM turn Background context, cold-start facts
memory_query (tool) LLM calls it explicitly On-demand deep reasoning mid-conversation
honcho_search / honcho_context (tools) LLM calls them explicitly Cheap raw recall without LLM synthesis
honcho_profile / honcho_conclude (tools) LLM calls them explicitly Actively curating long-term memory

Implementation note: Tool::execute is synchronous but the Honcho calls are async. All five tools share the run_blocking helper, which uses tokio::task::block_in_place + Handle::current().block_on(...) to drive the future from within the Tokio multi-thread scheduler without spawning a new thread.


Future Work

  • Session persistence — store the Honcho session UUID in a new chat_sessions.honcho_session_id column so the mapping survives a plugin restart.

When to Update This File

  • Config fields change
  • Honcho object model or peer setup changes
  • Filtering rules change
  • query_context flow changes (session vs peer fallback logic)
  • Docker Compose setup in honcho/ changes significantly
  • Public API of crates/honcho-client/ changes