Patterns

Recursive LLM

Use spawn to delegate recursive sub-tasks to child agents that run independently with empty context.

Overview

The Recursive LLM pattern uses spawn() to create child agents that start with an empty ItemLog. Since spawn provides full isolation by default, the child runs independently without inheriting the parent's conversation history. Memory layers can be attached to control what the child sees and how results flow back.

Pattern

import { spawn, react } from '@noetic/core';

const subTaskAgent = react({
  model: 'gpt-4o',
  system: 'Solve the given sub-task.',
  tools: [searchTool, codeTool],
  maxSteps: 10,
});

const recursiveStep = spawn({
  id: 'recursive-subtask',
  child: subTaskAgent,
});

Controlling Child Context with Memory Layers

Memory layers give you fine-grained control over what the child sees and how results return to the parent.

onSpawn hooks inject items into the child's initial context. Use these to provide instructions, background knowledge, or a curated subset of the parent's history.

onReturn hooks transform the child's results before they reach the parent. Use these to summarize verbose output, extract structured data, or update parent state.

const recursiveStep = spawn({
  id: 'recursive-subtask',
  child: subTaskAgent,
  memory: [
    {
      id: 'task-briefing',
      name: 'Task Briefing',
      slot: 100,
      scope: 'execution',
      hooks: {
        onSpawn: async ({ parentState }) => ({
          childState: null,
          items: [
            {
              kind: 'message',
              role: 'user',
              content: [
                { type: 'input_text', text: `Sub-task: ${parentState.task}` },
              ],
            },
          ],
        }),
        onReturn: async ({ result, parentState }) => ({
          parentState: { ...parentState, subResult: result },
          result: result,
        }),
      },
    },
  ],
});

Example: Divide and Conquer

import { spawn, react, step, fork } from '@noetic/core';

// The main agent decides how to decompose the problem
const mainAgent = step.run({
  id: 'decompose',
  execute: async (input: string, ctx) => {
    // Use an LLM to decompose the task
    const subtasks = await decompose(input);

    // Fork to handle each subtask in parallel
    const results = await Promise.all(
      subtasks.map((task) =>
        ctx.recv(/* channel with subtask result */)
      ),
    );

    return results.join('\n');
  },
});

// Each subtask runs in isolated context
const subtaskStep = spawn({
  id: 'solve-subtask',
  child: react({
    model: 'gpt-4o',
    tools: [searchTool],
  }),
  timeout: 6e4,
});

When to Use

  • Problems that decompose into independent sub-problems
  • Tasks where sub-agents should not see each other's work
  • Deep reasoning chains where each step benefits from a clean slate
  • Cost control -- empty context means smaller prompts for sub-agents

Tips

  • Attach a memory layer with an onReturn hook to compress child results before they flow back to the parent. This keeps the parent's context from growing unboundedly.
  • Set a timeout on the spawn to prevent runaway sub-tasks.
  • Combine with fork({ mode: 'all' }) to run multiple recursive sub-tasks in parallel.

On this page