Skip to main content
The introduction promised that agentic systems are built from familiar programming primitives. Part 1 explored those primitives individually—context, memory, agency, reasoning, coordination, artifacts, autonomy, evaluation, feedback, and learning. The rest of this chapter combines these elements into a complete working system. A virtual office is a team of software agents that work like a small company: tasks come in, a coordinator distributes them to appropriate workers, workers execute tasks and report results, a human manager provides oversight and handles escalations. This pattern applies to many problems where work is distributed across roles and tracked in a queue, such as content production, research operations, development workflows, and support systems. We build a virtual office that routes tasks through a coordinator, specialized workers, and a shared task board. The virtual office uses all ten elements from Part 1 in a single multi-agent system.

11.1 The System We’re Building

Before diving into code, let’s understand the architecture. The virtual office has four types of components: The Coordinator is the system’s brain. It receives incoming tasks, classifies them, assigns them to appropriate workers, monitors progress, handles stuck tasks, and escalates when necessary. The coordinator maintains the task board and makes routing decisions. Workers are specialized agents that execute specific types of tasks. A code worker writes and debugs code. A research worker gathers information and synthesizes findings. A writing worker produces documents and communications. Each worker has focused capabilities, relevant context, and appropriate tools. The Task Board is a shared artifact—a kanban-style board where tasks move through columns (backlog, in-progress, review, done). The coordinator and workers interact with the board to claim tasks, update status, and record results. The board makes system state visible to everyone, including the human manager. The Human Manager provides oversight. They can add tasks, approve high-stakes actions, review completed work, provide feedback, and intervene when things go wrong. The system is designed to operate autonomously but remain controllable.
┌─────────────────────────────────────────────────────────────────────────┐
│                           Human Manager                                  │
│                   (adds tasks, approves, reviews, intervenes)            │
└─────────────────────────────────┬───────────────────────────────────────┘

┌─────────────────────────────────▼───────────────────────────────────────┐
│                           Coordinator                                    │
│              (classifies, routes, monitors, escalates)                   │
└──────┬─────────────────────────┬────────────────────────┬───────────────┘
       │                         │                        │
┌──────▼──────┐          ┌───────▼──────┐         ┌──────▼──────┐
│   Code      │          │   Research   │         │   Writing   │
│   Worker    │          │   Worker     │         │   Worker    │
│             │          │              │         │             │
│  (writes,   │          │  (searches,  │         │  (drafts,   │
│   tests,    │          │   analyzes,  │         │   edits,    │
│   debugs)   │          │   synthesizes)│        │   polishes) │
└──────┬──────┘          └───────┬──────┘         └──────┬──────┘
       │                         │                        │
       └─────────────────────────┼────────────────────────┘

                    ┌────────────▼────────────┐
                    │       Task Board        │
                    │       (artifact)        │
                    │                         │
                    │  Backlog → In Progress  │
                    │     → Review → Done     │
                    └─────────────────────────┘
The information flow is straightforward: tasks enter through the human manager or external triggers, the coordinator routes them to workers, workers execute and update the task board, the coordinator monitors and handles completion, and the human manager reviews results and provides feedback. This flow manifests all ten elements from Part 1.

11.2 The Task Board Artifact

Every multi-agent system needs a coordination mechanism. The task board serves this role—a shared artifact that makes work visible and provides operations for managing it.
// types.ts
interface Task {
  id: string;
  title: string;
  description: string;
  type: 'code' | 'research' | 'writing' | 'general';
  priority: 'high' | 'medium' | 'low';
  status: 'backlog' | 'in-progress' | 'review' | 'done' | 'blocked';
  assignedTo: string | null;
  createdAt: number;
  startedAt: number | null;
  completedAt: number | null;
  result: string | null;
  feedback: string | null;
  attempts: number;
  metadata: Record<string, any>;
}

interface TaskBoard {
  tasks: Task[];
  history: TaskEvent[];
}

interface TaskEvent {
  timestamp: number;
  taskId: string;
  event: 'created' | 'assigned' | 'started' | 'completed' | 'blocked' | 'reviewed' | 'feedback';
  actor: string;
  details: string;
}
The task board stores tasks and enforces workflow rules through its methods. The task board exposes methods like addTask, assignTask, and completeTask that enforce valid state transitions (for example, only in-progress tasks can move to review). The task board encapsulates workflow rules in one place: instead of scattering status checks across the system, you call its methods to enforce valid state transitions. Tasks can only move forward through certain transitions. Assignment requires the task to be in backlog. Completion requires it to be in-progress with a result. These constraints are enforced by the artifact’s methods, not by code scattered throughout the system.
// taskboard.ts
export default class TaskBoardSystem extends AgenticSystem {
  @field board: TaskBoard = { tasks: [], history: [] };

  // ===== Task Lifecycle Operations =====

  @action()
  addTask(task: Omit<Task, 'id' | 'createdAt' | 'attempts' | 'status'>): string {
    const id = crypto.randomUUID();
    const newTask: Task = {
      ...task,
      id,
      status: 'backlog',
      createdAt: Date.now(),
      startedAt: null,
      completedAt: null,
      result: null,
      feedback: null,
      attempts: 0
    };

    this.board.tasks.push(newTask);
    this.recordEvent(id, 'created', 'manager', task.title);

    return id;
  }

  assignTask(taskId: string, workerId: string): boolean {
    const task = this.getTask(taskId);
    if (!task || task.status !== 'backlog') {
      return false; // Can only assign from backlog
    }

    task.assignedTo = workerId;
    task.status = 'in-progress';
    task.startedAt = Date.now();
    task.attempts += 1;

    this.recordEvent(taskId, 'assigned', 'coordinator', `Assigned to ${workerId}`);
    return true;
  }

