From d3887531e9d89e401d9044c7b04703d009612d8e Mon Sep 17 00:00:00 2001 From: Hyeonsang Cho Date: Sat, 10 Oct 2026 09:04:01 +0900 Subject: [PATCH] docs: list the summarize event tag and scope the extension example's claims (#6582) * docs: list the summarize event tag and scope the extension example's claims RUN_EVENT_STREAM.md listed seven middleware tags, but the catalog and the run event stream contract carry eight; `summarize` is emitted by SummarizationMiddleware for every successful summary result. Add the tag, describe the event, and pin the documented list to MIDDLEWARE_EVENT_TAGS so the next tag cannot drift silently. summarization.md now lists all eight `changes` fields of that event. The extension example said it showed all five (README, pyproject, module docstrings, test name) or every contribution kind, while ExtensionRegistry has eight. Say it demonstrates five, name the three it does not, point to the observers guide and the bookmarks plugin, and assert in the example's test that the two observer kinds stay unregistered. * docs(changelog): reference #6582 * docs: clarify extension example package layout scope --------- Co-authored-by: Willem Jiang --- CHANGELOG.md | 10 ++++++++++ CHANGELOG_zh.md | 8 ++++++++ backend/docs/RUN_EVENT_STREAM.md | 10 +++++++++- backend/docs/summarization.md | 3 +++ backend/tests/test_run_event_stream_contract.py | 10 ++++++++++ examples/deerflow-extension-example/README.md | 13 ++++++++++--- .../deerflow_extension_example/__init__.py | 4 ++-- examples/deerflow-extension-example/pyproject.toml | 2 +- .../deerflow-extension-example/tests/test_plugin.py | 5 ++++- 9 files changed, 57 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42d56829d0..988c0f3239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4423,6 +4423,15 @@ This release closes that milestone with **439 merged pull requests**. changes, and a generic `extension-api` hook plus an extension when existing contribution points cannot express the feature. ([#6178]) +- **docs:** Bring the run-event and extension-example docs back in line with + the code. `backend/docs/RUN_EVENT_STREAM.md` now lists the `summarize` + middleware tag that the catalog and contract already carried, describes the + `middleware:summarize` event, and a test pins the documented tag list to + `MIDDLEWARE_EVENT_TAGS`; `backend/docs/summarization.md` lists the event's + three missing `changes` fields. The extension example no longer claims to + cover every contribution kind: it shows five of the eight, and its README + points to the observers guide and the bookmarks plugin for the rest. ([#6582]) + ### Internal - **persistence:** Add historical regression coverage for the run-change clock repair @@ -9407,3 +9416,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#6537]: https://github.com/bytedance/deer-flow/pull/6537 [#6543]: https://github.com/bytedance/deer-flow/pull/6543 [#6556]: https://github.com/bytedance/deer-flow/pull/6556 +[#6582]: https://github.com/bytedance/deer-flow/pull/6582 diff --git a/CHANGELOG_zh.md b/CHANGELOG_zh.md index 9446e666e3..1e4a59d2b6 100644 --- a/CHANGELOG_zh.md +++ b/CHANGELOG_zh.md @@ -3453,6 +3453,13 @@ 时,为 `extension-api` 增加通用钩子并以扩展实现,而不是把业务逻辑硬编码 进核心。([#6178]) +- **文档:** 让运行事件与扩展示例文档重新与代码一致。 + `backend/docs/RUN_EVENT_STREAM.md` 现在列出目录与契约中早已存在的 + `summarize` 中间件标签并说明 `middleware:summarize` 事件,并新增测试将文档中 + 的标签列表固定为 `MIDDLEWARE_EVENT_TAGS`;`backend/docs/summarization.md` + 补充该事件缺失的三个 `changes` 字段。扩展示例不再声称覆盖全部贡献类型: + 它演示八种中的五种,README 为其余类型指向观察者指南和 bookmarks 插件示例。([#6582]) + ### 内部改进 - **测试:** 为 run-change 时钟修复及其回滚补充历史回归覆盖。从已 @@ -7596,3 +7603,4 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子 [#6520]: https://github.com/bytedance/deer-flow/pull/6520 [#6537]: https://github.com/bytedance/deer-flow/pull/6537 [#6556]: https://github.com/bytedance/deer-flow/pull/6556 +[#6582]: https://github.com/bytedance/deer-flow/pull/6582 diff --git a/backend/docs/RUN_EVENT_STREAM.md b/backend/docs/RUN_EVENT_STREAM.md index e89f65e68c..4a603abb22 100644 --- a/backend/docs/RUN_EVENT_STREAM.md +++ b/backend/docs/RUN_EVENT_STREAM.md @@ -86,7 +86,7 @@ through run-event or specialized APIs: Current middleware tags are `guardrail`, `loop_detection`, `safety_termination`, `skill_activation`, `skill_secrets`, `tool_promotion`, -and `tool_progress`. The pattern is intentionally open so new middleware tags +`tool_progress`, and `summarize`. The pattern is intentionally open so new middleware tags are additive. Because the full event type is limited to 32 characters and `middleware:` uses 11, a tag must contain 1-21 characters. @@ -134,6 +134,14 @@ no names emit no event. Queries, routing keywords, catalog hashes, tool schemas and descriptions, arguments, and results are not copied into this middleware event. Other event types retain their existing payload contracts. +`middleware:summarize` records each successful summary result from +`SummarizationMiddleware` with `action` `summary_result`, including results +reused from the no-op cache. The `changes` object carries the no-op and +cache-skip flags, summary and previous-summary lengths, the number of +summarized messages, and the middleware's cumulative counters; see +[summarization.md](summarization.md). Summary text and message content are +never copied into this event. + Ordinary task-tool subagents forward loop-detection, tool-promotion, and tool-progress appends to the parent run loop through dedicated recorder context keys. The loop-bound `RunJournal` itself never enters the isolated subagent diff --git a/backend/docs/summarization.md b/backend/docs/summarization.md index e328277622..d990f4faf9 100644 --- a/backend/docs/summarization.md +++ b/backend/docs/summarization.md @@ -30,6 +30,9 @@ Each successful summary result emits a `middleware:summarize` journal event with - `noop`: the result is byte-identical to the previous summary. - `llm_call_skipped`: this result came from the no-op cache. +- `summary_chars`: length of the new summary. +- `previous_summary_chars`: length of the previous summary, or `null` when there was none. +- `summarized_message_count`: messages folded into this summary. - `call_count`: completed summary results, including cache reuse and canned results. - `noop_count`: completed results identical to the previous summary. - `skip_count`: completed results reused from the no-op cache. diff --git a/backend/tests/test_run_event_stream_contract.py b/backend/tests/test_run_event_stream_contract.py index e2fd2a7ffd..d8f7412a5e 100644 --- a/backend/tests/test_run_event_stream_contract.py +++ b/backend/tests/test_run_event_stream_contract.py @@ -4,6 +4,7 @@ from __future__ import annotations import ast import json +import re from pathlib import Path from uuid import uuid4 @@ -33,6 +34,7 @@ from deerflow.subagents.step_events import SUBAGENT_STEP_MAX_CHARS, capture_step REPO_ROOT = Path(__file__).resolve().parents[2] CONTRACT_PATH = REPO_ROOT / "contracts" / "run_event_stream_contract.json" +DOC_PATH = REPO_ROOT / "backend" / "docs" / "RUN_EVENT_STREAM.md" def _load_contract() -> dict: @@ -369,6 +371,14 @@ def test_dynamic_middleware_event_rejects_tags_that_do_not_fit_persistence(tag): ) +def test_documented_middleware_tags_match_the_catalog(): + doc = DOC_PATH.read_text(encoding="utf-8") + sentence = re.search(r"Current middleware tags are (.+?)\.", doc, re.DOTALL) + + assert sentence is not None + assert set(re.findall(r"`([^`]+)`", sentence.group(1))) == set(MIDDLEWARE_EVENT_TAGS) + + def test_tool_promotion_tag_is_declared_and_fits_the_persisted_event_type(): pattern = _load_contract()["dynamic_event_patterns"][0] diff --git a/examples/deerflow-extension-example/README.md b/examples/deerflow-extension-example/README.md index e6ce490a64..59fbbd7593 100644 --- a/examples/deerflow-extension-example/README.md +++ b/examples/deerflow-extension-example/README.md @@ -1,9 +1,16 @@ # DeerFlow extension example -This directory is a compact, standalone Python package showing all five DeerFlow -extension contribution kinds. It depends on the public +This directory is a compact, standalone Python package showing five of the +eight DeerFlow extension contribution kinds: middleware, task lifecycle, +system-model observer, service, and router. It depends on the public `deerflow-extension-api` contract and never imports `deerflow.*` or `app.*`. +It does not register an agent-assembly observer, a context-compaction observer, +or a full-stack plugin. The observers are covered in the +[Lifecycle and Observers](../../frontend/src/content/en/harness/extensions/observers.mdx) +guide, and [`deerflow-extension-bookmarks`](../deerflow-extension-bookmarks/) +shows a plugin. + The contract package intentionally has no framework dependencies. An extension must therefore declare every framework it imports itself; this example explicitly depends on FastAPI, LangChain, and LangGraph in `pyproject.toml`. @@ -184,7 +191,7 @@ name or the `module:install` value. ```text deerflow_extension_example/ ├── __init__.py # version-stamped install() entry point -└── plugin.py # state plus all five small contribution implementations +└── plugin.py # state plus the five demonstrated contribution implementations tests/ ├── test_entry_point.py ├── test_plugin.py diff --git a/examples/deerflow-extension-example/deerflow_extension_example/__init__.py b/examples/deerflow-extension-example/deerflow_extension_example/__init__.py index 800fda7a26..0cfd152900 100644 --- a/examples/deerflow-extension-example/deerflow_extension_example/__init__.py +++ b/examples/deerflow-extension-example/deerflow_extension_example/__init__.py @@ -1,4 +1,4 @@ -"""A compact, standalone DeerFlow extension exercising every contribution kind.""" +"""A compact, standalone DeerFlow extension exercising five contribution kinds.""" from __future__ import annotations @@ -20,7 +20,7 @@ __all__ = ["install"] @extension(api="0.2.0", name="example") def install(registry: ExtensionRegistry, config: Mapping[str, Any]) -> None: - """Register one example of each supported contribution kind.""" + """Register one middleware, task lifecycle, system-model observer, service, and router.""" if config.get("enabled", True) is False: return diff --git a/examples/deerflow-extension-example/pyproject.toml b/examples/deerflow-extension-example/pyproject.toml index 9f9fa4919c..81267f227f 100644 --- a/examples/deerflow-extension-example/pyproject.toml +++ b/examples/deerflow-extension-example/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "deerflow-extension-example" version = "0.1.0" -description = "A compact standalone example covering every DeerFlow extension contribution kind" +description = "A compact standalone example covering five DeerFlow extension contribution kinds" readme = "README.md" requires-python = ">=3.12" dependencies = [ diff --git a/examples/deerflow-extension-example/tests/test_plugin.py b/examples/deerflow-extension-example/tests/test_plugin.py index 95b2c8f142..ac007b0334 100644 --- a/examples/deerflow-extension-example/tests/test_plugin.py +++ b/examples/deerflow-extension-example/tests/test_plugin.py @@ -70,7 +70,7 @@ class FakeToolRequest: runtime: FakeRuntime -def test_install_registers_all_five_contribution_kinds() -> None: +def test_install_registers_the_five_demonstrated_contribution_kinds() -> None: registry = FakeRegistry() install(registry, {}) @@ -81,6 +81,9 @@ def test_install_registers_all_five_contribution_kinds() -> None: assert len(registry.system_model_observers) == 1 assert len(registry.services) == 1 assert len(registry.contributed_routers) == 1 + # The README names these as not demonstrated here. + assert registry.agent_assembly_observers == [] + assert registry.context_compaction_observers == [] assert [route.path for route in registry.contributed_routers[0].routes] == ["/api/extension-example/stats"] assert install.__deerflow_api__ == "0.2.0" assert install.__deerflow_name__ == "example"