主题
任务与决策链路
定位:一条任务从“该不该做”到“在哪做”到“怎么送”的完整路径。
一句话:Jev 回答“这个判断成不成”,HiRoute 回答“这一步让谁做”,Scheduler 回答“什么时候开始”,LiteTopic 回答“怎么按会话有序、不丢不重地送到”;四段各管一段,用路由回执、类型化判断、任务信封与 Outbox 记录串成一条可审计的链。
没有这条链路时,平台会在三个位置出事:
- 路由盲选:所有步骤都走同一个主力模型,简单任务按贵的价钱计费;频繁换模型又把提示词缓存前缀全部打失效,省下的单价抵不过重算成本;选错了还没有回执,事后说不清“为什么当时选了它”。
- 判断不可解析:用聊天模型做“该不该、选哪个、有多严重”这类封闭判断,拿到的是自然语言,程序只能再写一层解析;没有概率就设不了阈值,挑不出低置信样本,同一输入两次给出不同措辞,分支走向无法回放。
- 消息重复与任务丢失:所有会话共用一个 Topic,慢会话阻塞快会话、顺序无法按会话保证;每会话建一个普通 Topic,百万会话就把 Broker 元数据压垮;只靠数据库轮询,延迟与空转成本自己扛。更糟的是没有 Outbox 与幂等键时,“状态写了但消息没发”和“重投导致重复执行”只能靠人肉对账发现。
这四个组件都是真实存在的产品与特性:HiRoute(2026-10 开源的本地模型与任务路由)、Jev(真实结构化决策服务,经 agentscope-extensions-jev 接入)、RocketMQ 5.5.0 LiteTopic(两级主题与单队列保序是 5.5.0 的真实特性);Scheduler 与 Outbox 是平台的胶水设计,Quartz/XXL-Job 触发语义来自 AgentScope Java 调度扩展。本篇把三段各自的设计缝合为一条链路,接缝字段以本篇 §一 的标注为准。
§一 全链路组件图
每段职责边界:
| 段 | 组件 | 负责 | 不负责 |
|---|---|---|---|
| 判断 | Jev | 类型化判断:该不该、选哪个、有多严重 | 生成正文、授权、执行 |
| 选路 | HiRoute | 这一步用哪个模型、子任务交给哪个 Harness | 跨租户配额与中心治理 |
| 触发 | Scheduler | 何时开始、派给哪个逻辑任务、错过怎么补 | 跨节点可靠投递 |
| 投递 | Outbox + Relay + LiteTopic | 会话级有序、独占消费、断线续传 | 任务是否完成、是否该重试 |
| 账本 | PostgreSQL | 权威状态、Task Run、结果、审计 | 实时唤醒消费者 |
§二 模型路由 HiRoute
HiRoute 是贴着 Harness 的本地路由平面:在请求离开本机前决定用哪个模型、把哪个子任务交给哪个编码 Agent,并把决策证据留在本地。它与中心网关的分工是本地先选一次,中心再做准入与兜底——本地路由不绕过中心网关的认证与配额,中心也不替本地决定“这一步该用便宜模型”,因为中心看不到“这一步属于哪个子任务、上一阶段做得怎么样”,也无法为了省一次调用而保住提示词缓存。
四种策略
| 策略 | 行为 | 适用 |
|---|---|---|
| 固定模型 | 按候选顺序尝试,跳过能力不满足或暂不可用的 | 结果要求稳定、模型已确定 |
| 难度分流 | 判断本轮简单概率,简单走常规组,复杂走主力组 | 长任务、成本敏感 |
| 自定义分支 | 先分任务类别(写稿、审稿等),再在分支内选组 | 多角色、多阶段流水线 |
| 免费优先 | 先试免费候选,耗尽后停止或进入主力兜底 | 有免费额度、可接受排队 |
每条新用户消息重新判断;同轮工具续接沿用当前组、优先保持当前模型,以延续上下文并复用提示词缓存。这是成本设计的关键:频繁换模型会让缓存前缀全部失效,省下的单价抵不过重算成本。
判定逻辑与低分保护
难度分流用双条件:本轮简单概率达到阈值(默认 0.8),且没有适用的上一阶段低分,才走常规组;上一阶段评分低于胜任度下限(默认 0.5)时触发主力保护。低分保护有四个前提,缺一不可:评分由本次决策返回、评价的是上一实际执行阶段、有完整证据、与本轮使用相同任务类别/发布版本/评分标准。缺失证据、跨分支、无法归属的评分不能触发保护;未评分不等于零分,旧低分不会在后续轮次反复生效。
故障接力规则:常规组候选都不可用时,才尝试同一任务分支的主力组;直接选中主力时只尝试主力候选,不降回常规。候选调用失败不等于不胜任,不会因此生成零分——把“技术失败”和“质量不达标”混为一谈,会让保护机制误触发。
路由回执字段
每次路由留下三类证据,其中回执是 HiRoute 与下游治理、评测之间最主要的交界物:
| 证据组 | 字段 | 用途 |
|---|---|---|
| 为什么选它 | 任务类别、简单概率、阈值、决策原因 | 复盘与校准决策模型 |
| 实际执行了什么 | 模型、思考配置、候选位置、是否组间接力 | 还原真实执行路径 |
| 后续评价如何 | 胜任度评分、评分标准、证据覆盖度 | 驱动下一轮低分保护 |
回执记录供应商报告的 Token 用量,但不返回受保护的对话正文。成本展示中的 api-equivalent 只是 API 等价单价估算,不改写历史回执,也不证明质量相同、订阅账单或现金节省;缺少可靠价格时金额显示未知,不能当零。
降级链:决策不可用时怎么办
决策层有三种判断方式:内置决策模型(Jev、TypeSafe 或兼容端点)、本地启发式规则(只按任务特征判断简单/复杂,不产生胜任度评分)、自定义扩展(自建 HTTP 服务,按扩展 API 返回 decision 与可选 assessment)。三者的可用性差别很大,所以降级顺序必须显式设计:
三个要点:
- 决策失败不阻断请求。路由是优化项不是准入项,判断拿不到时任务仍要执行,最差落到固定默认。
- 降级期间低分保护自动失效。启发式规则不产生评分,历史低分不能由降级路径触发主力保护;恢复决策模型后保护才重新生效。
- 降级本身要留痕。回执里标记本次判断来自决策模型、启发式还是默认兜底,否则事后校准会把降级期间的样本混进正常样本,误判决策模型的准确率。
任务路由与边界
除选模型外,HiRoute 能把独立子任务委派给编码 Agent(当前适配 Codex CLI、Claude Code、Qoder CLI 等),条件是目标、工作目录和交付物清楚、可独立执行;协调本身、计划发现、状态查询不应再委派。执行 Agent 不能递归发起委派,每份计划只选一个执行 Agent,失败不自动换另一种。安全边界:默认 approve-all 不提供操作系统级沙箱,任务路由适合可信环境与可信任务,不能当多租户隔离方案。
实证标注:HiRoute 为 2026-10 开源的真实本地路由工具,上述策略、阈值默认值与回执语义来自其开源版本;降级链为平台侧集成设计。
§三 结构化决策 Jev
Jev 把判断从“生成”里拿出来:输入问题与候选,输出概率分布与置信度,不生成正文。它替代的不是模型,而是模型之前的那一层分流与把关。JevClient 是 HTTP 客户端,不是模型提供者——不参与对话、不持有会话状态、不执行工具、不替代权限系统。
类型化判断
| 类型 | 返回 | 典型问题 | 不适合 |
|---|---|---|---|
NoulQuestion | 命题为真的概率 | 这个工具适用于当前请求吗;这个动作安全吗 | 需要多选一的场景 |
ChoiceQuestion | 选中项 + 每项概率 + 置信度 | 意图分类、模型档位选择、任务类别判断 | 选项之间边界模糊时 |
ScoreQuestion | 概率加权分 + 等级分布 + 置信度 | 风险等级、质量分级、证据充分度 | 需要精确数值的场景 |
选型规则:互斥选择用 Choice,独立开关用 Noul,有序程度用 Score。把互斥选择拆成多个 Noul 会丢掉选项间相关性;把独立开关塞进 Choice 会强迫在无关项间取舍。
概率是校准意义的:一批同类样本上预测 0.8 的事件约八成发生,不保证单次正确、不保证跨领域成立。所以生产上必须用本地已标注样本标定阈值(看 0.6、0.7、0.8 各档的误放与误拦,不照抄别人的默认值),且同时看置信度与间隔——只取最高概率不够,第一名与第二名各 0.45 时硬选等于赌博。“基础阈值随候选数调整 + top1 与 top2 比值不低于 2”是实践建议,必须用自己的样本重新标定。
低置信、显式否定、调用失败是三种不同情况:低置信应升级或转人工;显式否定是明确的“不”;调用失败是“没判断成”,不能当成“不通过”,否则一次网络抖动就把正常请求全部拦掉。
判断与执行分离
JevJudge 把多个 Noul 条件聚合成 PASS / FAIL / INCONCLUSIVE / ERROR,回答“条件是否成立”;JevExecution.Decision 是 DECIDED / INCONCLUSIVE / ERROR / CANCELLED / SKIPPED,回答“这次执行决策处于什么状态”。判断通过不等于动作已执行,判断失败也不等于动作被拒绝。两者合成一个布尔值,审批记录、失败重试与人工接管都会失去依据。
三种运行模式
| 模式 | 行为 | 用途 |
|---|---|---|
| OFF | 不调用,护栏完全不生效 | 默认状态 |
| SHADOW | 照常调用并记录建议,原请求照原样执行 | 积累“如果当时按它做会拦掉哪些请求”的证据 |
| ENFORCE | 按判断结果拦截或放行 | 阈值校准完成后的生产闸门 |
上线顺序固定为 OFF → SHADOW → 小流量 ENFORCE → 全量,默认关闭、按用例逐个打开。
与 HiRoute 的集成点
HiRoute 的内置决策模型槽位就是 Jev(或兼容端点)。接缝是一次 ChoiceQuestion:
| 方向 | 字段 | 说明 |
|---|---|---|
| HiRoute → Jev | 问题(本轮难度 / 任务类别)、候选(fast / standard / strong 等档位)、上下文窗口 | 只送近期若干条消息与固定字符数的窗口,不送全量会话;上下文里的敏感字段先脱敏 |
| Jev → HiRoute | topOption + 每项概率 + 置信度 | HiRoute 把选中档位映射到模型组 |
| 阈值分支 | 概率低于本地标定阈值 | 不硬选第一名,升级主力组或转人工(对应 §二 简单概率 ≥ 0.8 走常规组的判定) |
| 阶段路由 | JevPhaseRouting:宿主提供模型目录(能力、配额、Token 估算),Jev 在合格路由中选一个 | 选中结果只对一次调用有效,派发前复查;候选都不可用回退原模型;它只读配额状态,不管理配额 |
fail-open vs fail-closed
每个 Jev 决策点都要预先声明失败语义,且写在配置里、不埋在代码分支里:
| 落点 | 判断内容 | 调用失败时的行为 |
|---|---|---|
| 工具选择 | 每个候选工具是否适用(多选 Noul) | fail-open:保留原候选,交给模型自己选 |
| 模型/阶段路由(HiRoute 决策层) | 本轮难度、所需能力、候选可用性 | fail-open:走 §二 降级链,回退原模型,不阻断请求 |
| 执行护栏 | 受保护动作是否安全(如退款) | fail-closed:默认拦住,按风险等级显式配置 |
| 证据与答案 | 结论是否有证据支撑 | 标记待复核,不直接改写结论 |
同一条链路上混用两种语义而不标注,是最难排查的一类问题。每次决策记录问题、候选、概率、置信度、阈值版本、最终动作与 trace,缺一项就无法复盘。
实证标注:Jev 为真实结构化决策产品;接入面为 AgentScope Java 2.x 的
agentscope-extensions-jev(JevClient/JevJudge/JevExecution/JevPhaseRouting)。阈值与间隔的具体数值是实践建议,非官方默认。
§四 任务派发 LiteTopic
触发与投递必须分两层:触发层决定“何时开始、派给哪个逻辑任务”,投递层决定“消息怎么按会话有序、可续传地送达”。RocketMQ 5.5.0 的 LiteTopic 解决的是投递层。
两级主题
| 层级 | 作用 | 例子 |
|---|---|---|
| 父 Topic | 命名空间、权限、配额、运维单位;按环境/地域拆 | agent_task_prod |
| LiteTopic | 会话/任务级通道,独立顺序与生命周期 | t/tenant-a/s/s-8842 |
命名规则固定为 t/{tenant}/s/{session},禁止把用户输入拼进通道名。每个 LiteTopic 默认只有一个队列,通道内严格 FIFO;总吞吐靠通道数扩展,而不是单通道多队列。代价是单会话 TPS 有上限、同一通道不能并发,需要并发就拆通道,不能拿它做高吞吐批处理。
生命周期:发送或订阅时自动创建;超过 TTL 无新消息自动删除并释放配额,未消费消息同样过期;Broker 侧持久化消费位点;支持按 LiteTopic 暂停/恢复消费。TTL 是双刃剑——冷会话通道自动回收,但 LiteTopic 不能当审计账本,任务结果与产物索引必须写进 PostgreSQL/对象存储,消息历史与位点都不能反推任务状态。
版本对齐:服务端 5.5.0+、客户端 gRPC SDK 5.1.0+,并确认 LMQ、RocksDB 存储与多分发配置同时开启。
Outbox → Relay → Topic 投递链路
四个动作缺一不可:Outbox 保证“状态写了消息一定发”(触发层不需要知道 MQ 存在,MQ 也不需要理解任务语义);幂等键保证“重投不重复执行”;租约/栅栏保证“旧执行者写不回”;先提交结果再提交位点保证至少一次而不是最多一次。
Relay 投递的信封只放指针与约束、不放正文(大输入、工具结果、产物放对象存储):
json
{
"task_id": "task-8842-03",
"session_id": "s-8842",
"attempt": 2,
"tenant": "tenant-a",
"deadline_ms": 1750000000000,
"budget": {"tokens": 120000, "tool_calls": 20},
"idempotency_key": "task-8842-03-a2",
"traceparent": "00-...-...-01",
"payload_ref": "oss://agent-artifacts/tenant-a/task-8842-03/input.json"
}deadline 让 Worker 接手前就能判断是否已经太晚。信封只表达“这次投递带什么约束”,不表达“任务现在到哪了”——当前阶段、等待原因、预算余额以任务账本为准。attempt 对应账本的 Task Run:每次执行尝试生成一个 task_run_id,与 task_id 一对多;attempt 只用于去重与排序,不能当任务主键。
独占消费与租约
同一 LiteTopic 只被一个消费者线程独占;新连接接入时 Broker 让旧连接退订(“最新连接胜出”)。这匹配 Agent 会话:同一时刻只该有一个执行者,客户端重连到任意节点都能从 Broker 侧位点继续,不必把会话粘在某个 Pod 上。
但“最新连接胜出”不是高可用万能药:旧连接可能还在执行未确认的消息,新连接已经接手。必须配合租约/栅栏令牌——Worker 领取通道执行权时从账本取得带版本的租约,所有写回(结果、状态、产物索引)都携带当前栅栏令牌,账本拒绝旧令牌的写。这样即使旧 Worker 复活,其写入也被栅栏挡掉,与新 Worker 不产生双写。
幂等键分层
链路上有两层幂等,键的来源不同,不能混用:
| 层 | 键 | 生成规则 | 防的事故 |
|---|---|---|---|
| 调度层 | schedule_id + scheduled_fire_time | 用计划触发时间,绝不用实际开始时间 now() | 调度器重试/补跑导致同一计划时刻触发两次;用 now() 时补跑一次就多一条记录 |
| 投递层 | task_id + attempt(信封 idempotency_key) | 每次执行尝试一个键 | 至少一次投递的重投导致重复执行 |
Session ID 同样从计划触发时间派生(如 daily-report-20261024T0800Z):同一计划时刻的重试落回同一会话,不同计划时刻严格分开。Quartz/XXL-Job 只保证“触发一次”,业务幂等的防护仍然靠这两层键。
调度层还有一条硬纪律:每次触发创建新的 Agent 实例,不复用长期存活的实例,状态靠 (userId, sessionId) 从存储加载;每次触发使用唯一 Session ID,否则多次运行写进同一段会话历史。错过触发不能悄悄跳过,按业务选一:run_latest(日报、快照)/ run_all 设补跑上限(计费、对账)/ skip 记 missed 并告警(巡检)/ prompt 生成待确认项(对外通知);账本写 scheduled_fire_time、fired_at、policy_applied、missed_reason。
两个键的去重窗口不同:调度层窗口覆盖“同一计划时刻的所有重试与补跑”,命中即不产生任务;投递层窗口覆盖“同一次执行尝试的所有重投”,命中即只提交位点。下图把两层放在同一条时间线上看:
读法:上半段的重复来自触发(调度器重试、补跑、集群双触发),由账本唯一约束挡在写任务之前;下半段的重复来自投递(至少一次语义、位点未提交),由 Worker 侧幂等表挡在执行之前。两层各管各的重复来源,用同一把键必然漏判:调度层键不含 attempt,投递层键不含计划时刻。
背压
入口按租户/会话限流;某会话持续失败或超预算时暂停它的 LiteTopic 消费;Worker 容量按通道数而非消息总数估算——单通道串行意味着一个会话最多占一个执行槽;超过最大 Attempt 转死信或人工队列,不无限重投。LiteTopic 没有成熟的滞后指标,用“通道最后消息时间”“未确认消息数”“重投次数”自建告警。
实证标注:两级主题、单队列 FIFO、独占消费、最新连接胜出、Broker 侧位点、TTL 自动删除均为 RocketMQ 5.5.0 的真实特性;Outbox/Relay/租约是平台侧工程配合,
AgentSchedulerSPI 来自 AgentScope Java 调度扩展(Quartz / XXL-Job 两种实现)。
§五 已支持场景走线
场景一:同步对话——一条消息的即时往返
关键点:中心只做准入与兜底,选路在本地;Jev 不可用时 fail-open 走降级链,对话不中断;回执留在本地路由平面,平台治理靠主动上报。
场景二:异步任务——事件触发、按会话有序
关键点:会话内严格 FIFO 由单队列保证;重投只产生一个结果靠投递层幂等键;任务状态永远以账本为准,不查消息位点。
场景三:定时任务——从计划触发到通知
关键点:每次触发新建实例、新建唯一 Session;调度层幂等用计划触发时间而非 now(),补跑不会多出一条记录;集群部署防“同一定时任务被两个节点同时触发”(Quartz 共享库 / XXL-Job 路由策略)。
场景四:长任务审批——快照释放与恢复
关键点:waiting_approval 期间通道不悬着——提交位点、释放租约,会话的后续消息按序等待;审批恢复走同一 Outbox→LiteTopic 链路,用新 attempt 的幂等键防重复执行恢复动作;已批准的副作用若重复发生,靠动作层幂等键与对账兜底(取消是尽力而为,不是回滚)。
边界
- 本地路由的证据分散:HiRoute 回执在各自机器上,纳入平台治理必须主动上报,不能假设中心看得见;路由也补不了能力缺口,所有候选都不支持某能力时只能换能力。
- Jev 不是聊天模型也不是授权:不能生成文本,不适用于开放问答;它说“可以做”不代表租户有权限、配额充足、工具安全。托管决策服务意味着可用性、延迟与数据出境要单独评估,判断上下文离开内网前先脱敏。
- LiteTopic 不是数据库:TTL 会删除消息,位点不等于业务状态;只有顺序消费,单通道吞吐有限。
- 触发与投递不能互相替代:Scheduler 不懂消息可靠性,LiteTopic 不懂日历与重试策略。
- 缝合部分是平台设计:§二 降级链、§四 租约/栅栏的具体形态是工程选择,接缝字段以本篇标注为准。
讲清楚
一条任务的完整路径分四问:Jev 判“该不该”——用 Noul/Choice/Score 三类问题输出校准概率,OFF→SHADOW→ENFORCE 上线,失败语义按用例写死 fail-open 或 fail-closed;HiRoute 定“在哪做”——四种策略按确定性排序,简单概率阈值 0.8 与胜任度下限 0.5 双条件分流,同轮不换模型以保住缓存,决策模型不可用时降级到启发式再到固定默认;Scheduler 答“何时开始”——幂等键用计划触发时间,每次触发新建实例新建 Session;LiteTopic 保“怎么送”——t/{tenant}/s/{session} 单队列 FIFO、独占消费配租约、Outbox 保证状态与消息同生、先提交结果再提交位点。
判断这条链路接没接通,看五条:每个决策点有预先声明的失败语义;路由回执能回答“为什么选它”;同一计划时刻只执行一次;重投只产生一个结果;
waiting_approval释放了执行槽、恢复走的是同一套派发机制。
适用:HiRoute 2026-10 开源版本 | AgentScope Java 2.x + agentscope-extensions-jev | RocketMQ 5.5.0 LiteTopic / AgentScope scheduler 扩展 | 初稿 2026-10