# Orchestrating Multiple Agents in Flue: Task Sessions and Parent-Child Relationships Explained

> Master orchestrating multiple agents in Flue with task sessions and parent-child relationships. Build complex, parallel multi-agent workflows with isolated sessions and secure communication.

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

---

**Flue models every interaction as a session that can spawn isolated task sessions—detached child sessions running in parallel—that communicate through the parent's event stream, enabling complex multi-agent workflows without side effects.**

Flue (from the `withastro/flue` repository) treats agent orchestration as a conversation tree where parent sessions delegate work to sandboxed children. By leveraging **task sessions** and explicit **parent-child relationships**, developers can build recursive agent workflows that maintain isolation while preserving a unified audit trail.

## Core Architecture of Task Sessions

### The FlueSession Interface

At the heart of the system lies the `FlueSession` interface, implemented by the `Session` class in [`packages/sdk/src/session.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/session.ts). Each session holds a **harness** (`Agent`), a sandboxed file system (`FlueFs`), and a `SessionHistory` that tracks the conversation state. This architecture ensures that every agent interaction is persistent, traceable, and capable of spawning independent sub-agents.

### Session Hierarchy and Depth Limits

Flue enforces a strict **maximum nesting depth** of `MAX_TASK_DEPTH = 4` (defined at line 78 of [`session.ts`](https://github.com/withastro/flue/blob/main/session.ts)) to prevent runaway recursion. Each task session receives a unique storage key formatted as `task:${parentSession}:${taskId}`, constructed in the `harness.session()` implementation (lines 134‑150 of [`packages/sdk/src/harness.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/harness.ts)). The child inherits the parent’s environment variables, role, and current working directory, but runs in an isolated sandbox.

## Task Session Lifecycle

The orchestration flow follows a six-step lifecycle managed by `runTaskExclusive` (lines 994‑1060):

1.  **Invocation** – The parent calls `session.task(prompt, options)` (lines 620‑630), which generates a task ID and enters the exclusive execution block.
2.  **Depth validation** – Flue checks the current nesting level against `MAX_TASK_DEPTH`, throwing if the limit is exceeded.
3.  **Child construction** – The `createTaskSession` factory (passed during parent initialization) builds a new `Session` instance with a depth counter incremented from the parent.
4.  **Metadata bookkeeping** – `recordTaskSession` (lines 2550‑2560) appends `{ session, taskId, storageKey }` to the parent’s `metadata.taskSessions` array, creating a permanent record of the relationship.
5.  **Prompt forwarding** – The parent forwards the prompt via `child.prompt` (lines 1053‑1054), allowing the child to run its own harness and generate a fresh assistant turn.
6.  **Result aggregation** – Upon completion, the parent emits a `task` event (lines 1064‑1070), returns the child’s assistant text, and optionally persists the full `PromptResultResponse` when a schema is supplied.

All active children are tracked in `Session.activeTasks` (line 219), enabling bulk lifecycle operations.

## Parent-Child Communication Patterns

### Event Propagation

Child sessions report lifecycle events (`task_start`, `task`, `operation_start`) through the parent’s `emit` method. The parent decorates each event with its own session ID (line 1634) before forwarding it to listeners registered via `init({ onAgentEvent })`. This design provides a **unified, ordered stream** of sub-agent activity without requiring external message brokers.

### History Isolation

Unlike shared-memory architectures, Flue keeps conversation branches isolated. The child’s messages are not automatically merged into the parent’s tree. Instead, the parent updates its own `SessionHistory` only after the child finishes, through `recordTaskSession` and subsequent `save()` calls. This preserves the full execution graph while preventing context contamination between agents.

### Compaction Independence

Each task session manages its own token budget and compaction cycle. The parent’s `checkCompaction` logic (lines 1260‑1280) inspects only the parent’s messages, ensuring that a child’s summarization never interferes with the parent’s context window.

## The Built-in Task Tool

The harness automatically exposes orchestration capabilities to the LLM through the `task` tool. During session construction, `createBuiltinTools` (line 556) registers this tool, which forwards LLM-generated calls to `runTaskForTool` (lines 908‑928). This allows models to initiate sub-tasks dynamically without explicit JavaScript invocation, bridging the gap between code-level orchestration and autonomous agent behavior.

## Practical Implementation Examples

### Basic Parent-to-Child Delegation

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

const harness = init({ /* config */ });
const parent = await harness.session();

const result = await parent.task(
  'Summarize the following transcript:\n\n{{transcript}}',
  { role: 'summarizer' }
);

console.log('Child output:', result.text);

```

This creates a child session named `task:default:<uuid>` and returns the assistant’s generated text.

### Monitoring Task Events

```typescript
const harness = init({
  onAgentEvent: (ev) => {
    if (ev.type === 'task' && !ev.isError) {
      console.log(`[task ${ev.taskId}] completed in ${ev.durationMs}ms`);
    }
  },
});

const sess = await harness.session();
await sess.task('Write a short poem about cats');

```

All `task_*` events funnel through the `onAgentEvent` callback, providing a single observability surface.

### Accessing Child Metadata

```typescript
await sess.task('Analyze code complexity', { role: 'analyzer' });
const meta = sess.getMetadata();
const childInfo = meta.taskSessions?.find(t => t.taskId === taskId);

if (childInfo) {
  console.log('Child storage key:', childInfo.storageKey);
}

```

`recordTaskSession` guarantees that the child’s `storageKey` is persisted in the parent’s metadata.

### Aborting All Children

```typescript
// Aborts parent and every active child
await sess.abort();

```

The `abort()` implementation (lines 707‑711) walks `Session.activeTasks` and invokes `abort()` on each child session, ensuring clean teardown of distributed work.

## Summary

- **Task sessions** in Flue are isolated child sessions spawned via `session.task()`, running in parallel with depth-limited recursion (`MAX_TASK_DEPTH = 4`).
- **Parent-child relationships** are tracked in `metadata.taskSessions` (populated by `recordTaskSession` at lines 2550‑2560), enabling persistent audit trails.
- **Communication** occurs through the parent’s event stream, with children reporting to `onAgentEvent` while maintaining isolated conversation histories.
- **Built-in tooling** exposes task creation to LLMs via the `task` tool (registered at line 556), enabling autonomous delegation.
- **Lifecycle management** is centralized through `Session.activeTasks` (line 219), supporting bulk `abort()` operations and independent compaction cycles.

## Frequently Asked Questions

### What is the maximum nesting depth for task sessions in Flue?

Flue enforces a hard limit of **four levels** of nesting via `MAX_TASK_DEPTH = 4` (line 78 of [`session.ts`](https://github.com/withastro/flue/blob/main/session.ts)). This prevents runaway recursion when agents delegate to sub-agents that themselves spawn additional tasks.

### How do parent and child sessions communicate in Flue?

Children emit lifecycle events (`task_start`, `task`, etc.) that propagate through the parent’s `emit` method (line 1634). The parent decorates these events with its own session ID and forwards them to the `onAgentEvent` callback registered in `init()`, creating a unified stream of sub-agent activity without shared memory.

### Can child sessions modify the parent's working directory?

No. Each task session runs in its own **sandboxed** environment with an isolated `FlueFs` instance. Files written in a child’s sandbox never pollute the parent’s working directory, ensuring side-effect-free delegation according to the implementation in [`packages/sdk/src/session.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/session.ts).

### How do I abort all running child sessions simultaneously?

Call `await parent.abort()` (lines 707‑711). This method walks the `activeTasks` array (line 219) and invokes `abort()` on each child session, terminating the parent and all descendants in a single operation.