From: Anderson A. Giacomolli Date: Tue, 10 Nov 2026 03:23:14 +0000 (-0300) Subject: Revert compaction view block messages X-Git-Url: http://git.giacomolli.uk/onechat/commitdiff_plain/871743f6ab20d68dcede1673a9764730fda38308?ds=inline Revert compaction view block messages This reverts 0a98ccb. A compaction sends the view, its closing tag, a newline, and the task as one user message with the same joined bytes. Each retry adds its note as one separate user message. The prompt path, names, style, and summary-failure log stay unchanged. After the split, compactions read 32.8% of input from the cache across 493 calls, against 31.9% across 183 calls before it. Imports read 30.7%. The split adds code and messages for no measured gain. --- diff --git a/compactor.go b/compactor.go index be17c15..0089002 100644 --- a/compactor.go +++ b/compactor.go @@ -12,9 +12,6 @@ import ( // compactorTries is the most calls for one node. const compactorTries = 5 -// compactorBlock is the number of view lines in one block message. -const compactorBlock = 4 - // compactorRuler shows the model the size limit of a line. Models cannot count // bytes, and a sample line as the ruler gets its content copied. var compactorRuler = strings.Repeat("-", nodeBytes) @@ -33,18 +30,14 @@ func newCompactor(client *ModelClient, instructions func() (string, error), mode // Compactor builds the nodes of one agent with a model. A compaction is a call // like a turn, with the same system prompt and tools, so it reads them from the -// prompt cache of the turns. The tools are never called. After them come the -// compaction view up to the node and the task, as the user messages that -// compactorTexts gives. Each whole block of compactorBlock view lines is its -// own message, and the last message holds the rest of the lines and the task. -// The backend reads a cache entry only at a message end of a request, so a -// compaction whose view begins with the same lines as an earlier one begins -// with the same block messages. +// prompt cache of the turns. The tools are never called. After them comes one +// user message with the compaction view up to the node, its closing tag, a +// newline, and the task. // -// When the line is over nodeBytes, the next try sends the same messages, plus +// When the line is over nodeBytes, the next try sends the same message, plus // one more user message with a note with the size of the newest line and its // cut at the limit. A try never sends the model's earlier reply or reasoning -// back, so every try begins with the whole earlier try and has about the same +// back, so every try begins with the same view and task and has about the same // size. After at most compactorTries tries, the shortest line is the node. A // few bytes over the limit is fine, because the view measures the real sizes. A // call that fails before any line fails the build, and the core tries it again @@ -68,11 +61,7 @@ func (c *Compactor) Summarize(ctx context.Context, task Task) (string, error) { return "", err } - var first []any - - for _, message := range compactorTexts(task.Context, text) { - first = append(first, modelUserMessage(message)) - } + first := []any{modelUserMessage(task.Context + "\n" + text)} var lines []string @@ -147,27 +136,6 @@ of them from there too. return "", fmt.Errorf("node (%d, %d) has %d inputs", task.Level, task.Index, len(task.Input)) } -// compactorTexts returns the texts of the user messages of a first try for the -// compaction view and the task text. Each whole block of compactorBlock lines, -// counted from the first line of the view, is one message. The last message -// holds the rest of the lines, the closing tag, a newline, and the task. The -// first message starts with the opening tag. Joined, the texts are the view, a -// newline, and the task. -func compactorTexts(view, task string) []string { - body := strings.TrimSuffix(strings.TrimPrefix(view, "\n"), "") - lines := strings.SplitAfter(body, "\n") - lines = lines[:len(lines)-1] - whole := len(lines) - len(lines)%compactorBlock - - var texts []string - - for n := 0; n < whole; n += compactorBlock { - texts = append(texts, viewMessage(n == 0, lines[n:n+compactorBlock])) - } - - return append(texts, viewMessage(whole == 0, lines[whole:])+"\n"+task) -} - // compactorTooLong returns the note for a line over nodeBytes: its size and its // first nodeBytes bytes, which show the model where the limit cuts it. func compactorTooLong(line string) string { diff --git a/compactor_test.go b/compactor_test.go index 8c83594..eaed4fe 100644 --- a/compactor_test.go +++ b/compactor_test.go @@ -50,90 +50,6 @@ func TestCompactorSendsThePromptToolsAndTask(t *testing.T) { }, texts) } -func TestCompactorSendsEachWholeBlockOfTheViewAsAMessage(t *testing.T) { - testCases := []struct { - name string - context string - want []string - }{ - { - name: "empty view goes with the task", - context: "\n", - want: []string{"\n\nTASK"}, - }, - { - name: "lines short of a block go with the task", - context: "\n0+1|a\n1+1|b\n2+1|c\n", - want: []string{"\n0+1|a\n1+1|b\n2+1|c\n\nTASK"}, - }, - { - name: "one whole block and the task", - context: "\n0+1|a\n1+1|b\n2+1|c\n3+1|d\n", - want: []string{"\n0+1|a\n1+1|b\n2+1|c\n3+1|d\n", "\nTASK"}, - }, - { - name: "two whole blocks and the rest with the task", - context: "\n0+2|a\n2+1|b\n3+1|c\n4+1|d\n5+1|e\n6+1|f\n7+1|g\n8+1|h\n9+1|i\n", - want: []string{ - "\n0+2|a\n2+1|b\n3+1|c\n4+1|d\n", - "5+1|e\n6+1|f\n7+1|g\n8+1|h\n", - "9+1|i\n\nTASK", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - server := newModelFake(t) - task := compactorLeafTask() - task.Context = tc.context - - _, err := newTestCompactor(t, server).Summarize(t.Context(), task) - require.NoError(t, err) - - text, err := compactorTask(task) - require.NoError(t, err) - - want := make([]string, len(tc.want)) - for n, message := range tc.want { - want[n] = strings.Replace(message, "TASK", text, 1) - } - - texts := compactorInputTexts(t, server.Call(0)) - require.Equal(t, want, texts) - require.Equal(t, tc.context+"\n"+text, strings.Join(texts, "")) - }) - } -} - -func TestCompactorSendsTheSameBlocksForViewsWithTheSameFirstLines(t *testing.T) { - shared := "\n0+4|a\n4+1|b\n5+1|c\n6+1|d\n7+1|e\n8+1|f\n9+1|g\n10+1|h\n" - server := newModelFake(t) - compactor := newTestCompactor(t, server) - _, err := compactor.Summarize(t.Context(), Task{ - Level: 0, - Index: 11, - Context: shared + "11+1|i\n", - Input: []string{"user: eleven"}, - }) - require.NoError(t, err) - - _, err = compactor.Summarize(t.Context(), Task{ - Level: 1, - Index: 5, - Context: shared + "11+1|j\n12+1|k\n", - Input: []string{"left line", "right line"}, - }) - require.NoError(t, err) - - first := compactorInputTexts(t, server.Call(0)) - second := compactorInputTexts(t, server.Call(1)) - require.Len(t, first, 3) - require.Len(t, second, 3) - require.Equal(t, first[:2], second[:2]) - require.NotEqual(t, first[2], second[2]) -} - func TestCompactorWritesTheMergeTask(t *testing.T) { text, err := compactorTask(Task{ Level: 2, @@ -173,18 +89,21 @@ func TestCompactorRetriesALongLineWithTheNewestNote(t *testing.T) { require.Equal(t, len(long[n]), record.TextBytes) } + text, err := compactorTask(task) + require.NoError(t, err) + first := compactorInputTexts(t, server.Call(0)) - require.Len(t, first, 2) + require.Equal(t, []string{task.Context + "\n" + text}, first) for n, want := range []string{long[0], long[1]} { call := server.Call(n + 1) texts := compactorInputTexts(t, call) - require.Equal(t, first, texts[:2]) - require.Equal(t, []string{compactorTooLong(want)}, texts[2:]) - require.Contains(t, texts[2], "was "+strconv.Itoa(len(want))+" bytes,") - require.Contains(t, texts[2], "\n"+want[:512]+"| <- LIMIT") + require.Equal(t, first, texts[:1]) + require.Equal(t, []string{compactorTooLong(want)}, texts[1:]) + require.Contains(t, texts[1], "was "+strconv.Itoa(len(want))+" bytes,") + require.Contains(t, texts[1], "\n"+want[:512]+"| <- LIMIT") - note, err := json.Marshal(modelUserMessage(texts[2])) + note, err := json.Marshal(modelUserMessage(texts[1])) require.NoError(t, err) require.LessOrEqual(t, len(call.raw), len(server.Call(0).raw)+len(",")+len(note)) }