github plastic-labs/honcho v3.5.0

latest releases: excadrill-pin-fc49ba02, excadrill-pin-2fe6e6ce
3 hours ago

Added

  • The workspace session list can report and filter by scope membership. ?include_scopes=true gives each session a scopes list of the scopes it is in ([] for none), fetched in one query per page; without it the response is unchanged. Body scope lists only that scope's sessions (404 if the scope doesn't exist) and in_no_scope: true lists only sessions in no scope; both combine with filters and pagination, and total counts the filtered result. Clients no longer need to list every scope's members to show which scopes a session is in (#1477)
  • The scope list can filter by name and count each scope's sessions. Body filters takes {"id": {"icontains": "sup"}} and the other list operators, matching the unprefixed scope name (never the internal scope. prefix), plus created_at and metadata. ?include_session_count=true gives each scope a session_count of its active member sessions, counted the same way scopes/{id}/sessions/list lists them, in one query per page; without it the response is unchanged (#1478)
  • POST /scopes/{scope_id}/sessions/remove removes up to 100 sessions from a scope in one request, mirroring bulk add. The batch is all-or-nothing: if any named session doesn't exist it returns 404 and no membership changes. Sessions that aren't members are ignored, and only sessions whose membership actually ended get a scope_removal reconciliation task, so a stray ID can't clear the scope's dream notes. Memberships are locked in session order, so it can't deadlock against a concurrent bulk add of the same sessions (#1480)
  • DIALECTIC_MAX_QUERY_LENGTH ([dialectic] MAX_QUERY_LENGTH) sets the longest peer chat query accepted, in characters, up to 200,000. It defaults to 10,000, the previous fixed limit, so nothing changes unless you raise it; workspace chat keeps its fixed 10,000-character limit. The dialectic's prefetch search now truncates a query over the embedding token limit instead of erroring and silently losing every prefetched conclusion (#1443)
  • Explicit conclusions cite the messages they came from. The deriver emits source_indices per conclusion, resolved against the batch and stored as edges in a new conclusion_source_messages table (DDL-only migration c4d8e2f6a1b3, no backfill: citations are per-derivation, so rows written earlier surface null). Conclusions responses carry source_message_ids (public message ids, citation order; null for derived levels), and source_message_ids joins the documents filter allowlist with the same contains / in / bare-list grammar as source_ids, so POST /conclusions/list answers "which conclusions cite this message?". Cited messages may belong to any peer in the batch, not only the observed one. When a conclusion is derived again, exact or semantic deduplication merges the new citations into the kept conclusion, and copies made when a session joins a scope keep theirs. The batch-level message_ids metadata is unchanged (#935)

Changed

  • Updating a workspace's configuration replaces the stored object instead of merging into it, so {} clears explicit settings and the workspace falls back to system defaults. Before, keys left out of the update were silently kept. Omitting configuration, sending null, or updating only metadata leaves it as it is; peer and session configuration updates still merge. To change one setting, send the full configuration (#1458)
  • Peer chat (the dialectic) answers as a single turn. Answers no longer end with an offer or a question the caller can't reply to ("If you want, I can…", "Which statement is correct?"); contradictions in memory are still reported. A query where a harness wraps the end user's message is no longer answered as if Honcho were the assistant in that conversation. Workspace chat is unchanged (#1451)
  • Streamed peer and workspace chat return the answer the dialectic's tool loop already has, instead of discarding it and making a second, streamed LLM call over the same context. A streamed answer now usually arrives as a single chunk when the loop finishes rather than token by token; only an answer synthesized at the tool-call limit still streams as it is generated. Streaming saves that extra call, returns the same answer as non-streaming, and no longer comes back empty when the extra call returned only thinking (#1443)
  • Groundwork for deleting workspaces, peers, sessions and messages. Migration d8c4f6a2b901 adds a nullable deleted_at column to each (no default, backfill or index), and workspace, peer, session and scope reads skip rows that are tombstoned or whose parent is. Nothing sets deleted_at yet, so behavior is unchanged (#1429, #1450)
  • Langfuse Python SDK upgraded from 4.6.1 to 5.0.2, which sends traces on the Langfuse v4 ingestion path ahead of Langfuse Cloud's v4 cutover on 2026-11-16. Self-hosted Langfuse must be v4.59.0 or later; a v3 server no longer receives Honcho's traces. Observation metadata values are now JSON-encoded on the span ("honcho" rather than honcho), so saved Langfuse metadata filters written against the old shape may need updating. Trace names, user ids and the trace tree are unchanged (#1452)
  • Summaries the provider withholds for their content (finish reason content_filter, safety, …) are no longer reported to Sentry as errors, since nothing on Honcho's side can fix them. They are counted in the summary_rejections metric with reason="content_filtered" instead (#1457)

Fixed

  • Removing a session from a scope and adding it back no longer loses the scope's memory of that session. The removal's cleanup task could run after the re-add (another deriver replica, a retry, a backlog) and soft-delete the session's scope conclusions, including ones derived after the re-add, plus the scope's dream notes and the session's backfill status. The cleanup now locks the membership and skips if the session is a member again. Session and workspace deletion now delete memberships before conclusions, in the order the cleanup takes them, so the two can't deadlock (#1479)
  • Chat, the deriver, summaries and dreams no longer fail when an OpenAI-compatible model returns a usage total but omits the prompt and completion counts. The answer is kept and the missing counts are recorded as zero, instead of the request erroring and being sent again (#1460)
  • Claude 5.x models work as configured models instead of failing over to the fallback. The Anthropic backend sends adaptive thinking with an effort instead of a thinking budget on Claude 4.6+ and 5.x, relaxes a forced tool_choice to auto on models that reject it (Opus and Sonnet 5.5), skips the assistant prefill where it's rejected, and stops replaying earlier thinking blocks once the tool loop has changed the tools or history (#1442)
  • Requests the API can't store or parse return 422 instead of 500: a path ID that fails the name pattern on a get-or-create route, a number outside Postgres's range, NUL or a lone surrogate in a JSON field, a page above 2^31−1, and a malformed or empty PDF upload. NUL is stripped from workspace, peer and message configuration, as it already was from metadata. A numeric metadata filter ({"gte": 0}) no longer fails when the key holds mixed value types; strings that aren't safely numeric drop out of ordered comparisons. Two requests racing to create the same workspace no longer turn the loser into a 409 (#1445)
  • Peer cards written by the agents' update_peer_card tool and session summaries no longer fail to save when the model output contains a NUL character, which Postgres rejected along with the whole write. NUL is stripped first, and a summary that is only NUL is retried like an empty one (#1455)
  • When a message is both a short and a long summary boundary (every long boundary under the default 20/60 cadence), both summaries are now kept. The two saves run concurrently, and each wrote back the whole summaries map it had read, possibly from cache, so the later one could drop the other or revert it to its previous version. Each save now merges only its own entry, on the server (#1493)
  • A summary that fails to fetch or save at such a boundary is no longer dropped without a trace. Only a refused (empty) summary reached the queue; any other failure let the queue item complete with no log line, and that window of messages never reached a later summary. Every failure is now logged with its summary type and raised to the queue, preferring a transient error so the item is retried. On the retry, a summary that was already saved is skipped (#1493)
  • A deriver queue item whose error message contains a NUL or a lone surrogate is now marked errored. Postgres rejected that error text, so the item was never marked and the worker ran it again straight away, LLM call included, over and over. Those characters are now stored escaped (\x00, \ud83d). If marking an item errored fails for any other reason, the worker now releases the work unit and backs off instead of rerunning the item at once (#1492)

Security

  • pypdf moves from 6.18.1 to 6.21.2 for three high-severity advisories where a crafted PDF makes parsing run for a long time (GHSA-gmh7-3g9r-mjh5, GHSA-jr96-gvgh-j3r4, GHSA-6c7f-9265-g8fw). POST .../messages/upload passes any authenticated caller's PDF to the parser. Parsing also moves off the event loop, so a slow PDF holds up only its own upload instead of every request on that API worker (#1476)

Don't miss a new honcho release

NewReleases is sending notifications on new releases.