Orchestrating Multiple Agents in Flue: Task Sessions and Parent-Child Relationships Explained
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. 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) 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). 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):
- Invocation – The parent calls
session.task(prompt, options)(lines 620‑630), which generates a task ID and enters the exclusive execution block. - Depth validation – Flue checks the current nesting level against
MAX_TASK_DEPTH, throwing if the limit is exceeded. - Child construction – The
createTaskSessionfactory (passed during parent initialization) builds a newSessioninstance with a depth counter incremented from the parent. - Metadata bookkeeping –
recordTaskSession(lines 2550‑2560) appends{ session, taskId, storageKey }to the parent’smetadata.taskSessionsarray, creating a permanent record of the relationship. - 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. - Result aggregation – Upon completion, the parent emits a
taskevent (lines 1064‑1070), returns the child’s assistant text, and optionally persists the fullPromptResultResponsewhen 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
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
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
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
// 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 byrecordTaskSessionat lines 2550‑2560), enabling persistent audit trails. - Communication occurs through the parent’s event stream, with children reporting to
onAgentEventwhile maintaining isolated conversation histories. - Built-in tooling exposes task creation to LLMs via the
tasktool (registered at line 556), enabling autonomous delegation. - Lifecycle management is centralized through
Session.activeTasks(line 219), supporting bulkabort()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). 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.
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.
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 →