> ## Documentation Index
> Fetch the complete documentation index at: https://docs.open-harness.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Sessions

> Stateful multi-turn conversations with compaction, retry, and persistence

While `Agent` is a stateless executor, `Session` adds the statefulness and resilience you need for interactive, multi-turn conversations. It owns the message history and handles compaction, retry, persistence, and lifecycle hooks automatically.

## Creating a Session

```typescript theme={"dark"}
import { Session } from "@openharness/core";

const session = new Session({
  agent,
  contextWindow: 200_000,
});

for await (const event of session.send("Refactor the auth module")) {
  switch (event.type) {
    case "text.delta":
      process.stdout.write(event.text);
      break;
    case "compaction.done":
      console.log(`Compacted: ${event.tokensBefore} -> ${event.tokensAfter} tokens`);
      break;
    case "retry":
      console.log(`Retrying in ${event.delayMs}ms...`);
      break;
    case "turn.done":
      console.log(`Turn ${event.turnNumber} complete`);
      break;
  }
}
```

`session.send()` yields all the same `AgentEvent` types as `agent.run()`, plus additional session lifecycle events.

## Configuration

| Option               | Default                                  | Description                                                       |
| -------------------- | ---------------------------------------- | ----------------------------------------------------------------- |
| `agent`              | (required)                               | The `Agent` to use for execution                                  |
| `contextWindow`      | —                                        | Model context window size in tokens. Required for auto-compaction |
| `reservedTokens`     | `min(20_000, agent.maxTokens ?? 20_000)` | Tokens reserved for output                                        |
| `autoCompact`        | `true` when `contextWindow` is set       | Enable auto-compaction                                            |
| `shouldCompact`      | —                                        | Custom overflow detection function                                |
| `compactionStrategy` | `DefaultCompactionStrategy()`            | Custom compaction strategy                                        |
| `retry`              | —                                        | Retry config for transient API errors                             |
| `hooks`              | —                                        | Lifecycle hooks                                                   |
| `sessionStore`       | —                                        | Pluggable persistence backend                                     |
| `sessionId`          | auto-generated UUID                      | Session identifier                                                |

## Session Events

In addition to all `AgentEvent` types, `session.send()` yields:

| Event                | Description                                                         |
| -------------------- | ------------------------------------------------------------------- |
| `turn.start`         | A new turn is starting                                              |
| `turn.done`          | Turn completed (includes token usage)                               |
| `compaction.start`   | Compaction triggered (includes reason and token count)              |
| `compaction.pruned`  | Tool results pruned (phase 1)                                       |
| `compaction.summary` | Conversation summarized (phase 2)                                   |
| `compaction.done`    | Compaction finished (includes before/after token counts)            |
| `retry`              | Retrying after a transient error (includes attempt count and delay) |

## Compaction

When a conversation approaches the context window limit, the session automatically compacts the message history. The default strategy works in two phases:

1. **Pruning** — replaces tool result content in older messages with `"[pruned]"`, preserving the most recent \~40K tokens of context. No LLM call needed.
2. **Summarization** — when pruning isn't enough, calls the model to generate a structured summary and replaces the entire history with it.

You can customize compaction at multiple levels:

```typescript theme={"dark"}
import { DefaultCompactionStrategy } from "@openharness/core";

// Tune the default strategy
const session = new Session({
  agent,
  contextWindow: 128_000,
  compactionStrategy: new DefaultCompactionStrategy({
    protectedTokens: 60_000,    // protect more recent context
    summaryModel: cheapModel,   // use a cheaper model for summarization
  }),
});

// Or replace the strategy entirely
const session = new Session({
  agent,
  contextWindow: 128_000,
  compactionStrategy: {
    async compact(context) {
      // your own compaction logic
      return { messages: [...], messagesRemoved: 0, tokensPruned: 0 };
    },
  },
});

// Or go fully manual
const session = new Session({ agent, autoCompact: false });
// ...later:
for await (const event of session.compact()) { /* ... */ }
```

## Retry

Transient API errors (429, 500, 502, 503, 504, 529, rate limits, timeouts) are retried automatically with exponential backoff and jitter. Retries only happen **before** any content has been streamed — once the model starts producing output, the session commits to that attempt.

```typescript theme={"dark"}
const session = new Session({
  agent,
  retry: {
    maxRetries: 5,
    initialDelayMs: 2000,
    maxDelayMs: 60_000,
    isRetryable: (error) => error.message.includes("overloaded"),
  },
});
```

## Hooks

Hooks let you intercept and customize the session lifecycle:

```typescript theme={"dark"}
const session = new Session({
  agent,
  hooks: {
    // Modify messages before each LLM call
    onBeforeSend: (messages) => {
      return messages.filter(m => !isStale(m));
    },
    // Post-processing after each turn
    onAfterResponse: ({ turnNumber, messages, usage }) => {
      console.log(`Turn ${turnNumber}: ${usage.totalTokens} tokens`);
    },
    // Custom compaction prompt
    onCompaction: (context) => {
      return "Summarize with emphasis on code changes and file paths.";
    },
    // Custom error handling (return true to suppress)
    onError: (error, attempt) => {
      logger.warn(`Attempt ${attempt} failed: ${error.message}`);
    },
  },
});
```

## Persistence

Plug in any storage backend by implementing the `SessionStore` interface:

```typescript theme={"dark"}
const session = new Session({
  agent,
  sessionId: "user-123-conversation-1",
  sessionStore: {
    async load(id) { return db.get(id); },
    async save(id, messages) { await db.set(id, messages); },
    async delete(id) { await db.del(id); },
  },
});

// Restore a previous session
await session.load();

// Messages are auto-saved after each turn, or save manually:
await session.save();
```

## Direct State Access

The session's message history is directly readable and writable:

```typescript theme={"dark"}
// Read current state
console.log(session.messages.length, session.turns, session.totalUsage);

// Inject or modify messages
session.messages.push({
  role: "user",
  content: "Remember: always use TypeScript.",
});
```
