feat(skills): curator 健壮性加固 + 评审模型可配置 + 手动触发体验
调度健壮性:
- 技能进化总开关 skill_evolution.enabled 真正生效;CuratorConfig
与 CuratorSubConfig 默认 enabled 改为 false(显式 opt-in)
- 新增 per-user 运行锁(.curator_state 的 running/started_at),
防定时器与手动触发并发重复运行;6h stale 检测防进程残留死锁
- maybe_run_curator 改用 ThreadPoolExecutor 限并发(上限 5),
防首次部署所有用户同时到期的惊群
- 首次运行持续失败时按 6h 退避重试,不再每个 tick 烧 LLM 配额
- should_run_now 时间解析容忍 naive 时间串;损坏的 last_run_at
触发重跑而非永久死锁
- SkillStore 新增 list_published_names 批量接口,curator 一轮
只查一次 published,取代逐技能查库
LLM 评审超时:
- _run_llm_review 给 agent.invoke 套整体超时
(curator.review_timeout_seconds,默认 600s),超时中止本次评审
并在 curator 日志输出,curator 正常收尾、释放运行锁
- copy_context 把 user 上下文传入工作线程
模型可配置:
- 新增 curator.model_name;curator 评审与技能安全审核共用该模型,
未配置则回退主模型
- 移除独立的 moderation_model_name 配置
- 前端进化面板新增模型下拉选择器
手动触发体验:
- /api/curator/run 派发后台线程前先预检,返回 {triggered, reason},
不再无论开关状态都提示"已开始"
- 前端:总开关/curator 开关未开时禁用"立即执行"按钮,
被预检拦下时按原因(开关未开 / 已有任务在跑)给出准确提示
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5a25dd08f6
commit
39def1c20b
16 files changed
+448
-65
No files matched your search
@@ -302,6 +302,55 @@ Custom agent metadata lives in the `agents` ORM table (`deerflow.persistence.age
|
||||
- **Injection**: Enabled skills listed in agent system prompt with container paths
|
||||
- **Installation**: `POST /api/skills/install` extracts .skill ZIP archive to custom/ directory
|
||||
|
||||
#### Skill Curator (`packages/harness/deerflow/skills/curator.py`)
|
||||
|
||||
Background skill lifecycle manager. The Gateway `lifespan` starts a resident
|
||||
scheduler task (`_curator_scheduler_loop` in `app/gateway/app.py`) that — after a
|
||||
5-minute startup delay — calls `maybe_run_curator()` every hour. A cross-process
|
||||
file lock (`.curator_scheduler.lock`) elects a single leader worker so the
|
||||
scheduler runs once per deployment. Per run:
|
||||
|
||||
- **Master switch** — `skill_evolution.enabled` (default **`false`**) gates the
|
||||
whole feature; `curator.enabled` (default **`false`**) gates the curator
|
||||
specifically. Both must be `true` for the curator to act. `should_run_now()`
|
||||
and `_curator_thread_target()` both enforce this — so manual `POST
|
||||
/api/curator/run` also respects the master switch. Config is
|
||||
`config.yaml.skill_evolution` deep-merged with per-user
|
||||
`users/{user_id}/skill_evolution.json`.
|
||||
- **Per-user run lock** — `.curator_state` carries `running` / `started_at`;
|
||||
`_try_acquire_run_lock()` prevents the same user's curator from running
|
||||
concurrently (scheduler vs. manual trigger, or double manual clicks). A lock
|
||||
whose `started_at` is older than `_CURATOR_STALE_LOCK_HOURS` (6h) is treated as
|
||||
process-crash residue and can be taken over.
|
||||
- **Concurrency cap** — `maybe_run_curator()` collects all due users and hands
|
||||
them to `_dispatch_curator_runs()`, which runs them through a
|
||||
`ThreadPoolExecutor` capped at `_CURATOR_MAX_CONCURRENCY` (5) — avoids a
|
||||
thundering herd of LLM calls when many users come due at once (e.g. first
|
||||
deploy).
|
||||
- **Failure back-off** — `_release_run_lock()` always stamps `last_attempt_at`.
|
||||
When a first run keeps crashing (`last_run_at` never set), `should_run_now()`
|
||||
retries on `_CURATOR_RETRY_HOURS` (6h) instead of every hourly tick.
|
||||
- **`should_run_now()`** is the real gate: the hourly tick only checks "is the
|
||||
per-user `interval_hours` (default 168h) elapsed". Timestamp parsing tolerates
|
||||
naive (tz-less) strings — a corrupt `last_run_at` triggers a re-run rather than
|
||||
a permanent deadlock.
|
||||
- **Published-skill filter** — `SkillStore.list_published_names()` is a batch
|
||||
query; the curator fetches the published-name set once per run instead of one
|
||||
DB round-trip per candidate skill.
|
||||
- **LLM review timeout** — `_run_llm_review()` runs `agent.invoke()` in a worker
|
||||
thread bounded by `curator.review_timeout_seconds` (default 600s). The model's
|
||||
per-request `request_timeout` can't bound a multi-turn React agent loop, so on
|
||||
timeout the run is abandoned (a warning is written to `curator_log`, visible in
|
||||
the Web UI) and the curator finishes cleanly + releases the run lock. The
|
||||
worker context is copied via `contextvars.copy_context()` so tool calls still
|
||||
resolve the correct `user_id`.
|
||||
- **Review model** — `_run_llm_review()` uses `curator.model_name` (a
|
||||
`config.yaml` `models[]` entry name) if set, else the primary model; it is
|
||||
editable from the Web UI evolution panel. The skill security scanner
|
||||
(`scan_skill_content` in `security_scanner.py`) reuses the same
|
||||
`skill_evolution.curator.model_name` setting — review and moderation share one
|
||||
model. There is no separate moderation-model config field.
|
||||
|
||||
### Model Factory (`packages/harness/deerflow/models/factory.py`)
|
||||
|
||||
- `create_chat_model(name, thinking_enabled)` instantiates LLM from config via reflection
|
||||
|
||||
Reference in new issue
Block a user