resident_handoff.go RawHistoryFolder
1package subagents
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7 "unicode/utf8"
8
9 "github.com/bnema/zut/packages/core"
10)
11
12const residentHandoffBytes = 16 << 10
13
14// residentBudgetHandoff is an evidence-only fallback, not a model-generated
15// claim of completion. The durable transcript retains full tool arguments and
16// results, including side effects that cannot be inferred from a patch.
17func residentBudgetHandoff(dir string, spec ResidentChildSpec) string {
18 var b strings.Builder
19 fmt.Fprintf(&b, "Partial result: budget exhausted; work is NOT complete.\nHistory: %s\nResult: %s\nWorkspace: %s\n", HistoryRef(spec.ID), ResultRef(spec.ID), spec.Workspace)
20 b.WriteString("Recovery: inspect the saved history and artifacts before repeating side effects, then use subagent_resume with the remaining task. Explicit resume grants a fresh allowance of the same size; required work remains unmet until success.\n")
21 b.WriteString("The following are bounded recent observations, not verified findings or an exhaustive list of side effects. Unfinished steps must be reconciled against the accepted task. Raw tool arguments and output are omitted because they may contain secrets; inspect them in the child history.\n")
22 items, err := ReadResidentHistory(dir, 16)
23 if err != nil {
24 b.WriteString("Recent history unavailable; inspect the durable child session before retrying.\n")
25 return b.String()
26 }
27 // Do not mix in earlier turns when the latest prompt is in this page.
28 for i := len(items) - 1; i >= 0; i-- {
29 if items[i].Type == residentRecordUser {
30 items = items[i:]
31 break
32 }
33 }
34 for _, item := range items {
35 switch item.Type {
36 case residentRecordUser, residentRecordAssistant:
37 message, err := core.DecodeMessageJSON(item.Message)
38 if err == nil {
39 fmt.Fprintf(&b, "%s: %s\n", item.Type, handoffExcerpt(residentAssistantSummary(message), 1024))
40 }
41 case residentRecordToolCall:
42 fmt.Fprintf(&b, "tool %s (%s): called; arguments retained in history\n", item.ToolName, item.ToolID)
43 case residentRecordToolResult:
44 var result struct {
45 IsError bool
46 }
47 if err := json.Unmarshal(item.ToolResult, &result); err == nil {
48 fmt.Fprintf(&b, "tool result (%s): is_error=%t; output retained in history\n", item.ToolID, result.IsError)
49 }
50 }
51 if b.Len() >= residentHandoffBytes {
52 break
53 }
54 }
55 return handoffExcerpt(b.String(), residentHandoffBytes)
56}
57
58func handoffExcerpt(text string, limit int) string {
59 if len(text) <= limit {
60 return text
61 }
62 end := limit - len("…")
63 for end > 0 && !utf8.RuneStart(text[end]) {
64 end--
65 }
66 return text[:end] + "…"
67}