  completeTask(taskId: string, result: string): boolean {
    const task = this.getTask(taskId);
    if (!task || task.status !== 'in-progress') {
      return false; // Can only complete in-progress tasks
    }

    task.status = 'review';
    task.result = result;
    task.completedAt = Date.now();

    this.recordEvent(taskId, 'completed', task.assignedTo || 'unknown', 'Task completed');
    return true;
  }

  blockTask(taskId: string, reason: string): boolean {
    const task = this.getTask(taskId);
    if (!task || task.status !== 'in-progress') {
      return false;
    }

    task.status = 'blocked';
    task.metadata.blockReason = reason;

    this.recordEvent(taskId, 'blocked', task.assignedTo || 'unknown', reason);
    return true;
  }

  @action()
  approveTask(taskId: string): boolean {
    const task = this.getTask(taskId);
    if (!task || task.status !== 'review') {
      return false;
    }

    task.status = 'done';
    this.recordEvent(taskId, 'reviewed', 'manager', 'Approved');
    return true;
  }

  @action()
  rejectTask(taskId: string, feedback: string): boolean {
    const task = this.getTask(taskId);
    if (!task || task.status !== 'review') {
      return false;
    }

    task.status = 'backlog'; // Back to backlog for retry
    task.feedback = feedback;
    task.assignedTo = null;

    this.recordEvent(taskId, 'feedback', 'manager', feedback);
    return true;
  }

  // ===== Query Operations =====

  getBacklogTasks(): Task[] {
    return this.board.tasks
      .filter(t => t.status === 'backlog')
      .sort((a, b) => {
        // Priority order: high > medium > low
        const priorityOrder = { high: 0, medium: 1, low: 2 };
        return priorityOrder[a.priority] - priorityOrder[b.priority];
      });
  }

  getTasksForWorker(workerId: string): Task[] {
    return this.board.tasks.filter(
      t => t.assignedTo === workerId && t.status === 'in-progress'
    );
  }

  getBlockedTasks(): Task[] {
    return this.board.tasks.filter(t => t.status === 'blocked');
  }

  getTasksInReview(): Task[] {
    return this.board.tasks.filter(t => t.status === 'review');
  }

  getCompletedTasks(since: number): Task[] {
    return this.board.tasks.filter(
      t => t.status === 'done' && t.completedAt && t.completedAt > since
    );
  }

  // ===== Helpers =====

  private getTask(id: string): Task | undefined {
    return this.board.tasks.find(t => t.id === id);
  }

  private recordEvent(taskId: string, event: TaskEvent['event'], actor: string, details: string) {
    this.board.history.push({
      timestamp: Date.now(),
      taskId,
      event,
      actor,
      details
    });
  }
}
The task board demonstrates several elements working together. It’s an artifact with semantic structure and typed operations. It provides memory that persists task state across sessions. The event history supports evaluation by tracking what happened when. The status transitions encode workflow rules that support coordination between components.

11.3 The Coordinator

The coordinator decides which worker should handle each task, monitors progress, and escalates problems—centralizing routing logic that would otherwise be duplicated across workers.
// coordinator.ts
export default class Coordinator extends AgenticSystem {
  @field taskBoard: TaskBoardSystem;
  @field workers: Map<string, WorkerInfo> = new Map();
  @field status: stream<string>('');
  @field pendingEscalation: Escalation | null = null;

  // ===== Worker Management =====

  registerWorker(id: string, info: WorkerInfo) {
    this.workers.set(id, info);
    this.status.append(`Registered worker: ${id} (${info.specialties.join(', ')})\n`);
  }

  // ===== Core Coordination Loop =====

  @schedule('every 5 minutes')
  async coordinationCycle() {
    this.status.append(`\n--- Coordination Cycle ${new Date().toISOString()} ---\n`);

    // 1. Check for blocked tasks that need escalation
    await this.handleBlockedTasks();

    // 2. Check for tasks that have been in-progress too long
    await this.checkStuckTasks();

    // 3. Assign backlog tasks to available workers
    await this.assignBacklogTasks();

    // 4. Log cycle metrics
    this.logCycleMetrics();
  }

  async handleBlockedTasks() {
    const blocked = this.taskBoard.getBlockedTasks();

    for (const task of blocked) {
      this.status.append(`Handling blocked task: ${task.title}\n`);

      // Try to unblock with different worker
      if (task.attempts < 3) {
        const alternateWorker = this.findAlternateWorker(task);
        if (alternateWorker) {
          task.status = 'backlog';
          task.metadata.previouslyBlockedBy = task.assignedTo;
          task.assignedTo = null;
          this.status.append(`  Re-queuing for alternate worker\n`);
          continue;
        }
      }

      // Escalate to human
      this.status.append(`  Escalating to manager\n`);
      await this.escalateToHuman(task, task.metadata.blockReason);
    }
  }

  async checkStuckTasks() {
    const inProgress = this.taskBoard.board.tasks.filter(t => t.status === 'in-progress');
    const now = Date.now();
    const stuckThreshold = 30 * 60 * 1000; // 30 minutes

    for (const task of inProgress) {
      if (task.startedAt && now - task.startedAt > stuckThreshold) {
        this.status.append(`Task stuck: ${task.title} (${Math.round((now - task.startedAt) / 60000)}min)\n`);

        // Ping the worker first
        const worker = this.workers.get(task.assignedTo || '');
        if (worker) {
          // In a real system, this would check worker status
          this.status.append(`  Checking worker ${task.assignedTo} status\n`);
        }

        // If still stuck after 1 hour, escalate
        if (now - task.startedAt > 60 * 60 * 1000) {
          await this.escalateToHuman(task, 'Task exceeded time limit');
        }
      }
    }
  }

