fix: render collapsed turn summaries in outbound context

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Joey Yakimowich-Payne 2026-03-13 15:40:47 -06:00
commit 6719d3f3f0

View file

@ -20,15 +20,18 @@ from pathlib import Path
@dataclass
class BlockEntry:
"""A tracked conversation block."""
block_id: str # Short hex ID (first 8 chars of content hash)
content_hash: str # Full SHA-256 of content
size: int # Byte size of original content
turn: int # Turn when first seen
role: str # "user" or "assistant"
preview: str # First 80 chars for logging
block_id: str # Short hex ID (first 8 chars of content hash)
content_hash: str # Full SHA-256 of content
size: int # Byte size of original content
turn: int # Turn when first seen
role: str # "user" or "assistant"
preview: str # First 80 chars for logging
status: str = "resident" # resident | anchored | summarized | dropped
original_content: str | None = None # Full content for fault restoration
summary: str | None = None # Model-authored summary (if summarized)
collapse_start_turn: int | None = None
collapse_end_turn: int | None = None
class BlockStore:
@ -108,8 +111,7 @@ class BlockStore:
size_k = entry.size / 1024
block["text"] = f"[tensor:{entry.block_id} ({size_k:.1f}KB)]\n{text}"
def _get_or_create(self, content: str, role: str,
turn: int) -> BlockEntry | None:
def _get_or_create(self, content: str, role: str, turn: int) -> BlockEntry | None:
"""Get existing entry by content hash, or create a new one."""
content_hash = hashlib.sha256(content.encode()).hexdigest()
short_id = content_hash[:8]
@ -181,8 +183,7 @@ class BlockStore:
entry.status = "anchored"
return True
def collapse_range(self, start_turn: int, end_turn: int,
summary: str) -> list[str]:
def collapse_range(self, start_turn: int, end_turn: int, summary: str) -> list[str]:
"""Replace all blocks in a turn range with a summary marker.
Marks all resident/anchored blocks in [start_turn, end_turn] as
@ -194,8 +195,7 @@ class BlockStore:
"""
collapsed_ids = []
for entry in self._by_id.values():
if (start_turn <= entry.turn <= end_turn
and entry.status in ("resident", "anchored")):
if start_turn <= entry.turn <= end_turn and entry.status in ("resident", "anchored"):
entry.status = "dropped"
collapsed_ids.append(entry.block_id)
@ -203,9 +203,7 @@ class BlockStore:
return []
# Create a synthetic summary block for the range
synthetic_content = (
f"[Turns {start_turn}-{end_turn} collapsed: {summary}]"
)
synthetic_content = f"[Turns {start_turn}-{end_turn} collapsed: {summary}]"
content_hash = hashlib.sha256(synthetic_content.encode()).hexdigest()
short_id = content_hash[:8]
@ -223,6 +221,8 @@ class BlockStore:
preview=synthetic_content[:80],
status="summarized",
summary=summary,
collapse_start_turn=start_turn,
collapse_end_turn=end_turn,
)
self._by_id[short_id] = entry
self._by_hash[content_hash] = short_id
@ -237,23 +237,67 @@ class BlockStore:
Modifies messages in-place. Returns stats dict.
"""
stats = {"dropped": 0, "summarized": 0, "anchored": 0}
emitted_collapses: set[str] = set()
filtered_messages: list[dict] = []
for msg in messages:
content = msg.get("content", "")
if isinstance(content, str):
msg["content"] = self._apply_to_text(content, msg, stats)
new_text = self._apply_to_text(content, stats, emitted_collapses)
if not new_text.strip():
continue
msg["content"] = new_text
filtered_messages.append(msg)
elif isinstance(content, list):
new_blocks = []
for block in content:
if not isinstance(block, dict) or block.get("type") != "text":
if not isinstance(block, dict):
new_blocks.append(block)
continue
text = block.get("text", "")
block["text"] = self._apply_to_text(text, msg, stats)
if block.get("type") != "text":
new_blocks.append(block)
continue
text = block.get("text", "")
new_text = self._apply_to_text(text, stats, emitted_collapses)
if not new_text.strip():
continue
block["text"] = new_text
new_blocks.append(block)
if not new_blocks:
continue
msg["content"] = new_blocks
filtered_messages.append(msg)
else:
filtered_messages.append(msg)
messages[:] = filtered_messages
return stats
def _apply_to_text(self, text: str, msg: dict, stats: dict) -> str:
def _find_collapse_summary(self, turn: int) -> BlockEntry | None:
"""Return the newest synthetic collapse summary covering this turn."""
for entry in reversed(list(self._by_id.values())):
if (
entry.status == "summarized"
and entry.summary
and entry.collapse_start_turn is not None
and entry.collapse_end_turn is not None
and entry.collapse_start_turn <= turn <= entry.collapse_end_turn
):
return entry
return None
def _apply_to_text(
self,
text: str,
stats: dict,
emitted_collapses: set[str],
) -> str:
"""Apply block status to a single text content."""
m = self._BLOCK_LABEL_RE.match(text)
if not m:
@ -265,18 +309,28 @@ class BlockStore:
return text
if entry.status == "dropped":
collapse_summary = self._find_collapse_summary(entry.turn)
if collapse_summary is not None:
if collapse_summary.block_id not in emitted_collapses:
emitted_collapses.add(collapse_summary.block_id)
stats["summarized"] += 1
start_turn = collapse_summary.collapse_start_turn
end_turn = collapse_summary.collapse_end_turn
return (
f"[tensor:{collapse_summary.block_id} — summarized turns "
f"{start_turn}-{end_turn}]\n"
f"{collapse_summary.summary}"
)
stats["dropped"] += 1
return ""
stats["dropped"] += 1
turn_info = f"message {entry.turn} in session log"
return (
f"[...archived {entry.size:,} chars, {turn_info}...]"
)
return f"[...archived {entry.size:,} chars, {turn_info}...]"
if entry.status == "summarized" and entry.summary:
stats["summarized"] += 1
return (
f"[tensor:{block_id} — summarized, was {entry.size:,} chars]\n"
f"{entry.summary}"
)
return f"[tensor:{block_id} — summarized, was {entry.size:,} chars]\n{entry.summary}"
# resident or anchored — no change
if entry.status == "anchored":
@ -290,14 +344,12 @@ class BlockStore:
@property
def total_bytes(self) -> int:
return sum(e.size for e in self._by_id.values()
if e.status == "resident")
return sum(e.size for e in self._by_id.values() if e.status == "resident")
def large_blocks(self, min_size: int = 2000) -> list[BlockEntry]:
"""Return resident blocks larger than min_size, sorted by size."""
return sorted(
[e for e in self._by_id.values()
if e.status == "resident" and e.size >= min_size],
[e for e in self._by_id.values() if e.status == "resident" and e.size >= min_size],
key=lambda e: e.size,
reverse=True,
)
@ -325,16 +377,20 @@ class BlockStore:
"""
entries = []
for entry in self._by_id.values():
entries.append({
"block_id": entry.block_id,
"content_hash": entry.content_hash,
"size": entry.size,
"turn": entry.turn,
"role": entry.role,
"preview": entry.preview,
"status": entry.status,
"summary": entry.summary,
})
entries.append(
{
"block_id": entry.block_id,
"content_hash": entry.content_hash,
"size": entry.size,
"turn": entry.turn,
"role": entry.role,
"preview": entry.preview,
"status": entry.status,
"summary": entry.summary,
"collapse_start_turn": entry.collapse_start_turn,
"collapse_end_turn": entry.collapse_end_turn,
}
)
tmp = path.with_suffix(".tmp")
tmp.write_text(json.dumps(entries, indent=2))
@ -369,6 +425,8 @@ class BlockStore:
preview=rec["preview"],
status=rec.get("status", "resident"),
summary=rec.get("summary"),
collapse_start_turn=rec.get("collapse_start_turn"),
collapse_end_turn=rec.get("collapse_end_turn"),
original_content=None,
)
store._by_id[entry.block_id] = entry