Context Management
Context window management, decision tracing, session history, knowledge extraction, automatic memory consolidation, and auto-summarization. These components help agents operate effectively within token limits and learn from past interactions.
Auto-Summarization (Context Compaction)
LLMs have a fixed context window, and long conversations will eventually exceed it. Compaction strategies automatically shrink the conversation history to fit within the token budget while preserving as much important information as possible. Package: com.tnsai.context.compaction.
TruncatingCompactor
The simplest strategy: removes oldest messages (preserving system prompts) until the token budget is met. Zero cost -- no LLM calls required.
ContextCompactor compactor = new TruncatingCompactor();
List<Map<String, Object>> compacted = compactor.compact(messages, 4096);TwoPhaseCompactor
Two-phase strategy that first truncates tool call arguments, then uses an LLM to summarize older conversation turns. Preserves more semantic information than pure truncation.
TwoPhaseCompactor compactor = TwoPhaseCompactor.builder()
.llm(llmClient)
.argTruncationThreshold(500) // truncate tool args over 500 chars
.summaryRatio(0.3) // summarize oldest 30% of messages
.build();
List<Map<String, Object>> compacted = compactor.compact(messages, 8192);Phase 1 -- Argument Truncation: Tool call arguments and results exceeding the threshold are truncated to their keys and types. This often reclaims significant tokens without losing conversational context.
Phase 2 -- LLM Summarization: If Phase 1 does not reduce the context enough, older messages are sent to the LLM for summarization. The summary replaces the original messages as a single system message.
| Compactor | Cost | Information Loss | Best For |
|---|---|---|---|
TruncatingCompactor | Free | High (oldest context lost) | Short conversations, cost-sensitive |
TwoPhaseCompactor | Low (1 LLM call) | Low (summary preserves key facts) | Long conversations, multi-turn reasoning |
LLMContextCompactor
A standalone LLM-based compactor that gives you fine-grained control over when compaction triggers, how many recent messages to preserve, and how the summary is generated. Use this when you need precise configuration beyond what TwoPhaseCompactor offers.
LLMClient summarizer = LLMClientFactory.create("openai", "gpt-4o-mini");
LLMContextCompactor compactor = new LLMContextCompactor(summarizer);
CompactionConfig config = CompactionConfig.builder()
.thresholdRatio(0.80)
.preserveLastN(5)
.preserveSystemPrompt(true)
.minMessagesForCompaction(10)
.summarizerModel("gpt-4o-mini")
.maxSummaryTokens(500)
.build();
CompactionResult result = compactor.compact(messages, config);CompactionConfig
These settings control when compaction fires and how much context is preserved versus summarized.
| Parameter | Default | Description |
|---|---|---|
thresholdRatio | 0.80 | Token ratio at which compaction triggers |
preserveLastN | 5 | Recent messages to keep unmodified |
preserveSystemPrompt | true | Always keep system prompt |
minMessagesForCompaction | 10 | Minimum messages before compaction |
summarizerModel | null | Model for summarization (null = agent's default) |
summaryPromptTemplate | built-in | Custom template (use {messages} placeholder) |
maxSummaryTokens | 500 | Max tokens for the generated summary |
Compaction Process
The compactor follows a four-step process that ensures the system prompt and recent messages are always preserved, while older messages are summarized into a compact form.
- Separate system prompt (if preserved)
- Identify last N messages to preserve
- Summarize remaining messages via LLM
- Reconstruct: [system prompt] + [summary as system message] + [preserved messages]
AgentContextManager
The central hub for observing and recording what your agent does during a conversation. It captures state snapshots at key moments, traces the reasoning behind each decision, tracks entities mentioned in the conversation, and manages conversation lifecycle. This is essential for debugging, auditing, and building agents that can learn from their own history.
AgentContextManager contextManager = new AgentContextManager(
"agent-001",
new InMemorySnapshotStore(),
new InMemoryDecisionTraceStore()
);Context Snapshots
A snapshot captures the agent's complete state (beliefs, desires, intentions) at a specific moment. By taking snapshots before and after decisions, you can see exactly what changed and why.
ContextSnapshot snapshot = contextManager.createSnapshot(
SnapshotTrigger.PRE_DECISION,
agent.getBeliefs(),
agent.getDesires(),
agent.getIntentions()
);
List<ContextSnapshot> recent = contextManager.getRecentSnapshots(10);
Optional<SnapshotDiff> diff = contextManager.diffSnapshots(beforeId, afterId);Decision Tracing
Wrap any agent operation in a decision trace to automatically record what the agent was thinking before and after the decision, what it decided, and whether it succeeded. This creates an audit trail of every significant action.
// Full version with BDI suppliers
String response = contextManager.executeWithTrace(
"Process discount request",
() -> agent.chat("Should we approve 20% discount?"),
agent::getBeliefs,
agent::getDesires,
agent::getIntentions
);
// Simplified version (without BDI)
String result = contextManager.executeWithTrace(
"Summarize report",
() -> agent.chat("Summarize this...")
);This creates pre/post snapshots, records the full decision trace, and handles errors transparently.
Querying Decisions
Once decisions are recorded, you can query them by various criteria -- recent decisions, failures, successes, or by action name. The manager also supports finding similar past decisions and generating human-readable explanations.
List<DecisionTrace> recent = contextManager.getRecentDecisions(10);
List<DecisionTrace> failures = contextManager.findFailedDecisions();
List<DecisionTrace> successes = contextManager.findSuccessfulDecisions();
List<DecisionTrace> byAction = contextManager.findDecisionsByAction("approve");
Optional<String> explanation = contextManager.explainDecision(traceId);
List<DecisionTrace> precedents = contextManager.findPrecedents(referenceTrace, 0.7);
List<String> summaries = contextManager.summarizeRecentDecisions(5);Entity Tracking
Register external entities (users, resources, tasks) so they are included in context snapshots. This helps the agent maintain awareness of what objects it is working with.
contextManager.trackEntity(customerEntity);
contextManager.untrackEntity("entity-123");
Collection<Entity> tracked = contextManager.getTrackedEntities();Context Variables
Store arbitrary key-value metadata that gets included in every snapshot. Use this for session-level information like the current user, project, or priority level.
contextManager.setVariable("sessionType", "support");
contextManager.setVariable("priority", 5);
Optional<Integer> typed = contextManager.getVariable("priority", Integer.class);Conversation Lifecycle
Group related decisions into a conversation so you can query all decisions that happened during a specific interaction.
String convId = contextManager.startConversation();
// ... process messages ...
List<DecisionTrace> inConv = contextManager.findDecisionsInCurrentConversation();
contextManager.endConversation();Cleanup
Remove old traces and snapshots to prevent unbounded storage growth. Pass a cutoff time to delete everything older than that point.
Instant cutoff = Instant.now().minus(Duration.ofDays(30));
int deletedTraces = contextManager.cleanupOldTraces(cutoff);
int deletedSnapshots = contextManager.cleanupOldSnapshots(cutoff);BDIContextMapper
An internal utility that converts the agent's BDI (Belief-Desire-Intention) state into the context graph structures used by AgentContextManager. You typically do not need to use this directly -- the context manager handles the mapping automatically.
AgentSession
A session records the full conversation between a user and an agent: every message, tool call, and result, along with metadata and outcome. Sessions are the building blocks for history search, knowledge extraction, and learning from past interactions.
AgentSession session = AgentSession.start("code-assistant", "/path/to/project");
session.addUserMessage("Fix the authentication bug");
session.addAssistantMessage("I'll analyze the auth module...");
session.addToolCall("readFile", Map.of("path", "auth.java"));
session.addToolResult("readFile", "public class Auth { ... }");
session.addMetadata("model", "gpt-4o");
session.addTags("security", "bug-fix");
session.success();
// or: session.fail("Compilation error");
// or: session.cancel();
session.getTurnCount(); // 4
session.getDuration(); // Duration since start
session.getTurnsByType(TurnType.TOOL_CALL); // Tool call turns only
session.getFirstUserMessage(); // "Fix the authentication bug"
session.isComplete(); // true
session.isSuccess(); // trueSessionOutcome
Tracks how the session ended. This is useful for filtering sessions during analysis -- for example, finding all failed sessions to understand common failure modes.
| Value | Description |
|---|---|
IN_PROGRESS | Session is active |
SUCCESS | Completed successfully |
FAILED | Ended with error |
CANCELLED | User cancelled |
SessionStore / FileSessionStore
Persist agent sessions to disk so they survive application restarts. FileSessionStore saves each session as a JSON file and provides query methods to find sessions by agent, project, tag, outcome, or date range.
SessionStore store = SessionStore.file(".tnsai/history/");
store.save(session);
Optional<AgentSession> loaded = store.load("session-123");
store.findRecent(10);
store.findByAgent("code-assistant");
store.findByProject("/my/project");
store.findByTag("security");
store.findByOutcome(SessionOutcome.SUCCESS);
store.findByDateRange(LocalDate.now().minusDays(7), LocalDate.now());
store.count();SearchableSessionStore / SessionQuery
When you need to search session contents (not just metadata), use SearchableSessionStore. It supports full-text search combined with filters for agent, project, tags, outcome, and date range.
SessionQuery query = SessionQuery.builder()
.text("authentication bug")
.agent("code-assistant")
.project("/my/project")
.tags("security", "urgent")
.outcome(SessionOutcome.SUCCESS)
.dateRange(LocalDate.now().minusDays(7), LocalDate.now())
.searchMode(SessionQuery.SearchMode.ALL)
.limit(20)
.offset(0)
.build();
List<SessionSearchResult> results = searchableStore.search(query);SessionQuery Convenience
Shortcut factory methods for common query patterns so you do not need to use the builder for simple cases.
SessionQuery recent = SessionQuery.recent(10);
SessionQuery textSearch = SessionQuery.text("database migration");
SessionQuery lastWeek = SessionQuery.builder().lastDays(7).build();SearchMode
Controls how the text search matches against session content.
| Mode | Description |
|---|---|
ANY | Match any word (default) |
ALL | Match all words |
EXACT | Match exact phrase |
REGEX | Regular expression match |
AutoConsolidationManager
Monitors conversation state and triggers memory consolidation automatically based on configurable thresholds.
AutoConsolidationManager manager = AutoConsolidationManager.builder()
.pipeline(consolidationPipeline)
.triggers(Set.of(
ConsolidationTrigger.MESSAGE_COUNT,
ConsolidationTrigger.TOKEN_LIMIT,
ConsolidationTrigger.ON_SESSION_END))
.messageCountThreshold(50)
.tokenBudget(8192)
.tokenThresholdPercent(80)
.build();
// Call on each message
ConsolidationResult result = manager.onMessageAdded(session, estimatedTokens);
if (result != null) {
System.out.println("Consolidated: " + result.knowledgeCount() + " items");
}
// Call when session ends
manager.onSessionEnd(session);ConsolidationTrigger
These triggers define when automatic consolidation runs. You can enable one or more triggers depending on your needs.
| Trigger | Default | Fires When |
|---|---|---|
MESSAGE_COUNT | 50 messages | Counter reaches threshold |
TOKEN_LIMIT | 80% of budget | Estimated tokens exceed threshold |
ON_SESSION_END | always | onSessionEnd() is called |
Consolidation is thread-safe: concurrent triggers are skipped if one is already in progress.
LLMKnowledgeExtractor
Uses an LLM to analyze session conversations and extract reusable knowledge.
KnowledgeExtractor extractor = new LLMKnowledgeExtractor(llmClient, 0.5);
List<ExtractedKnowledge> knowledge = extractor.extract(session);
// Filter by type
knowledge.stream()
.filter(k -> k.type() == ExtractionType.SOLUTION)
.filter(k -> k.isHighConfidence())
.forEach(k -> System.out.println(k.summary()));
// Extract specific types only
List<ExtractedKnowledge> decisions = extractor.extract(session,
EnumSet.of(ExtractionType.DECISION, ExtractionType.LEARNING));
// Adjust confidence threshold
KnowledgeExtractor strict = extractor.withMinConfidence(0.8);ExtractionType
Five categories of knowledge that can be extracted from session conversations. Each type has a different practical use -- solutions help with similar problems in the future, patterns provide reusable templates, and antipatterns warn about approaches to avoid.
| Type | Key | Description |
|---|---|---|
SOLUTION | problem-solution | A problem and how it was solved |
PATTERN | pattern | A reusable code or design pattern |
DECISION | decision | An architectural decision with rationale |
LEARNING | learning | A lesson learned or insight |
ANTIPATTERN | antipattern | An approach to avoid |
Each ExtractedKnowledge item includes content, optional problem description, code snippets, related files, tags, and a confidence score (0.0-1.0).
Knowledge Storage
Persist extracted knowledge for later retrieval. Two implementations are provided: in-memory for testing and file-based for production.
KnowledgeStore store = new InMemoryKnowledgeStore();
// Or persist to files:
KnowledgeStore store = new FileKnowledgeStore(".tnsai/knowledge/");
store.save(knowledge);
List<ExtractedKnowledge> solutions = store.findByType(ExtractionType.SOLUTION);AutoTeamBuilder
TnsAI.Intelligence provides LLM-driven automatic team composition. Given a task description, `AutoTeamBuilder` decomposes it into subtasks, generates agent configurations, and selects the optimal coordination topology. Package: `com.tnsai.autoteam`.
Finite State Machine
Deterministic state machine for bounded agent autonomy. Provides guard-based transitions, entry/exit actions, automatic transitions, event payloads, listeners, and visualization to Mermaid and Graphviz DOT.