Files
DeepSeek-Reasonix/internal/runtime/agent/compact_archive_test.go
T

148 lines
5.7 KiB
Go

package agent
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"reasonix/internal/base/testenv"
"reasonix/internal/contract/event"
"reasonix/internal/contract/provider"
"reasonix/internal/contract/tool"
"reasonix/internal/state/sessionstore"
)
func archiveAgent(t *testing.T, sess *sessionstore.Session, sink event.Sink) (*Agent, *countingProvider, string) {
t.Helper()
prov := &countingProvider{reply: "## Standing facts\n- a summarizer was asked"}
dir := testenv.TempDir(t)
a := New(prov, tool.NewRegistry(), sess, Options{
ContextWindow: 200_000, CompactRatio: 0.8, SessionPath: filepath.Join(dir, "session.jsonl"),
WorkspaceID: "ws", ModelRef: "p/m",
ArchiveDir: testenv.TempDir(t),
}, sink)
return a, prov, dir
}
// A fold is archived, not summarized: no summarizer request is made, the
// folded span lands verbatim in a file beside the transcript, and what stays in
// context is a pointer to it.
func TestAnArchivedFoldMakesNoSummarizerCallAndPointsAtTheFile(t *testing.T) {
sess := foldableSessionOverForce(60)
for i := range sess.Messages {
if sess.Messages[i].Role == provider.RoleAssistant {
sess.Messages[i].Content = "decided: keep the public API stable"
}
}
var done event.Compaction
a, prov, dir := archiveAgent(t, sess, event.FuncSink(func(e event.Event) {
if e.Kind == event.CompactionDone {
done = e.Compaction
}
}))
verdict, err := a.CompactNow(context.Background(), CompactRequest{Instructions: "the API contract"})
if err != nil {
t.Fatalf("CompactNow: %v", err)
}
if !verdict.Compacted() {
t.Fatalf("verdict = %+v, want a fold", verdict)
}
if len(prov.got) != 0 {
t.Fatalf("an archived fold made %d summarizer request(s)", len(prov.got))
}
pointer := done.Summary
if !strings.Contains(pointer, "archived, not summarized") || !strings.Contains(pointer, "read_file") {
t.Fatalf("the pointer does not tell the model to read the archive:\n%s", pointer)
}
if !strings.Contains(pointer, "the API contract") {
t.Fatalf("the pointer dropped the requested focus:\n%s", pointer)
}
files, err := filepath.Glob(filepath.Join(dir, "session.outputs", "fold-*.md"))
if err != nil || len(files) != 1 {
t.Fatalf("archive files = %v (%v), want exactly one beside the transcript", files, err)
}
if !strings.Contains(pointer, files[0]) {
t.Fatalf("the pointer does not name %s:\n%s", files[0], pointer)
}
body, err := os.ReadFile(files[0])
if err != nil || !strings.Contains(string(body), "decided: keep the public API stable") {
t.Fatalf("the archive does not hold the folded work verbatim (%v)", err)
}
visible := a.window().modelVisibleMessages()
if len(visible) >= len(sess.Messages) {
t.Fatalf("nothing left the context: %d visible of %d", len(visible), len(sess.Messages))
}
for _, m := range visible {
if m.Role == provider.RoleSystem && strings.Contains(m.Content, "a summarizer was asked") {
t.Fatal("a summarizer digest reached the context")
}
}
}
// What the host keeps for display only never reaches the model, so it never
// reaches the archive the model reads back either.
func TestAnArchiveNeverHoldsLocalOnlyOutput(t *testing.T) {
sess := foldableSessionOverForce(60)
local := provider.Message{Role: provider.RoleAssistant, Content: "local-only output that must stay out", LocalOnly: true}
sess.Messages = append(sess.Messages[:20], append([]provider.Message{local}, sess.Messages[20:]...)...)
a, _, _ := archiveAgent(t, sess, event.Discard)
if _, err := a.CompactNow(context.Background(), CompactRequest{}); err != nil {
t.Fatalf("CompactNow: %v", err)
}
files, _ := filepath.Glob(filepath.Join(a.window().outputsDir(), "fold-*.md"))
if len(files) == 0 {
t.Fatal("no archive written")
}
for _, f := range files {
body, err := os.ReadFile(f)
if err != nil || strings.Contains(string(body), "local-only output that must stay out") {
t.Fatalf("the archive %s holds display-only output (%v)", f, err)
}
}
}
// Nothing of the conversation is held back: after a fold between turns the
// context is the system prompt and the one pointer, the first user turn, the
// recent turns and the user's own words included, and the archive has them all.
func TestAFoldRetainsNothingButThePointer(t *testing.T) {
sess := &sessionstore.Session{Messages: []provider.Message{
{Role: provider.RoleSystem, Content: "sys"},
{Role: provider.RoleUser, Content: "FIRST-TURN codeword blue-whale"},
{Role: provider.RoleAssistant, Content: "ok " + strings.Repeat("filler ", 200)},
{Role: provider.RoleUser, Content: "SECOND-TURN deadline friday"},
{Role: provider.RoleAssistant, Content: "noted " + strings.Repeat("filler ", 200)},
{Role: provider.RoleUser, Content: "LAST-TURN owner lin"},
{Role: provider.RoleAssistant, Content: "done " + strings.Repeat("filler ", 200)},
}}
a, _, dir := archiveAgent(t, sess, event.Discard)
verdict, err := a.CompactNow(context.Background(), CompactRequest{})
if err != nil || !verdict.Compacted() {
t.Fatalf("CompactNow = %+v, %v; want a fold", verdict, err)
}
visible := a.window().modelVisibleMessages()
if len(visible) != 2 || visible[0].Role != provider.RoleSystem || !isCompactionSummary(visible[1]) {
t.Fatalf("visible = %+v, want the system prompt and one pointer", visible)
}
for _, word := range []string{"blue-whale", "friday", "owner lin"} {
if strings.Contains(visible[1].Content, word) {
t.Fatalf("the pointer carries %q; it must only point", word)
}
}
files, _ := filepath.Glob(filepath.Join(dir, "session.outputs", "fold-*.md"))
if len(files) != 1 {
t.Fatalf("archive files = %v", files)
}
body, _ := os.ReadFile(files[0])
for _, word := range []string{"blue-whale", "friday", "owner lin"} {
if !strings.Contains(string(body), word) {
t.Fatalf("the archive lost %q", word)
}
}
}