  async assignBacklogTasks() {
    const backlog = this.taskBoard.getBacklogTasks();

    for (const task of backlog) {
      const worker = this.selectWorker(task);

      if (worker) {
        const assigned = this.taskBoard.assignTask(task.id, worker.id);
        if (assigned) {
          this.status.append(`Assigned "${task.title}" to ${worker.id}\n`);
          await this.notifyWorker(worker.id, task);
        }
      } else {
        this.status.append(`No available worker for "${task.title}" (type: ${task.type})\n`);
      }
    }
  }

  // ===== Worker Selection (Routing) =====

  selectWorker(task: Task): WorkerInfo | null {
    // Find workers that can handle this task type
    const capable = Array.from(this.workers.values())
      .filter(w => w.specialties.includes(task.type) || w.specialties.includes('general'));

    if (capable.length === 0) return null;

    // Find available workers (not overloaded)
    const available = capable.filter(w => {
      const currentTasks = this.taskBoard.getTasksForWorker(w.id);
      return currentTasks.length < w.maxConcurrent;
    });

    if (available.length === 0) return null;

    // Prefer specialist over generalist
    const specialists = available.filter(w => w.specialties.includes(task.type));
    const candidates = specialists.length > 0 ? specialists : available;

    // If task has feedback from previous attempt, prefer different worker
    if (task.metadata.previouslyBlockedBy) {
      const different = candidates.filter(w => w.id !== task.metadata.previouslyBlockedBy);
      if (different.length > 0) {
        return different[0];
      }
    }

    // Return worker with best performance on this task type
    return this.selectByPerformance(candidates, task.type);
  }

  selectByPerformance(workers: WorkerInfo[], taskType: string): WorkerInfo {
    // Simple: return worker with highest success rate for this task type
    return workers.reduce((best, current) => {
      const bestRate = best.metrics[taskType]?.successRate || 0.5;
      const currentRate = current.metrics[taskType]?.successRate || 0.5;
      return currentRate > bestRate ? current : best;
    });
  }

  // ===== Human Integration =====

  async escalateToHuman(task: Task, reason: string) {
    this.pendingEscalation = {
      taskId: task.id,
      task,
      reason,
      timestamp: Date.now(),
      options: ['reassign', 'modify', 'cancel']
    };

    this.status.append(`ESCALATION: ${task.title}\n  Reason: ${reason}\n  Awaiting manager decision\n`);
  }

  @action()
  async handleEscalation(decision: 'reassign' | 'modify' | 'cancel', details?: string) {
    if (!this.pendingEscalation) return;

    const task = this.taskBoard.board.tasks.find(t => t.id === this.pendingEscalation!.taskId);
    if (!task) return;

    switch (decision) {
      case 'reassign':
        task.status = 'backlog';
        task.assignedTo = null;
        task.attempts = 0; // Reset attempts
        this.status.append(`Manager: Reassigning "${task.title}"\n`);
        break;

      case 'modify':
        task.status = 'backlog';
        task.assignedTo = null;
        if (details) {
          task.description = details;
        }
        this.status.append(`Manager: Modified and re-queued "${task.title}"\n`);
        break;

      case 'cancel':
        task.status = 'done';
        task.result = 'Cancelled by manager';
        task.metadata.cancelled = true;
        this.status.append(`Manager: Cancelled "${task.title}"\n`);
        break;
    }

    this.pendingEscalation = null;
  }

  // ===== Metrics =====

  logCycleMetrics() {
    const tasks = this.taskBoard.board.tasks;
    const metrics = {
      total: tasks.length,
      backlog: tasks.filter(t => t.status === 'backlog').length,
      inProgress: tasks.filter(t => t.status === 'in-progress').length,
      review: tasks.filter(t => t.status === 'review').length,
      done: tasks.filter(t => t.status === 'done').length,
      blocked: tasks.filter(t => t.status === 'blocked').length
    };

    this.status.append(`Metrics: ${JSON.stringify(metrics)}\n`);
  }

  async notifyWorker(workerId: string, task: Task) {
    // In a real system, this would trigger the worker to start
    // For now, we rely on workers checking their assigned tasks
  }

  private findAlternateWorker(task: Task): WorkerInfo | null {
    const capable = Array.from(this.workers.values())
      .filter(w => w.specialties.includes(task.type) || w.specialties.includes('general'));

    const alternatives = capable.filter(w => w.id !== task.assignedTo);
    return alternatives[0] || null;
  }
}

interface WorkerInfo {
  id: string;
  specialties: string[];
  maxConcurrent: number;
  metrics: Record<string, { successRate: number; avgDuration: number }>;
}

interface Escalation {
  taskId: string;
  task: Task;
  reason: string;
  timestamp: number;
  options: string[];
}
The coordinator demonstrates coordination patterns—routing tasks to appropriate workers, monitoring progress, handling exceptions. It implements autonomy through scheduled coordination cycles that run without human prompting. The escalation mechanism provides human-in-the-loop integration, pausing for human input when automated handling fails. The coordinator never executes tasks itself; it only routes work and handles exceptions. Its job is meta-work: ensuring tasks flow correctly, workers are utilized effectively, and problems get handled. This separation of concerns keeps each component focused. To make performance-aware routing useful, you should update WorkerInfo.metrics[taskType] whenever tasks complete—for example, incrementing success and failure counts and recomputing successRate and avgDuration. The default 0.5 success rate acts as a neutral prior when there is no historical data for a worker and task type.

