diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionApproach.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionApproach.cs
index 4204a32c58..1b9a23cc55 100644
--- a/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionApproach.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionApproach.cs
@@ -5,6 +5,8 @@ using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Agents.AI.Compaction;
+#pragma warning disable IDE0001 // Simplify Names for namespace in comments
+
///
/// Describes the compaction approach used by a pre-configured .
///
@@ -33,3 +35,5 @@ public enum CompactionApproach
///
Aggressive,
}
+
+#pragma warning restore IDE0001 // Simplify Names
diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionSize.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionSize.cs
index a71ff63cd2..16e701c98e 100644
--- a/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionSize.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionSize.cs
@@ -19,19 +19,19 @@ namespace Microsoft.Agents.AI.Compaction;
public enum CompactionSize
{
///
- /// Targets models with smaller context windows (approximately 4,000 tokens).
+ /// Maintains a smaller context windows.
/// Compaction triggers earlier and keeps less history in context.
///
Compact,
///
- /// Targets models with a medium-sized context window (approximately 8,000 tokens).
+ /// Maintains a a medium-sized context window.
/// This is a reasonable default for most common models.
///
Moderate,
///
- /// Targets models with large context windows (approximately 16,000 tokens or more).
+ /// Maintains a large context windows.
/// Compaction triggers later and retains more history in context.
///
Generous,
diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionStrategy.Factory.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionStrategy.Factory.cs
new file mode 100644
index 0000000000..788cfd39ad
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionStrategy.Factory.cs
@@ -0,0 +1,157 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI.Compaction;
+
+///
+/// Base class for strategies that compact a to reduce context size.
+///
+///
+///
+/// Compaction strategies operate on instances, which organize messages
+/// into atomic groups that respect the tool-call/result pairing constraint. Strategies mutate the collection
+/// in place by marking groups as excluded, removing groups, or replacing message content (e.g., with summaries).
+///
+///
+/// Every strategy requires a that determines whether compaction should
+/// proceed based on current metrics (token count, message count, turn count, etc.).
+/// The base class evaluates this trigger at the start of and skips compaction when
+/// the trigger returns .
+///
+///
+/// An optional target condition controls when compaction stops. Strategies incrementally exclude
+/// groups and re-evaluate the target after each exclusion, stopping as soon as the target returns
+/// . When no target is specified, it defaults to the inverse of the trigger —
+/// meaning compaction stops when the trigger condition would no longer fire.
+///
+///
+/// Strategies can be applied at three lifecycle points:
+///
+/// - In-run: During the tool loop, before each LLM call, to keep context within token limits.
+/// - Pre-write: Before persisting messages to storage via .
+/// - On existing storage: As a maintenance operation to compact stored history.
+///
+///
+///
+/// Multiple strategies can be composed by applying them sequentially to the same
+/// via .
+///
+///
+public abstract partial class CompactionStrategy
+{
+ ///
+ /// Creates a pre-configured from a combination of
+ /// and .
+ ///
+ ///
+ ///
+ /// The controls which strategies are included in the pipeline:
+ ///
+ /// - : tool result collapsing + truncation backstop. No required.
+ /// - : tool result collapsing + LLM summarization + truncation backstop.
+ /// - : tool result collapsing + LLM summarization + sliding window + truncation backstop.
+ ///
+ ///
+ ///
+ /// The controls the token and message thresholds at which each stage triggers.
+ /// Choose a size that matches the input token limit of your model.
+ ///
+ ///
+ ///
+ /// The compaction approach that controls which strategy or pipeline to use.
+ /// does not require a ;
+ /// and require one.
+ ///
+ ///
+ /// The context-size profile that controls token and message thresholds.
+ ///
+ ///
+ /// The used for LLM-based summarization.
+ /// Required when is or
+ /// ; ignored for .
+ ///
+ /// A configured for the specified approach and size.
+ ///
+ /// is and requires one.
+ ///
+ ///
+ /// or is not a defined enum value.
+ ///
+ public static CompactionStrategy Create(CompactionApproach approach, CompactionSize size, IChatClient? chatClient = null)
+ {
+ if (approach is CompactionApproach.Balanced or CompactionApproach.Aggressive)
+ {
+ _ = Throw.IfNull(chatClient);
+ }
+
+ int tokenLimit = GetTokenLimit(size);
+ int messageLimit = GetMessageLimit(size);
+
+ return approach switch
+ {
+ CompactionApproach.Gentle => CreateGentlePipeline(tokenLimit, messageLimit),
+ CompactionApproach.Balanced => CreateBalancedPipeline(tokenLimit, messageLimit, chatClient!),
+ CompactionApproach.Aggressive => CreateAggressivePipeline(tokenLimit, messageLimit, GetTurnLimit(size), chatClient!),
+ _ => throw new ArgumentOutOfRangeException(nameof(approach), approach, null),
+ };
+ }
+
+ private static int GetTokenLimit(CompactionSize size) => size switch
+ {
+ CompactionSize.Compact => 0x1FFF, // 8k
+ CompactionSize.Moderate => 0x7FFF, // 32k
+ CompactionSize.Generous => 0xFFFF, // 64k
+ _ => throw new ArgumentOutOfRangeException(nameof(size), size, null),
+ };
+
+ private static int GetMessageLimit(CompactionSize size) => size switch
+ {
+ CompactionSize.Compact => 50,
+ CompactionSize.Moderate => 500,
+ CompactionSize.Generous => 1000,
+ _ => throw new ArgumentOutOfRangeException(nameof(size), size, null),
+ };
+
+ private static int GetTurnLimit(CompactionSize size) => size switch
+ {
+ CompactionSize.Compact => 25,
+ CompactionSize.Moderate => 250,
+ CompactionSize.Generous => 500,
+ _ => throw new ArgumentOutOfRangeException(nameof(size), size, null),
+ };
+
+ private static PipelineCompactionStrategy CreateGentlePipeline(int tokenLimit, int messageLimit) =>
+ new(
+ new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(messageLimit)),
+ new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(tokenLimit)));
+
+ private static PipelineCompactionStrategy CreateBalancedPipeline(int tokenLimit, int messageLimit, IChatClient chatClient)
+ {
+ // Early stages trigger at two-thirds of the limit so the pipeline has room to compact
+ // incrementally before reaching the emergency truncation backstop at the full limit.
+ int earlyMessageTrigger = messageLimit * 2 / 3;
+ int earlyTokenTrigger = tokenLimit * 2 / 3;
+
+ return new(
+ new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(earlyMessageTrigger)),
+ new SummarizationCompactionStrategy(chatClient, CompactionTriggers.TokensExceed(earlyTokenTrigger)),
+ new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(tokenLimit)));
+ }
+
+ private static PipelineCompactionStrategy CreateAggressivePipeline(int tokenLimit, int messageLimit, int turnLimit, IChatClient chatClient)
+ {
+ // Early stages trigger at half the limit so compaction kicks in sooner and
+ // the sliding window and truncation backstop are reached less often.
+ int earlyMessageTrigger = messageLimit / 2;
+ int earlyTokenTrigger = tokenLimit / 2;
+
+ return new(
+ new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(earlyMessageTrigger)),
+ new SummarizationCompactionStrategy(chatClient, CompactionTriggers.TokensExceed(earlyTokenTrigger)),
+ new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(turnLimit)),
+ new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(tokenLimit)));
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionStrategy.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionStrategy.cs
index 818908cd8e..eeaab3dfb3 100644
--- a/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionStrategy.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Compaction/CompactionStrategy.cs
@@ -5,7 +5,6 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Shared.DiagnosticIds;
@@ -48,7 +47,7 @@ namespace Microsoft.Agents.AI.Compaction;
///
///
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
-public abstract class CompactionStrategy
+public abstract partial class CompactionStrategy
{
///
/// Initializes a new instance of the class.
@@ -162,117 +161,4 @@ public abstract class CompactionStrategy
/// The target value.
/// 0 if negative; otherwise the value
protected static int EnsureNonNegative(int value) => Math.Max(0, value);
-
- ///
- /// Creates a pre-configured from a combination of
- /// and .
- ///
- ///
- ///
- /// The controls which strategies are included in the pipeline:
- ///
- /// - : tool result collapsing + truncation backstop. No required.
- /// - : tool result collapsing + LLM summarization + truncation backstop.
- /// - : tool result collapsing + LLM summarization + sliding window + truncation backstop.
- ///
- ///
- ///
- /// The controls the token and message thresholds at which each stage triggers.
- /// Choose a size that matches the input token limit of your model.
- ///
- ///
- ///
- /// The compaction approach that controls which strategy or pipeline to use.
- /// does not require a ;
- /// and require one.
- ///
- ///
- /// The context-size profile that controls token and message thresholds.
- ///
- ///
- /// The used for LLM-based summarization.
- /// Required when is or
- /// ; ignored for .
- ///
- /// A configured for the specified approach and size.
- ///
- /// is and requires one.
- ///
- ///
- /// or is not a defined enum value.
- ///
- public static CompactionStrategy Create(CompactionApproach approach, CompactionSize size, IChatClient? chatClient = null)
- {
- if (approach is CompactionApproach.Balanced or CompactionApproach.Aggressive)
- {
- _ = Throw.IfNull(chatClient);
- }
-
- int tokenLimit = GetTokenLimit(size);
- int messageLimit = GetMessageLimit(size);
-
- return approach switch
- {
- CompactionApproach.Gentle => CreateGentlePipeline(tokenLimit, messageLimit),
- CompactionApproach.Balanced => CreateBalancedPipeline(tokenLimit, messageLimit, chatClient!),
- CompactionApproach.Aggressive => CreateAggressivePipeline(tokenLimit, messageLimit, GetTurnLimit(size), chatClient!),
- _ => throw new ArgumentOutOfRangeException(nameof(approach), approach, null),
- };
- }
-
- private static int GetTokenLimit(CompactionSize size) => size switch
- {
- CompactionSize.Compact => 4_000,
- CompactionSize.Moderate => 8_000,
- CompactionSize.Generous => 16_000,
- _ => throw new ArgumentOutOfRangeException(nameof(size), size, null),
- };
-
- private static int GetMessageLimit(CompactionSize size) => size switch
- {
- CompactionSize.Compact => 10,
- CompactionSize.Moderate => 20,
- CompactionSize.Generous => 40,
- _ => throw new ArgumentOutOfRangeException(nameof(size), size, null),
- };
-
- private static int GetTurnLimit(CompactionSize size) => size switch
- {
- CompactionSize.Compact => 3,
- CompactionSize.Moderate => 6,
- CompactionSize.Generous => 12,
- _ => throw new ArgumentOutOfRangeException(nameof(size), size, null),
- };
-
- private static PipelineCompactionStrategy CreateGentlePipeline(int tokenLimit, int messageLimit) =>
- new(
- new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(messageLimit)),
- new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(tokenLimit)));
-
- private static PipelineCompactionStrategy CreateBalancedPipeline(int tokenLimit, int messageLimit, IChatClient chatClient)
- {
- // Early stages trigger at two-thirds of the limit so the pipeline has room to compact
- // incrementally before reaching the emergency truncation backstop at the full limit.
- int earlyMessageTrigger = messageLimit * 2 / 3;
- int earlyTokenTrigger = tokenLimit * 2 / 3;
-
- return new(
- new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(earlyMessageTrigger)),
- new SummarizationCompactionStrategy(chatClient, CompactionTriggers.TokensExceed(earlyTokenTrigger)),
- new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(tokenLimit)));
- }
-
- private static PipelineCompactionStrategy CreateAggressivePipeline(int tokenLimit, int messageLimit, int turnLimit, IChatClient chatClient)
- {
- // Early stages trigger at half the limit so compaction kicks in sooner and
- // the sliding window and truncation backstop are reached less often.
- int earlyMessageTrigger = messageLimit / 2;
- int earlyTokenTrigger = tokenLimit / 2;
-
- return new(
- new ToolResultCompactionStrategy(CompactionTriggers.MessagesExceed(earlyMessageTrigger)),
- new SummarizationCompactionStrategy(chatClient, CompactionTriggers.TokensExceed(earlyTokenTrigger)),
- new SlidingWindowCompactionStrategy(CompactionTriggers.TurnsExceed(turnLimit)),
- new TruncationCompactionStrategy(CompactionTriggers.TokensExceed(tokenLimit)));
- }
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/CompactionStrategyCreateTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/CompactionStrategyCreateTests.cs
index 8950c348af..aa54dd00ca 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/CompactionStrategyCreateTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/CompactionStrategyCreateTests.cs
@@ -211,17 +211,14 @@ public class CompactionStrategyCreateTests
// 1 tool-call group (assistant FunctionCall + tool result = 2 messages, 1 group)
new(ChatRole.Assistant, [new FunctionCallContent("c1", "fetch")]),
new(ChatRole.Tool, "data"),
- // 8 user/assistant pairs = 16 messages, 16 groups
- new(ChatRole.User, "Q1"), new(ChatRole.Assistant, "A1"),
- new(ChatRole.User, "Q2"), new(ChatRole.Assistant, "A2"),
- new(ChatRole.User, "Q3"), new(ChatRole.Assistant, "A3"),
- new(ChatRole.User, "Q4"), new(ChatRole.Assistant, "A4"),
- new(ChatRole.User, "Q5"), new(ChatRole.Assistant, "A5"),
- new(ChatRole.User, "Q6"), new(ChatRole.Assistant, "A6"),
- new(ChatRole.User, "Q7"), new(ChatRole.Assistant, "A7"),
- new(ChatRole.User, "Q8"), new(ChatRole.Assistant, "A8"),
];
+ for (int index = 0; index < 99; ++index)
+ {
+ messages.Add(new(ChatRole.User, $"Q{index}"));
+ messages.Add(new(ChatRole.Assistant, $"A{index}"));
+ }
+
// Two separate indexes so strategies run independently.
CompactionMessageIndex compactIndex = CompactionMessageIndex.Create(messages);
CompactionMessageIndex moderateIndex = CompactionMessageIndex.Create(messages);