返回 DeepSeek-Reasonix
1 // Cost-capped seed → resume → continue smoke for content-driven maintenance.
2 // Offline covers seed/resume; live continue needs DEEPSEEK_API_KEY and allows
3 // at most one summary after growing past compact_ratio=0.85.
4 package main
5
6 import (
7 "context"
8 "encoding/json"
9 "flag"
10 "fmt"
11 "os"
12 "path/filepath"
13 "strings"
14 "time"
15
16 "reasonix/internal/agent"
17 "reasonix/internal/event"
18 "reasonix/internal/provider"
19 _ "reasonix/internal/provider/openai"
20 "reasonix/internal/tool"
21 )
22
23 const (
24 model = "deepseek-v4-flash"
25 baseURL = "https://api.deepseek.com"
26 windowTokens = 1_000_000
27 compactRatio = 0.85
28 defaultMaxUSD = 0.50
29 fatBytes = 32 * 1024
30 )
31
32 type costCap struct {
33 maxUSD float64
34 spent float64
35 }
36
37 func (c *costCap) add(u *provider.Usage, pricing *provider.Pricing) error {
38 if c == nil || u == nil || pricing == nil {
39 return nil
40 }
41 c.spent += pricing.Cost(u)
42 if c.maxUSD > 0 && c.spent > c.maxUSD {
43 return fmt.Errorf("cost cap exceeded: spent $%.4f > max $%.4f", c.spent, c.maxUSD)
44 }
45 return nil
46 }
47
48 type meta struct {
49 SeededAt time.Time `json:"seeded_at"`
50 ProjectionVersion uint64 `json:"projection_version"`
51 CanonicalTokens int `json:"canonical_tokens"`
52 ProjectedTokens int `json:"projected_tokens"`
53 TriggerTokens int `json:"trigger_tokens"`
54 SummaryCalls int `json:"summary_calls"`
55 SpentUSD float64 `json:"spent_usd"`
56 ContinueUsage *provider.Usage `json:"continue_usage,omitempty"`
57 }
58
59 type recordSink struct {
60 summaryStarts int
61 }
62
63 func (s *recordSink) Emit(e event.Event) {
64 if e.Kind == event.CompactionStarted {
65 s.summaryStarts++
66 }
67 }
68
69 func prov() (provider.Provider, *provider.Pricing) {
70 key := os.Getenv("DEEPSEEK_API_KEY")
71 if key == "" {
72 fmt.Fprintln(os.Stderr, "DEEPSEEK_API_KEY not set (use -offline for seed/resume)")
73 os.Exit(1)
74 }
75 p, err := provider.New("openai", provider.Config{
76 Name: "e2e", BaseURL: baseURL, Model: model, APIKey: key,
77 Extra: map[string]any{"max_output_tokens": 1024},
78 })
79 if err != nil {
80 fmt.Fprintln(os.Stderr, err)
81 os.Exit(1)
82 }
83 pricing := &provider.Pricing{Input: 0.14, Output: 0.28, CacheHit: 0.014, Currency: "$"}
84 return p, pricing
85 }
86
87 func fatHistory(n int) []provider.Message {
88 result := strings.Repeat("x", fatBytes)
89 msgs := []provider.Message{
90 {Role: provider.RoleSystem, Content: "You are a terse coding agent."},
91 {Role: provider.RoleUser, Content: "Review the large tool outputs carefully."},
92 }
93 for i := range n {
94 id := fmt.Sprintf("tool-%d", i)
95 msgs = append(msgs,
96 provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{ID: id, Name: "read_file", Arguments: "{}"}}},
97 provider.Message{Role: provider.RoleTool, ToolCallID: id, Name: "read_file", Content: result},
98 )
99 }
100 return append(msgs,
101 provider.Message{Role: provider.RoleUser, Content: "summarize status when asked"},
102 provider.Message{Role: provider.RoleAssistant, Content: "standing by"},
103 )
104 }
105
106 func newAgent(p provider.Provider, sess *agent.Session, path string, sink event.Sink) *agent.Agent {
107 return agent.New(p, tool.NewRegistry(), sess, agent.Options{
108 ContextWindow: windowTokens,
109 CompactRatio: compactRatio,
110 RecentKeep: 2,
111 SessionPath: path,
112 WorkspaceID: "cm-e2e",
113 ModelRef: "deepseek/" + model,
114 MaxSteps: 2,
115 }, sink)
116 }
117
118 func writeMeta(dir string, m meta) {
119 b, _ := json.MarshalIndent(m, "", " ")
120 if err := os.WriteFile(filepath.Join(dir, "meta.json"), b, 0o644); err != nil {
121 fmt.Fprintln(os.Stderr, err)
122 os.Exit(1)
123 }
124 }
125
126 func readMeta(dir string) meta {
127 raw, err := os.ReadFile(filepath.Join(dir, "meta.json"))
128 if err != nil {
129 fmt.Fprintln(os.Stderr, err)
130 os.Exit(1)
131 }
132 var m meta
133 if err := json.Unmarshal(raw, &m); err != nil {
134 fmt.Fprintln(os.Stderr, err)
135 os.Exit(1)
136 }
137 return m
138 }
139
140 func seed(dir string, offline bool, cap *costCap) {
141 if err := os.MkdirAll(dir, 0o755); err != nil {
142 fmt.Fprintln(os.Stderr, err)
143 os.Exit(1)
144 }
145 path := filepath.Join(dir, "session.jsonl")
146 // ~80×32KB tool results: well under 85% of 1M for a cold estimate.
147 sess := &agent.Session{Messages: fatHistory(80)}
148 sink := &recordSink{}
149 var p provider.Provider
150 if !offline {
151 p, _ = prov()
152 }
153 a := newAgent(p, sess, path, sink)
154 before := a.ContextMaintenanceSnapshot()
155 if before.TriggerTokens <= 0 {
156 fmt.Fprintln(os.Stderr, "seed: missing trigger tokens")
157 os.Exit(1)
158 }
159 if before.ProjectedTokens >= before.TriggerTokens {
160 fmt.Fprintf(os.Stderr, "seed fixture already at/above trigger: projected=%d trigger=%d\n",
161 before.ProjectedTokens, before.TriggerTokens)
162 os.Exit(1)
163 }
164 if err := a.PrepareContext(context.Background()); err != nil {
165 fmt.Fprintln(os.Stderr, "seed prepare:", err)
166 os.Exit(1)
167 }
168 after := a.ContextMaintenanceSnapshot()
169 if after.ProjectionVersion != 0 {
170 fmt.Fprintf(os.Stderr, "seed installed projection version %d below compact_ratio\n", after.ProjectionVersion)
171 os.Exit(1)
172 }
173 if sink.summaryStarts != 0 {
174 fmt.Fprintf(os.Stderr, "seed started %d summary calls below trigger\n", sink.summaryStarts)
175 os.Exit(1)
176 }
177 if err := sess.Save(path); err != nil {
178 fmt.Fprintln(os.Stderr, err)
179 os.Exit(1)
180 }
181 m := meta{
182 SeededAt: time.Now(),
183 ProjectionVersion: after.ProjectionVersion,
184 CanonicalTokens: after.CanonicalTokens,
185 ProjectedTokens: after.ProjectedTokens,
186 TriggerTokens: after.TriggerTokens,
187 SummaryCalls: sink.summaryStarts,
188 SpentUSD: cap.spent,
189 }
190 writeMeta(dir, m)
191 fmt.Printf("seed ok projected=%d trigger=%d version=%d summary_calls=%d spent=$%.4f\n",
192 m.ProjectedTokens, m.TriggerTokens, m.ProjectionVersion, m.SummaryCalls, m.SpentUSD)
193 }
194
195 func resume(dir string, offline bool) {
196 path := filepath.Join(dir, "session.jsonl")
197 m := readMeta(dir)
198 sess, err := agent.LoadSession(path)
199 if err != nil {
200 fmt.Fprintln(os.Stderr, err)
201 os.Exit(1)
202 }
203 sink := &recordSink{}
204 var p provider.Provider
205 if !offline {
206 p, _ = prov()
207 }
208 a := newAgent(p, sess, path, sink)
209 snap := a.ContextMaintenanceSnapshot()
210 if snap.ProjectionVersion != m.ProjectionVersion {
211 fmt.Fprintf(os.Stderr, "resume version = %d, want %d\n", snap.ProjectionVersion, m.ProjectionVersion)
212 os.Exit(1)
213 }
214 if err := a.PrepareContext(context.Background()); err != nil {
215 fmt.Fprintln(os.Stderr, "resume prepare:", err)
216 os.Exit(1)
217 }
218 after := a.ContextMaintenanceSnapshot()
219 if after.ProjectionVersion != m.ProjectionVersion {
220 fmt.Fprintf(os.Stderr, "resume advanced version to %d\n", after.ProjectionVersion)
221 os.Exit(1)
222 }
223 if sink.summaryStarts != 0 {
224 fmt.Fprintf(os.Stderr, "resume re-ran summary %d times\n", sink.summaryStarts)
225 os.Exit(1)
226 }
227 fmt.Printf("resume ok idle=%s version=%d summary_calls=0\n",
228 time.Since(m.SeededAt).Round(time.Second), m.ProjectionVersion)
229 }
230
231 func cont(dir string, offline bool, cap *costCap) {
232 if offline {
233 fmt.Fprintln(os.Stderr, "continue requires live provider (omit -offline)")
234 os.Exit(1)
235 }
236 path := filepath.Join(dir, "session.jsonl")
237 m := readMeta(dir)
238 sess, err := agent.LoadSession(path)
239 if err != nil {
240 fmt.Fprintln(os.Stderr, err)
241 os.Exit(1)
242 }
243 // Grow past the 85% trigger with additional fat tool turns (skip system/user prelude).
244 for _, msg := range fatHistory(40)[2:] {
245 sess.Add(msg)
246 }
247 sink := &recordSink{}
248 p, pricing := prov()
249 a := newAgent(p, sess, path, sink)
250 before := a.ContextMaintenanceSnapshot()
251 if before.ProjectedTokens < before.TriggerTokens {
252 fmt.Fprintf(os.Stderr, "continue fixture still below trigger: projected=%d trigger=%d\n",
253 before.ProjectedTokens, before.TriggerTokens)
254 os.Exit(1)
255 }
256 if err := a.PrepareContext(context.Background()); err != nil {
257 fmt.Fprintln(os.Stderr, "continue prepare:", err)
258 os.Exit(1)
259 }
260 if sink.summaryStarts > 1 {
261 fmt.Fprintf(os.Stderr, "continue started %d summaries, want ≤1\n", sink.summaryStarts)
262 os.Exit(1)
263 }
264 after := a.ContextMaintenanceSnapshot()
265 if sink.summaryStarts == 1 && after.ProjectionVersion != m.ProjectionVersion+1 {
266 fmt.Fprintf(os.Stderr, "continue version = %d, want %d after one summary\n",
267 after.ProjectionVersion, m.ProjectionVersion+1)
268 os.Exit(1)
269 }
270 if u := a.LastUsage(); u != nil {
271 if err := cap.add(u, pricing); err != nil {
272 fmt.Fprintln(os.Stderr, err)
273 os.Exit(1)
274 }
275 m.ContinueUsage = u
276 }
277 startsBefore := sink.summaryStarts
278 _ = a.Run(context.Background(), "Reply with exactly: ok")
279 if u := a.LastUsage(); u != nil {
280 if err := cap.add(u, pricing); err != nil {
281 fmt.Fprintln(os.Stderr, err)
282 os.Exit(1)
283 }
284 }
285 if sink.summaryStarts != startsBefore {
286 fmt.Fprintf(os.Stderr, "post-checkpoint run started extra summaries (%d→%d)\n",
287 startsBefore, sink.summaryStarts)
288 os.Exit(1)
289 }
290 if err := sess.Save(path); err != nil {
291 fmt.Fprintln(os.Stderr, err)
292 os.Exit(1)
293 }
294 final := a.ContextMaintenanceSnapshot()
295 m.ProjectionVersion = final.ProjectionVersion
296 m.ProjectedTokens = final.ProjectedTokens
297 m.CanonicalTokens = final.CanonicalTokens
298 m.SummaryCalls = sink.summaryStarts
299 m.SpentUSD = cap.spent
300 writeMeta(dir, m)
301 fmt.Printf("continue ok version=%d projected=%d summary_calls=%d spent=$%.4f\n",
302 m.ProjectionVersion, m.ProjectedTokens, m.SummaryCalls, m.SpentUSD)
303 }
304
305 func main() {
306 dir := flag.String("dir", "benchmarks/context-maintenance-e2e/run", "state directory")
307 maxUSD := flag.Float64("max-usd", defaultMaxUSD, "hard cost cap for live API legs")
308 offline := flag.Bool("offline", false, "skip live provider (seed/resume only)")
309 flag.Parse()
310 cap := &costCap{maxUSD: *maxUSD}
311 switch flag.Arg(0) {
312 case "seed":
313 seed(*dir, *offline, cap)
314 case "resume":
315 resume(*dir, *offline)
316 case "continue":
317 cont(*dir, *offline, cap)
318 default:
319 fmt.Fprintln(os.Stderr, "usage: context-maintenance-e2e [-dir DIR] [-max-usd N] [-offline] seed|resume|continue")
320 os.Exit(1)
321 }
322 }
323
323 lines GO