Conversation Memory & Compaction
In AGBoss, a conversation thread is a sequence of completed workflow executions (turns). Agents and model-backed routers see a bounded window of recent finished turns (uncompactedTurns) plus compacted summaries of older turns generated by a @Compaction component, keeping context windows bounded and predictable without losing long-range facts.
How turn windows (uncompactedTurns) and @Compaction strategies work
Section titled “How turn windows (uncompactedTurns) and @Compaction strategies work”Only finished executions enter thread memory (each formatted as start <start-name>: <input JSON> and finish <finish-name>: <output JSON>; paused or failed executions and intermediate step outputs never pollute thread history).
In v2, compaction is declared as a dedicated @Compaction class with one of four strategies (CompactionStrategy.SummaryQueue, CompactionStrategy.SingleSummary, CompactionStrategy.Custom, or CompactionStrategy.None) and bound via compaction and uncompactedTurns on @Application, @Workflow, @Agent, or @Router (the most specific level wins). A compaction summarizes every turn older than the smallest uncompactedTurns among the agents and routers that share it, so no reader ever loses a turn:
import { Agent, Compaction, CompactionStrategy, ExecutionContext, Injectable, TokenLimit, Workflow, WorkflowFinish, WorkflowStart, start, type CompactionHandler, type CompactionInput, type CompactionTurn,} from "@agboss/core";import { WorkflowFinishTextDto, WorkflowStartTextDto } from "@agboss/core/dto";
@Compaction({ name: "support-thread-compaction", description: "Summarizes older support turns into a FIFO queue of 3 summaries", strategy: CompactionStrategy.SummaryQueue, model: "moonshotai/kimi-k2.6", turnsBetweenCompactions: 2, summaryQueueLength: 3, summaryMaxTokens: TokenLimit.MODEL_MAX,})export class SupportThreadCompaction {}
@Injectable()export class KnowledgeGraphService { saveFactsAndSummarize( threadId: string, turns: readonly CompactionTurn[], previous: readonly string[], ): Promise<readonly string[]> { const facts = turns.map((turn) => `[${threadId}] ${JSON.stringify(turn.input)}`); return Promise.resolve([...previous, ...facts]); }}
@Compaction({ name: "graph-memory-compaction", description: "Extracts facts from aged-out turns into a knowledge graph via DI", strategy: CompactionStrategy.Custom, turnsBetweenCompactions: 2, deps: [KnowledgeGraphService, ExecutionContext],})export class GraphMemoryCompaction implements CompactionHandler { constructor( private readonly knowledgeGraph: KnowledgeGraphService, private readonly executionContext: ExecutionContext, ) {}
async compact(input: CompactionInput): Promise<readonly string[]> { return this.knowledgeGraph.saveFactsAndSummarize( this.executionContext.threadId, input.turnsToCompact, input.previousSummaries, ); }}
@WorkflowStart({ name: "support-turn-workflow-start", description: "Customer message in a multi-turn support thread", input: WorkflowStartTextDto,})export class SupportTurnWorkflowStart {}
@WorkflowFinish({ name: "support-turn-workflow-finish", description: "Agent reply persisted in thread history", output: WorkflowFinishTextDto,})export class SupportTurnWorkflowFinish {}
@Agent({ name: "support-memory-agent", description: "Answers customer questions using recent turns and compacted summaries", model: "moonshotai/kimi-k2.6", prompt: "Answer the customer consistently with earlier turns in the thread.", uncompactedTurns: 6,})export class SupportMemoryAgent {}
@Workflow({ name: "compacting-support-workflow", version: "1.0.0", flow: [ start(SupportTurnWorkflowStart) .returns(WorkflowStartTextDto) .toAgent(SupportMemoryAgent) .toFinish(SupportTurnWorkflowFinish), ], compaction: GraphMemoryCompaction, uncompactedTurns: 1, providers: [KnowledgeGraphService],})export class CompactingSupportWorkflow {}import { expect } from "vitest";import { ExecutionStatus } from "@agboss/core";import { replyWith, testWith } from "@agboss/core/testing";import { KnowledgeGraphService, SupportApplication, SupportMemoryAgent, SupportTurnWorkflowStart,} from "../src/compacting-support.workflow.js";
const test = testWith(SupportApplication);
test("retains thread turns and invokes custom compaction when turns age out", async ({ app, mockLlm, mockOf,}) => { mockLlm(SupportMemoryAgent).thenReturnAlways( replyWith("Your cluster runs in eu-central-1."), ); const graph = mockOf(KnowledgeGraphService); graph.saveFactsAndSummarize.mockResolvedValue(["cluster region: eu-central-1"]);
for (let i = 1; i <= 4; i++) { const res = await app.execute( SupportTurnWorkflowStart, { text: `Turn ${String(i)}: cluster is in eu-central-1.` }, { threadId: "thread-acme-1" }, ); expect(res.status).toBe(ExecutionStatus.Finished); } expect(graph.saveFactsAndSummarize).toHaveBeenCalledTimes(1);});Memory and @Compaction configuration options
Section titled “Memory and @Compaction configuration options”| Option | Where Configured | Purpose |
|---|---|---|
uncompactedTurns |
@Application, @Workflow, @Agent, @Router |
Number of recent finished turns shown in full (0 or higher, or TurnLimit.ALL; default 5). Most specific level wins. |
compaction |
@Application, @Workflow, @Agent, @Router |
@Compaction class governing how turns older than uncompactedTurns are summarized. Most specific level wins. |
CompactionStrategy.SummaryQueue |
@Compaction({ strategy }) |
Writes a new self-contained summary every turnsBetweenCompactions turns and retains the latest summaryQueueLength summaries in FIFO order. |
CompactionStrategy.SingleSummary |
@Compaction({ strategy }) |
Maintains a single rolling summary rewritten from the previous summary plus the newly aged-out turns every turnsBetweenCompactions turns. |
CompactionStrategy.Custom |
@Compaction({ strategy }) |
Delegates summarization to compact(input: CompactionInput) on your CompactionHandler class with full DI (deps: [...]) and automatic PII masking/unmasking. |
CompactionStrategy.None |
@Compaction({ strategy }) |
Disables summarization (model and turnsBetweenCompactions are omitted); readers see only the uncompactedTurns window. |
summaryMaxTokens |
@Compaction |
Maximum tokens for each summary generation call (number or TokenLimit.MODEL_MAX). If a summary is truncated at the limit, AGBoss retries once asking for a shorter summary before throwing CompactionError. |