cantus-agent 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cantus_agent-0.4.2/CHANGELOG.md +983 -0
- cantus_agent-0.4.2/CONTRIBUTING.md +68 -0
- cantus_agent-0.4.2/CONTRIBUTING.zhTW.md +68 -0
- cantus_agent-0.4.2/LICENSE +203 -0
- cantus_agent-0.4.2/MANIFEST.in +16 -0
- cantus_agent-0.4.2/MIGRATION_v0.2_to_v0.3.md +239 -0
- cantus_agent-0.4.2/MIGRATION_v0.3.3_to_v0.3.4.md +96 -0
- cantus_agent-0.4.2/MIGRATION_v0.3.4_to_v0.3.5.md +97 -0
- cantus_agent-0.4.2/MIGRATION_v0.3.5_to_v0.3.6.md +59 -0
- cantus_agent-0.4.2/MIGRATION_v0.3.6_to_v0.4.0.md +95 -0
- cantus_agent-0.4.2/MIGRATION_v0.3_to_v0.3.1.md +164 -0
- cantus_agent-0.4.2/MIGRATION_v0.3_to_v0.3.2.md +147 -0
- cantus_agent-0.4.2/MIGRATION_v0.3_to_v0.3.3.md +145 -0
- cantus_agent-0.4.2/MIGRATION_v0.4.0_to_v0.4.1.md +128 -0
- cantus_agent-0.4.2/MIGRATION_v0.4.1_to_v0.4.2.md +79 -0
- cantus_agent-0.4.2/PKG-INFO +281 -0
- cantus_agent-0.4.2/README.md +210 -0
- cantus_agent-0.4.2/README.zhTW.md +210 -0
- cantus_agent-0.4.2/cantus/__init__.py +131 -0
- cantus_agent-0.4.2/cantus/adapters/__init__.py +135 -0
- cantus_agent-0.4.2/cantus/adapters/_remote_skill.py +72 -0
- cantus_agent-0.4.2/cantus/adapters/anthropic_memory.py +107 -0
- cantus_agent-0.4.2/cantus/adapters/dspy.py +149 -0
- cantus_agent-0.4.2/cantus/adapters/huggingface.py +117 -0
- cantus_agent-0.4.2/cantus/adapters/langchain.py +144 -0
- cantus_agent-0.4.2/cantus/adapters/mcp.py +156 -0
- cantus_agent-0.4.2/cantus/adapters/mcp_client.py +164 -0
- cantus_agent-0.4.2/cantus/adapters/mcp_server.py +141 -0
- cantus_agent-0.4.2/cantus/adapters/openhands.py +50 -0
- cantus_agent-0.4.2/cantus/config.py +73 -0
- cantus_agent-0.4.2/cantus/core/__init__.py +0 -0
- cantus_agent-0.4.2/cantus/core/action.py +38 -0
- cantus_agent-0.4.2/cantus/core/agent.py +420 -0
- cantus_agent-0.4.2/cantus/core/event_stream.py +48 -0
- cantus_agent-0.4.2/cantus/core/event_stream_persistence.py +79 -0
- cantus_agent-0.4.2/cantus/core/observation.py +74 -0
- cantus_agent-0.4.2/cantus/core/registry.py +83 -0
- cantus_agent-0.4.2/cantus/core/result.py +28 -0
- cantus_agent-0.4.2/cantus/env/__init__.py +26 -0
- cantus_agent-0.4.2/cantus/env/cloud_only.py +23 -0
- cantus_agent-0.4.2/cantus/env/colab.py +76 -0
- cantus_agent-0.4.2/cantus/env/local.py +62 -0
- cantus_agent-0.4.2/cantus/grammar/__init__.py +0 -0
- cantus_agent-0.4.2/cantus/grammar/tool_call.py +136 -0
- cantus_agent-0.4.2/cantus/hooks/__init__.py +37 -0
- cantus_agent-0.4.2/cantus/identity/__init__.py +15 -0
- cantus_agent-0.4.2/cantus/identity/soul.py +157 -0
- cantus_agent-0.4.2/cantus/inspect.py +41 -0
- cantus_agent-0.4.2/cantus/model/__init__.py +0 -0
- cantus_agent-0.4.2/cantus/model/bridge.py +46 -0
- cantus_agent-0.4.2/cantus/model/chat.py +96 -0
- cantus_agent-0.4.2/cantus/model/chat_template.py +62 -0
- cantus_agent-0.4.2/cantus/model/factory.py +93 -0
- cantus_agent-0.4.2/cantus/model/loader.py +151 -0
- cantus_agent-0.4.2/cantus/model/providers/__init__.py +6 -0
- cantus_agent-0.4.2/cantus/model/providers/_common.py +26 -0
- cantus_agent-0.4.2/cantus/model/providers/_translate.py +339 -0
- cantus_agent-0.4.2/cantus/model/providers/anthropic.py +88 -0
- cantus_agent-0.4.2/cantus/model/providers/google.py +91 -0
- cantus_agent-0.4.2/cantus/model/providers/groq.py +87 -0
- cantus_agent-0.4.2/cantus/model/providers/nvidia.py +39 -0
- cantus_agent-0.4.2/cantus/model/providers/openai.py +95 -0
- cantus_agent-0.4.2/cantus/protocols/__init__.py +0 -0
- cantus_agent-0.4.2/cantus/protocols/_common.py +72 -0
- cantus_agent-0.4.2/cantus/protocols/analyzer.py +82 -0
- cantus_agent-0.4.2/cantus/protocols/debug.py +76 -0
- cantus_agent-0.4.2/cantus/protocols/memory.py +191 -0
- cantus_agent-0.4.2/cantus/protocols/memory_auto.py +154 -0
- cantus_agent-0.4.2/cantus/protocols/memory_markdown.py +211 -0
- cantus_agent-0.4.2/cantus/protocols/skill.py +138 -0
- cantus_agent-0.4.2/cantus/protocols/validator.py +114 -0
- cantus_agent-0.4.2/cantus/py.typed +0 -0
- cantus_agent-0.4.2/cantus/serve/__init__.py +26 -0
- cantus_agent-0.4.2/cantus/serve/app.py +158 -0
- cantus_agent-0.4.2/cantus/serve/channel.py +64 -0
- cantus_agent-0.4.2/cantus/serve/dashboard.py +144 -0
- cantus_agent-0.4.2/cantus/serve/security.py +144 -0
- cantus_agent-0.4.2/cantus/workflows/__init__.py +24 -0
- cantus_agent-0.4.2/cantus/workflows/evaluator_optimizer.py +49 -0
- cantus_agent-0.4.2/cantus/workflows/orchestrator_worker.py +41 -0
- cantus_agent-0.4.2/cantus/workflows/parallel.py +24 -0
- cantus_agent-0.4.2/cantus/workflows/prompt_chain.py +26 -0
- cantus_agent-0.4.2/cantus/workflows/router.py +36 -0
- cantus_agent-0.4.2/cantus_agent.egg-info/PKG-INFO +281 -0
- cantus_agent-0.4.2/cantus_agent.egg-info/SOURCES.txt +88 -0
- cantus_agent-0.4.2/cantus_agent.egg-info/dependency_links.txt +1 -0
- cantus_agent-0.4.2/cantus_agent.egg-info/requires.txt +63 -0
- cantus_agent-0.4.2/cantus_agent.egg-info/top_level.txt +1 -0
- cantus_agent-0.4.2/pyproject.toml +232 -0
- cantus_agent-0.4.2/setup.cfg +4 -0
|
@@ -0,0 +1,983 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `cantus` will be documented in this file. Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres
|
|
5
|
+
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.4.2] - 2026-05-21 — cantus-pypi-publish
|
|
8
|
+
|
|
9
|
+
**Distribution-lifecycle release.** Zero code-level change — every public symbol, endpoint, default value, extras group, and `[tool.uv] conflicts` entry shipped by v0.4.1 is byte-identical in v0.4.2. The whole release lives at the distribution surface: first publish to PyPI as `cantus-agent`, complete PyPI project-page metadata, OIDC release pipeline, CI test matrix.
|
|
10
|
+
|
|
11
|
+
### Distribution
|
|
12
|
+
|
|
13
|
+
- **First PyPI publish as `cantus-agent`.** The unqualified `cantus` name on PyPI is held by an unrelated musicology placeholder release (Tim Eipert / University of Würzburg, version `0.0.0` "Coming soon"); the framework ships under the hyphenated distribution name `cantus-agent`. The Python import name remains `cantus` — `import cantus` is byte-identical and student notebook cells do not change. Install via `pip install cantus-agent==0.4.2`; the git+ install path (`pip install git+https://github.com/schola-cantorum/cantus@<ref>`) is retained as the escape hatch for `main`, feature branches, and arbitrary commit SHAs.
|
|
14
|
+
- **`pyproject.toml` PyPI metadata expansion.** `[project.urls]` declares five entries (Homepage / Documentation / Source / Issues / Changelog, all pointing under `github.com/schola-cantorum/cantus`); `[project].keywords` covers `llm` / `agent` / `framework` / `education` / `colab` / `polyphonic`; `classifiers` adds `Development Status :: 4 - Beta` and `Operating System :: OS Independent`; license declaration upgraded from the legacy PEP 621 table form `{ text = "ECL-2.0" }` to the PEP 639 SPDX expression `license = "ECL-2.0"` with explicit `license-files = ["LICENSE"]` so both sdist and wheel bundle the license file at the PEP 639 location. Per PEP 639 normative enforcement (setuptools ≥ 77), the legacy `License :: OSI Approved :: …` trove classifier is removed — declaring both the SPDX expression and a `License ::` classifier now raises `InvalidConfigError`; the SPDX expression is the sole source of truth and modern PyPI clients render it via the PEP 639 `License-Expression` core metadata field.
|
|
15
|
+
- **OIDC release pipeline.** New `.github/workflows/release.yml` runs on `release.published` and on manual `workflow_dispatch` (with `inputs.target` of `testpypi` or `pypi`). The job runs in the GitHub `environment: pypi` (or `testpypi`), requests `id-token: write`, builds sdist + wheel, gates with `twine check --strict`, then publishes via `pypa/gh-action-pypi-publish` using PyPI's Trusted Publisher OIDC exchange. Repository secrets contain no `PYPI_API_TOKEN` — there is no long-lived upload credential to rotate or leak.
|
|
16
|
+
- **CI test matrix.** New `.github/workflows/test.yml` runs `pytest` on Python 3.10 / 3.11 / 3.12 against every push to `main` and every pull request. The matrix caps at 3.12 because `cantus[openhands]` declares `python_version >= "3.12" and python_version < "3.13"` and `openhands` does not yet publish 3.13 wheels.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `cantus.__version__` reports `"0.4.2"`; `pyproject.toml [project].version` is the static source of truth, kept in lockstep with `cantus/__init__.py`.
|
|
21
|
+
|
|
22
|
+
### Notes
|
|
23
|
+
|
|
24
|
+
- **`cantus.__version__` is identical to `importlib.metadata.version("cantus-agent")`.** Note that `importlib.metadata.version(...)` takes the PyPI distribution name (`cantus-agent`), not the Python import name (`cantus`). This asymmetry matters for downstream diagnostic tooling.
|
|
25
|
+
- **PyPI publishes are not reversible.** PyPI does not allow same-version re-upload; yanking only hides a release. The release workflow gates with `twine check --strict` before upload, and the maintainer runs a TestPyPI dry-run before tagging the production release. If a serious metadata defect ships, the recovery path is `pip yank` plus a follow-up patch release — not a re-upload.
|
|
26
|
+
|
|
27
|
+
### Notes — out of scope (scheduled)
|
|
28
|
+
|
|
29
|
+
- **`bump-cantus-pin-to-v0-4-2`** (main repo overlay) — bumps the `libs/cantus` submodule pin in `schola-cantorum/colab-llm-agent` and updates the student Colab notebook setup cells to prefer `pip install cantus-agent==0.4.2`.
|
|
30
|
+
- **`cantus-spec-self-hosting`** (Phase 2) — moves the cantus framework spec from the umbrella repo into `cantus` itself.
|
|
31
|
+
- **Physical relocation** (Phase 3) — moves the cantus dev tree from `libs/cantus/` to `edu-projects/cantus/`.
|
|
32
|
+
- **PEP 541 reclaim of the `cantus` PyPI name** — slow, uncertain, and not a blocker; deferred to a future change if it ever becomes feasible.
|
|
33
|
+
|
|
34
|
+
## [0.4.1] - 2026-05-20 — cantus-serve-security
|
|
35
|
+
|
|
36
|
+
補上 v0.4.0 故意延後的 auth gate 與 `pydantic.SecretStr` token 載入。**完全 ADDITIVE** — 沒有 BREAKING、`auth_mode` 預設 `AuthMode.NONE` 維持 v0.4.0 行為,既有 cookbook / examples 無需改動即可升級。
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- `cantus.config.AuthMode`(`str, Enum`;values `"none"` / `"bearer"` / `"api-key"`)— FastAPI auth gate 三模式列舉,str-valued 以便 pydantic-settings 從 `CANTUS_SERVE_AUTH_MODE` 直接 coerce。
|
|
41
|
+
- `cantus.config.Settings` 新增四個欄位:`auth_mode: AuthMode = AuthMode.NONE`、`api_key: SecretStr | None = None`、`bearer_token: SecretStr | None = None`、`dashboard_requires_auth: bool = True`。env prefix 沿用既有 `CANTUS_SERVE_`,對應 env 變數為 `CANTUS_SERVE_AUTH_MODE` / `CANTUS_SERVE_API_KEY` / `CANTUS_SERVE_BEARER_TOKEN` / `CANTUS_SERVE_DASHBOARD_REQUIRES_AUTH`。
|
|
42
|
+
- `cantus.serve.security` 新模組,公開兩個 callable:`require_auth(request: Request) -> None`(FastAPI dependency,讀 `request.app.state.settings` 取 auth 設定)、`validate_auth_config(settings: Settings) -> None`(`cantus.serve()` 啟動前的 fail-fast 檢查,`auth_mode != NONE` 但對應 token 為 `None` 時 raise `ValueError` 含字面 `BEARER_TOKEN` / `API_KEY`)。
|
|
43
|
+
- `cantus.serve.AuthMode` / `cantus.serve.require_auth` — top-level re-export,方便 `from cantus.serve import AuthMode, require_auth` 一行帶到。
|
|
44
|
+
- `cantus[security]` extras — documentary alias,dependency closure 跟 `cantus[serve]` 完全相同(fastapi、uvicorn、pydantic-settings),不引入新第三方套件、不新增 `[tool.uv] conflicts` 條目。下游可寫 `pip install cantus[security]` 表達安裝意圖。
|
|
45
|
+
- 文件:`docs/protocols/serve.md` 新增「Authentication」段(三模式說明 + env 變數表 + bearer / api-key 兩種模式各一份 quick start + dashboard_requires_auth 行為 + Design notes)、`MIGRATION_v0.4.0_to_v0.4.1.md`。
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- `cantus.serve.app.serve()` 整合 `Depends(require_auth)`:當 `settings.auth_mode != AuthMode.NONE` 時自動把 dependency 掛到 `POST /skills/{name}` 與(依 `settings.dashboard_requires_auth`)dashboard endpoints。`auth_mode = AuthMode.NONE`(預設)路徑 byte-identical 保留 v0.4.0 行為 — `app.state.channels` 不動、Channel Protocol 不動、`POST /skills/{name}` 與 `GET /skills` / `GET /health` / `GET /events` request/response 形狀不動。
|
|
50
|
+
- `cantus.serve.dashboard.register_dashboard_routes()` 新增 `dependencies: list[Depends] | None = None` 關鍵字參數,三個 dashboard route 在註冊時把 `dependencies` 傳給 `@app.get(...)` 對應參數。預設 `None` → 不掛 dependency,行為跟 v0.4.0 一致。
|
|
51
|
+
- `cantus.serve.app.serve()` 在建構 FastAPI app 後設 `app.state.settings = effective_settings`,讓 `require_auth` 從 `request.app.state.settings` 取得 auth 設定(避免 dependency 每次重新 load Settings)。
|
|
52
|
+
- `cantus.__version__` 從 `"0.4.0"` 升到 `"0.4.1"`,`pyproject.toml [project].version` 同步。`test_dunder_version_aligned_with_pyproject` 仍鎖住兩者一致。
|
|
53
|
+
|
|
54
|
+
### Security
|
|
55
|
+
|
|
56
|
+
- **Constant-time token compare**:`cantus.serve.security._check_token` 走 `hmac.compare_digest(provided.encode(), expected.encode())`,防 timing-oracle 推測 token 前綴。`==` 比對在某些 Python 實作會 short-circuit 並洩漏長度差。
|
|
57
|
+
- **401 不區分缺/錯 token**:所有認證失敗(缺 header、錯 token、格式錯、未知 mode)一律回 HTTP 401 with body `{"detail": "Authentication required"}` byte-identical。差異化錯誤訊息會幫攻擊者區分「找對 header 名了嗎」vs「猜對 token 內容了嗎」,等於 username enumeration 的類比。
|
|
58
|
+
- **SecretStr 不洩漏**:`api_key` / `bearer_token` 兩個欄位以 `pydantic.SecretStr` 包裝,pydantic 內建 mask 行為確保 `repr(settings)` / `settings.model_dump_json()` / `serve(registry).openapi()` / `cantus.serve` 產生的任何 log line 都不出現 token 明文(測試以 substring 斷言驗證四個 surface)。
|
|
59
|
+
- **Fail-fast on missing / blank token**:`cantus.serve.security.validate_auth_config` 在 `cantus.serve()` 建構 FastAPI app 前執行;`auth_mode != NONE` 但對應 token 為 `None`、空字串、或 whitespace-only 時就 raise `ValueError` 含字面 `BEARER_TOKEN` / `API_KEY` 與 `non-empty` 字樣。避免使用者誤以為 auth 已啟用但實際每個請求都會通過、也擋掉 `CANTUS_SERVE_BEARER_TOKEN=""` 這類 foot-gun 設定。
|
|
60
|
+
- **Dashboard 預設套 auth**:`dashboard_requires_auth = True` 預設值;要把 `/health` 開放給 Prometheus / Grafana 等 monitoring 系統需顯式設 `false`。dashboard 暴露 Skill 名單與健康狀態本身就是 reconnaissance 資訊。
|
|
61
|
+
- **`_check_token` 包 try/except**:`hmac.compare_digest(provided.encode("utf-8"), expected.encode("utf-8"))` 外層包 `UnicodeEncodeError` / `ValueError` catch、return `False`。Starlette HTTP header 走 latin-1 decode 實務上不會產出 lone surrogate,但 defensive 處理保留 401 indistinguishability 保證(避免異常被冒出當成 500 形成 oracle)。
|
|
62
|
+
|
|
63
|
+
### Internal
|
|
64
|
+
|
|
65
|
+
- 新增測試:`tests/serve/test_security.py`(12 case 矩陣:NONE 預設無 auth、bearer 缺 header 401、bearer 錯 token 401、401 body byte-identical、bearer 對 token 200、api-key 對 / 錯 / 缺三種對應、SecretStr 不洩漏於 repr/JSON/OpenAPI/log、bearer / api-key 兩種 fail-fast、constant-time compare source-level 檢查)。`tests/serve/test_config.py` 新增 7 case(AuthMode 預設、env 解析 bearer / api-key、SecretStr 包裝 bearer_token / api_key、model_dump_json 不洩漏、`dashboard_requires_auth` env 解析)。`tests/serve/test_dashboard.py` 新增 2 case(`dashboard_requires_auth=true` 預設關閘 / `false` 開閘)。`tests/serve/test_lazy_import.py` 既有 happy-path 控制 case 擴成驗證 `AuthMode` + `require_auth` 從 `cantus.serve` 可 import。
|
|
66
|
+
- `tests/test_distribution_config.py` `test_pyproject_version_bumped_to_0_4_0` rename 為 `test_pyproject_version_bumped_to_0_4_1`、值對齊 `"0.4.1"`;`tests/test_public_api.py` `test_version_is_0_4_0` 同 rename 為 `test_version_is_0_4_1`。
|
|
67
|
+
- 整體測試從 v0.4.0 base 的 459 case + serve 41 case 擴成 527 pass / 3 skipped(v0.4.0 既有 case 全部 byte-identical 保留)。
|
|
68
|
+
- `uv run mypy cantus --strict` 全綠(含新 `cantus/serve/security.py` 與 `cantus/config.py` 4 新欄位、`cantus/serve/app.py` 與 `cantus/serve/dashboard.py` 的 `Depends` 整合)。
|
|
69
|
+
|
|
70
|
+
### Notes — 範圍外(已排程)
|
|
71
|
+
|
|
72
|
+
- **v0.4.2 `cantus-serve-tunnel`**:cloudflared / ngrok tunnel helper 整合,把 cantus serve 暴露到公網的 deploy 路徑。預期 spawn tunnel 時若偵測到 `auth_mode=NONE` 就以醒目警告或拒絕執行作為第二道防線。
|
|
73
|
+
- **v0.4.3 `cantus-supply-chain-cli`**:`cantus deps` / `cantus audit` / SBOM 生成 CLI,需先解 `[project.scripts]` 入口與 CLI 框架選型(typer / click / argparse stdlib)。
|
|
74
|
+
- **v0.5.x(暫定)**:multi-tenant auth(OAuth 2.0 / JWT / mTLS / OIDC)、per-Skill ACL(RBAC)、rate limiting、CORS / CSRF policy;超出 v0.4.x 教學弧 scope。
|
|
75
|
+
- **HTTPS / TLS termination**:仍走上游 reverse proxy 或 v0.4.2 tunnel helper。
|
|
76
|
+
|
|
77
|
+
## [0.4.0] - 2026-05-20 — cantus-serve-core
|
|
78
|
+
|
|
79
|
+
新增 `cantus.serve()` FastAPI app factory、`cantus.config` 12-factor 設定、Channel Protocol 抽象與 `cantus[serve]` extras;同支同步啟用 mypy `strict = true`、收尾 `cantus[all]` ⊗ `cantus[openhands]` extras resolver conflict。
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
- `cantus.serve(registry, *, channels=None, settings=None) -> FastAPI` — 將 Skill registry 自動 expose 為 REST endpoint,每個 Skill 變成 `POST /skills/<spec_for_llm.name>`,body 走 JSON、回傳 `{"result": ...}`;自動產出 OpenAPI 文件 + Swagger UI(`/docs`)/ ReDoc(`/redoc`)。非 `Registry` 入參 raise `TypeError("cantus.serve expects a Registry")`。
|
|
84
|
+
- `cantus.config.Settings`(`pydantic_settings.BaseSettings`)— 12-factor 設定物件,env prefix `CANTUS_SERVE_`,欄位 `host="127.0.0.1"` / `port=8765` / `dashboard=True` / `docs_url="/docs"` / `openapi_url="/openapi.json"` / `redoc_url="/redoc"`。
|
|
85
|
+
- Dashboard read-only endpoint:`GET /skills`(registry 內 Skill spec 列表)、`GET /health`(含 `cantus_version`)、`GET /events`(支援 `limit` / `offset` query,host code 可透過 `app.state.event_persistence = JsonLinesPersistence(...)` 自掛持久化來源)。`Settings(dashboard=False)` 可整組關閉、Skill invoke endpoint 不受影響。
|
|
86
|
+
- 保留名稱守衛:`spec_for_llm()["name"]` 為 `skills` / `health` / `events` 的 Skill 在 `cantus.serve()` 入口 raise `ValueError` 含 `"reserved dashboard path"`。
|
|
87
|
+
- `cantus.serve.channel.Channel`(`typing.Protocol`,`receive() -> dict` + `send(message: dict) -> None`,`@runtime_checkable`)與內建 in-memory FIFO `LocalMockReceiver` 實作,作為 ARCH-2 跨 capability smoke test 載具。
|
|
88
|
+
- 新 `cantus[serve]` optional extras 群組(`fastapi>=0.115,<1`、`uvicorn>=0.30,<1`、`pydantic-settings>=2.4,<3`);lazy-import gate 對齊 `cantus[mcp]` pattern — 未安裝 extras 時噴 `ImportError` 含 `pip install cantus[serve]`。
|
|
89
|
+
- `cantus/__init__.py` 採 PEP 562 `__getattr__` lazy-expose `cantus.serve` 與 `cantus.config`,base install `import cantus` 不需要 serve SDK。
|
|
90
|
+
- 文件:`docs/protocols/serve.md`(Quick start / Configuration / Dashboard endpoints / Channel Protocol)、`MIGRATION_v0.3.6_to_v0.4.0.md`、`README.md` / `README.zhTW.md` 同步 byte-identical Install + Quickstart code block。
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
|
|
94
|
+
- `pyproject.toml` 新增 `[tool.uv] conflicts` — 宣告 `cantus[openhands]` 與 `cantus[all]` / `cantus[providers]` / `cantus[openai]` / `cantus[anthropic]` / `cantus[google]` / `cantus[groq]` 兩兩互斥(fastmcp 系列拉 `websockets>=15` vs google-genai 拉 `websockets<15`;openhands>=1.16 拉 `openai>=2.20` vs cantus 自身的 provider extras 仍走業界較穩的 `<2` 上界)。Spec 要求 `[all]` ⊗ `[openhands]` 為最低門檻,本版以「at minimum」之外加全套互斥對組以實際解決 onboarding 痛點。各 extras 仍可獨立安裝;pip 不認 `[tool.uv]` table 但本來就不做 universal resolution,pip 路徑無感。
|
|
95
|
+
- `pyproject.toml` 的 `openhands` extras 加上 PEP 508 環境 marker `python_version >= '3.12' and python_version < '3.13'` — openhands>=1.16.0 上游僅發 py3.12 wheel,sequencing 到 cantus 的 `requires-python = ">=3.10"` 上 universal resolve 需要該 marker 才能在 py3.10 / 3.11 / 3.13 環境讓 extras 解析為空集合(不破壞 `cantus.adapters.openhands` 在 py3.12 環境正常 import 的 surface)。
|
|
96
|
+
- `[tool.mypy]` 從 v0.3.5 `disallow_untyped_defs = false` baseline 升級為 `strict = true`,並補加 `fastapi.*` / `uvicorn.*` / `pydantic_settings.*` 三組 `ignore_missing_imports = true` override。下游若開 `mypy --strict` 跑 cantus 程式碼,public symbols 從 `Any`-leaking 改成精確型別(屬收緊 / ADDITIVE — 過去 `Any`-相容的 narrowing 仍綠;若下游 `# type: ignore[assignment]` 抑制過 cantus 回傳值,可能觸發 `warn_unused_ignores`)。
|
|
97
|
+
- `cantus.__version__` 從 `"0.3.4"`(v0.3.6 期間 `cantus/__init__.py` 與 `pyproject.toml` 出現的 drift)正式對齊回 `"0.4.0"`,並新增 `test_dunder_version_aligned_with_pyproject` 鎖住此 invariant。
|
|
98
|
+
|
|
99
|
+
### Internal
|
|
100
|
+
|
|
101
|
+
- 補完 cantus 內部 27 處 type annotation 缺口,橫跨 14 個檔案(`cantus/__init__.py`、`cantus/adapters/langchain.py`、`cantus/model/chat.py` / `factory.py` / `loader.py` / `providers/{anthropic,google,groq,openai,_translate}.py`、`cantus/protocols/{analyzer,memory,skill,validator}.py`),達成 `uv run mypy cantus --strict` 全綠(`Success: no issues found in 60 source files`)。
|
|
102
|
+
- 新增測試:`tests/serve/test_app.py`(8 case)、`tests/serve/test_dashboard.py`(9 case)、`tests/serve/test_channel.py`(9 case)、`tests/serve/test_config.py`(8 case)、`tests/serve/test_lazy_import.py`(6 case)、`tests/serve/test_arch2_smoke.py`(1 case)、`tests/test_pyproject_extras_conflicts.py`(5 case)。既有 459 case 維持綠。
|
|
103
|
+
- `tests/test_distribution_config.py` 新增 `test_mypy_strict_rejects_untyped_def_regression` — 動態注入 untyped def 跑 mypy 驗證 `"Function is missing a return type annotation"` regression scenario。
|
|
104
|
+
|
|
105
|
+
### Notes — 範圍外(已排程 v0.4.1 cantus-serve-security)
|
|
106
|
+
|
|
107
|
+
- Auth / authorization gate(本版預設 bind `127.0.0.1`、不掛任何 auth)。
|
|
108
|
+
- Tunnel helpers(cloudflared / ngrok)。
|
|
109
|
+
- Supply-chain CLI(`cantus security audit`)。
|
|
110
|
+
- Secret management(`pydantic_settings.SecretStr`)與 `.env` 檔載入。
|
|
111
|
+
|
|
112
|
+
### Notes — 範圍外(已排程 v0.4.2 / v0.4.3 channel-gateway)
|
|
113
|
+
|
|
114
|
+
- 真實 channel 實作(LINE / Telegram / Discord / Google Chat webhook)。
|
|
115
|
+
- WebSocket / Server-Sent Events transport。
|
|
116
|
+
- Hot-reload / 動態 Skill 註冊 endpoint。
|
|
117
|
+
- HTTPS endpoint(由上游反向代理或 v0.4.1 tunnel helpers 處理)。
|
|
118
|
+
|
|
119
|
+
## [0.3.6] - 2026-05-18 — Internal Cleanup
|
|
120
|
+
|
|
121
|
+
**ADDITIVE — no public API change, no BREAKING, no new dependencies, no new
|
|
122
|
+
optional extras, no user-facing surface change.** Clears the 15 redundant
|
|
123
|
+
`# type: ignore[...]` comments that the v0.3.5 `warn_unused_ignores = true`
|
|
124
|
+
mypy baseline started reporting, so `mypy cantus` runs cleanly with no
|
|
125
|
+
`[unused-ignore]` warnings on a `cantus[dev]` install.
|
|
126
|
+
|
|
127
|
+
### Internal
|
|
128
|
+
|
|
129
|
+
- `cantus/adapters/openhands.py` — removed `# type: ignore[import-not-found]`
|
|
130
|
+
from the SDK-gate `from openhands.events import Action` (mypy override
|
|
131
|
+
`openhands.*` already covers the missing-import case).
|
|
132
|
+
- `cantus/adapters/mcp.py` — removed `# type: ignore[import-not-found]` from
|
|
133
|
+
the SDK-gate `import mcp as _mcp` and removed `# type: ignore[misc]` from
|
|
134
|
+
the `server.tool(...)` decorator call.
|
|
135
|
+
- `cantus/adapters/langchain.py` — removed `# type: ignore[import-not-found]`
|
|
136
|
+
from both SDK-gate imports (`import langchain_core`,
|
|
137
|
+
`from langchain_core.tools import BaseTool`) and removed
|
|
138
|
+
`# type: ignore[misc, valid-type]` from the `class _ExposedLangChainTool(BaseTool)`
|
|
139
|
+
declaration.
|
|
140
|
+
- `cantus/adapters/dspy.py` — removed `# type: ignore[import-not-found]` from
|
|
141
|
+
the SDK-gate `import dspy`.
|
|
142
|
+
- `cantus/adapters/huggingface.py` — narrowed
|
|
143
|
+
`# type: ignore[import-not-found,attr-defined]` to
|
|
144
|
+
`# type: ignore[attr-defined]` (the `import-not-found` code is now redundant
|
|
145
|
+
under the `transformers.*` mypy override; `attr-defined` is still needed
|
|
146
|
+
for the dynamic `Tool` name exposure).
|
|
147
|
+
- `cantus/protocols/debug.py` — narrowed
|
|
148
|
+
`# type: ignore[union-attr,attr-defined]` to `# type: ignore[union-attr]`
|
|
149
|
+
on the `target._debug_enabled = True` monkey-patch.
|
|
150
|
+
- `cantus/model/loader.py` — removed `# type: ignore` from the lazy `import
|
|
151
|
+
torch` and `from transformers import (...)` block inside
|
|
152
|
+
`_load_with_quant_config()`.
|
|
153
|
+
- `cantus/model/providers/openai.py`, `groq.py`, `anthropic.py` — removed
|
|
154
|
+
`# type: ignore[import-not-found]` from the `_get_client()` lazy imports.
|
|
155
|
+
- `cantus/model/providers/google.py` — narrowed
|
|
156
|
+
`# type: ignore[import-not-found,attr-defined,import-untyped]` to
|
|
157
|
+
`# type: ignore[attr-defined,import-untyped]` on the lazy
|
|
158
|
+
`from google import genai` import.
|
|
159
|
+
|
|
160
|
+
### Notes
|
|
161
|
+
|
|
162
|
+
- The `cantus[all]` + `cantus[openhands]` optional-extras pair is currently
|
|
163
|
+
unresolvable by `uv` / `pip` due to a transitive
|
|
164
|
+
`fastmcp` → `websockets>=15.0.1` requirement clashing with
|
|
165
|
+
`google-genai` → `websockets<15.0.dev0`. This is a release engineering
|
|
166
|
+
issue surfaced (not introduced) by v0.3.6 and is tracked as a separate
|
|
167
|
+
follow-up; it is intentionally out of scope for this internal-cleanup
|
|
168
|
+
release. As a workaround, `uv run --frozen --extra dev` reuses the existing
|
|
169
|
+
lockfile and bypasses the conflict.
|
|
170
|
+
- Strict mypy (`strict = true`) remains deferred to v0.4.x — narrowing
|
|
171
|
+
individual ignores does not move that gate.
|
|
172
|
+
- Maintainers adding new ignores SHALL prefer the narrowest error-code list
|
|
173
|
+
possible (`# type: ignore[specific-code]` over bare `# type: ignore`) so
|
|
174
|
+
that `warn_unused_ignores` can surface drift in future cantus releases.
|
|
175
|
+
|
|
176
|
+
## [0.3.5] - 2026-05-18 — Quality Baseline
|
|
177
|
+
|
|
178
|
+
PATCH release. **ADDITIVE — no BREAKING change, no new dependencies, no new
|
|
179
|
+
optional extras, no cantus public callable change.** Ships the v0.3.x
|
|
180
|
+
educational arc's deferred quality infrastructure so the next feature arc
|
|
181
|
+
starts on a verifiable baseline.
|
|
182
|
+
|
|
183
|
+
### Added
|
|
184
|
+
|
|
185
|
+
- `cantus/py.typed` — PEP 561 inline-typed marker. Downstream consumers running
|
|
186
|
+
`mypy --strict` against code that imports cantus symbols now see cantus'
|
|
187
|
+
declared annotations instead of `Any`. The marker is bundled into the wheel
|
|
188
|
+
via a new `[tool.setuptools.package-data]` entry (`cantus = ["py.typed"]`)
|
|
189
|
+
so `python -m build` produces wheels that ship the marker.
|
|
190
|
+
- `[tool.mypy]` baseline configuration in `pyproject.toml`. Pins
|
|
191
|
+
`python_version = "3.10"`, enables `warn_unused_ignores`,
|
|
192
|
+
`warn_redundant_casts`, and `check_untyped_defs`, leaves
|
|
193
|
+
`disallow_untyped_defs = false` (strict mode is deferred to v0.4.x), and
|
|
194
|
+
declares `[[tool.mypy.overrides]]` setting `ignore_missing_imports = true`
|
|
195
|
+
for the lazy-import adapter SDKs (`mcp.*`, `langchain_core.*`, `dspy.*`,
|
|
196
|
+
`transformers.*`, `openhands.*`, `anthropic.*`, `openai.*`,
|
|
197
|
+
`google.genai.*`, `groq.*`) so a bare `cantus[dev]` install can run
|
|
198
|
+
`mypy cantus` without optional extras installed.
|
|
199
|
+
- `[tool.coverage.run]` and `[tool.coverage.report]` baseline configuration.
|
|
200
|
+
Branch coverage is enabled (`branch = true`); the report shows missing
|
|
201
|
+
lines and excludes `pragma: no cover`, `if TYPE_CHECKING:`, and
|
|
202
|
+
`raise NotImplementedError` from coverage accounting. No `fail_under`
|
|
203
|
+
threshold is set in this release — baseline data is collected first.
|
|
204
|
+
- `pytest` addopts now trigger coverage by default
|
|
205
|
+
(`--cov=cantus --cov-report=term-missing --cov-report=xml`). Running
|
|
206
|
+
`pytest tests/` without any flag emits a terminal coverage section and a
|
|
207
|
+
`coverage.xml` artifact in the working directory.
|
|
208
|
+
- `MIGRATION_v0.3.4_to_v0.3.5.md` — user-facing migration note documenting
|
|
209
|
+
the ADDITIVE nature of this release and the new dev workflow signals.
|
|
210
|
+
|
|
211
|
+
### Changed
|
|
212
|
+
|
|
213
|
+
- `docs/protocols/adapters-batch2.md` — the existing v0.3.4 supersede
|
|
214
|
+
blockquote at the top of the file is reformatted to lead with
|
|
215
|
+
`**Status:** Superseded by [adapters-batch3.md](./adapters-batch3.md)
|
|
216
|
+
(cantus v0.3.4) for the HuggingFace and OpenHands import directions;
|
|
217
|
+
preserved as a v0.3.3 historical snapshot of the batch2 surface.` so the
|
|
218
|
+
file is unambiguously identifiable as a historical snapshot at a glance.
|
|
219
|
+
The spec body below the note is byte-identical.
|
|
220
|
+
|
|
221
|
+
### Internal
|
|
222
|
+
|
|
223
|
+
- Added `tests/test_distribution_config.py` with six assertions covering the
|
|
224
|
+
PEP 561 marker, the setuptools package-data wiring, the mypy baseline, the
|
|
225
|
+
coverage baseline, the pytest addopts contract, and the v0.3.5 version
|
|
226
|
+
pin. These tests double as the verification target for the
|
|
227
|
+
`Cantus ships PEP 561 py.typed marker and baseline tool configuration`
|
|
228
|
+
Requirement.
|
|
229
|
+
|
|
230
|
+
### Notes
|
|
231
|
+
|
|
232
|
+
- Strict mypy (`strict = true`) is intentionally deferred to v0.4.x — it
|
|
233
|
+
requires an audit + annotation pass on cantus' Protocol classes and
|
|
234
|
+
`getattr`-driven adapter shims.
|
|
235
|
+
- Coverage `fail_under` threshold is intentionally deferred until a
|
|
236
|
+
multi-release baseline has been collected; setting it now would either
|
|
237
|
+
inflate CI false positives (threshold too high) or anchor regressions
|
|
238
|
+
(threshold too low).
|
|
239
|
+
- Maintainers adding a new optional-extras adapter SDK SHALL append its
|
|
240
|
+
top-level module glob to `[[tool.mypy.overrides]]` so `mypy cantus`
|
|
241
|
+
continues to pass on a bare `cantus[dev]` install.
|
|
242
|
+
|
|
243
|
+
## [0.3.4] - 2026-05-18
|
|
244
|
+
|
|
245
|
+
PATCH release. **PATCH additive — no BREAKING.** Closes the cross-framework
|
|
246
|
+
adapter matrix by adding the HuggingFace import direction (`import_hf_tool`),
|
|
247
|
+
and converts the v0.3.3 "deferred to v0.3.4 batch3" wording for the OpenHands
|
|
248
|
+
import direction into a permanent "not applicable" decision. All v0.3.0,
|
|
249
|
+
v0.3.1, v0.3.2, and v0.3.3 imports, constructors, and behaviours remain
|
|
250
|
+
byte-identical.
|
|
251
|
+
|
|
252
|
+
### Added
|
|
253
|
+
|
|
254
|
+
- `cantus.adapters.import_hf_tool(tool: transformers.Tool) -> Skill` — wraps a
|
|
255
|
+
HuggingFace `transformers.Tool` as a cantus Skill (requires
|
|
256
|
+
`cantus[huggingface]`). Adds a `_HuggingFaceRemoteSkill(_RemoteSkillBase)`
|
|
257
|
+
internal subclass that derives the v0.3.0 JSON Schema from `tool.inputs`
|
|
258
|
+
(every declared input field becomes required, mirroring HF's lack of an
|
|
259
|
+
"optional input" concept) and dispatches `skill(**kwargs)` to
|
|
260
|
+
`tool(**kwargs)`. Errors during dispatch surface as
|
|
261
|
+
`RuntimeError("huggingface_remote_error: ...")`; schema parsing errors
|
|
262
|
+
surface as `RuntimeError("huggingface_handshake_failed: ...")`; non-Tool
|
|
263
|
+
inputs surface as `TypeError("import_hf_tool expects transformers.Tool")`.
|
|
264
|
+
- `docs/protocols/adapters-batch3.md` — new design document covering the
|
|
265
|
+
v0.3.4 close-out, including the four-framework bidirectional matrix and
|
|
266
|
+
the OpenHands "not applicable" rationale.
|
|
267
|
+
- `MIGRATION_v0.3.3_to_v0.3.4.md` — user-facing migration note with usage
|
|
268
|
+
examples for `import_hf_tool` and guidance on the OpenHands export-only
|
|
269
|
+
path.
|
|
270
|
+
|
|
271
|
+
### Changed
|
|
272
|
+
|
|
273
|
+
- `cantus.adapters.openhands` docstring now describes the OpenHands import
|
|
274
|
+
direction as permanently not applicable (was: "deferred to v0.3.4 batch3").
|
|
275
|
+
`openhands.events.Action` is a declarative event record dispatched by the
|
|
276
|
+
OpenHands host runtime; it exposes no `__call__` that
|
|
277
|
+
`Skill.run(**kwargs)` could delegate to, so wrapping it as a Skill is a
|
|
278
|
+
semantic mismatch rather than a tooling gap.
|
|
279
|
+
- `cantus.adapters.huggingface` docstring rewritten — the v0.3.3 "import
|
|
280
|
+
direction deferred" paragraph is removed; the module now documents the
|
|
281
|
+
bidirectional contract and points at `_RemoteSkillBase` as the import-path
|
|
282
|
+
shared base.
|
|
283
|
+
- `cantus.adapters.__init__` docstring expanded to enumerate ten top-level
|
|
284
|
+
callables (3 from v0.3.2 + 6 from v0.3.3 + 1 from v0.3.4) and to spell
|
|
285
|
+
out the OpenHands export-only stance.
|
|
286
|
+
- `docs/protocols/adapters-batch2.md` carries a supersede note pointing
|
|
287
|
+
readers at `adapters-batch3.md` for the current HF / OpenHands import
|
|
288
|
+
story.
|
|
289
|
+
- `openspec/specs/adapter-layer-batch2/spec.md` (in the main repo) gains a
|
|
290
|
+
new Requirement `import_hf_tool wraps HuggingFace transformers Tool as
|
|
291
|
+
cantus Skill`, and the `expose_as_hf_tool` / `expose_as_openhands_action`
|
|
292
|
+
Requirements have their "deferred to v0.3.4 batch3 evaluation" language
|
|
293
|
+
removed; the OpenHands counterpart now explains the omission as a
|
|
294
|
+
permanent semantic mismatch.
|
|
295
|
+
|
|
296
|
+
### Removed
|
|
297
|
+
|
|
298
|
+
- `tests/adapters/test_huggingface.py::test_import_hf_tool_not_exported` —
|
|
299
|
+
the v0.3.3 "defensive ImportError" test is gone; the symbol is now
|
|
300
|
+
exported. The OpenHands counterpart (`test_import_openhands_action_not_exported`)
|
|
301
|
+
stays, with its docstring updated to call out the permanence of the
|
|
302
|
+
decision.
|
|
303
|
+
|
|
304
|
+
### Not changed
|
|
305
|
+
|
|
306
|
+
- `cantus.adapters._RemoteSkillBase` is untouched; v0.3.4 only adds a new
|
|
307
|
+
concrete subclass.
|
|
308
|
+
- All LangChain / DSPy / MCP / Anthropic Memory adapter modules and tests
|
|
309
|
+
are byte-identical with v0.3.3.
|
|
310
|
+
- `Registry.KINDS` remains `("skill",)`.
|
|
311
|
+
- No new dependencies or extras; `cantus[huggingface]` (`transformers>=4.40,<5`)
|
|
312
|
+
is reused.
|
|
313
|
+
|
|
314
|
+
## [0.3.3] - 2026-05-18
|
|
315
|
+
|
|
316
|
+
MINOR release. **MINOR additive — no BREAKING.** Extends `cantus.adapters`
|
|
317
|
+
with six cross-framework callables (LangChain / DSPy / HuggingFace /
|
|
318
|
+
OpenHands) and lifts the v0.3.2 `_RemoteSkill` pattern into a private
|
|
319
|
+
`_RemoteSkillBase` shared base. All v0.3.0, v0.3.1, and v0.3.2 imports,
|
|
320
|
+
constructors, and behaviours remain byte-identical.
|
|
321
|
+
|
|
322
|
+
### Added
|
|
323
|
+
|
|
324
|
+
- `cantus.adapters` gains six new top-level callables (in addition to
|
|
325
|
+
the three v0.3.2 callables):
|
|
326
|
+
- `expose_as_langchain_tool(skill) -> BaseTool` and
|
|
327
|
+
`import_langchain_tool(tool) -> Skill` — bidirectional bridge to
|
|
328
|
+
`langchain_core.tools.BaseTool`.
|
|
329
|
+
- `expose_as_dspy_tool(skill) -> dspy.Tool` and
|
|
330
|
+
`import_dspy_tool(tool) -> Skill` — bidirectional bridge to
|
|
331
|
+
`dspy.Tool` with a `{str, int, float, bool}` ↔ JSON Schema type
|
|
332
|
+
mapping.
|
|
333
|
+
- `expose_as_hf_tool(skill) -> transformers.Tool` — export-only
|
|
334
|
+
bridge to HuggingFace `transformers.Tool` (import direction
|
|
335
|
+
deferred to v0.3.4 batch3).
|
|
336
|
+
- `expose_as_openhands_action(skill) -> openhands.events.Action` —
|
|
337
|
+
export-only bridge to OpenHands actions (import direction deferred
|
|
338
|
+
to v0.3.4 batch3).
|
|
339
|
+
- `cantus.adapters._remote_skill._RemoteSkillBase` — private shared
|
|
340
|
+
base for every `import_*` adapter. Subclass to add new `import_*`
|
|
341
|
+
bridges without re-implementing the v0.3.0 `Skill.spec_for_llm()`
|
|
342
|
+
shape contract or the `is_remote = True` marker. The class is private
|
|
343
|
+
(leading underscore in the module name) and is intentionally NOT
|
|
344
|
+
re-exported from `cantus.adapters.__init__`.
|
|
345
|
+
- `cantus.adapters.mcp_client._RemoteSkill` now inherits from
|
|
346
|
+
`_RemoteSkillBase` — refactor only; v0.3.2 observable behaviour
|
|
347
|
+
remains byte-identical and the existing `test_mcp_client.py`
|
|
348
|
+
test suite passes without modification.
|
|
349
|
+
- Four new extras groups in `pyproject.toml`:
|
|
350
|
+
- `cantus[langchain]` → `langchain-core>=0.3,<1`
|
|
351
|
+
- `cantus[dspy]` → `dspy-ai>=2.5,<3`
|
|
352
|
+
- `cantus[huggingface]` → `transformers>=4.40,<5`
|
|
353
|
+
- `cantus[openhands]` → `openhands>=1.16,<2`
|
|
354
|
+
Each adapter module gates on its respective SDK at import time; a
|
|
355
|
+
missing SDK surfaces as `ImportError("... pip install cantus[<name>]")`.
|
|
356
|
+
|
|
357
|
+
### Unchanged
|
|
358
|
+
|
|
359
|
+
- All v0.3.0 / v0.3.1 / v0.3.2 imports continue to resolve identically.
|
|
360
|
+
- `Skill.spec_for_llm()` JSON shape stays
|
|
361
|
+
`{"name", "description", "args_schema"}` before and after any
|
|
362
|
+
`cantus.adapters.*` submodule is imported — the existing
|
|
363
|
+
`test_skill_spec_for_llm_invariant.py` contract is extended to cover
|
|
364
|
+
all nine adapter modules (3 v0.3.2 + 5 v0.3.3).
|
|
365
|
+
- `Registry.KINDS` remains `("skill",)` — batch2 adapters do NOT
|
|
366
|
+
introduce a new protocol kind.
|
|
367
|
+
- The `providers` aggregator continues to install only OpenAI /
|
|
368
|
+
Anthropic / Google / Groq; the four batch2 extras require explicit
|
|
369
|
+
opt-in.
|
|
370
|
+
|
|
371
|
+
## [0.3.2] - 2026-05-18
|
|
372
|
+
|
|
373
|
+
MINOR release. **MINOR additive — no BREAKING.** Adds the
|
|
374
|
+
`cantus.adapters` subpackage with three MVP bridges: MCP server +
|
|
375
|
+
MCP client + Anthropic Memory tool dict. All v0.3.0 and v0.3.1
|
|
376
|
+
imports, constructors, and behaviours remain byte-identical.
|
|
377
|
+
|
|
378
|
+
### Added
|
|
379
|
+
|
|
380
|
+
- `cantus.adapters` subpackage with three top-level callables:
|
|
381
|
+
- `export_as_mcp_server(skills, *, name, version) -> McpServer`
|
|
382
|
+
wraps cantus Skills as a stdio / streamable-HTTP MCP server.
|
|
383
|
+
- `import_mcp_server(*, transport, command_or_url) -> list[Skill]`
|
|
384
|
+
connects to a remote MCP server and returns each remote tool as
|
|
385
|
+
a cantus Skill with `is_remote = True`.
|
|
386
|
+
- `expose_as_anthropic_memory_tool(memory) -> dict` returns the
|
|
387
|
+
Anthropic Memory tool spec dict (4-action `view`/`create`/
|
|
388
|
+
`str_replace`/`delete`); pure-Python, no SDK dependency.
|
|
389
|
+
- `cantus[mcp]` extras pinning `mcp>=0.1,<2` — required for any
|
|
390
|
+
`cantus.adapters.mcp_*` use; `expose_as_anthropic_memory_tool`
|
|
391
|
+
works in the core install.
|
|
392
|
+
- `cantus.adapters.mcp` gate module that raises `ImportError("...
|
|
393
|
+
pip install cantus[mcp]")` when the SDK is missing.
|
|
394
|
+
- `Skill.is_remote: bool = False` class attribute; MCP-imported
|
|
395
|
+
Skills override to `True`. The marker is NOT leaked into
|
|
396
|
+
`spec_for_llm()` — v0.3.0 shape contract `{"name", "description",
|
|
397
|
+
"args_schema"}` is preserved across adapter import and use.
|
|
398
|
+
|
|
399
|
+
### Security
|
|
400
|
+
|
|
401
|
+
- `export_as_mcp_server` rejects `name` / `version` values that fail
|
|
402
|
+
the regex `^[A-Za-z0-9][A-Za-z0-9._-]*$` (length 1-64) — guards
|
|
403
|
+
against JSON-RPC payload injection.
|
|
404
|
+
- `import_mcp_server(transport="stdio", ...)` rejects shell
|
|
405
|
+
metacharacters (`|`, `>`, `<`, `&`, `;`, `$`, backtick, newline)
|
|
406
|
+
in `command_or_url` — `subprocess.Popen` is never invoked via a
|
|
407
|
+
shell.
|
|
408
|
+
- `import_mcp_server(transport="http", ...)` rejects URLs whose
|
|
409
|
+
scheme is not `http` / `https` or whose netloc is empty.
|
|
410
|
+
- `McpServer.run(transport="http")` fails loud with
|
|
411
|
+
`OSError("Address already in use")` on a busy port; the framework
|
|
412
|
+
does NOT silently hang or retry.
|
|
413
|
+
|
|
414
|
+
### Unchanged
|
|
415
|
+
|
|
416
|
+
- All v0.3.0 / v0.3.1 imports continue to resolve identically.
|
|
417
|
+
- `Skill.spec_for_llm()` JSON shape stays
|
|
418
|
+
`{"name", "description", "args_schema"}` before and after any
|
|
419
|
+
`cantus.adapters` submodule is imported.
|
|
420
|
+
- `Registry.KINDS` remains `("skill",)` — adapters do NOT introduce
|
|
421
|
+
a new protocol kind.
|
|
422
|
+
|
|
423
|
+
## [0.3.1] - 2026-05-18
|
|
424
|
+
|
|
425
|
+
MINOR release. **PATCH-equivalent additive — no BREAKING.** Adds the
|
|
426
|
+
Memory dual-tier API, the `Soul` identity abstraction, and an opt-in
|
|
427
|
+
JSON-Lines persistence plug for `EventStream`. All v0.3.0 imports,
|
|
428
|
+
constructors, and behaviours remain byte-identical when the new
|
|
429
|
+
keywords / classes are not used.
|
|
430
|
+
|
|
431
|
+
### Added
|
|
432
|
+
|
|
433
|
+
- `cantus.protocols.memory.MarkdownMemory(path, top_k=10)` — file-backed
|
|
434
|
+
lower-tier Memory with frontmatter chunks and a resolve-then-classify
|
|
435
|
+
safe-path policy that rejects path traversal, Unix system roots
|
|
436
|
+
(`/etc`, `/sys`, `/proc`, `/dev`, `/root`, plus macOS `/private/*`
|
|
437
|
+
canonical equivalents), FIFO / socket / block-device entries, and
|
|
438
|
+
Windows UNC paths.
|
|
439
|
+
- `cantus.protocols.memory.AutoMemory(backend)` — upper-tier wrapper
|
|
440
|
+
that exposes 4 LLM-facing `Skill` tools (`view`, `create`,
|
|
441
|
+
`str_replace`, `delete`) mirroring the Anthropic Memory tool spec.
|
|
442
|
+
`AutoMemory` uses composition (NOT inheritance) and returns a cached
|
|
443
|
+
`tools` list whose docstring carries the literal `"LLM has full CRUD
|
|
444
|
+
access"` foot-gun warning.
|
|
445
|
+
- `cantus.identity.Soul` and `Soul.from_file(path)` / `Soul.from_text(text)`
|
|
446
|
+
parsers for the six-section SOUL.md format (`Name & Role`,
|
|
447
|
+
`Personality`, `Rules`, `Tools`, `Output format`, `Handoffs`).
|
|
448
|
+
Case-sensitive H2 matching; failures raise `SoulParseError` with
|
|
449
|
+
`missing_sections`, `duplicates`, and `unexpected` lists.
|
|
450
|
+
- `cantus.core.event_stream_persistence.JsonLinesPersistence(path)` —
|
|
451
|
+
optional append-only JSON-Lines persistence plug with `os.fsync` after
|
|
452
|
+
every write, POSIX `0o600` file mode on first creation, and a
|
|
453
|
+
serialise-before-open contract that prevents partial writes on
|
|
454
|
+
non-serialisable input.
|
|
455
|
+
- `Agent.__init__` now accepts a keyword-only `soul: Soul | None = None`.
|
|
456
|
+
When supplied, the agent prepends `soul.to_system_prompt() + "\n\n"`
|
|
457
|
+
to the system prompt; when `None` (default), system-prompt construction
|
|
458
|
+
is byte-identical to v0.3.0.
|
|
459
|
+
- `Turn` dataclass gains two optional metadata fields: `timestamp:
|
|
460
|
+
datetime | None` and `type: Literal["user", "assistant"] | None`. The
|
|
461
|
+
`type` Literal is restricted to the two derivable values; `"system"`
|
|
462
|
+
and `"tool"` are explicitly rejected to keep `Turn` semantically
|
|
463
|
+
unambiguous. Whitespace-only Turn(user=" ", assistant="") raises
|
|
464
|
+
`ValueError("empty Turn ...")`.
|
|
465
|
+
|
|
466
|
+
### Unchanged
|
|
467
|
+
|
|
468
|
+
- All v0.3.0 imports continue to resolve identically (`from cantus
|
|
469
|
+
import Skill, Memory, Agent, skill`, `from cantus.hooks import ...`,
|
|
470
|
+
`from cantus.workflows import ...`).
|
|
471
|
+
- `from cantus import memory` and `from cantus import register_memory`
|
|
472
|
+
still raise `ImportError` — Memory remains class-only entry per the
|
|
473
|
+
v0.3.0 `agent-protocols` Requirement.
|
|
474
|
+
- In-memory `EventStream` is unchanged; `JsonLinesPersistence` is a
|
|
475
|
+
separate opt-in plug that host code drives explicitly.
|
|
476
|
+
|
|
477
|
+
## [0.3.0] - 2026-05-18
|
|
478
|
+
|
|
479
|
+
MAJOR release. **BREAKING** — protocol surface reorganized: `Analyzer` and
|
|
480
|
+
`Validator` are demoted from top-level protocol kinds to `Skill` pre/post hook
|
|
481
|
+
helpers; the `@workflow` decorator is removed and replaced by explicit
|
|
482
|
+
`cantus.workflows` building blocks (`PromptChain`, `Router`, `Parallel`,
|
|
483
|
+
`OrchestratorWorker`, `EvaluatorOptimizer`). See
|
|
484
|
+
[`MIGRATION_v0.2_to_v0.3.md`](./MIGRATION_v0.2_to_v0.3.md) for the mechanical
|
|
485
|
+
conversion recipe.
|
|
486
|
+
|
|
487
|
+
### BREAKING
|
|
488
|
+
|
|
489
|
+
- `from cantus import Workflow, workflow, register_workflow` → `ImportError`.
|
|
490
|
+
`cantus.protocols.workflow` is hard-removed.
|
|
491
|
+
- `from cantus import Analyzer, Validator, analyzer, validator,
|
|
492
|
+
register_analyzer, register_validator` → `ImportError`. Use
|
|
493
|
+
`from cantus.hooks import …` instead.
|
|
494
|
+
- `Registry.KINDS` shrunk from `("skill", "analyzer", "validator", "workflow")`
|
|
495
|
+
to `("skill",)`. `Registry.register("analyzer"|"validator"|"workflow", …)`
|
|
496
|
+
raises `ValueError` with a migration hint pointing at `pre_hook=` / `post_hook=`
|
|
497
|
+
/ `cantus.workflows`.
|
|
498
|
+
- The agent loop no longer scans four protocol kinds. `Agent._dispatch_skill`
|
|
499
|
+
now performs a single `registry.lookup("skill", …)` followed by a linear
|
|
500
|
+
`pre_hook → body → post_hook` chain.
|
|
501
|
+
- `@debug` no longer accepts `@workflow` as a stack target (it can't — the
|
|
502
|
+
decorator no longer exists). Still works on `@skill`, `@analyzer`,
|
|
503
|
+
`@validator`.
|
|
504
|
+
|
|
505
|
+
### Added
|
|
506
|
+
|
|
507
|
+
- `cantus.hooks` submodule: re-exports `analyzer`, `validator`, `Analyzer`,
|
|
508
|
+
`Validator`, `Result`, and `ReservedValidatorNameError` from a single
|
|
509
|
+
namespace that emphasises their hook-helper role.
|
|
510
|
+
- `cantus.workflows` package: five orchestration primitives ported from
|
|
511
|
+
Anthropic's *Building Effective Agents* playbook. Each is a plain Python
|
|
512
|
+
class with a `.run(input) → output` method; none of them touch the runtime
|
|
513
|
+
registry.
|
|
514
|
+
- `Skill` instances gain `_pre_hook` and `_post_hook` attributes. The `@skill`
|
|
515
|
+
decorator now accepts both bare (`@skill`) and parameterised
|
|
516
|
+
(`@skill(pre_hook=…, post_hook=…)`) forms.
|
|
517
|
+
- `Skill.spec_for_llm()` JSON shape is preserved — top-level keys remain
|
|
518
|
+
exactly `{"name", "description", "args_schema"}` regardless of hook
|
|
519
|
+
attachment. A fixture-backed snapshot test guards this for downstream
|
|
520
|
+
adapter consumers (v0.3.2 `cantus-adapter-layer`).
|
|
521
|
+
|
|
522
|
+
### Changed
|
|
523
|
+
|
|
524
|
+
- `Agent._dispatch_skill` body shrunk and straight-lined: no per-kind
|
|
525
|
+
`if`/`elif` ladder, no four-kind fallback scan. Hook exceptions are wrapped
|
|
526
|
+
as `ToolErrorObservation` with `pre_hook` / `post_hook` labels in the
|
|
527
|
+
message. A `post_hook` returning `Result(ok=False, …)` still produces a
|
|
528
|
+
`ValidationErrorObservation` carrying the hook function's name.
|
|
529
|
+
- `@analyzer` and `@validator` decorators are no longer registry side-effects;
|
|
530
|
+
they return reusable callable helpers. The `RESERVED_VALIDATOR_NAMES` guard
|
|
531
|
+
(`non_empty_final_answer`, `action_parse`) continues to apply.
|
|
532
|
+
|
|
533
|
+
### Removed
|
|
534
|
+
|
|
535
|
+
- `cantus.protocols.workflow` module (hard-removed; no deprecated shim).
|
|
536
|
+
- `Workflow` class, `@workflow` decorator, `register_workflow` function.
|
|
537
|
+
- Top-level re-exports of `Analyzer`, `Validator`, `analyzer`, `validator`,
|
|
538
|
+
`register_analyzer`, `register_validator` from the `cantus` package.
|
|
539
|
+
|
|
540
|
+
## [0.2.1] - 2026-05-17
|
|
541
|
+
|
|
542
|
+
PATCH release that completes the v0.2.0 multi-provider scope. v0.2.0 shipped
|
|
543
|
+
OpenAI + Anthropic; this release adds Google Gemini, Groq, and NVIDIA NIM
|
|
544
|
+
direct-connect adapters. The dual-tier `ChatModel` Protocol, `ChatModelAsHandle`
|
|
545
|
+
bridge, `load_chat_model` factory, and Environment profiles from v0.2.0 are
|
|
546
|
+
**unchanged** — this is purely additive.
|
|
547
|
+
|
|
548
|
+
### Added
|
|
549
|
+
|
|
550
|
+
- **`GoogleChatModel` adapter** (`cantus.model.providers.google`) — direct
|
|
551
|
+
adapter against the `google-genai` SDK (`from google import genai`,
|
|
552
|
+
`client.models.generate_content`). Resolves API key from explicit
|
|
553
|
+
`api_key=` kwarg then `GOOGLE_API_KEY` env var; raises `MissingAPIKeyError`
|
|
554
|
+
when both are absent. Extracts system messages as the top-level
|
|
555
|
+
`system_instruction=` kwarg via `to_google_messages`. Translates
|
|
556
|
+
`assistant` → Gemini `model` and `tool` → Gemini `function` with
|
|
557
|
+
`function_response` parts.
|
|
558
|
+
- **`GroqChatModel` adapter** (`cantus.model.providers.groq`) — direct adapter
|
|
559
|
+
against the `groq` SDK's Chat Completions endpoint. Reuses the existing
|
|
560
|
+
`to_openai_messages` / `from_openai_response` pure functions (Groq is
|
|
561
|
+
OpenAI-compatible at the wire layer). Resolves `GROQ_API_KEY` from env.
|
|
562
|
+
- **`NvidiaChatModel` adapter** (`cantus.model.providers.nvidia`) — thin
|
|
563
|
+
subclass of `OpenAIChatModel` that hard-codes
|
|
564
|
+
`base_url="https://integrate.api.nvidia.com/v1"` and reads `NVIDIA_API_KEY`.
|
|
565
|
+
All `chat` / `stream` / translator behavior is inherited unchanged.
|
|
566
|
+
- **`to_google_messages` / `from_google_response` translators**
|
|
567
|
+
(`cantus.model.providers._translate`) — pure functions for Gemini's
|
|
568
|
+
`contents` / `parts` wire shape. Maps Gemini `STOP` / `MAX_TOKENS` /
|
|
569
|
+
`SAFETY` / `TOOL_CALL` finish reasons to cantus stop reasons.
|
|
570
|
+
- **`google` and `groq` optional extras** in `pyproject.toml` with pinned
|
|
571
|
+
upper bounds: `google = ["google-genai>=0.3,<1"]`,
|
|
572
|
+
`groq = ["groq>=0.11,<1"]`. The `providers` aggregator now installs all
|
|
573
|
+
four primary adapters (`cantus[openai,anthropic,google,groq]`).
|
|
574
|
+
- **`scripts/audit_cassettes.sh`** — secret-pattern scan for provider VCR
|
|
575
|
+
cassettes; extends the cantus-distribution Pre-push security audit
|
|
576
|
+
(Authorization / Bearer / `x-api-key` / `x-goog-api-key` / `sk-` / `hf_` /
|
|
577
|
+
`ghp_` / `AIza` / `AKIA` patterns). Closes the v0.2.0 follow-up to bring
|
|
578
|
+
cassette paths into the pre-push security gate.
|
|
579
|
+
- **`notebooks/multi_provider_smoke_batch2.ipynb`** — release-time human
|
|
580
|
+
smoke for Google / Groq / NVIDIA (chat + stream each).
|
|
581
|
+
|
|
582
|
+
### Changed
|
|
583
|
+
|
|
584
|
+
- `load_chat_model` factory `_REGISTRY` extended from two to five providers
|
|
585
|
+
(`openai`, `anthropic`, `google`, `groq`, `nvidia`). Unknown-prefix
|
|
586
|
+
`ValueError` now lists all five supported prefixes.
|
|
587
|
+
- `load_chat_model("nvidia/...")` missing-extras hint points at
|
|
588
|
+
`pip install cantus[openai]` (the OpenAI SDK is the actual runtime
|
|
589
|
+
dependency), **not** a phantom `cantus[nvidia]` group.
|
|
590
|
+
- `cantus.__version__` bumped from `0.2.0` to `0.2.1`.
|
|
591
|
+
- `README.md` and `README.zhTW.md` "Multi-provider quickstart" sections gain
|
|
592
|
+
Google, Groq, and NVIDIA quickstart code blocks (byte-identical between
|
|
593
|
+
language variants, matching the v0.2.0 contract).
|
|
594
|
+
|
|
595
|
+
### Notes
|
|
596
|
+
|
|
597
|
+
- **NVIDIA NIM ships through `cantus[openai]`**, by design. NIM's endpoint
|
|
598
|
+
is OpenAI Chat Completions-compatible (`base_url=...`), so the adapter is
|
|
599
|
+
a thin subclass of `OpenAIChatModel`. Opening a dedicated `cantus[nvidia]`
|
|
600
|
+
extras would mislead users into installing a phantom `nvidia` SDK package.
|
|
601
|
+
- **Google adapter uses the new `google-genai` SDK**, not the legacy
|
|
602
|
+
`google-generativeai`. Import path is `from google import genai`. The two
|
|
603
|
+
SDKs share the `google.*` namespace but expose different APIs
|
|
604
|
+
(`client.models.generate_content` vs. `GenerativeModel(...).generate_content`).
|
|
605
|
+
The legacy SDK is intentionally unsupported — silently falling back would
|
|
606
|
+
surface as an obscure `AttributeError` rather than a clear `ImportError`.
|
|
607
|
+
- **Groq SDK pin `groq>=0.11,<1`** acknowledges Groq's tool-use schema
|
|
608
|
+
churn during 2025–2026. Re-record cassettes when bumping the upper bound.
|
|
609
|
+
- **LiteLLM is still not a dependency** at any layer (v0.2.0 decision —
|
|
610
|
+
「不引入 LiteLLM」— driven by the 2026-03 LiteLLM 1.82.7/1.82.8
|
|
611
|
+
supply-chain incident). Direct adapters keep the supply-chain surface
|
|
612
|
+
auditable per provider.
|
|
613
|
+
- **`google-generativeai` is still not a dependency** at any layer
|
|
614
|
+
(intentional, see Google adapter note above).
|
|
615
|
+
- **No `[nvidia]` extras** — `pip install cantus[nvidia]` is intentionally
|
|
616
|
+
unresolvable so users hit a clear pip error rather than a misleading
|
|
617
|
+
installation path; README and the factory's missing-extras hint both
|
|
618
|
+
direct users to `cantus[openai]`.
|
|
619
|
+
|
|
620
|
+
## [0.2.0] - 2026-05-17
|
|
621
|
+
|
|
622
|
+
First framework-化 minor release. Introduces the **dual-tier API** (ARCH-1)
|
|
623
|
+
that the discussion `openspec/discussions/cantus-framework-shift.md` froze on
|
|
624
|
+
2026-05-17 as the design principle for all v0.2+ work. v0.1.x notebooks and
|
|
625
|
+
the existing `mount_drive_and_load()` entry point remain **100% behavior- and
|
|
626
|
+
signature-compatible** — no `DeprecationWarning` is emitted in v0.2.0.
|
|
627
|
+
|
|
628
|
+
### Added
|
|
629
|
+
|
|
630
|
+
- **Tier 2 `ChatModel` Protocol** (`cantus.model.chat`) — chat-style
|
|
631
|
+
multi-provider interface with `chat(messages, tools=None) -> ChatResponse`
|
|
632
|
+
and `stream(messages, tools=None) -> Iterator[str]`. Three companion
|
|
633
|
+
dataclasses: `Message` (role + content + tool_calls), `ToolCall` (id, name,
|
|
634
|
+
parsed-JSON arguments), and `ChatResponse` (message + stop_reason + usage +
|
|
635
|
+
provider-native `raw` escape hatch). Re-exported at top-level `cantus`.
|
|
636
|
+
- **`ChatModelAsHandle` bridge** (`cantus.model.bridge`) — wraps a Tier 2
|
|
637
|
+
`ChatModel` so it satisfies the existing Tier 1 `ModelHandle` Protocol,
|
|
638
|
+
letting any `Agent` consume a cloud provider without a single line of
|
|
639
|
+
Agent change.
|
|
640
|
+
- **`load_chat_model("provider/model_id")` factory** (`cantus.model.factory`)
|
|
641
|
+
— lazy-import dispatch with friendly missing-extras errors of the form
|
|
642
|
+
`pip install cantus[openai]`. v0.2.0 accepts the `openai` and `anthropic`
|
|
643
|
+
prefixes; unknown prefixes raise `ValueError` naming the supported set.
|
|
644
|
+
- **`OpenAIChatModel` adapter** (`cantus.model.providers.openai`) — direct
|
|
645
|
+
adapter against the `openai` SDK's Chat Completions API (not the Responses
|
|
646
|
+
API; revisit in v0.3.x). Accepts `base_url` from day one so v0.2.1 NVIDIA
|
|
647
|
+
NIM can reuse it without an API change. Resolves API key from explicit
|
|
648
|
+
`api_key=` kwarg then `OPENAI_API_KEY` env var; raises `MissingAPIKeyError`
|
|
649
|
+
with a Chinese guidance message when both are absent.
|
|
650
|
+
- **`AnthropicChatModel` adapter** (`cantus.model.providers.anthropic`) —
|
|
651
|
+
direct adapter against the `anthropic` SDK's Messages API. Correctly
|
|
652
|
+
extracts system messages from the `messages` list and passes them as the
|
|
653
|
+
top-level `system=` kwarg. Same auth resolution + `MissingAPIKeyError`
|
|
654
|
+
shape as the OpenAI adapter, against `ANTHROPIC_API_KEY`.
|
|
655
|
+
- **Environment profile module** (`cantus.env`) with three classes:
|
|
656
|
+
`ColabEnvironment` (mounts Drive when in Colab, then loads locally with
|
|
657
|
+
4-bit quantization — equivalent to the legacy `mount_drive_and_load`),
|
|
658
|
+
`LocalEnvironment` (same load path, never mounts Drive), and
|
|
659
|
+
`CloudOnlyEnvironment` (refuses to load locally; redirects callers to
|
|
660
|
+
`load_chat_model('provider/...')` and verifiably does NOT import
|
|
661
|
+
transformers / bitsandbytes / torch).
|
|
662
|
+
- **Three new optional-dependency groups** in `pyproject.toml`:
|
|
663
|
+
`openai` (`openai>=1.50,<2`), `anthropic` (`anthropic>=0.40,<1`), and
|
|
664
|
+
`providers` (aggregator pulling both). The `dev` group gains
|
|
665
|
+
`pytest-recording>=0.13` and `respx>=0.21`.
|
|
666
|
+
- **ARCH-2 integration smoke test** (`tests/test_integration_smoke.py`)
|
|
667
|
+
proves that `import cantus` does NOT transitively load `openai` or
|
|
668
|
+
`anthropic`, and that the SDK only loads on first `_get_client()` call —
|
|
669
|
+
protecting the Tier 1 teaching path from cloud-SDK import cost.
|
|
670
|
+
- **Multi-provider quickstart README section** in both `README.md` and
|
|
671
|
+
`README.zhTW.md`, with byte-identical OpenAI + Anthropic code blocks.
|
|
672
|
+
v0.1.x Gemma quickstart preserved unchanged above it.
|
|
673
|
+
- **Manual smoke notebook** `notebooks/multi_provider_smoke.ipynb` that the
|
|
674
|
+
release manager runs by hand against real provider endpoints before
|
|
675
|
+
tagging v0.2.0 (one cell each for OpenAI / Anthropic chat + stream + a
|
|
676
|
+
bridge round-trip through `Agent`).
|
|
677
|
+
|
|
678
|
+
### Changed
|
|
679
|
+
|
|
680
|
+
- **`mount_drive_and_load()`** internally refactored to a thin delegate of
|
|
681
|
+
`ColabEnvironment().prepare_model(...)`. Signature, return type, exception
|
|
682
|
+
types (`ValueError`, `MountError`, `ModelNotFoundError`), Chinese error
|
|
683
|
+
messages, and `CANTUS_MODEL_ROOT` environment variable resolution are
|
|
684
|
+
byte-for-byte preserved. **No `DeprecationWarning` is emitted** —
|
|
685
|
+
v0.1.x notebooks run unchanged on v0.2.0. The existing
|
|
686
|
+
`tests/test_loader.py` suite passes without a single modification.
|
|
687
|
+
- **`cantus.__init__`** exports the new Tier 2 symbols (`ChatModel`,
|
|
688
|
+
`Message`, `ToolCall`, `ChatResponse`, `ChatModelAsHandle`,
|
|
689
|
+
`load_chat_model`) plus the three Environment profiles. The version
|
|
690
|
+
string is bumped to `0.2.0`. `AgentState` is now also re-exported for
|
|
691
|
+
consistency with `Agent`.
|
|
692
|
+
|
|
693
|
+
### Notes
|
|
694
|
+
|
|
695
|
+
- **No LiteLLM at any layer.** The 2026-03 LiteLLM supply-chain compromise
|
|
696
|
+
(malicious code in versions 1.82.7 / 1.82.8) makes adding LiteLLM as
|
|
697
|
+
either a hard or optional dependency a non-trivial governance burden:
|
|
698
|
+
the framework would need to ship its own version-range check, document a
|
|
699
|
+
refusal policy, and educate users on detecting bad versions. v0.2.0
|
|
700
|
+
instead ships direct provider SDK adapters with their own optional
|
|
701
|
+
extras, accepting the trade-off of writing one adapter per provider in
|
|
702
|
+
exchange for a clean supply-chain story. See
|
|
703
|
+
`openspec/discussions/cantus-framework-shift.md` lines 290 and 359–367
|
|
704
|
+
for the framing.
|
|
705
|
+
- **ARCH-1 dual-tier API** is now a load-bearing principle. Tier 1
|
|
706
|
+
(`ModelHandle.generate(prompt) -> str`) stays the teaching entrypoint
|
|
707
|
+
because students should be able to plug in any `.generate`-shaped object
|
|
708
|
+
including a 5-line mock. Tier 2 (`ChatModel.chat / stream / tool use`)
|
|
709
|
+
is the industry-aligned surface. The two MUST connect through the
|
|
710
|
+
explicit `ChatModelAsHandle` bridge — `Agent` is **not** taught to
|
|
711
|
+
recognise `ChatModel`, because adding an `isinstance` branch would
|
|
712
|
+
pollute Tier 1 with Tier 2 knowledge.
|
|
713
|
+
- **Test strategy: SDK-level mocks, not VCR cassettes (yet).** Provider
|
|
714
|
+
contract tests under `tests/providers/` use `monkeypatch` on the SDK
|
|
715
|
+
client classes rather than recorded HTTP cassettes. CI does not hold any
|
|
716
|
+
real API keys; hand-crafted cassettes were rejected as fragile vs. the
|
|
717
|
+
signal they would carry. The cassette infrastructure (`conftest.py` with
|
|
718
|
+
`filter_headers` for `authorization` / `x-api-key` / `api-key` /
|
|
719
|
+
`x-goog-api-key`, and `record_mode='none'`) IS in place so v0.2.1 can
|
|
720
|
+
record real cassettes when adding Google / Groq / NVIDIA against the
|
|
721
|
+
same gate. **Follow-up for v0.2.1**: when the first real cassettes
|
|
722
|
+
land, extend the cantus-distribution pre-push secret-pattern hook
|
|
723
|
+
(currently `sk-`, `Bearer `, `api_key`, `authorization:`) to cover
|
|
724
|
+
`tests/providers/cassettes/**` paths.
|
|
725
|
+
- **Deferred to v0.2.1** (`cantus-multi-provider-di-batch2`): Google
|
|
726
|
+
(`google-genai`, NOT the older `google-generativeai`), Groq, and
|
|
727
|
+
NVIDIA NIM (which is the `openai` SDK pointed at
|
|
728
|
+
`https://integrate.api.nvidia.com/v1` — `OpenAIChatModel.base_url`
|
|
729
|
+
already supports this from day one).
|
|
730
|
+
- **Deferred to v0.3.x**: Anthropic content blocks (images, citations,
|
|
731
|
+
thinking) — currently reachable via `ChatResponse.raw`. OpenAI Responses
|
|
732
|
+
API. Tool-call streaming deltas (`stream()` yields text only).
|
|
733
|
+
- **Deferred to v0.4.1**: unified secret management via `pydantic-settings`
|
|
734
|
+
(belongs to the `cantus-serve-security` capability — pulling it forward
|
|
735
|
+
would have broken the planned capability ordering).
|
|
736
|
+
|
|
737
|
+
## [0.1.4] - 2026-05-17
|
|
738
|
+
|
|
739
|
+
Documentation-only release that bundles two long-standing dev/contributor needs
|
|
740
|
+
into a single patch tag: the cantus internal LLM Wiki (a curated knowledge base
|
|
741
|
+
for contributors and LLM agents working on the framework), and the previously
|
|
742
|
+
unreleased Traditional Chinese README variant carried over from commit
|
|
743
|
+
`744b4a7`. **No code changes, no API changes** — runtime, protocols, grammar,
|
|
744
|
+
and model loader are byte-for-byte identical to `v0.1.3`.
|
|
745
|
+
|
|
746
|
+
### Added
|
|
747
|
+
|
|
748
|
+
- **`docs/llm_wiki/` internal developer knowledge base** with `research/`,
|
|
749
|
+
`coding_style/`, `architecture/`, and `future_work/` sections. Every research
|
|
750
|
+
entry pins verified source URLs (10 entries spanning Anthropic Building
|
|
751
|
+
Effective Agents, OpenClaw, OpenHarness, OpenHands SDK, SOUL.md, MCP, the
|
|
752
|
+
LiteLLM March 2026 supply-chain incident, FastAPI + Pydantic, Cloudflare
|
|
753
|
+
Tunnel vs ngrok, and Google Chat HTTP/Pub-Sub). The `coding_style/` section
|
|
754
|
+
anchors on Linus Torvalds' four philosophical principles with a Python
|
|
755
|
+
adaptation table and a worked indirect-pointer linked-list example. The
|
|
756
|
+
`architecture/` section ships the authoritative ARCH-1 (two-tier API) and
|
|
757
|
+
ARCH-2 (10-item cross-capability integration audit) definitions that every
|
|
758
|
+
v0.2+ change proposal will link back to. The `future_work/` roadmap
|
|
759
|
+
enumerates the 9 ordered changes planned through v0.5.0. Scaffolded via the
|
|
760
|
+
`/wiki` suite (`wiki-init` with a custom `.profile.yaml` that overrides the
|
|
761
|
+
shipped `research` profile to add `required_dirs` for the four cantus
|
|
762
|
+
categories) and validated via `wiki-validator` on every commit.
|
|
763
|
+
- **`README.zhTW.md` Traditional Chinese variant** with bidirectional language
|
|
764
|
+
switch (carries over commit `744b4a7` from v0.1.3-1, previously unreleased).
|
|
765
|
+
The English and Traditional Chinese READMEs share byte-identical Install
|
|
766
|
+
commands, Quickstart code, and Open-in-Colab URL fragments so copy-paste
|
|
767
|
+
produces identical behavior across both variants. Both READMEs gain a new
|
|
768
|
+
link to `docs/llm_wiki/index.md` in their Documentation section, marking the
|
|
769
|
+
wiki as the developer / contributor entry point.
|
|
770
|
+
|
|
771
|
+
## [0.1.3] - 2026-05-11
|
|
772
|
+
|
|
773
|
+
This release bundles ready-to-run Colab notebooks and visual identity assets into
|
|
774
|
+
the cantus repository itself, and rewrites the README around a hero banner with
|
|
775
|
+
an Open-in-Colab call-to-action. **No code changes, no API changes** — the
|
|
776
|
+
framework runtime, protocols, grammar, and model loader are byte-for-byte
|
|
777
|
+
identical to `v0.1.2`. The release is purely distribution + documentation.
|
|
778
|
+
|
|
779
|
+
### Added
|
|
780
|
+
|
|
781
|
+
- **`notebooks/task_template.ipynb`.** End-user notebook with the four-cell
|
|
782
|
+
contract from the `task-template` capability: mount Drive → pick variant +
|
|
783
|
+
install Cantus + load model → write protocols → run agent → Inspector
|
|
784
|
+
replay. Pre-wired to `cantus_version = "v0.1.3"` and `model_variant = "E4B"`,
|
|
785
|
+
with the embedded E2B retry guidance markdown. Drive paths are presented as
|
|
786
|
+
generic `@param` form fields so any administrator can point the notebook at
|
|
787
|
+
the directory they populated.
|
|
788
|
+
- **`notebooks/admin_setup.ipynb`.** Administrator-facing one-time setup
|
|
789
|
+
notebook that mirrors `google/gemma-4-E2B-it` and `google/gemma-4-E4B-it`
|
|
790
|
+
from Hugging Face Hub to a Drive directory. The cell-zero header identifies
|
|
791
|
+
the audience as administrator (中文:管理者) — no role-specific organization
|
|
792
|
+
labels. Five-step structure (mount Drive → optional HF login → download both
|
|
793
|
+
variants → verify files → optional smoke test) plus an advanced
|
|
794
|
+
pre-quantised storage appendix.
|
|
795
|
+
- **`notebooks/README.md`.** Index for the bundled notebooks with audience
|
|
796
|
+
matrix and Open-in-Colab badge URLs pinned to the `v0.1.3` tag.
|
|
797
|
+
- **`assets/banner_hero.jpeg`.** Brand-identity hero banner (chorus + Cantus
|
|
798
|
+
wordmark + five protocol icons) committed as a binary blob. Referenced from
|
|
799
|
+
the README via the repo-relative path `assets/banner_hero.jpeg`.
|
|
800
|
+
- **`assets/banner_protocols.jpeg`.** Five-protocol overview banner (musical
|
|
801
|
+
staff weaving Skill / Analyzer / Validator / Workflow / Memory icons)
|
|
802
|
+
committed as a binary blob. Referenced from the README immediately above the
|
|
803
|
+
five-protocol introductions.
|
|
804
|
+
|
|
805
|
+
### Changed
|
|
806
|
+
|
|
807
|
+
- **`README.md` rewritten.** Top of the document now opens with the hero
|
|
808
|
+
banner, a badge bar (release `v0.1.3`, ECL-2.0 license, Open-in-Colab), an
|
|
809
|
+
Open-in-Colab CTA pointing at `notebooks/task_template.ipynb`, and a
|
|
810
|
+
five-minute "open in Colab" path table. The five-protocol overview now
|
|
811
|
+
appears below the inline `assets/banner_protocols.jpeg` reference. Install
|
|
812
|
+
command examples bump from `@v0.1.1` / `@v0.1.2` to `@v0.1.3`. The existing
|
|
813
|
+
30-second Quickstart, Documentation links, and License section are
|
|
814
|
+
preserved.
|
|
815
|
+
- **`llms.txt`.** New "Versioning" section names the current `v0.1.3` install
|
|
816
|
+
command and points external LLMs at the Open-in-Colab notebook URL. The
|
|
817
|
+
remaining priming content (public API surface, five-protocol templates,
|
|
818
|
+
tool-call grammar, style rules) is unchanged.
|
|
819
|
+
- **`cantus.__version__`** bumps from `"0.1.2"` to `"0.1.3"`.
|
|
820
|
+
- **`pyproject.toml`** `version` bumps from `"0.1.2"` to `"0.1.3"`.
|
|
821
|
+
|
|
822
|
+
### Notes
|
|
823
|
+
|
|
824
|
+
- **No code changes.** `cantus/core/`, `cantus/protocols/`, `cantus/grammar/`,
|
|
825
|
+
and `cantus/model/` are byte-for-byte identical to v0.1.2. The pytest suite
|
|
826
|
+
retains the v0.1.2 baseline of 95 passed / 2 skipped. v0.1.2 users upgrading
|
|
827
|
+
to v0.1.3 do not need to change any import, any `Agent.run` call site, any
|
|
828
|
+
`@skill` / `@analyzer` / `@validator` / `@workflow` definition, or any
|
|
829
|
+
`Memory` subclass.
|
|
830
|
+
- **No API changes.** The public surface listed in `cantus.__init__.py`
|
|
831
|
+
`__all__` is unchanged. No new exports, no removed exports, no signature
|
|
832
|
+
changes.
|
|
833
|
+
- The Open-in-Colab badge URLs hardcode the `v0.1.3` tag. Future releases will
|
|
834
|
+
bump those URLs alongside the `cantus_version` pin and `pyproject.toml`
|
|
835
|
+
version string — `grep -nF '@v0.1.3'` and `grep -nF 'blob/v0.1.3/'` give the
|
|
836
|
+
complete list of strings to update.
|
|
837
|
+
|
|
838
|
+
## [0.1.2] - 2026-05-11
|
|
839
|
+
|
|
840
|
+
This release implements the five failure-handling Requirements added to the
|
|
841
|
+
`agent-runtime` canonical spec by the `agent-loop-empty-finalanswer-hardening`
|
|
842
|
+
change, plus the `errors.md` cookbook section mandated by the `api-docs`
|
|
843
|
+
canonical spec. The originating bug observation: Gemma 4 E2B (sub-3B variant)
|
|
844
|
+
short-circuits `agent.run` on iteration 1 by emitting an empty `FinalAnswerAction`
|
|
845
|
+
without calling any skill. v0.1.2 closes that loophole from four angles
|
|
846
|
+
(schema-level, runtime-level, framework defaults, documentation).
|
|
847
|
+
|
|
848
|
+
### Added
|
|
849
|
+
|
|
850
|
+
- **`FinalAnswerAction.answer` is non-empty (schema + runtime).** The
|
|
851
|
+
`cantus/grammar/tool_call.py` schema now constrains the `final_answer` JSON
|
|
852
|
+
string with `{"type": "string", "minLength": 1}`, so grammar-constrained
|
|
853
|
+
decoders (`outlines`, `xgrammar`) reject empty answers at decode time. The
|
|
854
|
+
`parse_tool_call()` runtime check enforces the same invariant for callers who
|
|
855
|
+
bypass the grammar. When either layer trips, the agent loop appends a
|
|
856
|
+
`ValidationErrorObservation(validator_name="non_empty_final_answer",
|
|
857
|
+
feedback="FinalAnswerAction.answer must be non-empty after str.strip(); call a
|
|
858
|
+
skill or write a substantive answer")` to the EventStream and continues.
|
|
859
|
+
|
|
860
|
+
- **`Action parse failures fall back to ValidationErrorObservation`.** Malformed
|
|
861
|
+
JSON, missing `action` field, an `action` object that contains neither
|
|
862
|
+
`skill_name` nor `final_answer`, and an unknown `skill_name` at parse time all
|
|
863
|
+
produce `ValidationErrorObservation(validator_name="action_parse",
|
|
864
|
+
feedback=<three-segment>)`. The feedback format is a closed contract:
|
|
865
|
+
|
|
866
|
+
1. First line: `error_type: <json_syntax|missing_field|unknown_skill>`
|
|
867
|
+
(case-sensitive, closed vocabulary).
|
|
868
|
+
2. Optional `detail:` line with a one-sentence explanation.
|
|
869
|
+
3. `raw_output_preview:` block with up to 500 characters of the offending
|
|
870
|
+
raw output; longer payloads are truncated and suffixed with the literal
|
|
871
|
+
token `…[truncated]`. Newlines in the raw output are preserved as the
|
|
872
|
+
two-character sequence `\n` for greppability.
|
|
873
|
+
|
|
874
|
+
- **`MaxIterationsObservation.partial_state` (deep copy).** When `agent.run`
|
|
875
|
+
exhausts `max_iterations` without producing a `FinalAnswerAction`, it now
|
|
876
|
+
appends `MaxIterationsObservation(iterations=N, last_action_summary=...,
|
|
877
|
+
partial_state=<deep copy of EventStream>)` as the final event. The
|
|
878
|
+
`partial_state` is a `copy.deepcopy` of the stream as it stood *before* the
|
|
879
|
+
observation was appended, so caller mutation cannot leak back into subsequent
|
|
880
|
+
`agent.run` invocations. The framework never raises an exception nor
|
|
881
|
+
fabricates a `FinalAnswerAction` on this path.
|
|
882
|
+
|
|
883
|
+
- **`Default loop budgets and small-model recommendation`.** `Agent.run`
|
|
884
|
+
defaults remain `max_iterations=8`, `max_retries=3` (unchanged from v0.1.1).
|
|
885
|
+
The `Agent.run` docstring now records the sub-3B caller-supplied override:
|
|
886
|
+
Gemma 4 E2B and other sub-3B variants benefit from `max_iterations=12`. This
|
|
887
|
+
is documentation, not a framework default — `max_iterations=12` does NOT
|
|
888
|
+
apply unless the caller passes it explicitly.
|
|
889
|
+
|
|
890
|
+
- **`Validator name vocabulary is closed and case-sensitive`.** New module-level
|
|
891
|
+
constant `cantus.protocols.validator.RESERVED_VALIDATOR_NAMES = frozenset({
|
|
892
|
+
"non_empty_final_answer", "action_parse"})` plus a new
|
|
893
|
+
`ReservedValidatorNameError` (subclass of `ValueError`). The `@validator`
|
|
894
|
+
decorator and `register_validator()` function-pass entry both reject
|
|
895
|
+
collisions case-sensitively. User code attempting to register a validator
|
|
896
|
+
named `non_empty_final_answer` or `action_parse` raises immediately at
|
|
897
|
+
registration — no silent rename, no warning-only fallback.
|
|
898
|
+
|
|
899
|
+
- **`cantus.__version__ = "0.1.2"`** as a public module attribute.
|
|
900
|
+
|
|
901
|
+
- **`tests/test_failure_handling.py`** — 17 new pytest cases covering all five
|
|
902
|
+
Requirements above, including round-trip stream assertions and the deep-copy
|
|
903
|
+
isolation property.
|
|
904
|
+
|
|
905
|
+
- **`docs/cookbook/errors.md` section 8 (`空 FinalAnswer 與小模型 robustness`).**
|
|
906
|
+
Four-point cookbook entry (schema minLength → runtime fallback → sub-3B
|
|
907
|
+
`max_iterations=12` recommendation → EventStream replay worked example)
|
|
908
|
+
designed for NotebookLM upload + grammar-constrained retry diagnosis.
|
|
909
|
+
|
|
910
|
+
### Changed
|
|
911
|
+
|
|
912
|
+
- **BREAKING: malformed JSON from the model no longer becomes a
|
|
913
|
+
`FinalAnswerAction(answer=raw_output)`.** v0.1.1 silently wrapped raw text
|
|
914
|
+
as a final answer when `json.loads` failed; v0.1.2 returns a
|
|
915
|
+
`ValidationErrorObservation(validator_name="action_parse",
|
|
916
|
+
error_type=json_syntax)` from `Agent.step` and lets the loop retry. The
|
|
917
|
+
`Agent.step` return type is now `Union[Action, Observation]`; callers that
|
|
918
|
+
pattern-matched `Action` exclusively need to widen their match.
|
|
919
|
+
|
|
920
|
+
- **BREAKING: unknown `skill_name` at parse time produces a
|
|
921
|
+
`ValidationErrorObservation` instead of `ToolErrorObservation`.** v0.1.1
|
|
922
|
+
let unknown skill names flow through `_dispatch_skill` which then emitted
|
|
923
|
+
`ToolErrorObservation`; v0.1.2 catches them in `_parse_action` and emits
|
|
924
|
+
`ValidationErrorObservation(validator_name="action_parse",
|
|
925
|
+
error_type=unknown_skill)` instead. `ToolErrorObservation` remains the
|
|
926
|
+
response for runtime dispatch failures (registered skill that raises at
|
|
927
|
+
call time, args validation failure).
|
|
928
|
+
|
|
929
|
+
- **`pyproject.toml` version is now `0.1.2`** (the v0.1.1 git tag was
|
|
930
|
+
pushed without bumping the in-source version; this release fixes that
|
|
931
|
+
drift).
|
|
932
|
+
|
|
933
|
+
### Fixed
|
|
934
|
+
|
|
935
|
+
- The empty-`FinalAnswerAction` short-circuit bug originally observed on
|
|
936
|
+
Gemma 4 E2B inside `examples/01_book_recommender/notebook.ipynb` is now
|
|
937
|
+
framework-side hardened. Students who select E2B see retry events in their
|
|
938
|
+
EventStream instead of a silently-empty answer.
|
|
939
|
+
|
|
940
|
+
### Spec / Doc Notes
|
|
941
|
+
|
|
942
|
+
- This release brings the cantus codebase into conformance with the
|
|
943
|
+
`Effective Version` clauses in `colab-llm-agent/openspec/specs/agent-runtime/spec.md`,
|
|
944
|
+
`openspec/specs/api-docs/spec.md`, and `openspec/specs/task-template/spec.md`.
|
|
945
|
+
All five `agent-runtime` Requirements (`FinalAnswerAction.answer is
|
|
946
|
+
non-empty`, `Action parse failures fall back to ValidationErrorObservation`,
|
|
947
|
+
`max_iterations exhaustion appends MaxIterationsObservation`, `Default loop
|
|
948
|
+
budgets and small-model recommendation`, `Validator name vocabulary is
|
|
949
|
+
closed and case-sensitive`) and the `api-docs` `errors.md` cookbook
|
|
950
|
+
Requirement now have shipping implementations.
|
|
951
|
+
|
|
952
|
+
- The `api-docs` spec references the cookbook section under
|
|
953
|
+
`docs/api/cookbook/errors.md`; the actual cantus repo layout uses
|
|
954
|
+
`docs/cookbook/errors.md` (no `api/` segment). This release appends the new
|
|
955
|
+
section to the existing real-path file. The spec/repo path discrepancy is a
|
|
956
|
+
pre-existing inconsistency that predates this change and is not addressed
|
|
957
|
+
here; a future follow-up change is expected to either restructure cantus
|
|
958
|
+
docs to `docs/api/` or amend the spec path.
|
|
959
|
+
|
|
960
|
+
## [0.1.1] - 2026-05-11
|
|
961
|
+
|
|
962
|
+
### Fixed
|
|
963
|
+
|
|
964
|
+
- `cantus.mount_drive_and_load` and `load_gemma` public wrappers now correctly
|
|
965
|
+
pass through `**kwargs` (notably `drive_root`) to the underlying loader.
|
|
966
|
+
|
|
967
|
+
## [0.1.0] - 2026-05-11
|
|
968
|
+
|
|
969
|
+
### Added
|
|
970
|
+
|
|
971
|
+
- Initial release: framework extracted from `colab-llm-agent` and published as
|
|
972
|
+
the standalone `schola-cantorum/cantus` repository under ECL 2.0.
|
|
973
|
+
- Core: `Action` / `Observation` dataclass hierarchy, `EventStream`,
|
|
974
|
+
`Agent.step` / `Agent.run` bounded loop, `Registry`, `Result`.
|
|
975
|
+
- Protocols: `Skill`, `Analyzer`, `Validator`, `Workflow` (decorator,
|
|
976
|
+
function-pass, class-first), `Memory` (class-only base + `ShortTermMemory`
|
|
977
|
+
/ `BM25Memory` / `EmbeddingMemory`), `@debug` decorator.
|
|
978
|
+
- Grammar: `cantus.grammar.tool_call.build_schema()` and `parse_tool_call()`
|
|
979
|
+
for JSON-shape tool-call constraints with free-form `thought`.
|
|
980
|
+
- Model: `cantus.model.loader.mount_drive_and_load` for Colab + Drive
|
|
981
|
+
workflows.
|
|
982
|
+
- Docs: `docs/overview.md`, `docs/quickstart.md`, `docs/protocols/*.md`,
|
|
983
|
+
`docs/cookbook/*.md`, `docs/llms-txt.md`, plus `llms.txt` at repo root.
|