Writing a compactor¶
A compactor takes a transcript and returns a CompactionArtifact. That's the whole contract.
The Compactor class¶
from typing import Any
from compactbench.compactors import Compactor
from compactbench.contracts import (
CompactionArtifact,
StructuredState,
Transcript,
)
class MyCompactor(Compactor):
name = "my-method"
version = "0.1.0"
async def compact(
self,
transcript: Transcript,
config: dict[str, Any] | None = None,
previous_artifact: CompactionArtifact | None = None,
) -> CompactionArtifact:
return CompactionArtifact(
summaryText="A short overview of the conversation.",
structured_state=StructuredState(
immutable_facts=["project: X"],
locked_decisions=["use approach A"],
forbidden_behaviors=["do not B"],
unresolved_items=["decide on C"],
entity_map={"Alice": "primary_stakeholder"},
),
selectedSourceTurnIds=[t.id for t in transcript.turns],
warnings=[],
methodMetadata={"temperature": 0.0},
)
The artifact schema¶
Every field in CompactionArtifact is required — empty arrays and empty objects are fine; missing keys are not.
| Field | Purpose |
|---|---|
summaryText |
Free-form summary (≤ 8000 chars). Optional; leave empty for no-prose methods. |
structured_state.immutable_facts |
Facts that must never change (entities, versions, file paths). |
structured_state.locked_decisions |
Decisions the user has committed to. |
structured_state.deferred_items |
Items explicitly postponed. |
structured_state.forbidden_behaviors |
Things the assistant must never do. |
structured_state.entity_map |
Canonical mapping of entity names to roles. |
structured_state.unresolved_items |
Open tasks or questions. |
selectedSourceTurnIds |
Turn ids the method drew from. |
warnings |
Non-fatal issues the method encountered. |
methodMetadata |
Anything method-specific the scorer should ignore. |
The Pydantic validator will reject any extra fields.
Testing locally¶
Use the mock provider for reproducible local iteration:
compactbench run \
--method path/to/my_compactor.py:MyCompactor \
--suite starter \
--provider mock \
--drift-cycles 2
Then inspect results.jsonl.
Handling drift cycles¶
If you want to accumulate state across cycles (the strongest baseline — hybrid-ledger — does this), use the previous_artifact argument:
async def compact(
self,
transcript: Transcript,
config: dict[str, Any] | None = None,
previous_artifact: CompactionArtifact | None = None,
) -> CompactionArtifact:
carried_locked = (
list(previous_artifact.structured_state.locked_decisions)
if previous_artifact
else []
)
# ... merge with newly observed decisions ...
Stateless methods should simply ignore previous_artifact.