11.4 The Workers

Workers isolate domain-specific prompts, tools, and reasoning patterns from routing: a code worker focuses on implementing and validating code, while the coordinator just hands it the right tasks.

The Code Worker

// code-worker.ts
export default class CodeWorker extends AgenticSystem {
  @field workerId = 'code-worker-1';
  @field currentTask: Task | null = null;
  @field thinking: stream<string>('');
  @field taskBoard: TaskBoardSystem;

  systemPrompt = `You are an expert software developer.
    You write clean, well-tested code.
    When given a coding task:
    1. Understand the requirements
    2. Plan your implementation
    3. Write the code
    4. Verify it works
    5. Report your result

    If you encounter blockers you cannot resolve, report them clearly.`;

  @schedule('every 2 minutes')
  async workCycle() {
    // Check for assigned tasks
    const tasks = this.taskBoard.getTasksForWorker(this.workerId);
    if (tasks.length === 0) return;

    // Work on first task
    this.currentTask = tasks[0];
    await this.executeTask(this.currentTask);
  }

  async executeTask(task: Task) {
    this.thinking.append(`Starting task: ${task.title}\n`);

    try {
      // Plan the implementation
      const plan = await this.planImplementation(task);
      this.thinking.append(`Plan:\n${plan}\n`);

      // Execute with feedback loop
      const result = await this.implementWithFeedback(task, plan);

      // Complete the task
      this.taskBoard.completeTask(task.id, result);
      this.thinking.append(`Task completed successfully\n`);

    } catch (error: any) {
      this.thinking.append(`Error: ${error.message}\n`);
      this.taskBoard.blockTask(task.id, error.message);
    }

    this.currentTask = null;
  }

  async planImplementation(task: Task): Promise<string> {
    const context = this.buildContext(task);

    const response = await llm.complete([
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: `Plan the implementation for this task:

        Title: ${task.title}
        Description: ${task.description}

        ${task.feedback ? `Previous attempt feedback: ${task.feedback}` : ''}

        Context:
        ${context}

        Create a step-by-step plan. Be specific about what code needs to be written.` }
    ]);

    // Assume llm.complete returns a string; if it returns an object, use response.text instead
    return typeof response === 'string' ? response : (response.text as string);
  }

  async implementWithFeedback(task: Task, plan: string): Promise<string> {
    // Initial implementation
    let code = await this.generateCode(task, plan);
    this.thinking.append(`Generated initial code\n`);

    // Verification loop
    for (let attempt = 0; attempt < 3; attempt++) {
      const verification = await this.verifyCode(code, task);

      if (verification.passed) {
        this.thinking.append(`Code verified successfully\n`);
        return code;
      }

      this.thinking.append(`Verification failed: ${verification.errors}\n`);
      this.thinking.append(`Attempting fix (attempt ${attempt + 1}/3)\n`);

      // Fix based on errors
      code = await this.fixCode(code, verification.errors!, task);
    }

    throw new Error('Could not produce working code after 3 attempts');
  }

  async generateCode(task: Task, plan: string): Promise<string> {
    const response = await llm.complete([
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: `Implement the following plan:

        Task: ${task.title}
        Description: ${task.description}

        Plan:
        ${plan}

        Write the complete code implementation.` }
    ]);

    return typeof response === 'string' ? response : (response.text as string);
  }

  async verifyCode(code: string, task: Task): Promise<{ passed: boolean; errors?: string }> {
    // In a real system, this would run tests, type checking, etc.
    const verification = await llm.complete([
      { role: 'system', content: 'You are a code reviewer. Check this code for correctness, completeness, and potential issues.' },
      { role: 'user', content: `Task: ${task.description}\n\nCode:\n${code}\n\nList any issues or confirm the code is correct.` }
    ]);

    const text = typeof verification === 'string' ? verification : (verification.text as string);

    const lower = text.toLowerCase();
    const hasIssues = lower.includes('issue') ||
                      lower.includes('error') ||
                      lower.includes('problem');

    return hasIssues
      ? { passed: false, errors: text }
      : { passed: true };
  }

  async fixCode(code: string, errors: string, task: Task): Promise<string> {
    const response = await llm.complete([
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: `Fix the following code based on the errors:

        Original task: ${task.description}

        Code:
        ${code}

        Errors found:
        ${errors}

        Provide the corrected code.` }
    ]);

    return typeof response === 'string' ? response : (response.text as string);
  }

  buildContext(task: Task): string {
    // In a real system, this would include relevant code files, documentation, etc.
    return `Task context for ${task.title}`;
  }
}
The code worker uses a plan-then-implement loop so you can inspect and adjust the plan separately from the code generation prompt. It uses feedback loops—generating code, verifying, fixing based on errors. The agency manifests in its ability to produce artifacts (code) that have effects. Its scheduled work cycle provides autonomy, checking for and executing tasks without external prompting.

The Research Worker

// research-worker.ts
export default class ResearchWorker extends AgenticSystem {
  @field workerId = 'research-worker-1';
  @field currentTask: Task | null = null;
  @field thinking: stream<string>('');
  @field findings: stream<Finding[]>([]);
  @field taskBoard: TaskBoardSystem;
  @field knowledge: VectorStore<Knowledge>;

