Added
- ChromaDB vector store backend: set
VECTOR_STORE_TYPE=chromadband pick a client withVECTOR_STORE_CHROMA_CLIENT_MODE:http(self-hosted Chroma server, the default) orcloud(Chroma Cloud, needsVECTOR_STORE_CHROMA_API_KEY). Embeddedpersistentmode is rejected at startup, since it isn't safe across Honcho's API and deriver processes. Each Honcho namespace gets its own Chroma collection and index. Ships as an optional extra (uv sync --extra chromadb), so default installs and the Docker image don't include it (#868) - Custom instructions reach every agent, not just the deriver. A new top-level
custom_instructionsin workspace and session configuration is the shared fallback, andsummary,dialectic(new section),dream, andpeer_cardeach take their owncustom_instructionsoverride. Message, session, and workspace levels are checked in that order and the first that sets a value wins; within a level the module's value beats the shared one, and""means no instructions.reasoning.custom_instructionsstays deriver-only, andpeer_cardinstructions add to the dream instructions without falling back to the shared value. No migration: configuration is JSONB (#1303) - Telemetry records custom instructions.
representation.completed, the summary event,dialectic.completed, anddream.specialistcarrycustom_instructions_tokensandcustom_instructions_source(e.g.session.shared,workspace.summary);llm.call.tracedlinks the injected text throughcustom_instructions_ref; Langfuse generations carry it under thecustom_instructionsmetadata key. Additive fields, so_schema_versionis unchanged (#1309) python -m src.migrate [revision]runs migrations under a Postgres advisory lock, so replicas that start together no longer race onalembic upgrade(tuple concurrently updated). A waiter polls for up toDB_MIGRATION_LOCK_WAIT_SECONDS(default 300), then fails naming the holder's pid.scripts/migrate_db.pyandscripts/provision_db.pywrap it (#1268)- List endpoints take an opt-in
cursorquery parameter: send it empty for the first page, then pass each response'snext_page. Cursor mode returns a keyset-paginated page with noCOUNTand one index seek per page, so deep walks stay cheap. Withoutcursorthe response is the unchangedPage, and a server that predates this ignores the parameter, so clients can detect support from the response shape. A malformed cursor returns a 400 (#1326)
Changed
- The deriver prompt states the target peer's id as the subject of every conclusion about that peer, instead of "the target peer" or "the user" (#1273)
search_messages(and thesearch_memoryfallback andsearch_messages_temporal) drops whole trailing snippets when its output exceeds the tool budget and appends[N of M snippets shown - K omitted to fit output budget], instead of cutting the text mid-snippet with no notice. Tool-call telemetry reportswas_truncatedand the pre-truncation size (#1265)- Reconciler backfills are registered through a generic
Backfilldefinition, each scheduled asreconciler:backfill.<name>only while it has pending rows. The existing document-sources backfill keeps its queue shape, so pre-3.3 workers sharing the queue still run it during a rolling deploy (#1275) /openapi.jsonno longer hardcodeshttps://api.honcho.devas its first server. Self-hosted/docs"Try it out" and generated clients target the origin they were served from; behind a prefix-stripping proxy, set--root-path(#1103)- Gemini
gemini-embedding-2models embed up to 8192 input tokens instead of being capped at 2048. Other Gemini models keep the 2048 cap (#925) uv.lockbumpsstarlette(1.0.0 → 1.6.0, CVE-2026-48710),python-multipart,anyio, andidnato patched versions, which the Docker image picks up throughuv sync --frozen(#1266)- Session summaries ask for about 60% of their token cap instead of a hard word limit, leaving headroom so dense content finishes instead of being cut off mid-summary. The default caps rise from 1000/4000 to 1500/6000 (
SUMMARY_MAX_TOKENS_SHORT/SUMMARY_MAX_TOKENS_LONG), so default summaries stay about the same length. WhenSUMMARY_MODEL_CONFIG__MAX_OUTPUT_TOKENSis set it is a ceiling: settings fail to load if either cap exceeds it, so a deployment that sets it below 6000 must lower the caps to match or raise it (#1318) - The deriver's semantic deduplication looks up candidates for the whole batch in one query instead of one query per conclusion (#1202)
- With the cache enabled, offset-paginated list requests (
?page=N) seek from where the previous page ended instead of scanning past every earlier row, so installed SDKs walking long lists get faster without an upgrade. Responses are unchanged, except thattotalon pages after the first may be up toCACHE_PAGINATION_COUNT_TTL_SECONDS(default 60) old.CACHE_PAGINATION_OFFSET_SHIM(defaulttrue) turns it off,CACHE_PAGINATION_POSITION_TTL_SECONDS(default 600) sets how long a page position lives, andpagination_offset_shim{outcome}counts first, hit, and miss pages. Adds thesqlakeysetdependency (#1326) - Message search's
content ILIKEfallback is backed by a newpg_trgmGIN index,ix_messages_content_trgm, instead of scanning every message in the workspace (scans held connections for 10s+ on large deployments). This release includes a migration. The index is roughly 1.5× the size ofmessages, so plan disk headroom before upgrading. It is builtCONCURRENTLY, so writes continue, but the build waits for open transactions that touchedmessagesand for any older snapshot in the database, such as a long-running query on any table. After 60 minutes the migration fails and leaves an invalid index, which the next run drops and rebuilds; on large or busy databases, build the index ahead of the upgrade with the statement in the troubleshooting guide, and the migration then has nothing to do. Ifpg_trgmcan't be created (no privilege, or not available on the server), the migration skips the index and logs the statement to create it later (#1335)
Removed
DIALECTIC_HISTORY_TOKEN_LIMITandDREAM_HISTORY_TOKEN_LIMIT, along with the unusedget_recent_historyagent tool they fed. A leftover value is ignored (#1308)
Fixed
- Cloning a session matched the source's messages, peers, and cutoff message by session name alone, so it could copy a same-named session's data from another workspace or fail with a 500. Lookups are scoped to the workspace. Cloned messages also keep
token_count, and cloning a session with no messages copies its peers and commits instead of returning a 201 for a session that was rolled back (#1284) - JWTs with an expiry (
--expiresonscripts/generate_jwt.py,expires_atonPOST /v3/keys) failed every request with a 401, becauseexpwas written as an ISO string instead of a NumericDate. Expiring keys now authenticate until they expire, and an expired key returnsJWT expired(#1025) - OpenAI
gpt-6models failed with a 400unsupported_parameterbecause Honcho sentmax_tokens; they now getmax_completion_tokenslikegpt-5and the o-series (#1233) - A summary that hits its cap while repeating itself (fewer than 35% distinct 4-word sequences) is discarded and the previous summary kept, instead of overwriting it. Rejections are counted in
summary_rejections{summary_type,reason}(#1318) - Session peer and webhook list endpoints had no
ORDER BY, so their pages could skip or repeat rows. They order by(joined_at, id)and(created_at, id)(#1326) - A connection dropped by the pooler during the representation's semantic conclusion lookup no longer fails the request with
PendingRollbackError. On read-only sessions the connection is rolled back and the lookup degrades to no semantic conclusions (#1335)