返回 DeepSeek-Reasonix
sessionstate.go
根目录 / internal / agent / sessionstate.go
1 package agent
2
3 import (
4 "sync"
5 "sync/atomic"
6
7 "reasonix/internal/evidence"
8 )
9
10 // sessionRuntime is the host state one conversation owns. Its lifetime sits
11 // between the process and the task: SetSession replaces the conversation and
12 // reset restarts everything here that belongs to it. Atomics and mutexes make
13 // the whole-value assignment taskRuntime uses illegal, so the "no field is
14 // forgotten" property is enforced by sessionstate_test.go instead.
15 type sessionRuntime struct {
16 mu sync.Mutex // guards conversation for external Session()/SetSession
17 conversation *Session
18 output outputBudgetState
19
20 // cacheHit/cacheMiss are the session aggregate, which compaction must not
21 // reset — the hit-rate would crater every time the visible prefix is folded.
22 // Atomic: the run loop accumulates while the status line reads.
23 cacheHit atomic.Int64
24 cacheMiss atomic.Int64
25
26 missingReasoning missingReasoningWatch
27
28 // reasoningReplayStrongProjection records the provider-visible history cutoff
29 // after thinking-400 repair; later messages use normal replay. Its anchor
30 // resolves the cutoff after old tool-result messages are removed.
31 reasoningReplayStrongProjection int
32 reasoningReplayStrongProjectionAnchor string
33
34 // compactionMu guards projection snapshots/install and the in-memory sidecar
35 // generation. Network summarization never runs while this lock is held.
36 compactionMu sync.Mutex
37 // compactionRunMu singleflights the expensive summary transaction without
38 // holding the session lock during network I/O.
39 compactionRunMu sync.Mutex
40 compaction compactionProgress
41 compactionState CompactionState
42 cacheState string // legacy resume telemetry; never provider-visible
43
44 // path is rebound by preflight when a transcript is bound. Checkpoint state
45 // and any unconfirmed commit belong to the current conversation and reset.
46 path string // bound transcript path for projection sidecars
47 checkpointState string // none|restored|pending|applied; runtime-only
48 // pendingModelContextCommit is an event-log commit that was accepted but
49 // whose durability barrier did not complete. The exact payload is retained
50 // so the next model boundary can retry idempotently before any provider work.
51 pendingModelContextCommit *SessionModelContextCommit
52
53 // todoState is an executor-local mirror populated only after the semantic
54 // ToolResult commit succeeds. It never rebuilds from transcript text and is
55 // never used as frontend or authorization state.
56 todoMu sync.Mutex
57 todoState []evidence.TodoItem
58 todoWritten bool
59
60 // lastPrefixShape records the previous provider request's cacheable prefix
61 // so usage events can explain prefix churn on the next request. Carried
62 // across a conversation swap; see sessionCarryOver.
63 lastPrefixShape PrefixShape
64 haveLastPrefixShape bool
65 }
66
67 // reset rebinds the runtime to a new conversation. Every field is named here or
68 // in sessionCarryOver, and sessionstate_test.go checks both lists against the
69 // struct: an atomic-bearing type cannot be replaced by one assignment, so the
70 // guarantee has to be tested rather than compiled.
71 func (r *sessionRuntime) reset(s *Session) {
72 r.mu.Lock()
73 r.conversation = s
74 r.mu.Unlock()
75 r.cacheHit.Store(0)
76 r.cacheMiss.Store(0)
77 r.output.reset()
78 r.missingReasoning = missingReasoningWatch{}
79 r.reasoningReplayStrongProjection = 0
80 r.reasoningReplayStrongProjectionAnchor = ""
81 r.compactionMu.Lock()
82 r.compactionState = CompactionState{} // lineage change; disk reloaded on Resume
83 r.cacheState = CacheStateUnknown
84 r.checkpointState = "none"
85 r.pendingModelContextCommit = nil
86 r.compactionMu.Unlock()
87 r.compaction.stuck = false
88 r.compaction.stuckInputHash = ""
89 r.compaction.consecutive = 0
90 r.compaction.failedTurn.Store(0)
91 r.compaction.lastTurn.Store(0)
92 r.todoMu.Lock()
93 r.todoState = nil
94 r.todoWritten = false
95 r.todoMu.Unlock()
96 }
97
98 // clearReasoningReplayStrongProjection drops the process-local repair overlay.
99 // The overlay is tied to one canonical history shape; any rewind, branch, or
100 // other lineage rewrite must not let an old cutoff/anchor govern the new view.
101 func (r *sessionRuntime) clearReasoningReplayStrongProjection() {
102 if r == nil {
103 return
104 }
105 r.reasoningReplayStrongProjection = 0
106 r.reasoningReplayStrongProjectionAnchor = ""
107 }
108
109 // session returns the bound conversation under the lock that guards the
110 // pointer against a concurrent SetSession.
111 func (r *sessionRuntime) session() *Session {
112 r.mu.Lock()
113 defer r.mu.Unlock()
114 return r.conversation
115 }
116
116 lines GO