Files
Agent-System-Backend/internal/runtime/work_checkpoint.go
agent-exec-transport 837480d4c1
governance / governance (pull_request) Successful in 23s
ci / vet-and-test (pull_request) Successful in 1m26s
agent-exec transport fe547395130f262af6f15a09d8a4aa2d/1f4b762f53be
2026-10-20 16:15:37 +00:00

106 lines
7.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package runtime
import (
agentllm "agent-native/runtime/llm"
agenttools "agent-native/runtime/tools"
core "agent-system-core"
"bytes"
"encoding/json"
"fmt"
"io"
)
const workCheckpointTool = "publish_work_checkpoint"
type WorkCheckpointSink interface {
PublishWorkCheckpoint(core.WorkCheckpointCandidate) (string, error)
}
func checkpointEnabled(rc RunCtx) bool {
if capability, ok := rc.(interface{ WorkCheckpointAvailable() bool }); ok && !capability.WorkCheckpointAvailable() {
return false
}
src, ok := rc.(interface{ SubagentID() string })
if !ok || src.SubagentID() != materialSubagentID {
return false
}
if _, ok = rc.(WorkCheckpointSink); !ok {
return false
}
if !rc.Profile().Tools.ToolAllowed(workCheckpointTool) || rc.Profile().Tools.NeedsConfirm(workCheckpointTool) {
return false
}
for _, tool := range rc.Tools() {
if tool.Name == workCheckpointTool {
return false
}
}
return true
}
func checkpointDecl() agentllm.ToolDecl {
return agentllm.ToolDecl{Name: workCheckpointTool, Description: `及时保存本次材料整理的阶段发现。只引用已提供的 sourceId/blockIndex 和连续原文短引;sourceIndex 是本条 findings.sources 的零起始索引。material_record 默认不附 evidence,由宿主按短引扩展整句并原样展示。唯一例外:单一来源、短引即完整原句且原句明确表达关系时,material_record 可附 direct_relation,恰 1 个前提 {"role":"direct_relation","sourceIndex":0},不写 value、不写 result;不满足则宿主转待确认。analysis/provisional/needs_user 的数字或分期归属只用 difference 或 unresolved_relation(不要用 direct_relation,宿主会把 analysis+direct_relation 转为 needs_user):difference 须有 total、phase 各一带 value,且 result 必须等于 total−phase;unresolved_relation 须有 total、phase、dependent 各一带 value,不写 result。允许模式示例:{"findingKey":"rec","text":"上线六月","kind":"material_record","sources":[{"sourceId":"s1","blockIndex":0,"quote":"上线六月"}]};{"findingKey":"rel","text":"第二期包含3项依赖外部接口的需求","kind":"material_record","sources":[{"sourceId":"s1","blockIndex":0,"quote":"第二期包含3项依赖外部接口的需求"}],"evidence":{"mode":"direct_relation","premises":[{"role":"direct_relation","sourceIndex":0}]}};{"findingKey":"diff","text":"若同一范围则差额4项","kind":"analysis","assumptions":"两处数字指同一需求范围","sources":[{"sourceId":"s1","blockIndex":0,"quote":"12项"},{"sourceId":"s2","blockIndex":0,"quote":"8项"}],"evidence":{"mode":"difference","premises":[{"role":"total","sourceIndex":0,"value":12},{"role":"phase","sourceIndex":1,"value":8}],"result":4}};{"findingKey":"gap","text":"接口依赖归属待确认","kind":"needs_user","sources":[{"sourceId":"s1","blockIndex":0,"quote":"12项"},{"sourceId":"s2","blockIndex":0,"quote":"8项"}],"evidence":{"mode":"unresolved_relation","premises":[{"role":"total","sourceIndex":0,"value":12},{"role":"phase","sourceIndex":1,"value":8},{"role":"dependent","sourceIndex":0,"value":3}]}}。evidence 结构不符(如 direct_relation 带 result)会整次检查点被拒,工具结果说明原因,须修正后重发;结构合规但宿主核不上的(前提与短引不符、direct_relation 条件不满足、原句语境不唯一、无 evidence 的非审慎分析、无 evidence 的 needs_user 且原文被替换)该条转为待确认并在工具结果说明。不得把推测写成已批准或确定归属。不额外调用摘要模型;每次发布1至3条新发现,最多6次;更正旧发现时用 corrects。本工具不创建确认按钮或暂停执行。`, Parameters: json.RawMessage(`{
"type":"object","additionalProperties":false,"required":["checkpointId","phaseId","findings"],
"properties":{
"checkpointId":{"type":"string"},
"phaseId":{"type":"string","enum":["reading","crosscheck","summarizing","draft-ready"]},
"focus":{"type":"string"},"remaining":{"type":"string"},"nextStep":{"type":"string"},
"findings":{"type":"array","minItems":1,"maxItems":3,"items":{
"type":"object","additionalProperties":false,"required":["findingKey","text","kind","sources"],
"properties":{
"findingKey":{"type":"string"},"text":{"type":"string"},"impact":{"type":"string"},
"kind":{"type":"string","enum":["material_record","analysis","provisional","needs_user"]},
"assumptions":{"type":"string"},"corrects":{"type":"string"},
"sources":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,
"required":["sourceId","blockIndex","quote"],"properties":{
"sourceId":{"type":"string"},"blockIndex":{"type":"integer","minimum":0},
"quote":{"type":"string","description":"同一段落连续出现的原文短句;material_record 默认只靠此短引,宿主扩展为整句展示"}}}},
"evidence":{"type":"object","additionalProperties":false,"required":["mode","premises"],
"description":"按 mode 的必填/禁填见工具说明;结构不符会整次被拒","properties":{
"mode":{"type":"string","enum":["direct_relation","difference","unresolved_relation"],
"description":"material_record 仅在例外用 direct_relation;analysis/provisional/needs_user 只用 difference 或 unresolved_relation"},
"premises":{"type":"array","minItems":1,"maxItems":3,
"description":"direct_relation 恰 1 个无 value;difference 为 total+phase 带 value;unresolved_relation 为 total+phase+dependent 带 value",
"items":{"type":"object","additionalProperties":false,
"required":["role","sourceIndex"],"properties":{
"role":{"type":"string","enum":["total","phase","dependent","direct_relation"],
"description":"与 mode 匹配的前提角色,每种角色至多一次"},
"sourceIndex":{"type":"integer","minimum":0},
"value":{"type":"integer","minimum":0,"maximum":1000000,
"description":"difference/unresolved_relation 的计数前提必填;direct_relation 禁止"}}}},
"result":{"type":"integer","minimum":0,"maximum":1000000,
"description":"difference 必填且须等于 total−phase;direct_relation 与 unresolved_relation 禁止"}}}
}}}
}
}`)}
}
func publishCheckpoint(rc RunCtx, args string) agenttools.Result {
if !checkpointEnabled(rc) {
return agenttools.Result{IsError: true, Content: "检查点发布不适用于当前任务或工具策略不允许"}
}
if len(args) > 16*1024 {
return agenttools.Result{IsError: true, Content: "检查点超过16KiB;已有成果保留"}
}
var candidate core.WorkCheckpointCandidate
d := json.NewDecoder(bytes.NewBufferString(args))
d.DisallowUnknownFields()
if err := d.Decode(&candidate); err != nil {
return agenttools.Result{IsError: true, Content: err.Error()}
}
var extra any
if err := d.Decode(&extra); err != io.EOF {
return agenttools.Result{IsError: true, Content: "检查点只接受一个JSON对象"}
}
if err := core.ValidateWorkCheckpoint(candidate); err != nil {
return agenttools.Result{IsError: true, Content: err.Error()}
}
receipt, err := rc.(WorkCheckpointSink).PublishWorkCheckpoint(candidate)
if err != nil {
return agenttools.Result{IsError: true, Content: fmt.Sprintf("阶段发现未发布,已有成果保留:%v", err)}
}
content := "阶段发现已保存到当前任务卡。继续处理剩余工作;发布成功只表示来源片段通过校验,不表示分析结论已独立核实。"
if receipt != "" {
content += " " + receipt
}
return agenttools.Result{Content: content}
}