mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-27 17:03:49 +08:00
174 lines
8.7 KiB
Go
174 lines
8.7 KiB
Go
package agent
|
||
|
||
import (
|
||
"strings"
|
||
|
||
"github.com/Autumn-27/norma/harness"
|
||
)
|
||
|
||
// 收尾提示词(wrap-up / settlement prompt):当 agent 因【步数耗尽(MaxTurns)】或
|
||
// 【超时(run_seconds/MaxDuration)】被终止时,SDK 的 settlement 阶段会注入这段提示,
|
||
// 让 agent 先把已识别但未写回的内容落库、再输出一句总结,避免烂尾。
|
||
//
|
||
// 每个 agent 的收尾提示词可在后台按需覆盖(存 agents.wrapup_prompt),留空则用这里的
|
||
// 内置默认。仅【提示词正文】可编辑;禁用哪些工具、收尾自身给几轮预算属代码固定策略。
|
||
|
||
// WrapupOverride, if set, returns the stored wrap-up prompt for an agent key and
|
||
// whether a non-empty one exists. Wired by the server to the agents table (like
|
||
// PromptOverride for system prompts). nil / empty → the built-in default is used.
|
||
var WrapupOverride func(agentKey string) (string, bool)
|
||
|
||
// WrapupMaxTurnsOverride, if set, returns the admin-configured turn budget for the
|
||
// wrap-up phase of an agent and whether a positive one exists. Wired to the agents
|
||
// table. nil / ≤0 → the built-in per-agent default (wrapupTurnDefaults) is used.
|
||
var WrapupMaxTurnsOverride func(agentKey string) (int, bool)
|
||
|
||
// 内置默认收尾提示词,按 agent key 索引。worker 复用历史上硬编码的 settleWrapUpPrompt
|
||
// (定义在 worker.go),planner/mainagent 各有一版;未命中的(自定义 agent)走通用兜底。
|
||
var wrapupDefaults = map[string]string{
|
||
"worker": settleWrapUpPrompt,
|
||
"planner": plannerWrapUpDefault,
|
||
"mainagent": mainAgentWrapUpDefault,
|
||
}
|
||
|
||
// wrapupTurnDefaults: 各 agent 收尾阶段【自身】的轮数预算内置默认(可被后台 >0 覆盖)。
|
||
// 均给 10 轮,保证收尾阶段有足够步数落库。未命中走 genericWrapupTurns。
|
||
var wrapupTurnDefaults = map[string]int{
|
||
"worker": 10,
|
||
"planner": 10,
|
||
"mainagent": 10,
|
||
}
|
||
|
||
const genericWrapupTurns = 10
|
||
|
||
const plannerWrapUpDefault = "你本轮规划的步数即将用尽——注意只是【这一轮】结束,系统之后仍会随态势变化再次唤醒你继续规划,并非任务终止,你无需在此收束整个规划。请把本轮已经想清楚的结论落地、别让这一轮白跑,但也【不要为了收尾硬凑意图】(本轮 0 个意图仍是完全正常的结果):(1) 若已判断出【当前就该派发】的探索方向,用一次 add_intent 批量提交(想好的别憋着不发);(2) 对已被某发现/事实证明达成的目标,调 prove_goal 标记 met(别漏判);(3) 若识别出需要分步的串行利用链,用 TodoWrite 记下,便于下次唤醒接着派。做完直接结束本轮,无需输出总结文本。"
|
||
|
||
const mainAgentWrapUpDefault = "你的步数即将用尽,本次交互就要结束。不要再发起新的探索/操作。请**单独用一句话纯文本**向用户总结当前进展、关键结论,以及建议的下一步。"
|
||
|
||
const genericWrapUpDefault = "你即将因预算耗尽被终止。请先把已完成但未落库的结果写回,再**单独用一句话纯文本**总结你做了什么、得到哪些关键结论(这句会作为本次运行的结果展示)。"
|
||
|
||
// WrapupDefault returns the built-in default wrap-up prompt for an agent key —
|
||
// used by the admin UI as the "restore default" value and empty-field placeholder.
|
||
func WrapupDefault(agentKey string) string {
|
||
if d, ok := wrapupDefaults[agentKey]; ok {
|
||
return d
|
||
}
|
||
return genericWrapUpDefault
|
||
}
|
||
|
||
// WrapupTurnsDefault returns the built-in wrap-up turn budget for an agent key —
|
||
// used by the admin UI as the "0 = default N" hint.
|
||
func WrapupTurnsDefault(agentKey string) int {
|
||
if n, ok := wrapupTurnDefaults[agentKey]; ok {
|
||
return n
|
||
}
|
||
return genericWrapupTurns
|
||
}
|
||
|
||
// resolveWrapup returns the effective wrap-up prompt: the DB override (if set and
|
||
// non-empty) over the built-in default.
|
||
func resolveWrapup(agentKey string) string {
|
||
if WrapupOverride != nil {
|
||
if t, ok := WrapupOverride(agentKey); ok && strings.TrimSpace(t) != "" {
|
||
return t
|
||
}
|
||
}
|
||
return WrapupDefault(agentKey)
|
||
}
|
||
|
||
// resolveWrapupTurns returns the effective wrap-up turn budget: a positive DB
|
||
// override over the built-in per-agent default.
|
||
func resolveWrapupTurns(agentKey string) int {
|
||
if WrapupMaxTurnsOverride != nil {
|
||
if v, ok := WrapupMaxTurnsOverride(agentKey); ok && v > 0 {
|
||
return v
|
||
}
|
||
}
|
||
return WrapupTurnsDefault(agentKey)
|
||
}
|
||
|
||
// wrapupSettlement builds the settlement config for an agent's run. Prompt and the
|
||
// turn budget are admin-editable per agent; disabled tools are code-owned policy so
|
||
// a user can't edit away the "stop probing" guardrail. Resolved fresh each run
|
||
// (reads DB live), so edits apply on the next run without a restart.
|
||
func wrapupSettlement(agentKey string, disabledTools []string) *harness.Settlement {
|
||
return &harness.Settlement{
|
||
Prompt: resolveWrapup(agentKey),
|
||
DisabledTools: disabledTools,
|
||
MaxTurns: resolveWrapupTurns(agentKey),
|
||
}
|
||
}
|
||
|
||
// ---------- 任务级超时收尾词(见 docs/任务级超时与收尾设计.md)----------
|
||
//
|
||
// 与 per-run 收尾词是【两套】:per-run 是"你这一次 run 的预算用完了";任务超时是
|
||
// "整个任务到点、即将结束"。语义常相反(尤其 planner:per-run 说"别停继续规划",
|
||
// 任务超时说"到点停止规划、做最后判定")。只给 worker/planner 配置。
|
||
|
||
// WrapupTaskTimeoutOverride / …TurnsOverride:任务超时收尾词与轮数的 DB 覆盖
|
||
// (wire 到 agents.task_timeout_wrapup_prompt / _max_turns,仅 worker/planner)。
|
||
var (
|
||
WrapupTaskTimeoutOverride func(agentKey string) (string, bool)
|
||
WrapupTaskTimeoutTurnsOverride func(agentKey string) (int, bool)
|
||
)
|
||
|
||
var taskTimeoutWrapupDefaults = map[string]string{
|
||
"worker": workerTaskTimeoutDefault,
|
||
"planner": plannerTaskTimeoutDefault,
|
||
}
|
||
|
||
const workerTaskTimeoutDefault = "**整个任务已到达超时上限,即将结束**(不是你这次 run 的预算,是整场探索到点了)。这是最后机会:(1) 把你已识别但还没写回的内容【全部】落库——新资产 insert_assets、探索结论/事实 record_fact、确认漏洞 report_finding;(2) 不要再启动任何新命令/探测;(3) **最后单独用一句话纯文本**总结你在本意图上的关键结论。"
|
||
|
||
const plannerTaskTimeoutDefault = "**整个任务已到达超时上限,即将结束**(不是本轮,是整个任务终止)。请基于当前【全部】事实与发现,做最后一次目标判定:对已被证据证明达成的目标调 prove_goal 标记 met(别漏判)。**不要再生成任何新意图**(此时派意图也不会再被执行)。判定完即收束,无需输出总结文本。"
|
||
|
||
// TaskTimeoutWrapupDefault 返回某 agent 的任务超时内置默认收尾词(供后台占位/恢复默认)。
|
||
func TaskTimeoutWrapupDefault(agentKey string) string {
|
||
return taskTimeoutWrapupDefaults[agentKey] // 未配置(mainagent/chat)返回空串
|
||
}
|
||
|
||
// resolveTaskTimeoutWrapup:DB 覆盖(非空) > 内置默认。空串表示该 agent 无任务超时词
|
||
// (非 worker/planner),此时调用方应回退 per-run 词。
|
||
func resolveTaskTimeoutWrapup(agentKey string) string {
|
||
if WrapupTaskTimeoutOverride != nil {
|
||
if t, ok := WrapupTaskTimeoutOverride(agentKey); ok && strings.TrimSpace(t) != "" {
|
||
return t
|
||
}
|
||
}
|
||
return TaskTimeoutWrapupDefault(agentKey)
|
||
}
|
||
|
||
func resolveTaskTimeoutTurns(agentKey string) int {
|
||
if WrapupTaskTimeoutTurnsOverride != nil {
|
||
if v, ok := WrapupTaskTimeoutTurnsOverride(agentKey); ok && v > 0 {
|
||
return v
|
||
}
|
||
}
|
||
return resolveWrapupTurns(agentKey) // 默认沿用 per-run 轮数
|
||
}
|
||
|
||
// wrapupSettlementForTask builds settlement for a worker/planner run that is aware
|
||
// of the task deadline. See §5 of the design doc:
|
||
// - clamped=true → 本次 run 被任务 deadline 夹逼:因 Timeout 收尾=任务到点→任务超时词;
|
||
// 因 MaxTurns 收尾=夹逼窗口内步数先耗尽、任务还剩几分钟→回落 per-run 词。
|
||
// - clamped=false → 任务还早:两种 reason 都用 per-run 词(即退化为 wrapupSettlement)。
|
||
//
|
||
// 交给 harness 的 PromptByReason 在收尾时按【实际】reason 现场挑,无 build 时错配。
|
||
func wrapupSettlementForTask(agentKey string, disabledTools []string, clamped bool) *harness.Settlement {
|
||
perRun := resolveWrapup(agentKey)
|
||
st := &harness.Settlement{
|
||
Prompt: perRun, // 兜底(也是非 clamped 时两种 reason 的取值)
|
||
DisabledTools: disabledTools,
|
||
MaxTurns: resolveWrapupTurns(agentKey),
|
||
}
|
||
if clamped {
|
||
if tt := resolveTaskTimeoutWrapup(agentKey); tt != "" {
|
||
st.PromptByReason = map[harness.TerminalReason]string{
|
||
harness.ReasonTimeout: tt, // 任务到点
|
||
harness.ReasonMaxTurns: perRun, // 步数先耗尽、任务还剩时间
|
||
}
|
||
st.MaxTurns = resolveTaskTimeoutTurns(agentKey)
|
||
}
|
||
}
|
||
return st
|
||
}
|