feat(agent): 墙钟超时改为就地收尾,移除外部硬 ctx 兜底

依赖 norma v0.3.6:MaxDuration 到点会打断在跑工具并就地进收尾(在活 ctx
上按收尾轮数续跑落库+总结,终态 timeout),不再依赖 worker/planner 各自
的 maxDur+90s 外部硬 ctx 把卡住的 run 直接杀成 aborted_tools。

- worker/planner: 删除 WithTimeoutCause 硬 ctx 与 settleHardGrace 常量,
  run 直接用 ctx(仅承载 pause/kill/shutdown)。
- chat 走同一 harness 自动获得同样行为;mainagent 无 MaxDuration 不受影响。
- terminalreason: 更新 ReasonTimeout 文案。
This commit is contained in:
Autumn-27 committed 2026-09-09 13:49:59 -04:00
1 parent aa12706ed1
commit 64b08f518f
5 files changed
+12 -30

No files matched your search

+3 -8
View File
@@ -431,14 +431,9 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts
}
input := lead + situational + "\n\n据上面的态势,判定目标。目标已【真正达成】(已拿到目标成果/已确认目标漏洞)时用 prove_goal 逐个标记。**硬底线:只要目标尚未达成、且当前没有任何 open 或 running 意图(frontier_open=0 且 running_intents 为空),本轮就必须产出至少一个向目标推进的意图——此时没有在跑的 work 可等、也没有在排队的方向,产出 0 意图=任务停摆。仅当已有 open/running 意图在推进、或目标已达成时,本轮才可以不产出新意图。**" +
renderPlannerTodos(opts.Todos.List())
// 有 deadline 夹逼时加硬 ctx 兜底(软预算 + grace),防单轮卡死绕过轮边界软超时。
runCtx := ctx
if maxDur > 0 {
var cancel context.CancelFunc
runCtx, cancel = context.WithTimeoutCause(ctx, maxDur+settleHardGrace, AbortRunHardTimeout)
defer cancel()
}
_, _, err = captureRun(runCtx, opts, input,
// MaxDuration 现在会在墙钟到点打断在跑工具并就地进收尾(在活 ctx 上),单轮卡死不再
// 绕过收尾,无需外部硬 ctx 兜底。ctx 只承载 pause / kill / shutdown。
_, _, err = captureRun(ctx, opts, input,
func(r db.Activity) {
if emit != nil {
r.Worker = "planner" // planner activity has no intent_id (it generates them)
+1 -1
View File
@@ -33,7 +33,7 @@ func (t *runTrace) done(id string) {
var reasonHint = map[harness.TerminalReason]string{
harness.ReasonCompleted: "模型正常结束了本轮,但没有留下文字总结;事实和资产以本轮工具调用记录为准",
harness.ReasonMaxTurns: "达到步数上限(MaxTurns):SDK 已执行收尾并写回事实和资产,意图会标记为 exhausted,供规划者换方向继续,而不是作为失败处理",
harness.ReasonTimeout: "达到单次运行的软墙钟预算(MaxDuration):SDK 已在回合边界收尾并写回事实和资产,意图会标记为 exhausted",
harness.ReasonTimeout: "达到单次运行的墙钟预算(MaxDuration):到点会打断在跑的工具并就地进收尾,把已识别的事实和资产写回,意图会标记为 exhausted",
harness.ReasonModelError: "模型或 API 调用失败(网络、鉴权、限流、供应商 5xx 等),重试用尽后意图标记为 blocked——传输层故障导致这条意图基本没真正探成;查其执行过程(get_worker_trace)后再决定重派或换法",
harness.ReasonBlockingLimit: "上下文长度达到硬上限,请求在发出前被拦截;应收窄意图粒度或压缩工具返回",
harness.ReasonPromptTooLong: "提示词过长且上下文压缩重试已经用尽,无法继续执行",
+5 -18
View File
@@ -128,11 +128,6 @@ func (w *Worker) SetRunTimeout(run time.Duration) {
w.runTimeout = run
}
// settleHardGrace is how far past the soft wall-clock budget (runTimeout) the hard
// ctx deadline sits — a backstop for a hung turn; the soft budget handles the
// normal case at the turn boundary.
const settleHardGrace = 90 * time.Second
// settleWrapUpPrompt is injected by the SDK settlement phase when a worker hits its
// turn/time budget: stop probing, write back what was found, then end with a
// plain-text one-liner (which becomes this run's displayed result).
@@ -489,18 +484,10 @@ func (w *Worker) execute(ctx context.Context, name string, taskID int64, as *db.
// Budgets + settlement are owned by the SDK (MaxTurns/MaxDuration + Settlement):
// on hit it runs a wrap-up turn and finishes with ReasonMaxTurns/ReasonTimeout.
// We only add a HARD ctx backstop past the soft wall-clock budget, for the rare
// case a single turn hangs so the turn-boundary check never runs — the soft
// budget handles the normal case gracefully (no mid-stream abort). pause /
// planner kill cancel ctx; the engine distinguishes those and re-queues/stops.
// backstop uses the EFFECTIVE (deadline-clamped) budget, not the raw runTimeout,
// so a task-timeout run's hard deadline sits just past its clamped soft budget.
runCtx := ctx
if maxDur > 0 {
var runCancel context.CancelFunc
runCtx, runCancel = context.WithTimeoutCause(ctx, maxDur+settleHardGrace, AbortRunHardTimeout)
defer runCancel()
}
_, reason, err := captureRunSession(runCtx, s, input, emitWrap)
// MaxDuration now interrupts an in-flight tool at the wall-clock deadline and
// enters the wrap-up phase on the live ctx, so a run whose tool overran the budget
// still settles (no external hard-timeout backstop needed). ctx itself carries only
// pause / planner kill / shutdown, which the engine distinguishes and re-queues/stops.
_, reason, err := captureRunSession(ctx, s, input, emitWrap)
return reason, tsx.Writes(), err
}
+1 -1
View File
@@ -3,7 +3,7 @@ module github.com/Autumn-27/artex
go 1.26.3
require (
github.com/Autumn-27/norma v0.3.5
github.com/Autumn-27/norma v0.3.6
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/jackc/pgx/v5 v5.10.0
github.com/klauspost/compress v1.18.6
+2 -2
View File
@@ -1,5 +1,5 @@
github.com/Autumn-27/norma v0.3.5 h1:zYJKoN+SNi3iscednrw21xsW48jUD55CT6RdySP15MA=
github.com/Autumn-27/norma v0.3.5/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw=
github.com/Autumn-27/norma v0.3.6 h1:cx68eNW8igG2I9Ed9EIsElelJn9ll9WW2HH3OhYh6fA=
github.com/Autumn-27/norma v0.3.6/go.mod h1:BudWrf7iHl96FgHqFr/HwN3mKYmGjJDt+xL2ZHXfFZw=
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=