  systemPrompt = `You are an expert researcher.
    You gather information from multiple sources, analyze it critically,
    and synthesize clear findings.

    When researching:
    1. Identify the key questions to answer
    2. Search for relevant information
    3. Evaluate source quality
    4. Synthesize findings
    5. Note gaps or uncertainties`;

  @schedule('every 2 minutes')
  async workCycle() {
    const tasks = this.taskBoard.getTasksForWorker(this.workerId);
    if (tasks.length === 0) return;

    this.currentTask = tasks[0];
    await this.executeResearch(this.currentTask);
  }

  async executeResearch(task: Task) {
    this.thinking.append(`Starting research: ${task.title}\n`);
    this.findings.reset([]);

    try {
      // Decompose into research questions
      const questions = await this.decomposeToQuestions(task);
      this.thinking.append(`Research questions:\n${questions.map((q, i) => `${i + 1}. ${q}`).join('\n')}\n`);

      // Research each question
      const allFindings: Finding[] = [];
      for (const question of questions) {
        this.thinking.append(`\nResearching: ${question}\n`);
        const questionFindings = await this.researchQuestion(question);
        allFindings.push(...questionFindings);

        // Update streaming findings
        this.findings.set([...allFindings]);
      }

      // Score and filter findings
      const scoredFindings = await this.scoreFindings(allFindings, task);
      const topFindings = scoredFindings
        .sort((a, b) => b.score - a.score)
        .slice(0, 10);

      this.findings.set(topFindings);

      // Synthesize into result
      const synthesis = await this.synthesize(topFindings, task);
      this.thinking.append(`\nSynthesis complete\n`);

      // Store valuable findings for future use
      await this.storeKnowledge(topFindings);

      // Complete task
      this.taskBoard.completeTask(task.id, synthesis);

    } catch (error: any) {
      this.thinking.append(`Error: ${error.message}\n`);
      this.taskBoard.blockTask(task.id, error.message);
    }

    this.currentTask = null;
  }

  async decomposeToQuestions(task: Task): Promise<string[]> {
    const response = await llm.complete([
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: `Break this research task into 3-5 specific questions:

        Task: ${task.title}
        Description: ${task.description}

        List the questions, one per line.` }
    ]);

    const text = typeof response === 'string' ? response : (response.text as string);
    return text.split('\n').filter(q => q.trim().length > 0);
  }

  async researchQuestion(question: string): Promise<Finding[]> {
    // Check existing knowledge first
    const existingKnowledge = await this.knowledge.search(question, 3);

    // Search for new information
    const searchResults = await this.search(question);

    // Combine and structure as findings
    const findings: Finding[] = [];

    for (const result of searchResults) {
      findings.push({
        content: result.content,
        source: result.source,
        relevance: 0, // Will be scored later
        score: 0
      });
    }

    // Add relevant existing knowledge
    for (const knowledge of existingKnowledge) {
      // search(question, 3) returns the top 3 items as
      // { item: Knowledge; similarity: number }[], similarity ∈ [0, 1]
      if (knowledge.similarity > 0.7) {
        findings.push({
          content: knowledge.item.content,
          source: 'knowledge-base',
          relevance: knowledge.similarity,
          score: 0
        });
      }
    }

    return findings;
  }

  async scoreFindings(findings: Finding[], task: Task): Promise<Finding[]> {
    // Score each finding for relevance and quality
    const scored = await Promise.all(findings.map(async finding => {
      const scoreResponse = await llm.complete([
        { role: 'system', content: 'Score this finding from 0-10 for relevance to the task and quality of information. Return only a number.' },
        { role: 'user', content: `Task: ${task.description}\n\nFinding: ${finding.content}\n\nReturn just a number 0-10.` }
      ]);

      const text = typeof scoreResponse === 'string' ? scoreResponse : (scoreResponse.text as string);
      let score = parseInt(text, 10);
      if (Number.isNaN(score)) {
        score = 5;
      }
      // Clamp between 0 and 10
      score = Math.max(0, Math.min(10, score));

      return {
        ...finding,
        score
      };
    }));

    return scored;
  }

  async synthesize(findings: Finding[], task: Task): Promise<string> {
    const response = await llm.complete([
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: `Synthesize these findings into a coherent research result:

        Task: ${task.title}
        Description: ${task.description}

        Findings:
        ${findings.map(f => `- ${f.content} (Source: ${f.source})`).join('\n')}

        Provide a clear synthesis addressing the original task. Note any gaps or uncertainties.` }
    ]);

    return typeof response === 'string' ? response : (response.text as string);
  }

  async storeKnowledge(findings: Finding[]) {
    for (const finding of findings) {
      if (finding.score >= 7) { // Only store high-quality findings
        await this.knowledge.insert({
          content: finding.content,
          source: finding.source,
          timestamp: Date.now()
        });
      }
    }
  }

  @tool({ description: 'Search for information on a topic' })
  async search(query: string): Promise<{ content: string; source: string }[]> {
    // In a real system, this would search the web, databases, etc.
    // For illustration, we return mock results
    return [
      { content: `Information about ${query}`, source: 'web-search' }
    ];
  }
}

interface Finding {
  content: string;
  source: string;
  relevance: number;
  score: number;
}

interface Knowledge {
  content: string;
  source: string;
  timestamp: number;
}

