Skip to main content

Multi-Agent Orchestrator

3 min read

Cross-Agent State

Reactive derived state across agents and a shared scratchpad for coordination.

Cross-agent state lets you compute values from the combined state of all agents and share mutable state through a scratchpad – both integrated with the debug timeline.


Cross-Agent Derivations

Define derived values that react to changes across any registered agent's state:

import { createMultiAgentOrchestrator } from '@directive-run/ai';

const orchestrator = createMultiAgentOrchestrator({
  runner,
  agents: {
    researcher: { agent: researcher },
    writer: { agent: writer },
    reviewer: { agent: reviewer },
  },

  derive: {
    totalTokens: (snapshot) => {
      let sum = 0;
      for (const [, agentState] of Object.entries(snapshot.agents)) {
        sum += agentState.totalTokens ?? 0;
      }

      return sum;
    },

    allIdle: (snapshot) =>
      Object.values(snapshot.agents).every(
        (s) => s.status === 'idle' || s.status === 'completed'
      ),

    progress: (snapshot) => {
      const agents = Object.values(snapshot.agents);
      const completed = agents.filter((s) => s.status === 'completed').length;

      return `${completed}/${agents.length} agents done`;
    },
  },
});

Reading Derived Values

// Read all derived values (frozen record)
const derived = orchestrator.derived;
console.log(derived.totalTokens);
console.log(derived.allIdle);
console.log(derived.progress);

Subscribing to Changes

const unsubscribe = orchestrator.onDerivedChange((id, value) => {
  console.log(`Derivation ${id} changed to:`, value);
});

// Later
unsubscribe();

CrossAgentSnapshot

The snapshot passed to derivation functions contains:

interface CrossAgentSnapshot {
  agents: Record<string, AgentState>;            // All agent states
  coordinator: { globalTokens: number; status: string };  // Coordinator facts
  scratchpad?: Record<string, unknown>;          // Current scratchpad state
}

Timeline Integration

Derivation changes emit derivation_update events on the debug timeline, recording which derivation changed and its new value.


Shared Scratchpad

A key-value store shared across all agents for mutable coordination state:

const orchestrator = createMultiAgentOrchestrator({
  runner,
  agents: { /* ... */ },

  scratchpad: {
    init: {
      taskList: [],
      completedCount: 0,
      lastUpdate: null,
    },
  },
});

Reading and Writing

const pad = orchestrator.scratchpad!;

// Basic operations
pad.set('taskList', ['research', 'write', 'review']);
pad.get('taskList');      // ['research', 'write', 'review']
pad.has('taskList');       // true
pad.delete('lastUpdate');

// Batch update (merges into scratchpad)
pad.update({ completedCount: 1 });

// Read all values
const all = pad.getAll();
// { taskList: [...], completedCount: 1 }

// Reset to initial values
pad.reset();

Subscribing to Changes

// Subscribe to specific keys
const unsub = pad.subscribe(['completedCount', 'taskList'], (key, value) => {
  console.log(`Scratchpad: ${String(key)} =`, value);
});

// Subscribe to all changes
const unsub2 = pad.onChange((key, value) => {
  console.log(`Changed: ${key} =`, value);
});

Scratchpad API

MethodReturnsDescription
get(key)TRead a value
set(key, value)voidWrite a value
has(key)booleanCheck if key exists
delete(key)voidRemove a key
update(values)voidMerge partial values into scratchpad
getAll()TRead all values
subscribe(keys, cb)() => voidSubscribe to specific key changes
onChange(cb)() => voidSubscribe to all changes
reset()voidReset to initial values

Timeline Integration

Scratchpad mutations emit scratchpad_update events on the debug timeline with the key and new value.


Reactive Agent Triggering

Combine derivations with derivedConstraint to trigger agent runs when derived state changes:

import { derivedConstraint } from '@directive-run/ai';

const orchestrator = createMultiAgentOrchestrator({
  runner,
  agents: {
    researcher: { agent: researcher },
    writer: { agent: writer },
    optimizer: { agent: optimizer },
  },

  derive: {
    totalCost: (snapshot) => {
      let sum = 0;
      for (const [, s] of Object.entries(snapshot.agents)) {
        sum += s.totalTokens ?? 0;
      }

      return sum * 0.00001;  // Estimated cost
    },
  },

  constraints: {
    costAlert: derivedConstraint(
      'totalCost',
      (value) => (value as number) > 0.50,
      { agent: 'optimizer', input: 'Reduce token usage', priority: 100 }
    ),
  },
});

When totalCost crosses $0.50, the optimizer agent is automatically triggered.


Next Steps

Previous
Communication

We care about your data. We'll never share your email.

Powered by Directive. This signup uses a Directive module with facts, derivations, constraints, and resolvers – zero useState, zero useEffect. Read how it works

Directive - Constraint-Driven State Management for TypeScript