agent_start.go RawHistoryFolder
1package core
2
3import "context"
4
5// BaseSystem returns the prompt before BeforeStart replacements. Hosts editing
6// instructions should rebuild from this value, then call SetSystem.
7func (a *Agent) BaseSystem() string {
8 a.mu.Lock()
9 defer a.mu.Unlock()
10 return a.baseSystemLocked()
11}
12
13func (a *Agent) baseSystemLocked() string {
14 if a.startPrepared && a.System == a.startSystem {
15 return a.startBase
16 }
17 return a.System
18}
19
20// SetSystem replaces the unmodified prompt and invalidates its prepared value,
21// even when the replacement is identical. A pending BeforeStart result is
22// discarded and preparation repeats with the new base prompt.
23func (a *Agent) SetSystem(system string) {
24 a.mu.Lock()
25 defer a.mu.Unlock()
26 a.startGeneration++
27 a.startPrepared = false
28 a.System = system
29}
30
31func (a *Agent) resetStartLocked() {
32 if a.startPrepared && a.System == a.startSystem {
33 a.System = a.startBase
34 }
35 a.startGeneration++
36 a.startPrepared = false
37}
38
39// startCurrentLocked reports whether the effective prompt matches the current
40// runtime. Callers hold mu when checking it and when consuming that prompt.
41func (a *Agent) startCurrentLocked() bool {
42 return a.startPrepared && a.System == a.startSystem && a.Model == a.startModel && a.SessionID == a.startSession
43}
44
45func (a *Agent) prepareStart(ctx context.Context) error {
46 for {
47 if err := ctx.Err(); err != nil {
48 return err
49 }
50 if a.BeforeStart == nil {
51 return nil
52 }
53 a.mu.Lock()
54 if a.startCurrentLocked() {
55 a.mu.Unlock()
56 return nil
57 }
58 base := a.baseSystemLocked()
59 generation := a.startGeneration
60 original, model, session := a.System, a.Model, a.SessionID
61 a.mu.Unlock()
62
63 // Do not hold the agent lock while invoking an external hook: prompt
64 // rebuilds, context inspection, and cancellation must remain responsive.
65 system := a.BeforeStart(ctx, base)
66 if err := ctx.Err(); err != nil {
67 return err
68 }
69 a.mu.Lock()
70 if generation != a.startGeneration || original != a.System || model != a.Model || session != a.SessionID {
71 // A reset can retain exactly the same prompt string. Comparing the
72 // generation, not just its contents, prevents stale results from
73 // becoming current after that reset (including an A-B-A rebuild).
74 a.mu.Unlock()
75 continue
76 }
77 a.System = system
78 a.startBase, a.startSystem = base, system
79 a.startModel, a.startSession = model, session
80 a.startPrepared = true
81 a.mu.Unlock()
82 return nil
83 }
84}