// Example VectorStore interface used by the knowledge field:
// interface VectorStore<T> {
//   search(query: string, k: number): Promise<{ item: T; similarity: number }[]>;
//   insert(item: T): Promise<void>;
// }
The research worker adds patterns you need for real research tasks: decomposing broad prompts into questions, ranking findings, and persisting useful results. It uses decomposition reasoning to break research tasks into questions. The signal boosting pattern appears in scoring and filtering findings. Memory accumulates through the knowledge store, with high-quality findings persisted for future use—this is learning in action. The streaming findings provide real-time visibility into progress. Knowledge extracted from research tasks persists for future use: subsequent research queries call knowledge.search before external search to reuse prior findings.

The Writing Worker

// writing-worker.ts
export default class WritingWorker extends AgenticSystem {
  @field workerId = 'writing-worker-1';
  @field currentTask: Task | null = null;
  @field thinking: stream<string>('');
  @field draft: stream<string>('');
  @field taskBoard: TaskBoardSystem;

  systemPrompt = `You are an expert writer.
    You produce clear, well-structured content appropriate for the audience.

    When writing:
    1. Understand the purpose and audience
    2. Create an outline
    3. Draft content
    4. Review and refine
    5. Polish final output`;

  @schedule('every 2 minutes')
  async workCycle() {
    const tasks = this.taskBoard.getTasksForWorker(this.workerId);
    if (tasks.length === 0) return;

    this.currentTask = tasks[0];
    await this.executeWriting(this.currentTask);
  }

  async executeWriting(task: Task) {
    this.thinking.append(`Starting writing task: ${task.title}\n`);
    this.draft.reset('');

    try {
      // Plan the document
      const outline = await this.createOutline(task);
      this.thinking.append(`Outline:\n${outline}\n`);

      // Draft with streaming
      await this.writeDraft(task, outline);

      // Self-critique
      const critique = await this.selfCritique(this.draft.current(), task);
      this.thinking.append(`Critique:\n${critique}\n`);

      // Revise based on critique
      const revised = await this.revise(this.draft.current(), critique, task);
      this.draft.set(revised);

      // Complete task
      this.taskBoard.completeTask(task.id, revised);
      this.thinking.append(`Writing complete\n`);

    } catch (error: any) {
      this.thinking.append(`Error: ${error.message}\n`);
      this.taskBoard.blockTask(task.id, error.message);
    }

    this.currentTask = null;
  }

  async createOutline(task: Task): Promise<string> {
    const response = await llm.complete([
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: `Create an outline for this writing task:

        Title: ${task.title}
        Description: ${task.description}

        ${task.feedback ? `Previous feedback to address: ${task.feedback}` : ''}

        Provide a structured outline with main sections and key points.` }
    ]);

    return typeof response === 'string' ? response : (response.text as string);
  }

  async writeDraft(task: Task, outline: string) {
    // Stream the draft as it's written
    const streamResult = llm.stream([
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: `Write the full content based on this outline:

        Task: ${task.title}
        Description: ${task.description}

        Outline:
        ${outline}

        Write clear, engaging prose. Follow the outline structure.` }
    ]);

    // llm.stream returns an async iterator of string chunks (partial model output).
    // We build the draft incrementally by appending each chunk.
    for await (const chunk of streamResult as AsyncIterable<string>) {
      this.draft.append(chunk);
    }
  }

  async selfCritique(draft: string, task: Task): Promise<string> {
    const response = await llm.complete([
      { role: 'system', content: 'You are a critical editor. Identify weaknesses and suggest improvements.' },
      { role: 'user', content: `Review this draft:

        Original task: ${task.description}

        Draft:
        ${draft}

        What could be improved? Be specific.` }
    ]);

    return typeof response === 'string' ? response : (response.text as string);
  }

  async revise(draft: string, critique: string, task: Task): Promise<string> {
    const response = await llm.complete([
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: `Revise this draft based on the feedback:

        Original task: ${task.description}

        Current draft:
        ${draft}

        Feedback to address:
        ${critique}

        Provide the improved version.` }
    ]);

    return typeof response === 'string' ? response : (response.text as string);
  }
}
The writing worker structures writing as draft → critique → revision, so you can plug in different editors or constraints at the critique step without changing the drafting logic. The streaming draft provides visibility into work in progress. The self-critique demonstrates the generate-evaluate pattern from Chapter 4, with separate passes for creation and evaluation.

11.5 Human Manager Interface

The human manager interacts with the system through actions that add tasks, provide feedback, and handle escalations.
// manager-interface.ts
export default class ManagerInterface extends AgenticSystem {
  @field coordinator: Coordinator;
  @field taskBoard: TaskBoardSystem;

  // Status views
  @field dashboard: stream<DashboardState>({ tasks: [], workers: [], alerts: [] });

  // ===== Task Management =====

  @action()
  async addTask(
    title: string,
    description: string,
    type: 'code' | 'research' | 'writing' | 'general',
    priority: 'high' | 'medium' | 'low' = 'medium'
  ): Promise<string> {
    const taskId = this.taskBoard.addTask({
      title,
      description,
      type,
      priority,
      assignedTo: null,
      startedAt: null,
      completedAt: null,
      result: null,
      feedback: null,
      metadata: {}
    });

    await this.updateDashboard();
    return taskId;
  }

  @action()
  async reviewTask(taskId: string, approved: boolean, feedback?: string) {
    if (approved) {
      this.taskBoard.approveTask(taskId);
    } else {
      this.taskBoard.rejectTask(taskId, feedback || 'Needs revision');
    }

    await this.updateDashboard();
  }

