Relationship Between Agent Instances, Harnesses, and Sessions in Flue
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, the instanceId is parsed from the route and passed into handleAgentRequest, which then forwards it to the harness constructor:
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 (lines 27–33), the Harness class initializes:
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 (starting at line 94), the Session class implements FlueSession and receives the instanceId to compute its storage key:
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:
- HTTP request hits
/agents/:name/:id, andhandleAgentRequestextracts theinstanceId. - Harness initialization occurs when the SDK’s
init()function is called, internally executingnew Harness(instanceId, …)as implemented inpackages/sdk/src/harness.ts. - Session retrieval happens when the developer calls
harness.session('default'), which triggersopenSessionto either load persisted state or instantiate a freshSession. - Task delegation creates child sessions via
runTaskExclusiveinpackages/sdk/src/session.ts(lines 1024–1033), maintaining the sameinstanceIdbut 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:
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.
Using Multiple Sessions Under the Same Instance
You can maintain independent conversations that share the same instance context but isolated histories:
// 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:
// 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).
Summary
- Agent instances provide the root identity (the
:idURL 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 viacreateSessionStorageKey. - 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. 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 only tracks in-memory references; the authoritative state lives in the store.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →