feat(context): compact successful write/edit tool results to receipts (#289)
A successful file_write/file_edit echoes the whole file body (+ diff) back in its tool result — up to ~30k chars, which pushed the traced Gemma4 request past its context window. The write already happened and its full output is durable in the event log/CAS; the model only needs a receipt that it landed. Replace each exit=0 write result with a one-line receipt (path + elision marker) in the context builder; reads, gate output, and nonzero-exit writes stay verbatim. Derived-only pass over the transcript — authoritative events are untouched, so it's replay-safe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+41
-2
@@ -94,16 +94,23 @@ class DefaultContextPackBuilder(
|
|||||||
// raw ToolCallRequest JSON into a dotted-line format the fingerprint parser can't read.
|
// raw ToolCallRequest JSON into a dotted-line format the fingerprint parser can't read.
|
||||||
val deduped = dedupeRepeatedToolCalls(stamped)
|
val deduped = dedupeRepeatedToolCalls(stamped)
|
||||||
|
|
||||||
|
// #289: a successful file_write/file_edit echoes the whole file body (+ diff) back in its
|
||||||
|
// tool result — up to ~30k chars, which pushed the traced Gemma4 request past its context
|
||||||
|
// window. The write already happened and its full output is durable in the event log/CAS;
|
||||||
|
// the model only needs a receipt that it landed. Compact those results to a one-line receipt
|
||||||
|
// (reads and gate output stay verbatim). Derived-only — authoritative events are untouched.
|
||||||
|
val compacted = compactWriteReceipts(deduped)
|
||||||
|
|
||||||
// Stage 1 (FORMAT_COMPRESS): lossless json→dotted-line compaction of structured entries.
|
// Stage 1 (FORMAT_COMPRESS): lossless json→dotted-line compaction of structured entries.
|
||||||
val formatted = if (policy.enabled(CompressionStage.FORMAT_COMPRESS)) {
|
val formatted = if (policy.enabled(CompressionStage.FORMAT_COMPRESS)) {
|
||||||
deduped.map { entry ->
|
compacted.map { entry ->
|
||||||
if (classifier.classify(entry) == ContextClass.STRUCTURED) {
|
if (classifier.classify(entry) == ContextClass.STRUCTURED) {
|
||||||
reencode(entry, formatCompressor.compress(entry.content))
|
reencode(entry, formatCompressor.compress(entry.content))
|
||||||
} else {
|
} else {
|
||||||
entry
|
entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else deduped
|
} else compacted
|
||||||
|
|
||||||
// Stage 3 (TOKEN_PRUNE): prune freeform prose, preserving protected spans. When TIER_SPLIT
|
// Stage 3 (TOKEN_PRUNE): prune freeform prose, preserving protected spans. When TIER_SPLIT
|
||||||
// is on, the newest TIER0_TURNS freeform turns are left full-fidelity (tier 0).
|
// is on, the newest TIER0_TURNS freeform turns are left full-fidelity (tier 0).
|
||||||
@@ -283,6 +290,38 @@ class DefaultContextPackBuilder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A successful tool result is framed "[<tool> exit=<code>]\n..." by renderToolResult; failures
|
||||||
|
// use ERROR:/FATAL: sentinels (never matched here). Only exit=0 writes are compacted.
|
||||||
|
private val successFrame = Regex("""^\[(\S+) exit=(\d+)]""")
|
||||||
|
private val writeToolNames = setOf("file_write", "file_edit")
|
||||||
|
|
||||||
|
private fun compactWriteReceipts(entries: List<ContextEntry>): List<ContextEntry> {
|
||||||
|
val writeCallPaths = entries
|
||||||
|
.filter { it.sourceType == "assistantToolCall" && toolCallName(it.content) in writeToolNames }
|
||||||
|
.associate { it.sourceId to toolCallPath(it.content) }
|
||||||
|
if (writeCallPaths.isEmpty()) return entries
|
||||||
|
return entries.map { entry ->
|
||||||
|
if (entry.sourceType != "toolResult" || entry.sourceId !in writeCallPaths) return@map entry
|
||||||
|
val header = entry.content.substringBefore('\n')
|
||||||
|
val match = successFrame.find(header) ?: return@map entry
|
||||||
|
// A nonzero-exit write is a Success carrying a real advisory (e.g. a partial patch) —
|
||||||
|
// keep it verbatim; only a clean exit=0 write body is pure echo we can drop.
|
||||||
|
if (match.groupValues[2] != "0") return@map entry
|
||||||
|
val path = writeCallPaths[entry.sourceId].orEmpty()
|
||||||
|
reencode(entry, "$header wrote $path — succeeded; body elided (full result in event log)".trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toolCallName(content: String): String? = runCatching {
|
||||||
|
Json.parseToJsonElement(content).jsonObject["function"]?.jsonObject?.get("name")?.jsonPrimitive?.content
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
private fun toolCallPath(content: String): String? = runCatching {
|
||||||
|
val args = Json.parseToJsonElement(content).jsonObject["function"]?.jsonObject
|
||||||
|
?.get("arguments")?.jsonPrimitive?.content ?: return null
|
||||||
|
Json.parseToJsonElement(args).jsonObject["path"]?.jsonPrimitive?.content
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
private fun toolCallFingerprint(content: String): String? = runCatching {
|
private fun toolCallFingerprint(content: String): String? = runCatching {
|
||||||
val obj = Json.parseToJsonElement(content).jsonObject
|
val obj = Json.parseToJsonElement(content).jsonObject
|
||||||
val fn = obj["function"]?.jsonObject ?: return null
|
val fn = obj["function"]?.jsonObject ?: return null
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import com.correx.core.context.builder.DefaultContextPackBuilder
|
||||||
|
import com.correx.core.context.compression.DefaultContextCompressor
|
||||||
|
import com.correx.core.context.model.ContextEntry
|
||||||
|
import com.correx.core.context.model.ContextLayer
|
||||||
|
import com.correx.core.context.model.EntryRole
|
||||||
|
import com.correx.core.context.model.TokenBudget
|
||||||
|
import com.correx.core.events.types.ContextEntryId
|
||||||
|
import com.correx.core.events.types.ContextPackId
|
||||||
|
import com.correx.core.events.types.SessionId
|
||||||
|
import com.correx.core.events.types.StageId
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
// #289: a successful file_write/file_edit result echoes the whole file body back — it must be
|
||||||
|
// compacted to a receipt so it can't blow the context window, while reads and failures stay full.
|
||||||
|
class WriteReceiptCompactionTest {
|
||||||
|
|
||||||
|
private val builder = DefaultContextPackBuilder(DefaultContextCompressor())
|
||||||
|
private val bigBody = "x".repeat(20_000)
|
||||||
|
|
||||||
|
private fun call(sourceId: String, name: String, path: String) = ContextEntry(
|
||||||
|
id = ContextEntryId("call-$sourceId"),
|
||||||
|
layer = ContextLayer.L2,
|
||||||
|
content = """{"id":"$sourceId","function":{"name":"$name","arguments":"{\"path\":\"$path\"}"}}""",
|
||||||
|
sourceType = "assistantToolCall",
|
||||||
|
sourceId = sourceId,
|
||||||
|
tokenEstimate = 40,
|
||||||
|
role = EntryRole.ASSISTANT,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun result(sourceId: String, content: String) = ContextEntry(
|
||||||
|
id = ContextEntryId("res-$sourceId"),
|
||||||
|
layer = ContextLayer.L2,
|
||||||
|
content = content,
|
||||||
|
sourceType = "toolResult",
|
||||||
|
sourceId = sourceId,
|
||||||
|
tokenEstimate = content.length / 4,
|
||||||
|
role = EntryRole.TOOL,
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun build(entries: List<ContextEntry>) =
|
||||||
|
builder.build(ContextPackId("p"), SessionId("s"), StageId("st"), entries, TokenBudget(limit = 1_000_000))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `successful write result is compacted to a receipt`(): Unit = runBlocking {
|
||||||
|
val entries = listOf(
|
||||||
|
call("tc1", "file_write", "src/A.kt"),
|
||||||
|
result("tc1", "[file_write exit=0]\n$bigBody"),
|
||||||
|
)
|
||||||
|
val res = build(entries).layers.values.flatten().first { it.sourceType == "toolResult" }
|
||||||
|
assertFalse(res.content.contains(bigBody), "body should be elided from the receipt")
|
||||||
|
assertTrue(res.content.contains("src/A.kt"), "receipt names the path")
|
||||||
|
assertTrue(res.content.contains("body elided"), "receipt marks the elision")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `read results and failed writes are left verbatim`(): Unit = runBlocking {
|
||||||
|
val readBody = "[file_read exit=0]\n$bigBody"
|
||||||
|
val failBody = "ERROR: could not write src/B.kt"
|
||||||
|
val entries = listOf(
|
||||||
|
call("r1", "file_read", "src/A.kt"),
|
||||||
|
result("r1", readBody),
|
||||||
|
call("w1", "file_write", "src/B.kt"),
|
||||||
|
result("w1", failBody),
|
||||||
|
)
|
||||||
|
val results = build(entries).layers.values.flatten().filter { it.sourceType == "toolResult" }
|
||||||
|
assertTrue(results.any { it.content == readBody }, "read result stays verbatim")
|
||||||
|
assertTrue(results.any { it.content == failBody }, "failed write stays verbatim")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user