Skip to main content
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

OptionDefaultDescription
agent(required)The Agent to use for execution
contextWindowModel context window size in tokens. Required for auto-compaction
reservedTokensmin(20_000, agent.maxTokens ?? 20_000)Tokens reserved for output
autoCompacttrue when contextWindow is setEnable auto-compaction
shouldCompactCustom overflow detection function
compactionStrategyDefaultCompactionStrategy()Custom compaction strategy
retryRetry config for transient API errors
hooksLifecycle hooks
sessionStorePluggable persistence backend
sessionIdauto-generated UUIDSession identifier

Session Events

In addition to all AgentEvent types, session.send() yields:
EventDescription
turn.startA new turn is starting
turn.doneTurn completed (includes token usage)
compaction.startCompaction triggered (includes reason and token count)
compaction.prunedTool results pruned (phase 1)
compaction.summaryConversation summarized (phase 2)
compaction.doneCompaction finished (includes before/after token counts)
retryRetrying 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:
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.",
});