v3.0.11 Release Candidate (#841)
* chore(docs): Release Candidate Changelog and Version Updates * chore: fix basedpyright error
This commit is contained in:
parent
810e25c487
commit
60a15e664d
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -5,20 +5,30 @@ 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/).
|
||||
|
||||
## [Unreleased]
|
||||
## [3.0.11] - 2026-06-24
|
||||
|
||||
### Added
|
||||
|
||||
- `DERIVER_REPRESENTATION_BATCH_MAX_AGE_SECONDS` (default 1800s) lets sub-threshold representation work units flush once their oldest unprocessed queue item ages out. Set it to `0` to keep the legacy behavior where sub-threshold tails wait indefinitely unless `DERIVER_FLUSH_ENABLED=true`.
|
||||
- `api_request_duration_seconds` Prometheus histogram tracking per-route request latency, labeled by method and endpoint (#837)
|
||||
- LLM `provider_params` passthroughs (`extra_body` / `extra_headers` / `extra_query`) are now forwarded to the underlying provider transport across all backends, with shape validation that rejects non-mapping values (#821)
|
||||
- `structured_output_mode` model-config option to use `json_object` mode for OpenAI-compatible providers that lack native Structured Outputs support (used by the deriver) (#820)
|
||||
- OpenRouter app-attribution headers (`HTTP-Referer` / `X-Openrouter-Title`) are now sent on OpenAI-compatible clients when the configured base URL is OpenRouter, so requests are attributed to "Honcho" in OpenRouter's dashboard (#805)
|
||||
- Langfuse traces are now tagged with user and session IDs for easier trace filtering (#814)
|
||||
- `DERIVER_REPRESENTATION_BATCH_MAX_AGE_SECONDS` (default 1800s) lets sub-threshold representation work units flush once their oldest unprocessed queue item ages out. Set it to `0` to keep the legacy behavior where sub-threshold tails wait indefinitely unless `DERIVER_FLUSH_ENABLED=true` (#826)
|
||||
|
||||
### Changed
|
||||
|
||||
- Peer-scoped JWTs now get read-only access to the sessions their peer is an active member of (session context, summaries, peers, their own per-session config, search, and message reads). Session-scoped JWTs remain confined to their session and cannot reach peer routes.
|
||||
- Peer-scoped JWTs now get read-only access to the sessions their peer is an active member of (session context, summaries, peers, their own per-session config, search, and message reads). Session-scoped JWTs remain confined to their session and cannot reach peer routes (#679)
|
||||
- Compacted Honcho's log output, with guarded ms/s metric formatting that falls back to a plain string for non-numeric values (#836)
|
||||
- Sentry now drops noisy infra/scrape transactions: the reconciler opens a transaction only once a batch has rows (idle cycles emit none), and a `traces_sampler` returns `0.0` for `/metrics`, `/health`, `/openapi.json`, `/docs`, `/redoc`, and the deriver metrics server. `SENTRY.TRACES_SAMPLE_RATE` still governs real traffic (#834)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Peer- and session-scoped JWTs were effectively workspace-scoped: authorization walked the route's declared scope and fell through to a workspace match, so a `{w, p: alice}` token could act on any peer in the workspace. JWTs are now authorized by their narrowest claim and never widen to workspace access.
|
||||
- The keys API now rejects creating a peer- or session-scoped key without a workspace. Such keys were minted successfully but failed verification on every request.
|
||||
- Peer- and session-scoped JWTs were effectively workspace-scoped: authorization walked the route's declared scope and fell through to a workspace match, so a `{w, p: alice}` token could act on any peer in the workspace. JWTs are now authorized by their narrowest claim and never widen to workspace access (#679)
|
||||
- The keys API now rejects creating a peer- or session-scoped key without a workspace. Such keys were minted successfully but failed verification on every request (#679)
|
||||
- Agent-supplied observation IDs carrying the display-format `id:` prefix are now normalized (prefix and trailing whitespace stripped) before `source_ids` are stored and on `get_reasoning_chain` lookups, fixing corrupted provenance links and broken reasoning-chain traversal (#795)
|
||||
- Fixed a `create_tree` keyword-argument mismatch in the Dreamer's surprisal tree construction (#749)
|
||||
- Providers that omit output-token counts (observed with Gemini on tool-loop completions) returned `output_tokens=None`, which raised a Pydantic validation error that aborted the call and crashed the Dreamer's induction phase before inductive conclusions were persisted. `None` is now coerced to `0` so token accounting degrades gracefully (#809)
|
||||
|
||||
## [3.0.10] - 2026-06-15
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ This guide helps you match the right SDK version to your Honcho API version. New
|
|||
|
||||
| Honcho API Version | TypeScript SDK | Python SDK |
|
||||
|-------------------|---------------|------------|
|
||||
| v3.0.9 (Current) | v2.1.2 | v2.1.2 |
|
||||
| v3.0.11 (Current) | v2.1.2 | v2.1.2 |
|
||||
| v3.0.10 | v2.1.2 | v2.1.2 |
|
||||
| v3.0.9 | v2.1.2 | v2.1.2 |
|
||||
| v3.0.8 | v2.1.2 | v2.1.2 |
|
||||
| v3.0.7 | v2.1.2 | v2.1.2 |
|
||||
| v3.0.6 | v2.1.1 | v2.1.1 |
|
||||
|
|
|
|||
|
|
@ -27,7 +27,32 @@ Welcome to the Honcho changelog! This section documents all notable changes to t
|
|||
### Honcho API and SDK Changelogs
|
||||
<Tabs>
|
||||
<Tab title="Honcho API">
|
||||
<Update label="v3.0.10 (Current)">
|
||||
<Update label="v3.0.11 (Current)">
|
||||
### Added
|
||||
|
||||
- `api_request_duration_seconds` Prometheus histogram tracking per-route request latency, labeled by method and endpoint (#837)
|
||||
- LLM `provider_params` passthroughs (`extra_body` / `extra_headers` / `extra_query`) are now forwarded to the underlying provider transport across all backends, with shape validation that rejects non-mapping values (#821)
|
||||
- `structured_output_mode` model-config option to use `json_object` mode for OpenAI-compatible providers that lack native Structured Outputs support (used by the deriver) (#820)
|
||||
- OpenRouter app-attribution headers (`HTTP-Referer` / `X-Openrouter-Title`) are now sent on OpenAI-compatible clients when the configured base URL is OpenRouter, so requests are attributed to "Honcho" in OpenRouter's dashboard (#805)
|
||||
- Langfuse traces are now tagged with user and session IDs for easier trace filtering (#814)
|
||||
- `DERIVER_REPRESENTATION_BATCH_MAX_AGE_SECONDS` (default 1800s) lets sub-threshold representation work units flush once their oldest unprocessed queue item ages out. Set it to `0` to keep the legacy behavior where sub-threshold tails wait indefinitely unless `DERIVER_FLUSH_ENABLED=true` (#826)
|
||||
|
||||
### Changed
|
||||
|
||||
- Peer-scoped JWTs now get read-only access to the sessions their peer is an active member of (session context, summaries, peers, their own per-session config, search, and message reads). Session-scoped JWTs remain confined to their session and cannot reach peer routes (#679)
|
||||
- Compacted Honcho's log output, with guarded ms/s metric formatting that falls back to a plain string for non-numeric values (#836)
|
||||
- Sentry now drops noisy infra/scrape transactions: the reconciler opens a transaction only once a batch has rows (idle cycles emit none), and a `traces_sampler` returns `0.0` for `/metrics`, `/health`, `/openapi.json`, `/docs`, `/redoc`, and the deriver metrics server. `SENTRY.TRACES_SAMPLE_RATE` still governs real traffic (#834)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Peer- and session-scoped JWTs were effectively workspace-scoped: authorization walked the route's declared scope and fell through to a workspace match, so a `{w, p: alice}` token could act on any peer in the workspace. JWTs are now authorized by their narrowest claim and never widen to workspace access (#679)
|
||||
- The keys API now rejects creating a peer- or session-scoped key without a workspace. Such keys were minted successfully but failed verification on every request (#679)
|
||||
- Agent-supplied observation IDs carrying the display-format `id:` prefix are now normalized (prefix and trailing whitespace stripped) before `source_ids` are stored and on `get_reasoning_chain` lookups, fixing corrupted provenance links and broken reasoning-chain traversal (#795)
|
||||
- Fixed a `create_tree` keyword-argument mismatch in the Dreamer's surprisal tree construction (#749)
|
||||
- Providers that omit output-token counts (observed with Gemini on tool-loop completions) returned `output_tokens=None`, which raised a Pydantic validation error that aborted the call and crashed the Dreamer's induction phase before inductive conclusions were persisted. `None` is now coerced to `0` so token accounting degrades gracefully (#809)
|
||||
</Update>
|
||||
|
||||
<Update label="v3.0.10">
|
||||
### Added
|
||||
|
||||
- Messages are now embedded via a background task rather than blocking API request
|
||||
|
|
@ -671,7 +696,7 @@ Welcome to the Honcho changelog! This section documents all notable changes to t
|
|||
|
||||
<Tab title="Python SDK">
|
||||
[Python SDK](https://pypi.org/project/honcho-ai/)
|
||||
<Update label="v2.1.2 (Current)">
|
||||
<Update label="v2.1.2">
|
||||
### 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.
|
||||
|
|
@ -823,7 +848,7 @@ Welcome to the Honcho changelog! This section documents all notable changes to t
|
|||
|
||||
<Tab title="TypeScript SDK">
|
||||
[TypeScript SDK](https://www.npmjs.com/package/@honcho-ai/sdk)
|
||||
<Update label="v2.1.2 (Current)">
|
||||
<Update label="v2.1.2">
|
||||
### 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).
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
"navigation": {
|
||||
"versions": [
|
||||
{
|
||||
"version": "v3.0.10",
|
||||
"version": "v3.0.11",
|
||||
"api": {
|
||||
"openapi": ["v3/openapi.json"]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "honcho"
|
||||
version = "3.0.10"
|
||||
version = "3.0.11"
|
||||
description = "Honcho Server"
|
||||
authors = [
|
||||
{name = "Plastic Labs", email = "hello@plasticlabs.ai"},
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ This script helps update version numbers across the Honcho repository.
|
|||
It handles the main API, Python SDK, and TypeScript SDK in a single operation.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
|
@ -17,11 +18,11 @@ from datetime import datetime
|
|||
|
||||
class VersionUpdater:
|
||||
def __init__(self, base_path: str):
|
||||
self.base_path = base_path
|
||||
self.base_path: str = base_path
|
||||
|
||||
def get_current_versions(self) -> dict[str, str]:
|
||||
"""Get current version numbers from the repository."""
|
||||
versions = {}
|
||||
versions: dict[str, str] = {}
|
||||
|
||||
# Main API version
|
||||
with open(os.path.join(self.base_path, "pyproject.toml")) as f:
|
||||
|
|
@ -89,7 +90,7 @@ TYPESCRIPT_VERSION=
|
|||
os.unlink(temp_file)
|
||||
|
||||
# Extract all versions and changelogs
|
||||
updates = {}
|
||||
updates: dict[str, dict[str, str]] = {}
|
||||
|
||||
# Parse API version
|
||||
api_match = re.search(r"^API_VERSION=(.*)$", content, re.MULTILINE)
|
||||
|
|
@ -131,7 +132,7 @@ TYPESCRIPT_VERSION=
|
|||
) -> str:
|
||||
"""Extract changelog content between markers."""
|
||||
lines = content.split("\n")
|
||||
changelog_lines = []
|
||||
changelog_lines: list[str] = []
|
||||
in_section = False
|
||||
|
||||
for line in lines:
|
||||
|
|
@ -158,8 +159,8 @@ TYPESCRIPT_VERSION=
|
|||
"""Remove empty changelog sections."""
|
||||
sections = ["Added", "Changed", "Fixed", "Deprecated", "Removed", "Security"]
|
||||
lines = changelog.split("\n")
|
||||
cleaned_lines = []
|
||||
current_section = None
|
||||
cleaned_lines: list[str] = []
|
||||
current_section: str | None = None
|
||||
section_has_content = False
|
||||
section_start_idx = -1
|
||||
for i, line in enumerate(lines):
|
||||
|
|
@ -346,28 +347,30 @@ TYPESCRIPT_VERSION=
|
|||
self._update_compatibility_guide("typescript", new_version)
|
||||
|
||||
def _update_docs_json(self, new_version: str):
|
||||
"""Update docs.json - only update versions with same major version."""
|
||||
"""Update docs.json version label(s) sharing the new version's major.
|
||||
|
||||
Uses a targeted regex replacement rather than a JSON round-trip so the
|
||||
file's existing formatting (compact inline arrays) is preserved instead
|
||||
of being reflowed.
|
||||
"""
|
||||
file_path = os.path.join(self.base_path, "docs/docs.json")
|
||||
|
||||
with open(file_path) as f:
|
||||
data = json.load(f)
|
||||
content = f.read()
|
||||
|
||||
# Get major version of new version
|
||||
new_major = new_version.split(".")[0]
|
||||
|
||||
# Update only matching major versions
|
||||
if "navigation" in data and "versions" in data["navigation"]:
|
||||
for version_entry in data["navigation"]["versions"]:
|
||||
if "version" in version_entry:
|
||||
current_version = version_entry["version"].lstrip("v")
|
||||
current_major = current_version.split(".")[0]
|
||||
def _replace(match: re.Match[str]) -> str:
|
||||
# Only update labels whose major version matches the new version's.
|
||||
if match.group(1) == new_major:
|
||||
return f'"version": "v{new_version}"'
|
||||
return match.group(0)
|
||||
|
||||
if current_major == new_major:
|
||||
version_entry["version"] = f"v{new_version}"
|
||||
content = re.sub(r'"version": "v(\d+)\.\d+\.\d+"', _replace, content)
|
||||
|
||||
with open(file_path, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
f.write("\n")
|
||||
f.write(content)
|
||||
|
||||
def _update_sdk_changelog(self, version: str, changelog: str, relative_path: str):
|
||||
"""Update an SDK's CHANGELOG.md file."""
|
||||
|
|
@ -405,12 +408,39 @@ TYPESCRIPT_VERSION=
|
|||
f.write(new_content)
|
||||
|
||||
def _update_changelog_md(self, version: str, changelog: str):
|
||||
"""Update the main CHANGELOG.md file."""
|
||||
"""Update the main CHANGELOG.md file.
|
||||
|
||||
If an ``## [Unreleased]`` section is present, it is promoted to the new
|
||||
version (its contents replaced by ``changelog``, which the caller is
|
||||
expected to have already merged). Otherwise a new version entry is
|
||||
prepended above the most recent release, preserving the legacy behavior.
|
||||
"""
|
||||
file_path = os.path.join(self.base_path, "CHANGELOG.md")
|
||||
|
||||
with open(file_path) as f:
|
||||
content = f.read()
|
||||
|
||||
date = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
# Ensure changelog content is properly formatted
|
||||
if changelog.strip():
|
||||
formatted_changelog = changelog.strip()
|
||||
else:
|
||||
formatted_changelog = "### Changed\n\n- Updated version"
|
||||
|
||||
# Promote an existing [Unreleased] section if one exists. Match from the
|
||||
# "## [Unreleased]" header up to (but not including) the next release
|
||||
# heading, and replace the whole block with the new version section.
|
||||
unreleased_re = re.compile(
|
||||
r"\n## \[Unreleased\][\s\S]*?(?=\n## \[)", re.IGNORECASE
|
||||
)
|
||||
if unreleased_re.search(content):
|
||||
replacement = f"\n## [{version}] - {date}\n\n{formatted_changelog}\n"
|
||||
new_content = unreleased_re.sub(replacement, content, count=1)
|
||||
with open(file_path, "w") as f:
|
||||
f.write(new_content)
|
||||
return
|
||||
|
||||
# Find the position after the header
|
||||
header_end = content.find("\n## [")
|
||||
if header_end == -1:
|
||||
|
|
@ -420,15 +450,6 @@ TYPESCRIPT_VERSION=
|
|||
# No existing entries, add after title
|
||||
header_end = content.find("\n", content.find("# Changelog"))
|
||||
|
||||
# Create new entry with proper formatting
|
||||
date = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
# Ensure changelog content is properly formatted
|
||||
if changelog.strip():
|
||||
formatted_changelog = changelog.strip()
|
||||
else:
|
||||
formatted_changelog = "### Changed\n\n- Updated version"
|
||||
|
||||
new_entry = f"\n\n## [{version}] - {date}\n\n{formatted_changelog}\n"
|
||||
|
||||
# Insert the new entry
|
||||
|
|
@ -624,7 +645,68 @@ TYPESCRIPT_VERSION=
|
|||
f.write(content)
|
||||
|
||||
|
||||
def _resolve_changelog(value: str | None) -> str:
|
||||
"""Resolve a changelog argument that is either inline text or a file path."""
|
||||
if not value:
|
||||
return ""
|
||||
if os.path.isfile(value):
|
||||
with open(value) as f:
|
||||
return f.read().strip()
|
||||
return value.strip()
|
||||
|
||||
|
||||
def _updates_from_args(args: argparse.Namespace) -> dict[str, dict[str, str]]:
|
||||
"""Build the updates dict from CLI flags (headless mode)."""
|
||||
updates: dict[str, dict[str, str]] = {}
|
||||
if args.api_version:
|
||||
updates["api"] = {
|
||||
"version": args.api_version,
|
||||
"changelog": _resolve_changelog(args.api_changelog),
|
||||
}
|
||||
if args.python_version:
|
||||
updates["python_sdk"] = {
|
||||
"version": args.python_version,
|
||||
"changelog": _resolve_changelog(args.python_changelog),
|
||||
}
|
||||
if args.typescript_version:
|
||||
updates["typescript_sdk"] = {
|
||||
"version": args.typescript_version,
|
||||
"changelog": _resolve_changelog(args.typescript_changelog),
|
||||
}
|
||||
return updates
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Update Honcho version numbers and changelogs. With no version "
|
||||
"flags, opens an interactive editor; pass one or more --*-version "
|
||||
"flags to run headless (agent-friendly)."
|
||||
)
|
||||
)
|
||||
parser.add_argument("--api-version", help="New Main API version.")
|
||||
parser.add_argument("--python-version", help="New Python SDK version.")
|
||||
parser.add_argument("--typescript-version", help="New TypeScript SDK version.")
|
||||
parser.add_argument(
|
||||
"--api-changelog",
|
||||
help="API changelog markdown, or a path to a file containing it.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--python-changelog",
|
||||
help="Python SDK changelog markdown, or a path to a file containing it.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--typescript-changelog",
|
||||
help="TypeScript SDK changelog markdown, or a path to a file containing it.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-y",
|
||||
"--yes",
|
||||
action="store_true",
|
||||
help="Skip the confirmation prompt (implied in headless mode).",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get the parent directory of the scripts folder (the project root)
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
base_path = os.path.dirname(script_dir)
|
||||
|
|
@ -633,6 +715,8 @@ def main():
|
|||
# Get current versions
|
||||
current_versions = updater.get_current_versions()
|
||||
|
||||
headless = any([args.api_version, args.python_version, args.typescript_version])
|
||||
|
||||
print("Honcho Version Updater")
|
||||
print("=" * 50)
|
||||
print("\nCurrent versions:")
|
||||
|
|
@ -640,12 +724,15 @@ def main():
|
|||
print(f" Python SDK: {current_versions['python_sdk']}")
|
||||
print(f" TypeScript SDK: {current_versions['typescript_sdk']}")
|
||||
print()
|
||||
print("Opening editor for version updates...")
|
||||
print("Leave version fields blank to skip updating that component.")
|
||||
print()
|
||||
|
||||
# Get all updates at once
|
||||
updates = updater.get_all_versions_from_editor(current_versions)
|
||||
if headless:
|
||||
updates = _updates_from_args(args)
|
||||
else:
|
||||
print("Opening editor for version updates...")
|
||||
print("Leave version fields blank to skip updating that component.")
|
||||
print()
|
||||
# Get all updates at once
|
||||
updates = updater.get_all_versions_from_editor(current_versions)
|
||||
|
||||
if not updates:
|
||||
print("No versions specified. Exiting...")
|
||||
|
|
@ -661,11 +748,12 @@ def main():
|
|||
}[component]
|
||||
print(f" {component_name}: {current_versions[component]} → {info['version']}")
|
||||
|
||||
# Confirm
|
||||
response = input("\nProceed with updates? (y/n): ").strip().lower()
|
||||
if response != "y":
|
||||
print("Cancelled.")
|
||||
sys.exit(0)
|
||||
# Confirm (skipped in headless mode or with --yes)
|
||||
if not headless and not args.yes:
|
||||
response = input("\nProceed with updates? (y/n): ").strip().lower()
|
||||
if response != "y":
|
||||
print("Cancelled.")
|
||||
sys.exit(0)
|
||||
|
||||
# Apply all updates
|
||||
updater.update_all(updates, current_versions)
|
||||
|
|
@ -673,6 +761,7 @@ def main():
|
|||
print("\nVersion updates complete!")
|
||||
print("\nDon't forget to:")
|
||||
print(" - Review the changes with `git diff`")
|
||||
print(" - Run `uv lock` to refresh the lockfile")
|
||||
print(" - Commit the changes")
|
||||
print(" - Create git tags for the new versions")
|
||||
print(" - Push the changes and tags")
|
||||
|
|
|
|||
4
uv.lock
4
uv.lock
|
|
@ -8,7 +8,7 @@ resolution-markers = [
|
|||
]
|
||||
|
||||
[options]
|
||||
exclude-newer = "2026-06-10T19:45:16.447512Z"
|
||||
exclude-newer = "2026-06-19T14:44:24.535479Z"
|
||||
exclude-newer-span = "P5D"
|
||||
|
||||
[manifest]
|
||||
|
|
@ -1159,7 +1159,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "honcho"
|
||||
version = "3.0.10"
|
||||
version = "3.0.11"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "alembic" },
|
||||
|
|
|
|||
Loading…
Reference in New Issue