deerflow2/backend/packages/harness/deerflow/config
Xinmin Zeng befe334f10
fix(config): make the reload boundary discoverable from code (#3144) (#3153)
* fix(config): make the reload boundary discoverable from code, not just docs

Closes #3144.

The hot-reload contract — per-run fields are resolved through
`get_app_config()` on every request, infrastructure fields snapshot at
gateway startup — landed in `backend/CLAUDE.md` as part of #3131. A
maintainer reading `get_config()` or an `AppConfig` field still had to
context-switch to that document to know which fields require a process
restart, and there was no enforcement that the prose list stayed in
sync with the code.

This commit moves the boundary to a machine-readable single source of
truth and surfaces it where the code lives:

- New `deerflow.config.reload_boundary` module owns the registry of
  restart-required fields (`STARTUP_ONLY_FIELDS`) and a tiny helper
  API (`is_startup_only_field`, `iter_startup_only_field_paths`,
  `format_field_description`). The standardised `"startup-only:"`
  prefix is exported as `STARTUP_ONLY_PREFIX` so future scanners /
  lint hooks / doc generators can pivot off it without re-parsing
  prose.
- `AppConfig`'s `database`, `checkpointer`, `run_events`,
  `stream_bridge`, `sandbox`, and `log_level` fields now build their
  `Field(description=...)` from `format_field_description(...)`. The
  same text shows up in IDE hover (Pydantic v2 exposes `description`
  via `model_fields[...]`).
- `channels` is restart-required too but lives outside the AppConfig
  Pydantic schema (the config section is consumed directly by
  `start_channel_service`). The registry owns it so the boundary is
  not split between two places.
- `get_config()` docstring points to the registry instead of leaving
  the reader to find `CLAUDE.md`. The `CLAUDE.md` table collapses to
  a one-liner pointing back at `reload_boundary.py` so the boundary
  has one canonical location, not two.

Drift coverage in `tests/test_reload_boundary.py`:

- Every registered field has a non-trivial reason.
- Iterator / membership helpers stay in sync with the dict.
- Every registry entry that maps to an `AppConfig` field also carries
  the `"startup-only:"` prefix in the schema (catches "forgot to
  update the schema").
- Reverse drift: any AppConfig field whose description starts with
  the prefix must be registered (catches "marked restart-required in
  the schema but forgot the registry").
- The runtime introspection that IDE hover depends on
  (`AppConfig.model_fields["database"].description`) is pinned, so a
  future Pydantic upgrade or schema swap that breaks the hover surface
  shows up as a test failure rather than a silent regression.

Refs: bytedance/deer-flow#3138 (split summary), #3107 (origin), #3131
(prior boundary fix in prose form).

* fix(config): preserve field doc and correct log_level reload reason

Two follow-ups on the PR #3153 review:

1. The `log_level` STARTUP_ONLY_FIELDS reason previously claimed
   `apply_logging_level()` mutates the root logger level. It does not:
   only the `deerflow` / `app` logger levels are set, and root handler
   thresholds are conditionally lowered so messages from those loggers
   can propagate. Reword to match the actual behavior so operators
   reading IDE hover get accurate restart guidance.

2. `format_field_description(field_path)` was the sole `Field(description=)`
   for every restart-required field, which silently overwrote the
   original human-facing documentation — most visibly the `log_level`
   field that used to list debug/info/warning/error and clarify that
   third-party libraries are not affected. Extend the helper with a
   keyword-only `field_doc` parameter that composes the startup-only
   marker with the original prose so IDE hover documents both *why*
   the field is restart-required and *what* it actually accepts.
   Updated all six restart-required AppConfig fields (`log_level`,
   `database`, `sandbox`, `run_events`, `checkpointer`, `stream_bridge`)
   to pass their original descriptions through the helper.

Tests: two new cases in `test_reload_boundary.py` pin (a) the helper
composition and (b) every AppConfig restart-required field still
surfaces a recognisable substring of its original documentation.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-06-07 21:27:14 +08:00
..
__init__.py feat(loop-detection): make loop detection configurable with per-tool frequency overrides (#2711) 2026-05-07 16:15:15 +08:00
acp_config.py feat(acp): add env field to ACPAgentConfig for subprocess env injection (#1447) 2026-03-27 20:03:30 +08:00
agents_api_config.py fix: disable custom-agent management API by default (#2161) 2026-04-14 00:03:38 +08:00
agents_config.py feat(agent): add custom-agent self-updates with user isolation (#2713) 2026-05-05 23:17:42 +08:00
app_config.py fix(config): make the reload boundary discoverable from code (#3144) (#3153) 2026-06-07 21:27:14 +08:00
checkpointer_config.py fix(packaging): add postgres extra for store/checkpointer supportFix postgres extra install guidance (#2584) 2026-05-09 09:49:08 +08:00
database_config.py feat(persistence):Unified persistence layer with event store, feedback, and rebase cleanup (#2134) 2026-04-26 11:09:55 +08:00
extensions_config.py fix(mcp): accept transport field as alias for type (#3238) (#3243) 2026-06-03 18:11:38 +08:00
guardrails_config.py feat(guardrails): add pre-tool-call authorization middleware with pluggable providers (#1240) 2026-03-23 18:07:33 +08:00
loop_detection_config.py feat(loop-detection): make loop detection configurable with per-tool frequency overrides (#2711) 2026-05-07 16:15:15 +08:00
memory_config.py feat(persistence): per-user filesystem isolation, run-scoped APIs, and state/history simplification (#2153) 2026-04-26 11:13:01 +08:00
model_config.py fix(#3189): prevent write_file streaming timeout on long reports (#3195) 2026-06-07 17:47:11 +08:00
paths.py fix(mcp): add auth interceptor with channel user_id and keep header propagation to mcp tools (#3294) 2026-06-03 15:48:19 +08:00
reload_boundary.py fix(config): make the reload boundary discoverable from code (#3144) (#3153) 2026-06-07 21:27:14 +08:00
run_events_config.py feat(persistence): add unified persistence layer with event store, token tracking, and feedback (#1930) 2026-04-26 11:05:47 +08:00
runtime_paths.py fix(harness): resolve runtime paths from project root (#2642) 2026-05-01 22:19:50 +08:00
safety_finish_reason_config.py fix(runtime): suppress tool execution when provider safety-terminates with tool_calls (#3035) 2026-05-22 21:20:28 +08:00
sandbox_config.py fix: add output truncation to ls_tool to prevent context window overflow (#1896) 2026-04-06 15:09:57 +08:00
skill_evolution_config.py Implement skill self-evolution and skill_manage flow (#1874) 2026-04-06 22:07:11 +08:00
skills_config.py fix(harness): restore legacy skills path fallback (#2694) (#2696) 2026-05-03 23:40:59 +08:00
stream_bridge_config.py fix(config): reset config-backed singletons on hot reload (#2588) 2026-05-06 10:17:55 +08:00
subagents_config.py fix(config): reset config-backed singletons on hot reload (#2588) 2026-05-06 10:17:55 +08:00
summarization_config.py fix(middleware): avoid rescuing non-skill tool outputs during summarization (#2458) 2026-04-24 21:19:46 +08:00
title_config.py fix(tracing): propagate session_id and user_id into Langfuse traces (#2944) 2026-05-21 16:49:31 +08:00
token_usage_config.py enable token usage by default (#2841) 2026-05-10 22:00:57 +08:00
tool_config.py refactor: split backend into harness (deerflow.*) and app (app.*) (#1131) 2026-03-14 22:55:52 +08:00
tool_output_config.py feat(agent): add ToolOutputBudgetMiddleware for oversized tool output protection (#3303) 2026-05-29 22:59:26 +08:00
tool_search_config.py feat(tools): add tool_search for deferred MCP tool loading (#1176) 2026-03-17 20:43:55 +08:00
tracing_config.py fix(tracing): propagate session_id and user_id into Langfuse traces (#2944) 2026-05-21 16:49:31 +08:00