  @action()
  async handleEscalation(decision: 'reassign' | 'modify' | 'cancel', details?: string) {
    await this.coordinator.handleEscalation(decision, details);
    await this.updateDashboard();
  }

  // ===== Visibility =====

  @schedule('every 1 minute')
  async updateDashboard() {
    const tasks = this.taskBoard.board.tasks;
    const workers = Array.from(this.coordinator.workers.values());

    const alerts: Alert[] = [];

    // Alert for tasks in review
    const inReview = tasks.filter(t => t.status === 'review');
    if (inReview.length > 0) {
      alerts.push({
        type: 'review',
        message: `${inReview.length} task(s) awaiting review`,
        tasks: inReview.map(t => t.id)
      });
    }

    // Alert for escalations
    if (this.coordinator.pendingEscalation) {
      alerts.push({
        type: 'escalation',
        message: `Escalation: ${this.coordinator.pendingEscalation.reason}`,
        tasks: [this.coordinator.pendingEscalation.taskId]
      });
    }

    // Alert for blocked tasks
    const blocked = tasks.filter(t => t.status === 'blocked');
    if (blocked.length > 0) {
      alerts.push({
        type: 'blocked',
        message: `${blocked.length} task(s) blocked`,
        tasks: blocked.map(t => t.id)
      });
    }

    this.dashboard.set({
      tasks: tasks.map(t => ({
        id: t.id,
        title: t.title,
        status: t.status,
        assignedTo: t.assignedTo,
        priority: t.priority
      })),
      workers: workers.map(w => ({
        id: w.id,
        specialties: w.specialties,
        currentTasks: this.taskBoard.getTasksForWorker(w.id).length
      })),
      alerts
    });
  }

  // ===== Performance Metrics =====

  @action()
  getPerformanceReport(period: 'day' | 'week' | 'month'): PerformanceReport {
    const since = period === 'day' ? Date.now() - 86400000
                : period === 'week' ? Date.now() - 604800000
                : Date.now() - 2592000000;

    const completed = this.taskBoard.getCompletedTasks(since);
    const byType = this.groupBy(completed, 'type');
    const byWorker = this.groupBy(completed, 'assignedTo');

    const avgDuration = completed.length > 0
      ? completed.reduce((sum, t) => sum + (t.completedAt! - t.startedAt!), 0) / completed.length
      : 0;

    return {
      period,
      totalCompleted: completed.length,
      byType: Object.fromEntries(
        Object.entries(byType).map(([type, tasks]) => [type, tasks.length])
      ),
      byWorker: Object.fromEntries(
        Object.entries(byWorker).map(([worker, tasks]) => [
          worker || 'unassigned',
          {
            completed: tasks.length,
            avgDuration: tasks.reduce((sum, t) =>
              sum + (t.completedAt! - t.startedAt!), 0) / tasks.length
          }
        ])
      ),
      avgDurationMs: avgDuration
    };
  }

  private groupBy<T>(items: T[], key: keyof T): Record<string, T[]> {
    return items.reduce((groups, item) => {
      const value = String(item[key] || 'unknown');
      groups[value] = groups[value] || [];
      groups[value].push(item);
      return groups;
    }, {} as Record<string, T[]>);
  }
}

interface DashboardState {
  tasks: { id: string; title: string; status: string; assignedTo: string | null; priority: string }[];
  workers: { id: string; specialties: string[]; currentTasks: number }[];
  alerts: Alert[];
}

interface Alert {
  type: 'review' | 'escalation' | 'blocked';
  message: string;
  tasks: string[];
}

interface PerformanceReport {
  period: string;
  totalCompleted: number;
  byType: Record<string, number>;
  byWorker: Record<string, { completed: number; avgDuration: number }>;
  avgDurationMs: number;
}
The manager interface provides observability—the dashboard shows system state, alerts highlight issues needing attention, and dashboard can feed a UI or API endpoint. In production, you might push updates on state changes rather than polling every minute. The action methods give humans control: they can add work, provide feedback, handle escalations. Evaluation appears in performance reports that track what’s getting done and how efficiently.

11.6 Wiring It Together

The complete system connects these components. In a deployment, each component would run as its own Durable Object communicating through Idyllic’s infrastructure; the following class shows how they connect.
// virtual-office.ts
export default class VirtualOffice extends AgenticSystem {
  // Core components
  @field taskBoard: TaskBoardSystem;
  @field coordinator: Coordinator;
  @field manager: ManagerInterface;

  // Workers
  @field codeWorker: CodeWorker;
  @field researchWorker: ResearchWorker;
  @field writingWorker: WritingWorker;

  // System status
  @field status: stream<string>('');

  async initialize() {
    this.status.append('Initializing Virtual Office...\n');

    // Create shared task board
    this.taskBoard = new TaskBoardSystem();

    // Create coordinator with task board reference
    this.coordinator = new Coordinator();
    this.coordinator.taskBoard = this.taskBoard;

    // Create workers with task board reference
    this.codeWorker = new CodeWorker();
    this.codeWorker.taskBoard = this.taskBoard;

    this.researchWorker = new ResearchWorker();
    this.researchWorker.taskBoard = this.taskBoard;

    this.writingWorker = new WritingWorker();
    this.writingWorker.taskBoard = this.taskBoard;

    // Register workers with coordinator
    this.coordinator.registerWorker('code-worker-1', {
      id: 'code-worker-1',
      specialties: ['code'],
      maxConcurrent: 2,
      metrics: {}
    });

    this.coordinator.registerWorker('research-worker-1', {
      id: 'research-worker-1',
      specialties: ['research'],
      maxConcurrent: 3,
      metrics: {}
    });

    this.coordinator.registerWorker('writing-worker-1', {
      id: 'writing-worker-1',
      specialties: ['writing'],
      maxConcurrent: 2,
      metrics: {}
    });

    // Create manager interface
    this.manager = new ManagerInterface();
    this.manager.coordinator = this.coordinator;
    this.manager.taskBoard = this.taskBoard;

    this.status.append('Virtual Office ready.\n');
    this.status.append(`Workers: code, research, writing\n`);
    this.status.append(`Awaiting tasks...\n`);
  }

