# 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.

```java
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.

```java
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.

```java
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.

1. Separate system prompt (if preserved)
2. Identify last N messages to preserve
3. Summarize remaining messages via LLM
4. 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.

```java
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.

`Agent` has no `getBeliefs()` / `getDesires()` / `getIntentions()`. Use the trigger-only snapshot, or pass application-owned BDI lists if you maintain them yourself.

<!-- java-contract: src/main/java/com/example/tnsai/docs/ContextSnapshotExample.java -->
```java
package com.example.tnsai.docs;

import com.tnsai.context.integration.AgentContextManager;
import com.tnsai.context.snapshot.ContextSnapshot;
import com.tnsai.context.snapshot.SnapshotDiff;
import com.tnsai.context.snapshot.SnapshotTrigger;

import java.util.List;
import java.util.Optional;

public final class ContextSnapshotExample {
    private ContextSnapshotExample() {}

    public static ContextSnapshot capture(AgentContextManager contextManager) {
        return contextManager.createSnapshot(SnapshotTrigger.PRE_DECISION);
    }

    public static Optional<SnapshotDiff> recentDiff(
        AgentContextManager contextManager,
        String beforeId,
        String afterId
    ) {
        List<ContextSnapshot> recent = contextManager.getRecentSnapshots(10);
        return recent.isEmpty()
            ? Optional.empty()
            : 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.

```java
// Application-owned BDI lists, if you keep them
String response = contextManager.executeWithTrace(
    "Process discount request",
    () -> agent.chat("Should we approve 20% discount?"),
    () -> beliefs,
    () -> desires,
    () -> intentions
);

// Trigger-only version — Agent has no BDI getters
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.

```java
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.

```java
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.

```java
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.

```java
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.

```java
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.

```java
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();                               // true
```

### SessionOutcome

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.

```java
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.

```java
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.

```java
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.

```java
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.

```java
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.

```java
KnowledgeStore store = new InMemoryKnowledgeStore();
// Or persist to files:
KnowledgeStore store = new FileKnowledgeStore(".tnsai/knowledge/");

store.save(knowledge);
List<ExtractedKnowledge> solutions = store.findByType(ExtractionType.SOLUTION);
```
