# Relationship Between Agent Instances, Harnesses, and Sessions in Flue

> Understand the relationship between agent instances, harnesses, and sessions in Flue. Learn how Flue manages conversation state, tools, and task history for isolated sessions.

- Repository: [Astro/flue](https://github.com/withastro/flue)
- Tags: internals
- Published: 2026-05-11

---

**In Flue, an agent instance (identified by the URL `:id`) spawns a harness that manages one or more isolated sessions, where each session maintains its own conversation state, tools, and task history under a unique storage key scoped to that instance.**

Flue’s architecture decouples the physical HTTP request from the logical conversation state through three distinct layers. Understanding how agent instances, harnesses, and sessions interact is essential for building reliable AI workflows with the Astro Flue SDK.

## The Three Core Primitives

Flue structures every execution around three tightly‑coupled concepts that separate identity, management, and state.

### Agent Instance

The **agent instance** is the logical agent identified by an `instanceId`—typically the `:id` parameter extracted from the request URL. One instance can run many independent conversations concurrently, but its identifier serves as the root namespace for all persistence.

In [`packages/sdk/src/runtime/handle-agent.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/runtime/handle-agent.ts), the `instanceId` is parsed from the route and passed into `handleAgentRequest`, which then forwards it to the harness constructor:

```ts
const lifecycle = await createRunLifecycle({
  // ...
  id,  // ← the `:id` from the URL – the agent instance ID
  // ...
});

```

This identifier is used to build storage keys (e.g., `agent-session:${instanceId}...`) that keep each instance’s data isolated from others.

### Harness

The **harness** is the per-instance manager that owns a map of open sessions (`openSessions`). It creates a per-instance sandbox (`FlueFs`) and wires up the underlying `Agent` (the core LLM driver).

In [`packages/sdk/src/harness.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/harness.ts) (lines 27–33), the `Harness` class initializes:

```ts
readonly sessions: FlueSessions = {
  get: (name?, options?) => this.openSession(name, 'get', options),
  create: (name?, options?) => this.openSession(name, 'create', options),
  delete: (name?) => this.deleteSession(name),
};
private openSessions = new Map<string, Session>();

```

When code calls `harness.session(name)`, the harness either returns an existing `Session` from `openSessions` or creates a new one via `openSession`, computing a storage key from the `instanceId`, harness name, and session name.

### Session

The **session** holds all runtime state for a single conversation: the message history (`SessionHistory`), compaction data, available tools, task depth, and the underlying `Agent` that communicates with the LLM.

Defined in [`packages/sdk/src/session.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/session.ts) (starting at line 94), the `Session` class implements `FlueSession` and receives the `instanceId` to compute its storage key:

```ts
const storageKey = createSessionStorageKey(this.instanceId, this.name, sessionName);
// → “agent-session:<JSON‑encoded‑[instanceId, harness, sessionName]>”

```

Sessions can also spawn **task sessions**—child sessions for delegated work—using the same instance ID but distinct names like `task:${parentSession}:${taskId}`.

## How They Connect at Runtime

The lifecycle follows a strict hierarchy from HTTP request to persistent conversation:

1. **HTTP request** hits `/agents/:name/:id`, and `handleAgentRequest` extracts the `instanceId`.
2. **Harness initialization** occurs when the SDK’s `init()` function is called, internally executing `new Harness(instanceId, …)` as implemented in [`packages/sdk/src/harness.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/harness.ts).
3. **Session retrieval** happens when the developer calls `harness.session('default')`, which triggers `openSession` to either load persisted state or instantiate a fresh `Session`.
4. **Task delegation** creates child sessions via `runTaskExclusive` in [`packages/sdk/src/session.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/session.ts) (lines 1024–1033), maintaining the same `instanceId` but incrementing task depth.

All actions are recorded in storage keyed by the `instanceId`, guaranteeing isolation between different agents while allowing many concurrent conversations under a single instance.

## Working with Sessions in Code

### Initializing a Harness Inside an Agent Handler

When handling a request, bind the harness to the request’s instance ID to ensure state persistence:

```ts
import { init } from '@flue/sdk';

export default async function handler(ctx) {
  // ctx.id is the agent instance identifier (the :id part of the URL)
  const harness = await init({ name: 'my-agent', id: ctx.id, config });

  // Open (or create) a default session
  const sess = await harness.session(); // defaults to name "default"

  // Run a simple prompt
  const response = await sess.prompt('What is the weather today?');
  console.log(response.text);
}

```

*`init` internally creates a `Harness` with the supplied `instanceId`, as seen in the `Harness` constructor in [`packages/sdk/src/harness.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/harness.ts).*

### Using Multiple Sessions Under the Same Instance

You can maintain independent conversations that share the same instance context but isolated histories:

```ts
// Two independent conversations sharing the same instance
const sessA = await harness.session('order-123');
await sessA.prompt('Create an order for 5 widgets.');

const sessB = await harness.session('order-456');
await sessB.prompt('Create an order for 2 gadgets.');

```

Both sessions store their history under different keys (`agent-session:<instanceId, "my-agent", "order-123">` and `...order-456>`), yet the underlying instance remains the same.

### Delegating Work with a Task Session

Task sessions allow concurrent sub-tasks without blocking the parent conversation:

```ts
// Inside a session, launch a detached task
const result = await sess.task(
  'Research the latest TypeScript release notes.',
  { role: 'researcher' }
);
console.log('Task result:', result.text);

```

The `task` method creates a child `Session` named `task:<parent>:<taskId>` that runs in parallel, using the same `instanceId` but a distinct session name and depth (see `runTaskExclusive` in [`packages/sdk/src/session.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/session.ts)).

## Summary

- **Agent instances** provide the root identity (the `:id` URL parameter) that scopes all persistence keys.
- **Harnesses** manage the lifecycle of sessions for a specific instance, maintaining an in-memory map (`openSessions`) and computing storage keys via `createSessionStorageKey`.
- **Sessions** encapsulate conversation state, history, and tools, and can spawn child task sessions that inherit the parent’s instance ID.
- Storage isolation is achieved by encoding the `instanceId`, harness name, and session name into a single key, ensuring data segregation between different agent instances.

## Frequently Asked Questions

### What is the difference between a harness and a session?

A **harness** is the manager that lives for the duration of the request (or longer if cached), maintaining a map of open sessions and providing the `FlueFs` sandbox. A **session** is a specific conversation context with its own message history, tools, and state. One harness can manage many sessions, but each session belongs to exactly one harness and one agent instance.

### How does Flue isolate data between different agent instances?

Data isolation relies on the `instanceId` (the URL `:id` parameter). When creating a session, Flue generates a storage key using `createSessionStorageKey(instanceId, harnessName, sessionName)` in [`packages/sdk/src/session.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/session.ts). This ensures that requests to `/agents/foo/abc` and `/agents/foo/xyz` write to completely separate namespaces, even if they use the same harness and session names.

### Can multiple sessions share the same conversation history?

No. Each session maintains its own isolated `SessionHistory`. While you can open multiple sessions with the same name sequentially (reloading the persisted state), concurrent sessions with distinct names cannot share history. However, task sessions (child sessions) can access parent context through the `task` API, though they still write to separate storage keys.

### What happens to session data when the server restarts?

Session data persists in the configured `SessionStore` (keyed by the `instanceId`-scoped storage key). When the server restarts and a request arrives with the same `:id`, the harness will call `openSession`, which loads the previous state from storage rather than creating a fresh session. The `openSessions` Map in [`packages/sdk/src/harness.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/harness.ts) only tracks in-memory references; the authoritative state lives in the store.