- 人工智能
- AI Agent
- Agent 框架
- 大模型
- 工具调用
- RAG
- 提示工程
- 强化学习
【免费下载链接】agent-core
openJiuwen agent-core可提供AI Agent开发、运行、调优与演进相关的全套SDK能力
调度模式(dispatch_mode == "scheduled")是 openJiuwen agent-core 中智能体团队(Agent Teams)的第二种任务分发范式:不同于自主模式由成员自发领取任务,调度模式在 leader 进程内内置一个决策引擎(TeamScheduler),由它统一负责任务的开工、送审、投票判定、打回返工与升级处置。本文以 调度模块契约文档 为主体,结合 TeamScheduler 源码、设计文档 F_62、规格文档 S_22 与 单元测试 逐层展开,读完你将掌握调度器的触发模型、双幂等扫描、评审投票判定、两阶段消息渲染以及 review feedback 驱动的 Skill 演进旁路,具备直接阅读和扩展这套运行时所需的全貌。
一、定位:与 coordination 平齐的 leader 侧决策层
在 agent-core 的智能体团队架构中,成员唤醒与任务决策是两条职责截然不同的链路:
- coordination(唤醒层):只负责将 transport 事件分发给各 handler、唤醒成员进程,不做任何决策(其铁律"不做决策"始终保持成立);
- scheduling(决策层):本包只做决策——启动任务、派发评审、结算票数、升级处置——不直接触碰其他成员的 round,所有交接都以 leader 身份的邮箱消息完成。
从包结构看(见 scheduling 包),该模块由四个文件组成:
| 文件 | 职责 |
|---|---|
| scheduler.py | TeamScheduler:事件粗筛 + 双幂等扫描(开工 / 验票)+SchedulerHost窄协议 + 已挂载TeamSkillEvolutionRail的 reviewer feedback 旁路(F_73) |
| review_feedback_evolution.py | ReviewFeedbackEvolutionCoordinator:与产品无关的逐 Task Feedback 归因、团队终态汇总演进与新 Skill 候选路由 |
| verdict.py | 纯函数投票判定(settle_review_tally)——策略可整体替换,不碰票据存储与状态机 |
| render.py | 两类收件人两套消息机制(成员走模板、leader 走 i18n 短串) |
调度器只在spec.dispatch_mode == "scheduled"的 leader kernel 上构造,teammate 进程与 autonomous leader 的scheduler恒为None。这一判断直接来自 blueprint schema:dispatch_mode: Literal["autonomous", "scheduled"] = "autonomous"是构建期静态配置,运行期没有任何模式切换路径。
二、核心设计哲学:九条不变量
调度模式运行时的一切行为都由 AGENTS.md 中"核心不变量"一节约束。理解这九条,就理解了整套设计的骨架。
1. 调度器不理解事件,只理解看板
任何触发(task/member transport 事件、POLL_TASK心跳、SCHEDULER_SCAN回声、激活)都跑同一对幂等扫描;恢复就是激活扫描本身,不存在独立的恢复路径。事件仅承担两个扫描推不出的职责:终态摘要与TASK_LIST_DRAINED收尾——因为事件只发一次,而扫描永远看到的是"刚刚发生"之前的旧状态。
这一洞察来自 F_62 的设计评审:状态机已经把"解锁"编码为BLOCKED → PENDING自动翻转、把"占用"编码为一活跃探针,因此调度器只需轮询看板,无需 per-event 分支,崩溃恢复天然免费。
2. 交接 = leader 身份邮箱消息,投递即启动
_send_as_leader(scheduler.py)先落邮箱行(持久化,离线成员首次 sweep 补投),再调用host.auto_start_member(复用UNSTARTED→STARTINGCAS,幂等)。成员在线与否不是开工前置条件,调度器不做在线过滤。这条通道免费获得三样东西:
- 消息持久化(离线不丢、重启后补投);
- 幂等懒启动(已启动成员自动跳过);
- 与人发消息完全一致的成员侧接收路径(
MessageHandler零改动)。
成员之间不直接send_message交接,verify 闸事件自反应被收窄为自主模式专用。
3. 发送存意图,投递才成文(F_63)
邮箱行的content恒为空,meta携带模板 key +refs;文案由message_template.expand_message在投递时刻按收件人语言渲染,{{task.*}}取的是当时的任务行——所以队列里躺了很久的交接不会投出过期的任务简报,leader 中途update_task也立刻对未投递消息生效。能表查的一律进refs现查;params只放表答不出的瞬时值(某轮 fail feedback 聚合、解析后的轮数上限)。不要为了"省一次查询"把任务正文快照进消息——这是 F_63 明确禁止的做法。
4.SchedulerHost.deliver_input只准注入 leader 自身
终态摘要 / 升级 / 收尾三类消息直投 leader 输入流;对成员永不直投,一律走邮箱。SchedulerHost是刻意做小的窄协议(scheduler.py):
@runtime_checkable class SchedulerHost(Protocol): async def deliver_input(self, content: Any, *, use_steer: bool = True) -> None: """Inject content into the leader's own input stream.""" ... async def auto_start_member(self, member_name: str) -> bool: """Best-effort start of one UNSTARTED member runtime.""" ...5. 判定在本包,票据在 DB
verify_task(scheduled 团队)只追加票行(INSERT,改票 = 再插一行、tally 取每 reviewer 最新一票,天然无并发冲突且全量留痕可审计),不翻转状态;判定由verdict.settle_review_tally三值完成,settle 经task_manager.settle_review的IN_REVIEW源态 CAS——单判定者(leader 单事件循环)+ CAS 保证不双结算。工具只记事实,调度器只做判定。
6. 轮数升级与停摆升级共用一条注入路径
按(task_id, review_round)去重;升级后任务留在IN_REVIEW,决定性迟票仍可正常 settle(不再重复升级)。
7. leader 自发事件的可见性靠SCHEDULER_SCAN回声
kernel 的_filter_self丢弃 self 事件时,若调度器激活且是task_*事件,改投一个SCHEDULER_SCANinner 事件(见 kernel.py)——coordination 无 handler 监听它,调度器把它当纯扫描提示。这解决了"leader 自己 create_task / settle 产生的事件被自过滤、调度器天然看不到"的死角。
8. 异常语义与 coordination 对齐
on_event吞普通异常(log + 下次触发重试),绝不让 bus loop 挂掉,与AsyncCallbackFramework.trigger的 swallow 语义一致。
9. review feedback 是可选旁路(F_73)
失败轮次 settle 后,scheduler 从 host harness 查找已挂载且开启该能力的TeamSkillEvolutionRail,后台调用其窄入口,不阻塞返工;全部任务终态时先等待已启动 callback,再调用同一 Rail 的终态汇总入口。callback 按(task_id, review_round)进程内去重、异常只记录。scheduler 不理解 LLM、轨迹、Skill 归因、审批或持久化,也不自行构造 Rail——这是边界设计的核心。
三、双幂等扫描:开工与验票
调度器的全部决策逻辑收敛在_scan(scheduler.py)中,它以有界不动点方式运行两轮扫描:
async def _scan(self) -> None: task_manager = self._infra.task_manager if task_manager is None: return for _ in range(_MAX_SCAN_PASSES): # 有界不动点,最多 4 轮 acted = await self._reconcile_starts(task_manager) acted = await self._reconcile_reviews(task_manager) or acted if not acted: return_MAX_SCAN_PASSES = 4的意义:一次 settle 会解锁依赖者,其开工本应由 leader 自身的事件回声在下一次 wake 才拾起;在扫描内循环几次即可立即收敛,且幂等 CAS 使多余的 pass 成为 no-op。
开工扫描_reconcile_starts
对每个成员:
- 列出全部
PENDING任务,按 assignee 分组; - 取该成员名下
updated_at最早的一个任务(min(queue, key=lambda task: (task.updated_at or 0, task.task_id))); - 经
get_other_active_task_id探活——若成员已有活跃任务则跳过; - 调用
task_manager.start_task(task_id)(CAS,并发/重复触发无害); - 成功后以 leader 身份发送开工消息(
render.meta_task_start(started))。
plan_mode 成员由start_task感知成员 mode,任务落PLANNING而非IN_PROGRESS,开工消息对应scheduler_task_start_plan模板。TASK_STARTED事件保留为 board 观测真相,不再承担成员唤醒职责。
验票扫描_reconcile_reviews
对每个IN_REVIEW任务依次执行四个动作:
送审派发——当前轮(task_id, review_round)尚未派发过(内存记账_review_dispatched),则逐个 DM reviewer 评审请求;_dispatch_to_reviewer通过一次性临时 reviewer harness实现(scheduler.py):以团队基础 agent spec 为基础,注入VerifyTaskTool+ViewTaskToolV2两个团队工具,按 reviewer 类型(verifier / inspector / challenger)加载对应提示词模板,run_once后立即 dispose。模型调用瞬时失败(错误码181001)最多重试 3 次;全部重试耗尽则从_review_dispatched撤记账,让下一次扫描重新派发。
判定——读取当前轮票(每 reviewer 取最新一票),交给纯函数settle_review_tally。
结算——
VERDICT_PASS→settle_review(task_id, "pass"):IN_REVIEW → COMPLETED+ 解依赖 + 发TASK_VERIFIED,随后 DM author 汇报请求,并注入 leader 一行终态摘要;VERDICT_FAIL且review_round < max_review_rounds→settle_review(task_id, "fail", feedback):IN_REVIEW → IN_PROGRESS+ 发TASK_REVISION_REQUESTED,随后 DM author 返工消息(聚合 feedback + 轮次 r/N);VERDICT_FAIL且review_round >= max_review_rounds→轮数升级:不再自动打回,任务留IN_REVIEW,升级消息直接注入 leader作为 user message(附各轮票与 feedback 摘要、可选处置:update_task改派 / reset / cancel / 增删成员),由 leader 调整任务规划与成员配置。
停摆处理(_handle_undecided)——未定且本轮开启已超review_stall_timeout(默认 1800s)→停摆升级,与轮数升级同一注入路径与去重,附已投/未投票 reviewer 名单;未超时但超过软催办窗口(_REVIEW_RENUDGE_SECONDS = 600s 包内常量)→ 对未投票 reviewer 重发催办提醒(render.meta_review_renudge),每轮每窗口只发一次。
settle 与升级的内存记账均不持久化:leader 重启最坏重发一次送审或升级,看板真相在 DB。
四、投票判定:verdict.py 纯函数数学
verdict.py 是无 IO、无状态的纯函数模块,F_62 评审决策 A 明确"票据落 DB(高效追加写)、判定在调度器(策略可替换,不焊死阈值制)",因此策略更换 = 替换settle_review_tally,不碰票表与状态机:
VERDICT_PASS = "pass" VERDICT_FAIL = "fail" VERDICT_UNDECIDED = "undecided" def settle_review_tally(tally: dict, inspector_threshold: float = 0.85) -> str: # — binary pool: verifier + challenger — bin_total = tally.get("verdict_total", 0) if bin_total > 0: if tally.get("verdict_voted", 0) < bin_total: return VERDICT_UNDECIDED if tally.get("verdict_fail_count", 0) > 0: return VERDICT_FAIL # — score pool: inspector — insp_total = tally.get("inspector_count", 0) if insp_total > 0: if tally.get("inspector_voted", 0) < insp_total: return VERDICT_UNDECIDED avg = tally.get("inspector_avg") if avg is None or avg < inspector_threshold: return VERDICT_FAIL return VERDICT_PASS判定规则可归纳为:二元票池(verifier + challenger)一票否决——全部投票后任一 fail 即败;检视者分数池——全部投票后平均分须 ≥ 0.85;任一票池未集齐则UNDECIDED。这正是 F_73 文档中"verifier + challenger 二元票 + inspector 分数"的多类型 reviewer 结算设计。单 reviewer 在阈值下数学自然退化:一票 pass 即完成、一票 fail 即打回,与自主模式行为一致。
五、消息面:两类收件人、两套机制
F_63 将 F_62 的消息面从"发送时刻定格全文快照"升级为两阶段渲染,消息行是持久事实、投给 LLM 的是视图。完整契约见 S_22 消息面 与 F_63 设计文档。
成员交接:模板 + 投递时渲染
调度器不组装文案,只组装投递载荷:render.meta_*产出{"template", "refs", "params"}(见 render.py),_send_as_leader以content=""+meta=...落邮箱行。文案在投递时刻由message_template.expand_message渲染。
| 场景 | 收件人 | template key |
|---|---|---|
| 开工(build / plan 闸) | assignee | scheduler_task_start/scheduler_task_start_plan |
| 送审派发 / 催办 | 每个 reviewer / 未投票 reviewer | scheduler_review_request/scheduler_review_renudge |
| 打回返工(params: 聚合 fail feedback + 解析后轮数上限) | author | scheduler_rework |
| 验收通过后要求汇报 | author | scheduler_verified_report |
模板正文按语言存放于 prompts/cn 与 prompts/en(共 14 份scheduler_*.md),是成员侧文案的唯一来源——F_63 评审明确否决了 i18n 保留成员侧降级短句的方案,防止同一条消息文案双源漂移。
meta 三铁律(落 S_12):① meta 单一事实,模板消息 content 恒空;② framework-only 投递载荷,meta 只影响"如何投递 / 渲染",永不承载业务事实;③protocol字段不动,调度器消息恒为"plain"。
占位符标准{{namespace.field}}:单遍正则替换、填充值永不二次扫描(任务正文是 LLM 写的,防占位符注入);命名空间走字段白名单(不做 getattr 直通,新增 DB 列不会无意泄进提示词);未知命名空间渲染为<missing:ns.field>。三个命名空间:
| 命名空间 | 数据源 | 时机 | 白名单 |
|---|---|---|---|
task.* | 任务行(refs.task) | 投递时现查 | task_idtitlecontentstatusassigneereviewerreview_roundmax_review_rounds |
member.* | 成员行(refs.member) | 投递时现查 | member_namedisplay_namedesc |
param.* | meta.params | 发送时定格 | 键即字段(标量);只放表答不出的瞬时值 |
降级链:模板缺失 / refs 行已删 / parse 错 → 投递点从 meta现场合成一行 fallback(template key + task_id,成员view_task兜底),不预存副本;meta 整体缺失 = 普通消息,原样投content。展开点共四处:handlers/message.py的_format_message(进程内邮箱)与_notify_human_agent_inbound(HITT 人类成员回调)、_bridge_deliverable_for(bridge relay)、external/format.py+client.read_inbox(外部成员 pull)。
leader 直投:i18n 短串
leader 摘要/升级不经邮箱 → 无 meta 通道、无投递时展开,维持一行式 i18n 运行时短串(i18n.py的scheduler.leader_*键):scheduler.leader_task_done/scheduler.leader_escalation_rounds/scheduler.leader_escalation_stall/scheduler.leader_all_done,分别对应终态摘要、轮数升级、停摆升级与收尾总结。
六、review feedback 旁路:失败反馈驱动的 Skill 演进(F_73)
调度器本身不理解 LLM、轨迹与 Skill——它只是把审核事实发布给已挂载的演进 Rail。完整数据流见 F_73 文档:
review round failed -> TeamScheduler finds mounted TeamSkillEvolutionRail -> rail.handle_review_feedback(payload) -> coordinator loads assignee trajectory -> ReviewFeedbackContextBuilder -> ReviewFeedbackAttributor -> evolve_existing_skill -> suggest_new_skill -> record_task_failure -> skip_unattributed all tasks terminal -> wait for outstanding task callbacks -> rail.finalize_review_feedback(payload) -> coordinator aggregates observations -> existing Skill: mounted TeamSkillEvolutionRail -> repeated missing capability: mounted TeamSkillCreateRail -> existing team approval transport and endpointReviewFeedbackEvolutionCoordinator(review_feedback_evolution.py)是 Core 内与产品无关的协调器,关键设计点:
- 逐 Task 归因:每个失败轮次 payload 到达后,从
team_rail.get_trajectory取团队轨迹、经select_member_spans按 assignee 投影出成员独立轨迹(避免被 leader 或其他成员污染),再经ReviewFeedbackAttributor一次结构化 LLM 调用区分四种情况; - 确定性策略负责安全放行:模型输出不能直接触发修改——
skill_issue必须有轨迹中SKILL.md读取证据且目标 Skill 属于已读集合才能演进;executor_error只记录不演进;new_skill_pattern必须有重复证据与可复用指导才能进入创建提议;空输入 / 无效 JSON / 模型异常全部 fail closed 为skip_unattributed; - 团队终态汇总:
on_team_completed把同一 Skill 的 observations 合并后调用team_rail.evolve_from_external_signals(绕过被动触发检测,但不绕过标准 optimizer、semaphore、ExperienceManager、审批与EvolutionStore持久化);重复缺失能力经TeamSkillCreateRail.propose_from_external_evidence进入审批,只进审批、不直接创建(默认置信度阈值min_confidence = 0.7,聚合 feedback 截断 16_000 字符); - 去重与并发:按
(task_id, review_round, feedback)去重,归因串行化(共享模型 + 重复证据顺序),汇总演进串行化。
scheduler 侧只有三组进程内状态:_review_feedback_dispatched[(task_id, review_round)](失败轮次 callback 去重)、_review_feedback_tasks(后台 callback 任务集合)、_team_review_feedback_dispatched(团队终态 callback 去重)——它们不是任务事实、不持久化,任务状态、票据和 feedback 仍以 DB 为真相。
七、生命周期与 kernel 接线
调度器的激活与失活完全跟随 leader kernel(S_22 不变量 1-2):
CoordinationKernel.setup仅在spec.dispatch_mode == "scheduled"的 leader 上构造(休眠态);其余 kernel 不构造;- 激活点两个:
notify_team_built()(build_team 成功回调,先于任何 spawn / create_task)与kernel.start()(team 行已存在——warm resume / 冷恢复,激活即恢复扫描); - wake 路径:
kernel._build_wake_callback()(kernel.py)组合"coordination dispatch → scheduler.on_event"——coordination 先观察事件、调度器后做决策; pause()/stop()即deactivate();所有内存记账(送审去重 / 催办节流 / 升级去重 / 摘要去重)不持久化,重启后最坏重发一次,board 真相在 DB;- review feedback callback 不进入状态机关键路径;团队终态 callback 是旁路汇总边界,不改变 all-done 摘要与 pause/stop 决策。
对应地,成员侧由ScheduledTaskBoardHandler/ScheduledStaleTaskHandler两个模式子类接管:verify 闸三事件与TASK_REVIEW_VOTE一律路由到 resume_polls-only 的on_task_board_event(交接是调度器的 leader 身份邮箱消息,事件自反应会双投递),poll tick 只扫自己的 stale 活跃任务作漏投递兜底。handler 方法内禁止出现 dispatch_mode 分支——模式差异只发生在装配点。
八、配置消费
调度器消费的配置全部只在 leader 侧、不跨进程镜像,定义于 TeamAgentSpec:
| 配置项 | 默认值 | 校验 | 语义 |
|---|---|---|---|
dispatch_mode | "autonomous" | Literal["autonomous", "scheduled"] | 构建期静态分发模式,决定调度器是否构造 |
enable_task_verification | False | 布尔 | 团队级"强制任务校验"开关(提示词驱动,CapabilityOverrides可覆盖;只在 scheduled 下向模型暴露) |
default_max_review_rounds | 3 | >= 1 | per-task 未设max_review_rounds时的评审轮数上限默认值 |
review_stall_timeout | 1800(秒) | > 0 | 单轮评审停摆升级阈值 |
verify_vote_threshold | 2/3 | 0 < t <= 1 | 投票通过阈值(配置项存在,当前settle_review_tally以二元票一票否决 + inspector ≥0.85 实现策略) |
催办间隔_REVIEW_RENUDGE_SECONDS = 600s 是包内常量(scheduler.py),不进 spec。TeamSpec.dispatch_mode镜像 spec 值随 spawn 载荷到达成员进程;team_info.dispatch_mode列只是记录,spec 是运行时真相。调度器在构造时直接从blueprint.spec读取default_max_review_rounds与review_stall_timeout(scheduler.py)。
九、测试与验证
调度器行为有完整单元测试覆盖,核心在 test_team_scheduler.py(657 行),另含test_review_voting.py、test_mode_handlers.py等。测试夹具在 SQLite 内存库上真实构建TeamDatabase+TeamTaskManager+FakeHost(记录deliver_input注入与auto_start_member启动),覆盖:开工扫描(一活跃 / 最早优先 / 幂等 / plan 闸落点 / DM 编排与懒启动)、验票扫描(阈值边界 / 不可达早判 / 轮数上限升级 / 派发去重 / 崩溃补判 / 停摆重发)、投票判定纯函数全分支、verify_task 双模式分流与消息 meta 正确组装。F_62 落地时基线为python -m pytest tests/unit_tests/agent_teams→1878 passed / 16 skipped(净新增 36 例),F_63 后为1905 passed / 16 skipped(净新增 27 例)。
十、进一步阅读
调度模式的完整设计脉络沉淀于agent_teams/docs目录,建议按以下顺序深入:
- F_62 调度模式 runtime + 评审投票:背景、四条核心洞察、被拒绝的方案(事件广播 / 内存聚票 / 超限自动打回等)与评审确认决策 A-G;
- S_22 scheduling-runtime:系统当前形态的 16 条不变量、接口契约与消息面契约;
- F_63 调度器消息模板 + 投递时渲染:两阶段渲染的动机、meta 三铁律与降级链演进;
- F_73 Reviewer Feedback 驱动的 Skill 演进边界:归因安全策略、成员轨迹投影与新建 Skill 审批边界。
调度器相关事件类型(POLL_TASK、SCHEDULER_SCAN、TASK_REVIEW_VOTE)定义在 event_bus.py 与 events.py,可作为阅读接线细节的入口。理解"coordination 唤醒、scheduling 决策"这条边界后,即可在需要时为团队扩展新的调度策略或评审规则。
- 人工智能
- AI Agent
- Agent 框架
- 大模型
- 工具调用
- RAG
- 提示工程
- 强化学习
【免费下载链接】agent-core
openJiuwen agent-core可提供AI Agent开发、运行、调优与演进相关的全套SDK能力
相关推荐
openJiuwen agent-core 调度模式决策引擎(F_62)深度解析:TeamScheduler 双幂等扫描、评审投票与两阶段消息渲染
openJiuwen agent core 调度模式决策引擎(F_62)深度解析:TeamScheduler 双幂等扫描、评审投票与两阶段消息渲染 调度模式(
人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习Apache Groovy 治理机制详解:决策渠道、共识投票与 CTR/RTC 评审模式
Apache Groovy 治理机制详解:决策渠道、共识投票与 CTR/RTC 评审模式 Apache Groovy 作为 Apache 软件基金会(ASF)旗
编程语言编译器语言运行时openJiuwen agent-core Runner 统一执行引擎实战:Workflow/Agent/Tool 调度、资源管理与异步回调框架
openJiuwen agent core Runner 统一执行引擎实战:Workflow/Agent/Tool 调度、资源管理与异步回调框架 本篇指南以 o
人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考