  // ===== Public Interface =====

  @action()
  async addTask(
    title: string,
    description: string,
    type: 'code' | 'research' | 'writing' | 'general',
    priority: 'high' | 'medium' | 'low' = 'medium'
  ): Promise<string> {
    return await this.manager.addTask(title, description, type, priority);
  }

  @action()
  async reviewTask(taskId: string, approved: boolean, feedback?: string) {
    await this.manager.reviewTask(taskId, approved, feedback);
  }

  @action()
  async handleEscalation(decision: 'reassign' | 'modify' | 'cancel', details?: string) {
    await this.manager.handleEscalation(decision, details);
  }

  @action()
  getStatus(): string {
    return this.status.current();
  }

  @action()
  getDashboard(): DashboardState {
    return this.manager.dashboard.current();
  }

  @action()
  getPerformanceReport(period: 'day' | 'week' | 'month'): PerformanceReport {
    return this.manager.getPerformanceReport(period);
  }
}

11.7 Elements in Action

The following walkthrough shows how the ten elements appear when the system processes a task. A human manager calls addTask("Implement user authentication", "Add login and logout endpoints with JWT tokens", "code", "high"). The task enters the task board artifact, persisted in memory, status set to backlog. The coordinator’s scheduled cycle runs. It sees the high-priority task in backlog. Reasoning about worker selection considers which workers can handle code tasks, their availability, and performance history. The code worker is selected and the task is assigned—this is coordination through routing. The code worker’s scheduled cycle runs, retrieves its assigned tasks from the board, and starts work on the next one. First, it generates a step-by-step implementation plan via the planning prompt. Then it generates code—agency, producing an artifact with effects. It verifies the code and finds issues—feedback through verification. It fixes the code based on errors and tries again—the iterate loop. After one or more fix-and-verify iterations, the code passes verification. The task moves to review status. The dashboard’s scheduled update notices this and adds an alert—observability. The human manager sees the alert and reviews the work. They might approve (task moves to done) or reject with feedback (task goes back to backlog with the feedback attached for the next attempt). If the code worker had gotten stuck—say, it couldn’t resolve a dependency issue—it would call blockTask with a reason. The coordinator would see the blocked task in its next cycle. After checking if a different approach might work, it would escalate to the human manager. The manager would receive an alert and could decide to reassign, modify the task, or cancel it—human-in-the-loop at work. Over time, performance metrics accumulate. If you update the worker metrics based on completed tasks, the coordinator can prefer workers that have higher success rates on a given task type. You can refine prompts based on failure patterns by analyzing blocked or rejected tasks and updating the worker prompts accordingly. Knowledge extracted from research tasks persists for future use: subsequent research queries call knowledge.search before external search to reuse what the system already knows. The system operates autonomously—scheduled cycles keep work flowing without constant human prompting. But it remains controllable—humans can add tasks, provide feedback, handle escalations, and intervene at any point. This balance between autonomy and control is necessary for production systems, alongside reliability, security, and monitoring concerns covered elsewhere.

11.8 Extending the Pattern

The virtual office pattern adapts to many domains. The same pattern extends to several domains: Content Production Office: Writers, editors, designers, and SEO specialists. Tasks are articles, graphics, and campaigns. The task board tracks content through ideation, drafting, editing, design, and publishing phases. The coordinator balances deadlines against quality. Development Shop: Architects, frontend developers, backend developers, QA engineers. Tasks are features, bugs, and technical debt items. The task board is a sprint board. The coordinator manages dependencies between tasks—some can’t start until others complete. Research Lab: Data collectors, analysts, synthesizers, and report writers. Tasks are research questions that decompose into collection, analysis, and synthesis phases. The knowledge base grows continuously, informing future research. Support Center: Classifiers, handlers by category, escalation specialists, and knowledge maintainers. Tasks are customer inquiries. The coordinator routes by intent and complexity. Successful resolutions feed back into the knowledge base. Here are some variations of the pattern: each variant adjusts the worker types, coordination rules, and artifact structure, but the underlying pattern remains. Tasks flow through a shared artifact, a coordinator manages routing and exceptions, specialized workers execute, humans provide oversight, and the system learns from outcomes.

Key Takeaways

  • A virtual office coordinates specialized workers through a shared task board artifact
  • The coordinator handles routing, monitoring, and escalation without executing tasks itself
  • Workers have focused capabilities, appropriate tools, and domain-specific reasoning
  • Human managers provide oversight through task creation, review, and escalation handling
  • All ten elements work together: context in worker prompts, memory in task state and knowledge stores, agency in task execution, reasoning in planning and decomposition, coordination in routing and handoffs, artifacts in the task board, autonomy in scheduled cycles, evaluation in performance metrics, feedback in verification loops, and learning in accumulated insights

Transition

Chapter 11 demonstrated a complete multi-agent coordination system. Chapter 12: Research System shows a different application—knowledge accumulation and synthesis over extended operation, building a system that gets smarter with every research task it completes.