返回 DeepSeek-Reasonix
plan_submission.go
根目录 / internal / agent / plan_submission.go
1 package agent
2
3 import (
4 "context"
5 "sync"
6
7 "reasonix/internal/plancontract"
8 )
9
10 // PlanSubmission is where submit_plan leaves an accepted plan for the host that
11 // armed the planning turn. It rides the context rather than the tool so one
12 // shared registry can serve concurrent turns with no mutable state between them,
13 // and so a call made outside a planning turn has nowhere to land.
14 type PlanSubmission struct {
15 mu sync.Mutex
16 plan plancontract.Plan
17 previous plancontract.Plan
18 attempts int
19 }
20
21 type planSubmissionKey struct{}
22
23 // WithPlanSubmission arms submit_plan for one planning turn and returns the
24 // submission the host reads once the planner finishes.
25 func WithPlanSubmission(ctx context.Context) (context.Context, *PlanSubmission) {
26 s := &PlanSubmission{}
27 return context.WithValue(ctx, planSubmissionKey{}, s), s
28 }
29
30 func planSubmissionFromContext(ctx context.Context) (*PlanSubmission, bool) {
31 s, ok := ctx.Value(planSubmissionKey{}).(*PlanSubmission)
32 return s, ok && s != nil
33 }
34
35 // Plan returns the submitted plan, or ok=false when the planner never submitted
36 // one and the host must fall back to reading its prose.
37 func (s *PlanSubmission) Plan() (plancontract.Plan, bool) {
38 if s == nil {
39 return plancontract.Plan{}, false
40 }
41 s.mu.Lock()
42 defer s.mu.Unlock()
43 return s.plan, s.attempts > 0
44 }
45
46 // record keeps the latest submission and stamps its revision. A planner that
47 // resubmits after fixing a validation error gets revision 2, so the count is the
48 // host's own record of how many tries the plan took.
49 func (s *PlanSubmission) record(p plancontract.Plan) plancontract.Plan {
50 s.mu.Lock()
51 defer s.mu.Unlock()
52 s.previous = s.plan
53 s.attempts++
54 p.Revision = s.attempts
55 s.plan = p
56 return p
57 }
58
59 // Revised compares a resubmission against the revision it replaces, so the
60 // planner is told what its own edit changed rather than assuming. ok is false
61 // for a first submission, which replaces nothing.
62 func (s *PlanSubmission) Revised() (plancontract.Diff, bool) {
63 if s == nil {
64 return plancontract.Diff{}, false
65 }
66 s.mu.Lock()
67 defer s.mu.Unlock()
68 if s.attempts < 2 {
69 return plancontract.Diff{}, false
70 }
71 return plancontract.Compare(s.previous, s.plan), true
72 }
73
73 lines GO