返回 DeepSeek-Reasonix
child.go
根目录 / internal / evidence / child.go
1 package evidence
2
3 import "sort"
4
5 // ChildEvidenceSummary is the ordered, host-observable evidence a sub-agent
6 // produced. Parents merge these receipts so delegated writes, reads, commands,
7 // verifications, and structured reviews count toward delivery gates without
8 // treating the meta tool call itself as a mutation.
9 type ChildEvidenceSummary struct {
10 Receipts []Receipt
11 // WorkspaceRoot is host-only context for classifying background evidence.
12 // Durable artifacts store only the resulting risk and mutation paths.
13 WorkspaceRoot string `json:"-"`
14 }
15
16 // HasMutation reports whether any successful receipt is a real state change.
17 func (s ChildEvidenceSummary) HasMutation() bool {
18 for _, r := range s.Receipts {
19 if r.Success && r.Mutation {
20 return true
21 }
22 }
23 return false
24 }
25
26 // MutationPaths returns distinct production paths written by the child.
27 func (s ChildEvidenceSummary) MutationPaths() []string {
28 seen := map[string]bool{}
29 var out []string
30 for _, r := range s.Receipts {
31 if !r.Success || !r.Mutation {
32 continue
33 }
34 for _, p := range r.Paths {
35 if p == "" || seen[p] {
36 continue
37 }
38 seen[p] = true
39 out = append(out, p)
40 }
41 }
42 sort.Strings(out)
43 return out
44 }
45
46 // EvidencePaths returns every distinct path the child produced a successful
47 // receipt for, reads included. MutationPaths answers what the child changed;
48 // this answers what it looked at, which is what evidence origin scores.
49 func (s ChildEvidenceSummary) EvidencePaths() []string {
50 seen := map[string]bool{}
51 var out []string
52 for _, r := range s.Receipts {
53 if !r.Success {
54 continue
55 }
56 for _, p := range r.Paths {
57 if p == "" || seen[p] {
58 continue
59 }
60 seen[p] = true
61 out = append(out, p)
62 }
63 }
64 sort.Strings(out)
65 return out
66 }
67
68 // Summary returns a snapshot of every receipt recorded this turn in order.
69 func (l *Ledger) Summary() ChildEvidenceSummary {
70 if l == nil {
71 return ChildEvidenceSummary{}
72 }
73 l.mu.Lock()
74 defer l.mu.Unlock()
75 out := make([]Receipt, len(l.receipts))
76 copy(out, l.receipts)
77 return ChildEvidenceSummary{Receipts: out}
78 }
79
80 // MergeChild appends successful child receipts into the parent ledger. Failed
81 // child receipts are retained for auditability with Success=false so they never
82 // satisfy host matchers.
83 func (l *Ledger) MergeChild(summary ChildEvidenceSummary) {
84 if l == nil || len(summary.Receipts) == 0 {
85 return
86 }
87 for _, r := range summary.Receipts {
88 // Drop nested bookkeeping that the parent already owns.
89 switch r.ToolName {
90 case "todo_write", "complete_step", "complete_subtask", "ask":
91 continue
92 }
93 l.Record(r)
94 }
95 }
96
97 // MergeChildren merges multiple child summaries in the given order.
98 func (l *Ledger) MergeChildren(summaries ...ChildEvidenceSummary) {
99 for _, s := range summaries {
100 l.MergeChild(s)
101 }
102 }
103
103 lines GO