mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
Projection, compaction and token estimation were 121 methods on *Agent, free to touch any of its state. They now form one subsystem inside the agent package: - windowState holds what the window owns for a conversation: the installed projection, the compaction in flight, the pressure latches. It used to sit as eight loose fields on sessionRuntime. sessionRuntime.reset hands a new conversation to windowState.reset, and the lifetime guard in sessionstate_test.go now holds both structs to their reset and carry-over lists. - The window's methods live on *contextWindow, which carries only its state, the session, the configuration, the services and a windowHost. - windowHost is everything the window asks of the loop: eight callbacks (the todo and host-context tails, tool facts, the provider projection, preparing a sampling request, the extension compaction intercepts, failure annotation) and Session. It was derived from the compiler, not written by hand, so it is the whole dependency. - The loop reaches the window through a.window(). The agent's public context API (ContextBudget, CacheState, LoadProjectionSidecar and the rest) is unchanged and forwards to it. The new repolint rule window-state holds the boundary. Only methods on the window's own types may read sess.win, and the owners are declared in tools/repolint/windowstate.go. REASONIX.md names the boundary. Behaviour is unchanged. No test was added or removed. Tests reach the window's internals through a.window() like the loop does.
76 lines
3.0 KiB
Go
76 lines
3.0 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"reasonix/internal/state/sessionstore"
|
|
"slices"
|
|
"testing"
|
|
|
|
"reasonix/internal/contract/event"
|
|
"reasonix/internal/contract/provider"
|
|
"reasonix/internal/contract/tool"
|
|
)
|
|
|
|
type estimationProbe struct{ name string }
|
|
|
|
func (p estimationProbe) Name() string { return p.name }
|
|
func (p estimationProbe) Description() string { return p.name + " occupies schema tokens" }
|
|
func (p estimationProbe) ReadOnly() bool { return true }
|
|
func (p estimationProbe) Schema() json.RawMessage { return json.RawMessage(`{"type":"object"}`) }
|
|
func (p estimationProbe) Execute(context.Context, json.RawMessage) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
type gatedProbe struct{ estimationProbe }
|
|
|
|
func (gatedProbe) ProviderVisible(context.Context) bool { return false }
|
|
|
|
func (gatedProbe) Unavailable(context.Context) tool.Refusal {
|
|
return tool.Refusal{Code: "probe.closed", Message: "closed"}
|
|
}
|
|
|
|
// A token estimate is compared against a window the provider enforces, so it
|
|
// has to measure the request that went out. The surface narrowed to what a turn
|
|
// admits while the estimate still read the whole registry, which overstates by
|
|
// every contextual tool the turn left behind and folds earlier than it needs to.
|
|
func TestEstimateMeasuresTheSurfaceTheRequestCarried(t *testing.T) {
|
|
reg := tool.NewRegistry()
|
|
reg.Add(estimationProbe{name: "always_here"})
|
|
reg.Add(gatedProbe{estimationProbe{name: "never_admitted"}})
|
|
reg.SetProviderVisibleTools([]string{"always_here", "never_admitted"})
|
|
|
|
prov := &scriptedProvider{name: "estimation", turns: [][]provider.Chunk{
|
|
{{Type: provider.ChunkText, Text: "done"}, {Type: provider.ChunkDone}},
|
|
}}
|
|
a := New(prov, reg, sessionstore.NewSession("sys"), Options{}, event.Discard)
|
|
if err := a.Run(context.Background(), "go"); err != nil {
|
|
t.Fatalf("Run: %v", err)
|
|
}
|
|
if len(prov.requests) == 0 {
|
|
t.Fatal("provider received no request")
|
|
}
|
|
sent := toolSchemaNames(prov.requests[0].Tools)
|
|
if slices.Contains(sent, "never_admitted") {
|
|
t.Fatalf("the gated tool reached the request, so this arm measures nothing: %v", sent)
|
|
}
|
|
|
|
estimated := toolSchemaNames(a.window().estimationSurface())
|
|
if !slices.Equal(estimated, sent) {
|
|
t.Fatalf("the estimate measures a surface no request carried\nestimated %v\nsent %v", estimated, sent)
|
|
}
|
|
}
|
|
|
|
// Before a request has gone out there is nothing to have carried, and an
|
|
// estimate still has to answer. The whole visible set is the answer that errs
|
|
// toward folding early rather than overrunning the window.
|
|
func TestEstimateFallsBackToTheWholeSurfaceBeforeAnyRequest(t *testing.T) {
|
|
reg := tool.NewRegistry()
|
|
reg.Add(estimationProbe{name: "always_here"})
|
|
reg.SetProviderVisibleTools([]string{"always_here"})
|
|
a := New(&scriptedProvider{name: "estimation-cold"}, reg, sessionstore.NewSession("sys"), Options{}, event.Discard)
|
|
if got := toolSchemaNames(a.window().estimationSurface()); !slices.Equal(got, []string{"always_here"}) {
|
|
t.Fatalf("cold estimate = %v, want the whole visible surface", got)
|
|
}
|
|
}
|