-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsnapshot.go
More file actions
99 lines (89 loc) · 2.76 KB
/
Copy pathsnapshot.go
File metadata and controls
99 lines (89 loc) · 2.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package agentgo
import (
"context"
"fmt"
)
// Snapshot returns the stateful Agent's state and accepted input queues from
// one critical section. Its portable message slices are safe for the caller to
// encode or retain.
func (a *Agent) Snapshot() AgentSnapshot {
a.mu.Lock()
defer a.mu.Unlock()
return a.snapshotLocked()
}
func (a *Agent) snapshotLocked() AgentSnapshot {
return AgentSnapshot{
State: a.stateLocked(),
SteeringQueue: copyMessages(a.steeringQ),
FollowUpQueue: copyMessages(a.followUpQ),
}
}
// SetSnapshot directly replaces the portable state and accepted input queues
// of an idle Agent. Normal recovery should return a snapshot from BeforeRun so
// Continue can restore it automatically. Hold the lifecycle first with
// HoldRuns when replacing a snapshot around live runs.
func (a *Agent) SetSnapshot(snapshot AgentSnapshot) error {
a.runMu.Lock()
defer a.runMu.Unlock()
a.mu.Lock()
defer a.mu.Unlock()
if a.isRunning {
return fmt.Errorf("cannot set snapshot: %w", ErrAlreadyRunning)
}
a.applySnapshotLocked(snapshot)
return nil
}
// prepareRun must run with runMu held. BeforeRun runs without a.mu so adapters
// may perform storage I/O without blocking state observation; runMu keeps the
// admission snapshot stable until the returned baseline is installed.
func (a *Agent) prepareRun(ctx context.Context, kind RunKind, input []AgentMessage) error {
a.mu.Lock()
hook := a.beforeRun
if hook == nil {
a.mu.Unlock()
return nil
}
run := BeforeRunContext{
Kind: kind,
Snapshot: a.snapshotLocked(),
Input: copyMessages(input),
}
a.mu.Unlock()
snapshot, err := callBeforeRun(ctx, hook, run)
if err != nil {
return fmt.Errorf("before run: %w", err)
}
a.mu.Lock()
defer a.mu.Unlock()
if a.held > 0 {
return ErrRunsHeld
}
if a.isRunning {
return ErrAlreadyRunning
}
a.applySnapshotLocked(snapshot)
return nil
}
func callBeforeRun(ctx context.Context, hook BeforeRunHook, run BeforeRunContext) (snapshot AgentSnapshot, err error) {
defer func() {
if recovered := recover(); recovered != nil {
err = fmt.Errorf("panic: %v", recovered)
}
}()
return hook(ctx, run)
}
func (a *Agent) applySnapshotLocked(snapshot AgentSnapshot) {
a.messages = copyMessages(snapshot.State.Messages)
a.totalUsage = snapshot.State.TotalUsage
a.runProgress = cloneRunProgress(snapshot.State.Progress)
a.steeringQ = copyMessages(snapshot.SteeringQueue)
a.followUpQ = copyMessages(snapshot.FollowUpQueue)
// In-flight projections are process-local. A restored snapshot always
// starts from a stable boundary and rebinds these values on the next run.
a.streamMessage = nil
a.pendingToolCalls = make(map[string]struct{})
a.lastError = ""
a.skipNextInitialSteeringPoll = false
a.wantAbortMarker.Store(false)
a.syncContextManagerLocked()
}