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.
Session bundles compaction, retry, persistence, turn tracking, and hooks into a single class. If you want more control — composing only the behaviors you need, or writing custom middleware — use the functional Runner / Middleware / Conversation API instead.
Runners and Middleware
A Runner is an async generator function with the same shape as agent.run(). A Middleware transforms one Runner into another. You compose them with apply():
import {
Agent, Conversation, toRunner, apply,
withTurnTracking, withCompaction, withRetry, withPersistence, withHooks,
} from "@openharness/core";
const agent = new Agent({
name: "dev",
model: openai("gpt-5.4"),
tools: { ...fsTools, bash },
});
// Compose only the middleware you need
const runner = apply(
toRunner(agent),
withTurnTracking(),
withCompaction({ contextWindow: 200_000, model: agent.model }),
withRetry({ maxRetries: 5 }),
withPersistence({ store: myStore, sessionId: "abc" }),
);
const chat = new Conversation({ runner });
for await (const event of chat.send("Fix the bug in auth.ts")) {
if (event.type === "text.delta") process.stdout.write(event.text);
}
// chat.messages is automatically updated from the done event
Middleware listed first in apply() wraps outermost. The ordering above means: turn tracking brackets everything, compaction runs once before retries, retry wraps only the agent call, and persistence saves after a successful response.
Available Middleware
| Middleware | Description |
|---|
withTurnTracking() | Emits turn.start/turn.done events. Maintains a turn counter across calls. |
withCompaction(config) | Auto-compacts history when approaching the context window limit. Tracks lastInputTokens from step.done events. |
withRetry(config?) | Retries on transient API errors (429, 500, etc.) with exponential backoff. Only retries before content has been streamed. |
withPersistence(config) | Auto-saves messages to a SessionStore on every done event. |
withHooks(hooks) | Applies SessionHooks (onBeforeSend, onAfterResponse, onError) around the inner runner. |
Conversation
Conversation is a thin stateful wrapper over a composed Runner. It manages messages (updating from done events) and provides the same toUIMessageStream() and toResponse() methods as Session for AI SDK 5 integration:
const chat = new Conversation({ runner, sessionId: "abc", store: myStore });
// Optionally load previous messages
await chat.load();
// Send messages — chat.messages is updated automatically
for await (const event of chat.send("hello")) { /* ... */ }
// Manual save (separate from withPersistence auto-save)
await chat.save();
// Next.js route handler
return chat.toResponse(input, { signal: req.signal });
Stream Combinators
For lightweight event stream transforms, four curried combinators are available:
import { tap, filter, map, takeUntil } from "@openharness/core";
// Log every event
const logged = tap(e => console.log(e.type));
for await (const event of logged(agent.run([], "hello"))) { /* ... */ }
// Drop reasoning events (done events are never filtered)
const noReasoning = filter(e => e.type !== "reasoning.delta");
// Transform text events
const uppercased = map(e =>
e.type === "text.delta" ? { ...e, text: e.text.toUpperCase() } : e
);
// Stop after first text completion
const firstText = takeUntil(e => e.type === "text.done");
Writing Custom Middleware
A middleware is a function that takes a Runner and returns a Runner:
import type { Middleware } from "@openharness/core";
const withLogging: Middleware = (runner) =>
async function* (history, input, options) {
console.log(`Sending: ${typeof input === "string" ? input : "[messages]"}`);
for await (const event of runner(history, input, options)) {
if (event.type === "done") console.log(`Done: ${event.result}`);
yield event;
}
};
const runner = apply(toRunner(agent), withLogging, withRetry());
Composing with pipe
For reusable middleware stacks, use pipe() to create a combined middleware:
import { pipe } from "@openharness/core";
const production = pipe(
withTurnTracking(),
withCompaction({ contextWindow: 200_000, model }),
withRetry({ maxRetries: 5 }),
);
// Apply the same stack to multiple runners
const runner1 = production(toRunner(agent1));
const runner2 = production(toRunner(agent2));