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.
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
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:
- Pruning — replaces tool result content in older messages with
"[pruned]", preserving the most recent ~40K tokens of context. No LLM call needed.
- 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:
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.
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:
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:
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:
// 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.",
});