返回 DeepSeek-Reasonix
goal_durable.go
根目录 / internal / control / goal_durable.go
1 package control
2
3 import (
4 "encoding/json"
5 "os"
6 "path/filepath"
7
8 "reasonix/internal/evidence"
9 "reasonix/internal/fileutil"
10 )
11
12 // goalMachineSnapshot is an in-memory rollback point for durable Goal updates.
13 // Persistence paths and mutexes are deliberately excluded.
14 type goalMachineSnapshot struct {
15 disarmed bool
16 goal string
17 status string
18 scopeID string
19 deliveryCheckpoint evidence.DeliveryCheckpoint
20 block string
21 strict bool
22 budgetClass string
23 turnsUsed int
24 turnsLimit int
25 tokensUsed int
26 requestsUsed int
27 workDurationMs int64
28 tokensLimit int
29 noProgressTurns int
30 noProgressLimit int
31 lastContinuationReason string
32 lastEvaluatorReason string
33 stopCause string
34 budgetExtensions int
35 progressEvidence []string
36 stateExtra map[string]json.RawMessage
37 }
38
39 func (g *goalMachine) capture() goalMachineSnapshot {
40 g.mu.Lock()
41 defer g.mu.Unlock()
42 return g.captureLocked()
43 }
44
45 func (g *goalMachine) captureLocked() goalMachineSnapshot {
46 return goalMachineSnapshot{
47 disarmed: g.disarmed,
48 goal: g.goal, status: g.status,
49 scopeID: g.scopeID, deliveryCheckpoint: g.deliveryCheckpoint,
50 block: g.block, strict: g.strict,
51 budgetClass: g.budgetClass, turnsUsed: g.turnsUsed,
52 turnsLimit: g.turnsLimit, tokensUsed: g.tokensUsed,
53 requestsUsed: g.requestsUsed,
54 workDurationMs: g.workDurationMs,
55 tokensLimit: g.tokensLimit, noProgressTurns: g.noProgressTurns,
56 noProgressLimit: g.noProgressLimit,
57 lastContinuationReason: g.lastContinuationReason,
58 lastEvaluatorReason: g.lastEvaluatorReason,
59 stopCause: g.stopCause, budgetExtensions: g.budgetExtensions,
60 progressEvidence: append([]string(nil), g.progressEvidence...),
61 stateExtra: cloneGoalStateExtra(g.stateExtra),
62 }
63 }
64
65 func (g *goalMachine) restore(snapshot goalMachineSnapshot) {
66 g.mu.Lock()
67 g.goal, g.status = snapshot.goal, snapshot.status
68 g.disarmed = snapshot.disarmed
69 g.scopeID = snapshot.scopeID
70 g.deliveryCheckpoint, g.block = snapshot.deliveryCheckpoint, snapshot.block
71 g.strict = snapshot.strict
72 g.budgetClass = snapshot.budgetClass
73 g.turnsUsed, g.turnsLimit = snapshot.turnsUsed, snapshot.turnsLimit
74 g.tokensUsed, g.tokensLimit = snapshot.tokensUsed, snapshot.tokensLimit
75 g.requestsUsed = snapshot.requestsUsed
76 g.workDurationMs = snapshot.workDurationMs
77 g.noProgressTurns, g.noProgressLimit = snapshot.noProgressTurns, snapshot.noProgressLimit
78 g.lastContinuationReason = snapshot.lastContinuationReason
79 g.lastEvaluatorReason = snapshot.lastEvaluatorReason
80 g.stopCause = snapshot.stopCause
81 g.budgetExtensions = snapshot.budgetExtensions
82 g.progressEvidence = append([]string(nil), snapshot.progressEvidence...)
83 g.stateExtra = cloneGoalStateExtra(snapshot.stateExtra)
84 g.continuationEpoch++
85 g.mu.Unlock()
86 }
87
88 func (g *goalMachine) writeStateErr(path string, data []byte) error {
89 if path == "" || data == nil {
90 return nil
91 }
92 g.writeMu.Lock()
93 defer g.writeMu.Unlock()
94 return writeGoalStateData(path, data)
95 }
96
97 func (g *goalMachine) writeStateAtEpoch(epoch uint64) (bool, error) {
98 g.writeMu.Lock()
99 defer g.writeMu.Unlock()
100 g.mu.Lock()
101 if g.continuationEpoch != epoch {
102 g.mu.Unlock()
103 return false, nil
104 }
105 path, data, ok := g.buildStateLocked()
106 g.mu.Unlock()
107 if !ok {
108 return true, nil
109 }
110 return true, writeGoalStateData(path, data)
111 }
112
113 func writeGoalStateData(path string, data []byte) error {
114 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
115 return err
116 }
117 return fileutil.AtomicWriteFile(path, data, 0o644)
118 }
119
119 lines GO