返回 DeepSeek-Reasonix
pinned_context.go
根目录 / internal / control / pinned_context.go
1 package control
2
3 import (
4 "context"
5 "log/slog"
6
7 "reasonix/internal/agent"
8 "reasonix/internal/provider"
9 )
10
11 type controllerPromptState struct {
12 base string
13 }
14
15 // PinnedContextLoader resolves the sidecar-owned desired context for the
16 // controller's current session immediately before an admitted model turn.
17 // Implementations perform I/O without holding Controller or Session locks.
18 type PinnedContextLoader func(context.Context, string) (agent.PinnedContextSnapshot, error)
19
20 func newControllerPromptState(base string, executor *agent.Agent) controllerPromptState {
21 current := ""
22 if executor != nil && executor.Session() != nil {
23 messages := executor.Session().Snapshot()
24 if len(messages) > 0 && messages[0].Role == provider.RoleSystem {
25 current = messages[0].Content
26 if base == "" {
27 base = current
28 }
29 }
30 }
31 state := controllerPromptState{base: base}
32 if executor != nil && executor.Session() != nil && state.base != current {
33 executor.Session().SetLeadingSystemPromptWithReason(state.base, "legacy_pinned_system_migration")
34 }
35 return state
36 }
37
38 // ApplyExtensionSystemPrompt replaces only the host/extension-owned base
39 // prompt. Pinned context is transcript state and is never composed into it.
40 func (c *Controller) ApplyExtensionSystemPrompt(prompt string) {
41 if c == nil || c.executor == nil {
42 return
43 }
44 c.mu.Lock()
45 c.prompt.base = prompt
46 c.mu.Unlock()
47 c.executor.SetSession(agent.NewSession(prompt))
48 if err := c.replaceSessionEventProjection(context.Background(), "extension-system-prompt", c.executor.Session().Snapshot()); err != nil {
49 slog.Warn("controller: record extension system prompt", "err", err)
50 }
51 }
52
53 func (c *Controller) basePrompt() string {
54 c.mu.Lock()
55 defer c.mu.Unlock()
56 return c.prompt.base
57 }
58
59 // SystemPrompt returns the authoritative host/extension system prompt.
60 func (c *Controller) SystemPrompt() string {
61 if c == nil {
62 return ""
63 }
64 c.mu.Lock()
65 defer c.mu.Unlock()
66 return c.prompt.base
67 }
68
69 // runModelTurn is the sole controller-to-runner entry point. The controller's
70 // turn gate is already held, so its session path cannot rotate while the loader
71 // reads the sidecar. StagePinnedContext only mutates Agent host state; the
72 // revision is appended atomically with the real user message after
73 // agent.before_start accepts the turn.
74 func (c *Controller) runModelTurn(ctx context.Context, input string) error {
75 if c == nil || c.runner == nil {
76 return nil
77 }
78 if c.goals.active() {
79 ctx = agent.WithContinuationPolicy(ctx, agent.ContinuationExplicitFlow)
80 }
81 if c.pinnedContextLoader != nil && c.executor != nil {
82 snapshot, err := c.pinnedContextLoader(ctx, c.SessionPath())
83 if err != nil {
84 return err
85 }
86 if err := c.executor.StagePinnedContext(snapshot); err != nil {
87 return err
88 }
89 }
90 return c.runner.Run(ctx, input)
91 }
92
92 lines GO