Skip to content

Compaction

Compaction reduces conversation history before a model call and can optionally compact the durable branch projection.

Use this mental model:

text
strategy changes what the next model sees
retention changes what future branch projection keeps
fork compaction rewrites the new target branch before it is committed

There are three related surfaces:

  • model-visible compaction: middleware reduces the non-system messages used for the next model turn
  • durable branch-history compaction: hard retention removes messages from the projected branch history and may insert replacement messages
  • fork compaction: a newly forked target branch is reduced before its initial branch history is saved

Enable Compaction

Compaction is opt-in. Set Enabled = true when configuring compaction:

csharp
builder.WithCompaction(config =>
{
    config.Enabled = true;
    config.Strategy = new MessageCountingCompactionOptions
    {
        TargetMessageCount = 50
    };
    config.Trigger = new CountCompactionTriggerOptions
    {
        TargetCount = 20,
        Threshold = 5
    };
    config.Retention = new PreserveBranchHistoryOptions();
});

This configuration performs soft compaction when the count trigger fires. The next model call sees reduced history, but durable branch history remains intact.

Per-run controls can force or skip compaction. SkipCompaction wins over TriggerCompaction.

Strategies

The strategy decides what remains visible to the next model turn.

MessageCountingCompactionOptions keeps recent messages. Its default target is 50 messages.

SummarizingCompactionOptions summarizes older history and keeps recent messages. The default shape keeps 20 recent messages, resummarizes after 5 new messages, uses a single summary, and uses handoff-style summaries. A summarizing strategy can use a separate summarizer provider through ClientProviderConfig? SummarizerProvider; otherwise it can use the main chat client.

System messages are separated before reduction and added back before the model call.

Choose the smallest strategy that solves the pressure you are seeing:

PressureUse
The chat is simply getting longMessageCountingCompactionOptions with preserve retention.
Older turns matter, but exact wording does notSummarizingCompactionOptions with preserve retention.
The branch projection itself must stay smallA strategy plus CompactBranchHistoryOptions or DeleteCompactedMessagesOptions.
Tool calls/results are involvedAdd boundary options that keep tool-call groups intact.
A new fork should start lighter than its source branchFork compaction through BranchForkOptions.CompactionIntent.
One request needs full context for debugging or a critical decisionAgentRunConfig.SkipCompaction = true.
One request should compact before continuingAgentRunConfig.TriggerCompaction = true.

Triggers

Triggers decide when compaction runs:

TriggerBehavior
CountCompactionTriggerOptionsRuns after message or message-turn count exceeds TargetCount + Threshold.
TokenBudgetCompactionTriggerOptionsRuns when the last observed input token count exceeds TargetTokenBudget + TokenBudgetThreshold.
ContextWindowCompactionTriggerOptionsRuns when the last observed input token count crosses a configured percentage of the context window.
CompositeCompactionTriggerOptionsRuns when any child trigger in AnyOf runs.

Token and context-window triggers use usage observed from prior turns. They are not preflight token counters for the current turn.

Retention

Retention decides what happens to durable branch history after model-visible compaction.

RetentionDurable branch projection
PreserveBranchHistoryOptionsPreserves branch history. This is the default and safest mode.
CompactBranchHistoryOptionsRemoves durable compacted messages and inserts replacement messages where the removed range began.
DeleteCompactedMessagesOptionsRemoves durable compacted messages without replacement messages.

Preserve retention is soft compaction. It changes what the model sees but does not remove projected branch messages.

Compact and delete retention are hard retention modes. They can change future LoadBranchAsync(...) projection and can make old message ids unavailable as fork points.

Boundary Policies

Boundary policies control which durable messages are removed under hard retention:

BoundaryDurable removal scope
ExactCompactedMessagesBoundaryOptionsRemoves exactly the durable compacted message ids selected by compaction, excluding retained and system messages.
IncludePreviousMessagesBoundaryOptionsIncludes previous non-system messages before the compacted range.
IncludeMessageTurnBoundaryOptionsExpands to messages in the same message turn.
IncludeToolCallGroupBoundaryOptionsExpands to matching tool-call and tool-result messages.
CompositeCompactionBoundaryOptionsApplies multiple boundary policies.

Message-turn boundaries depend on projected message metadata such as hpd.messageTurnId.

Events And State

Compaction uses three different records:

RecordMeaning
CompactionEventLive middleware observability for skipped or performed compaction.
CompactionStateDataBranch-scoped persistent middleware state with last compaction, trigger counts, and usage observations.
BranchHistoryCompactedEventDurable branch event that changes branch projection under hard retention.

CompactionEvent is useful for diagnostics and live UI. It is not the durable projection instruction.

BranchHistoryCompactedEvent is appended when hard retention is applied to a branch with a store. Projection applies it by removing DurableCompactedMessageIds and inserting any ReplacementMessages.

Fork And Compact

A normal fork copies source messages through the fork point, copies branch-scoped middleware state, shares session-scoped state, and prepares target branch metadata in memory.

Fork compaction runs in the pre-commit fork middleware hook. If enabled, CompactionMiddleware reduces the target branch messages before the target branch's initial event document is saved.

The source branch is unchanged.

Fork compaction does not append a standalone BranchHistoryCompactedEvent. The target branch starts with the already-compacted initial history.

Direct in-process callers can override the fork compaction choice with BranchForkOptions:

csharp
await agent.ForkBranchAsync(
    sessionId,
    sourceBranchId,
    newBranchId,
    fromMessageId,
    new BranchForkOptions
    {
        CompactionIntent = BranchForkCompactionIntent.Enabled,
        Metadata = new Dictionary<string, object>
        {
            ["name"] = "Compacted exploration"
        }
    },
    cancellationToken);

ASP.NET Core hosted fork requests do not currently include a per-request compaction intent. In hosted apps, fork compaction is controlled by the configured server-side agent and middleware pipeline unless the application exposes its own higher-level route.

UI Guidance

For transcript views, render the projected branch messages as canonical.

After hard branch-history compaction:

  • replacement messages appear where the compacted range used to be
  • delete retention closes the gap without replacement messages
  • the compaction event can be shown in an audit or debug lane
  • compacted-away message ids may no longer be valid fork points

Do not render deleted compacted messages as ordinary transcript rows unless your application has a separate archival source.

Example projection:

text
Before hard branch-history compaction:
  user-1, assistant-1, user-2, assistant-2

After compact retention with a replacement message:
  summary-1, user-2, assistant-2

After delete retention without replacement:
  user-2, assistant-2

Built for production .NET agent applications.