☰
openJiuwen agent-core 调度模式决策引擎:Scheduled Dispatch Runtime 架构与评审投票机制全解
2026/10/12 14:22:41 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 框架
  • 大模型
  • 工具调用
  • RAG
  • 提示工程
  • 强化学习

【免费下载链接】agent-core

openJiuwen agent-core可提供AI Agent开发、运行、调优与演进相关的全套SDK能力

项目地址:https://gitcode.com/openJiuwen/agent-core
点击查看免费下载

调度模式(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.pyTeamScheduler:事件粗筛 + 双幂等扫描(开工 / 验票)+SchedulerHost窄协议 + 已挂载TeamSkillEvolutionRail的 reviewer feedback 旁路(F_73)
review_feedback_evolution.pyReviewFeedbackEvolutionCoordinator:与产品无关的逐 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

对每个成员:

  1. 列出全部PENDING任务,按 assignee 分组;
  2. 取该成员名下updated_at最早的一个任务(min(queue, key=lambda task: (task.updated_at or 0, task.task_id)));
  3. 经get_other_active_task_id探活——若成员已有活跃任务则跳过;
  4. 调用task_manager.start_task(task_id)(CAS,并发/重复触发无害);
  5. 成功后以 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 闸)assigneescheduler_task_start/scheduler_task_start_plan
送审派发 / 催办每个 reviewer / 未投票 reviewerscheduler_review_request/scheduler_review_renudge
打回返工(params: 聚合 fail feedback + 解析后轮数上限)authorscheduler_rework
验收通过后要求汇报authorscheduler_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 endpoint

ReviewFeedbackEvolutionCoordinator(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_verificationFalse布尔团队级"强制任务校验"开关(提示词驱动,CapabilityOverrides可覆盖;只在 scheduled 下向模型暴露)
default_max_review_rounds3>= 1per-task 未设max_review_rounds时的评审轮数上限默认值
review_stall_timeout1800(秒)> 0单轮评审停摆升级阈值
verify_vote_threshold2/30 < 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能力

项目地址:https://gitcode.com/openJiuwen/agent-core
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询