/// engine_challenge.mbt —— Challenger 进阶变体(SAGE 四专家环的另一半,AI 自进化增强)
///
/// 论文背景:SAGE 四智能体专家环 = Challenger/Planner/Solver/Critic。Critic 负责把关
/// (R13 已落地 `evolve_critic` 防漂移)、R29 再把防漂移前推到挑战题本身;Challenger 负责
/// "生成更难的任务"以抬高整体能力上限。与 R12 的 LADDER「由易到难梯度」呼应:完成一个
/// "易/中"任务后,可对其发起进阶变体,构成"由易到难"的自推进序列。
///
/// 实现(规则驱动的"挑战框架",非假装 AI 智能):
/// - 对**已完成/已归档**的源任务,按策略生成一个"更难变体"文本框架并发布为新根任务
/// (带 `[challenge]` 标记 + `from ` 溯源,重要性升一档),使复杂能力的
/// 训练/推进持续上探,而非停在每个完成点。
/// - 用引擎内建 publish 之外的原生 `first_free_root_id + create_task` 创建根任务,
/// 绕开 `publish` 的人类指挥官门禁(Challenger 由 agent 触发)。
/// - **R29 防漂移门禁(可选 `critic`,缺省关闭零回归)**:指定传入 DGM 档案库时,
/// 生成挑战题前先经 `critic_review`(与 `evolve_critic` **同一单一真源**)评审——
/// SAGE 的 Critic 会过滤"题目"防课程漂移、R-Few 补防"diversity collapse":若当前策略的
/// 变体与档案库既有资产重合 ≥ `critic_dup_sim()`(疑似课程漂移/同质坍缩),自动降档
/// 尝试其它策略;全部策略皆漂移则**拒发**(不出新根任务),防止"由易到难"退化成
/// "反复套同一模板"。非漂移但稳健性偏低不阻断(挑战题本就偏新颖/高风险,属预期)。
///
/// 纯 MoonBit、无新表/依赖;供 server 的 `task_challenge` 工具调用。
///|
/// 进阶策略白名单:为已完成任务"上难度"的几种规则化变体框架。
/// generalize:去具体场景 → 抽象为通用能力并覆盖边界
/// unhint :去提示/约束 → 从空白规格重推且不降正确性
/// constrain :加硬约束 → 本次在性能/并发/兼容上加严并保证不回归
/// scale :扩规模 → 把规模/负载提升 factor 倍并保持语义正确
pub fn challenge_strategies() -> Array[String] {
["generalize", "unhint", "constrain", "scale"]
}
///|
/// 依据策略生成"更难变体"的框架文本(规则模板,非 LLM 生成)。
fn challenge_frame(strat : String, factor : Int) -> String {
match strat {
"generalize" =>
"摆脱对具体场景的依赖,抽象为通用能力并覆盖边界与异常(泛化上难度)"
"unhint" =>
"去掉自带提示/约束,从空白规格重新推导且不降低正确性与完备性(去提示上难度)"
"constrain" =>
"增加硬约束(性能/并发/兼容),并在新约束成立的同时保证原有行为零回归(加约束上难度)"
_ => {
let f = if factor < 2 { 2 } else { factor }
"把规模/负载/覆盖度提升到 \{f} 倍并保持语义正确与可验证(扩规模上难度)"
}
}
}
///|
/// 重要度升一档(低→中→高),用于挑战变体体现"更难/更关键"。
fn bump_importance(imp : String) -> String {
match imp {
"低" => "中"
_ => "高"
}
}
///|
/// 从 critic_review 的 Json 中读 sim(与档案库最相似资产重合度)。
fn review_sim(review : Json) -> Double {
match review.value("sim") {
Some(Json::Number(d, ..)) => d
_ => 0.0
}
}
///|
/// 组装一条候选挑战题的描述(任一策略共用同一模板,保证可比)。
pub fn ch_desc(task_id : String, src_desc : String, frame : String) -> String {
"[challenge] 进阶变体 · from \{task_id} · \{frame}「\{src_desc}」"
}
///|
/// 挑战题"选型 + 防漂移选帧"结果。
priv struct ChaPick {
strat : String
frame : String
desc : String
review : Json
status : String // off(未开 critic) | admitted(放行) | degraded(漂移后降档放行) | rejected(全部漂移拒发)
}
///|
/// 依据策略白名单 +(可选)DGM 档案库决定"发布哪一帧挑战题"。
/// critc 为 None → 直取调用方策略(旧行为,零回归);为 Some(arc) → 开启防漂移门禁:
/// 当前策略变体与档案库重合 ≥ critic_dup_sim() 判疑似课程漂移/同质坍缩,自动降档尝试其余
/// 策略(固定次序 generalize→unhint→constrain→scale 中非当前者);全部皆漂移则 status=rejected。
fn pick_frame(
task_id : String,
src_desc : String,
strat : String,
factor : Int,
arc : @evolve.Archive?,
) -> ChaPick {
let s = if challenge_strategies().contains(strat) { strat } else { "scale" }
let mk = fn(os : String) -> ChaPick {
let frame = challenge_frame(os, factor)
{
strat: os,
frame,
desc: ch_desc(task_id, src_desc, frame),
review: Json::null(),
status: "off",
}
}
match arc {
None => mk(s)
Some(a) => {
let order = [s] + challenge_strategies().filter(fn(x) { x != s })
let mut best : ChaPick? = None
let mut degraded : Bool = false
for os in order {
let c = mk(os)
let review = @evolve.critic_review(a, c.desc, 0.5)
if review_sim(review) < @evolve.critic_dup_sim() {
// 非漂移:放行(稳健性偏低不阻断,挑战题本就偏新颖/高风险)
best = Some({
strat: os,
frame: c.frame,
desc: c.desc,
review,
status: if degraded {
"degraded"
} else {
"admitted"
},
})
break
}
degraded = true
}
match best { // 全部策略皆漂移 → 拒发(拿首个策略的 review 作原因)
None => {
let c = mk(s)
{
strat: s,
frame: c.frame,
desc: c.desc,
review: @evolve.critic_review(a, c.desc, 0.5),
status: "rejected",
}
}
Some(b) => b
}
}
}
}
///|
/// 对一条已完成/已归档任务发起 Challenger 进阶变体:发布一个新根任务 `[challenge]`。
/// 可选 `critic`(DGM 档案库,缺省 None = 关闭防漂移门禁,旧行为零回归):传入时先对生成
/// 的挑战题做 Critic 防漂移评审(与 `evolve_critic` 同一单一真源),全部策略皆漂移则拒发。
/// 返回 { new_id, source, by, factor, strat, importance, ns, description, critic, critic_status }。
pub fn FistEngine::challenge(
self : FistEngine,
task_id~ : String,
by~ : String,
factor? : Int = 2,
strat? : String = "scale",
now~ : String,
critic? : @evolve.Archive? = None,
) -> Result[Json, String] {
match self.store.get_task(task_id) {
None => Err("task not found: \{task_id}")
Some(src) => {
let st = src.get_status()
if not(st.is_completed()) && not(st.is_archived()) {
return Err(
"Challenger 仅对「已完成/已归档」任务发起进阶变体,当前状态 [{src.status_to_string()}]",
)
}
let pick = pick_frame(task_id, src.description, strat, factor, critic)
if pick.status == "rejected" {
return Err(
"task_challenge 被防漂移门禁拒绝:与档案库既有资产疑似课程漂移/同质坍缩(全部策略变体重合均 ≥70%),未发布新挑战题;critic=" +
pick.review.stringify(),
)
}
let new_id = self.first_free_root_id()
let child = @core.Task::new(
id=new_id,
project_dir=src.project_dir,
description=pick.desc,
priority=src.priority,
importance=bump_importance(src.importance),
depth=3,
split_n=3,
created_at=now,
ns=src.ns,
)
let imp = bump_importance(src.importance)
match self.store.create_task(child) {
Ok(_) =>
Ok(
Json::object({
"new_id": Json::string(new_id),
"source": Json::string(task_id),
"by": Json::string(by),
"factor": Json::number(factor.to_double()),
"strat": Json::string(pick.strat),
"importance": Json::string(imp),
"ns": Json::string(src.ns),
"description": Json::string(pick.desc),
"critic": pick.review,
"critic_status": Json::string(pick.status),
"note": Json::string(
"Challenger 已发布进阶变体,供新一轮拆解/执行推进",
),
}),
)
Err(e) => Err(e)
}
}
}
}