smithery.ai

mesh-builder

Use when creating, editing, or debugging any mesh — config.yaml, agent prompts, routing, FSM, guardrails, dispatcher, fan-out, ensemble, workspace, lifecycle hooks, parallelism, or permissions. Activate for any work in meshes/ directory or multi-agent workflow design.

First seen Apr 7, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Declared agents claude-code

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 55,202 B
  • docs SUMMARY.md 191 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Mesh Builder

Build meshes (agent workflows) for TX V4.

Core Philosophy: Earn Your Keep

Start with the minimum. Add only what the mesh actually needs.

Every config option added is complexity that can break. The default question for each field is: "Does this mesh fail without it?" If no — leave it out.

Config Options and When They're Warranted

Option Warranted when... Default
dev_mode: true Testing a new mesh end-to-end before committing to full model costs Omit
fsm: Routing depends on computed state/counters/file presence — NOT agent judgment Omit
parallelism: Agents truly run in parallel and need a sync gate Omit
routing_mode: dispatcher Fan-out to N parallel workers is the core mechanic Omit
routing_mode: free Agents self-organize — each sees full roster, decides routing autonomously Omit
routing_mode: static Sequential agent chain — worker exit fires next agent, no messaging Omit
routing_mode: manifest Workflow is a file pipeline — agents produce files that unlock downstream agents Omit
type: persistent / auto_despawn: false Mesh must survive indefinitely (daemon pattern) Omit
continuation: false You explicitly need cold starts for isolation Omit (continuation is default-on)
lifecycle: hooks Quality gates or auto-commits are genuinely required Omit
workspace: Agents need structured file workspace management Omit
checkpoint: / fork_from: Multiple agents need shared prior context Omit
load: Files must be in context before any work starts Omit
ensemble: Same task, multiple perspectives, aggregated output Omit
injectOriginalMessage: Downstream agents truly need the original task Omit
rearmatter: FSM routing depends on self-assessment scores Omit
guardrails: Custom limits differ from system defaults Omit
brain: true Agents need project context they can't get from preloaded files alone Omit
loadclaudemd: false Mesh agents should not inherit project CLAUDE.md instructions true

The minimal working mesh:

mesh: my-mesh
description: "What it does"
agents:
  - name: worker
    model: sonnet
    prompt: worker.md
entry_point: worker

This is complete. Everything else is optional and should be justified.

Common Over-Engineering Patterns to Avoid

  • Orchestrator doing implementation work — add orchestrator: true to enforce routing-only (Read + Write msgs-only). System-level enforcement beats prompt instructions.
  • FSM + orchestrator that handles all routing — pick one. If orchestrator routes, drop FSM.
  • type: persistent on a mesh that runs once — persistent is for daemons only.
  • lifecycle: hooks "just in case" — add when quality gates are actually required.
  • parallelism: on 2 agents — just route them sequentially, parallelism overhead isn't worth it.
  • checkpoint:/fork_from: when agents don't share context — preloading context agents don't need wastes tokens.
  • Ensemble with 1 agent — that's just a regular agent.
  • Working in .ai/tx/msgs/ instead of workspace — agents MUST use {workspace} token for all intermediate files, analysis, outputs. msgs/ is the message queue only.

Workspace Isolation — Critical Design Pattern

The rule: Agents read from .ai/tx/msgs/ (message queue). They write to their intended output location — either project files (src/, lib/, etc.) or a dedicated workspace. .ai/tx/msgs/ is message transport only — never do agent work there.

Two Patterns

Pattern A: Developer Meshes (dev, dev-full, etc.)

  • Agents write directly to project: src/, lib/, root config files, etc.
  • No workspace config needed
  • Messages are coordination only, actual work lives in codebase

Example:

# ✅ CORRECT — implementer writes to src/
echo "export function foo() { ... }" > src/foo.ts

Pattern B: Analysis/Reasoning Meshes (lens, research, etc.)

  • Agents write to dedicated workspace defined in config
  • Workspace isolates intermediate analysis from codebase
  • Useful for multi-agent workflows, game turns, reasoning loops

Config:

workspace:
  path: ".ai/tx/workspaces/my-mesh"
  create_on_init: true

Agent prompt:

## Output Location
All analysis goes to `{workspace}/`.

## Workflow
1. Create workspace subdirectories as needed: `mkdir -p {workspace}/analysis`
2. Write working files: `{workspace}/analysis/draft-1.md`
3. Include output content in completion message to next agent

At runtime, {workspace} resolves to .ai/tx/workspaces/my-mesh.

Anti-Pattern: Working in msgs/

WRONG — Pollutes message queue:

# Agent does this — BAD
echo "analysis" > .ai/tx/msgs/my-work-123.md

This breaks the clean separation:

  • Transport: .ai/tx/msgs/ (messages between agents)
  • Work: Project files OR dedicated workspace (agent output, analysis, code)

Result: Message queue fills with intermediate files, becomes unreadable, routing breaks.

Best Practice: Parallel Isolation (Pattern B)

When multiple analysis agents run in parallel, each writes to its own subdirectory within the shared workspace:

# General parallel analysis pattern
coordinator:        → {workspace}/request.md  (shared)
analyst-1:          → {workspace}/runs/analysis-1.md
analyst-2:          → {workspace}/runs/analysis-2.md
analyst-3:          → {workspace}/runs/analysis-3.md
synthesizer:        → {workspace}/synthesis.md  (reads all, synthesizes)

Each agent:

  1. Creates its subdirectory: mkdir -p {workspace}/runs
  2. Writes to it: {workspace}/runs/{agent-id}-output.md
  3. Routes completion back to coordinator
  4. Synthesizer reads all files and combines results

Result: Clean isolation, no message pollution, work visible and debuggable.

Dev Mode — Cheap Workflow Testing

dev_mode: true  # Forces ALL agents to haiku regardless of config

Enable when: You've built a new mesh and want to test the routing, workflow, and agent coordination end-to-end before paying for sonnet/opus runs. Haiku is fast and cheap — use it to validate the plumbing works before the real thing.

Enable for:

  • First run of any new mesh (always test with dev_mode first)
  • Debugging routing issues (agent A → B → C flow)
  • Validating FSM state transitions
  • Testing ask-human and HITL flows
  • Any mesh with 4+ agents where a full run would be expensive

Disable when:

  • The workflow is confirmed working and you need quality output
  • Haiku's reasoning is insufficient for the task (complex synthesis, conflict resolution, architectural decisions)
  • Running in production

Never commit dev_mode: true — it's a testing flag. Remove it before the mesh is considered production-ready. If you see it in a mesh config, that mesh hasn't been signed off yet.

# ✅ Testing a new mesh
dev_mode: true
agents:
  - name: synthesizer
    model: opus   # ignored — all agents become haiku in dev_mode

# ✅ Production — remove dev_mode entirely
agents:
  - name: synthesizer
    model: opus   # now respected

Quick Start

# Test prompt output before deploying
tx prompt <mesh> <agent>              # View built prompt with injected protocol
tx prompt narrative-engine narrator   # Example
tx prompt dev --raw                   # Raw output, no metadata

Documentation

Topic Location
Config fields docs/mesh-config.md
FSM (state tracking) .ai/docs/mesh-fsm-config.md
Manifest routing docs/manifest-routing.md
Available meshes docs/meshes.md
Message format docs/message-format.md

Minimal Config

mesh: example
description: "What this mesh does"

agents:
  - name: worker
    model: sonnet       # opus | sonnet | haiku
    prompt: prompt.md

entry_point: worker

Command Agents

Agents can invoke slash commands instead of (or in addition to) prompt files. The command is prepended to the user prompt when processing messages.

agents:
  - name: builder
    model: opus
    command: "/know:build"
    prompt: builder/prompt.md  # optional extra context

  - name: reviewer
    model: sonnet
    command: "/know:review"
    # no prompt needed - command expands to full workflow

Precedence:

  1. Message frontmatter command: (highest)
  2. Agent config command: (default)
  3. No command (just prompt as system prompt)

Commands are prepended to the user prompt at dispatch time — no special SDK options required.

Command Template Interpolation

Commands support {key} template tokens that resolve from the message payload at runtime. Use this to pass dynamic values (like feature names) through the mesh pipeline.

agents:
  - name: prebuild
    model: haiku
    command: "/know:prebuild {feature}"   # {feature} replaced from payload

  - name: builder
    model: opus
    command: "/know:build {feature}"      # same token, resolved per-message

Resolution rules:

  • {key} matches msg.payload[key] — if present, replaced with the string value
  • Unresolved tokens stay as literal text (no silent failures, no crashes)
  • Payload values come from message frontmatter (e.g., feature: auth-system)

Propagation: Upstream agents must include the key in their completion message frontmatter for downstream agents to receive it. The consumer maps frontmatter fields to payload automatically.

Reliability front-matter fields (used by core agent for recovery, not in mesh configs):

  • recover: true — triggers DLQ recovery for the target mesh
  • rewind-to: <state> — override recovery session with checkpoint from named FSM state
  • session-id: <id> — resume a specific SDK session
  • resume-mesh: true — preserve mesh state instead of clearing on new entry
User message:  feature: auth  → prebuild gets "/know:prebuild auth"
Prebuild msg:  feature: auth  → builder gets "/know:build auth"

Message File Path Format — CRITICAL

Every agent prompt that writes messages must explicitly specify the file path format.

Format Rules

Message files go to .ai/tx/msgs/ with this format:

{timestamp}-{from}-{to}-{action}-{id}.md

Where:

  • {timestamp} = Unix seconds (date +%s)
  • {from} = Agent's qualified name with slashes replaced by hyphens (e.g., lens-coordinator for lens/coordinator)
  • {to} = Target agent/mesh with slashes replaced by hyphens (e.g., lens-historical for lens-historical)
  • {action} = What the message does (e.g., dispatch, complete, response)
  • {id} = Random or sequential ID (e.g., 12345 or abc123)

Examples

CORRECT:

File: 1774562950-lens-framer-coordinator-frame-99999.md
---
to: lens/coordinator
from: lens/framer
msg-id: frame-1774562950
---

ALSO CORRECT:

File: 1740362400-lens-coordinator-lens-historical-dispatch-47392.md
---
to: lens-historical
from: lens/coordinator
msg-id: dispatch-historical
---

WRONG (creates directory):

File: /Users/god/projects/tx/tx-core/.ai/tx/msgs/1774562400-lens/coordinator--lens-historical.md

WRONG (slashes in agent name):

File: 1774562400-lens/coordinator--lens/historical-dispatch.md

Prompt Template

Include this in every agent prompt that writes messages:

## Message Format

Write messages to `.ai/tx/msgs/` with this filename structure:

\`{timestamp}-{from}-{to}-{action}-{id}.md\`

Example:
\`1774562950-my-mesh-coordinator-my-mesh-worker-task-12345.md\`

File content:
\`\`\`markdown
---
to: my-mesh/worker
from: my-mesh/coordinator
msg-id: task-12345
headline: Brief description
---

Message body...
\`\`\`

Replace:

  • my-mesh with your actual mesh name
  • coordinator/worker with actual agent names
  • task with action (dispatch, complete, response, etc.)

Writing Prompts

Focus on workflow only.

System Auto-Injects (DO NOT WRITE IN PROMPTS):

  • ❌ Message protocol (frontmatter schema, message types, paths format)
  • ❌ Routing instructions (how to write messages to other agents)
  • ❌ Rearmatter format (success_signal, grade, confidence fields)
  • ❌ Workspace structure and paths (auto-injected from config.yaml)
  • ❌ Message file naming conventions
  • ❌ Tool availability and usage instructions (system provides)

Dispatcher-Mode Prompt Examples (CRITICAL)

In routing_mode: dispatcher meshes, prompt examples must use the sentinel address (mesh/dispatch), never direct agent addresses. The system auto-injects routing instructions, but if your prompt includes message examples they must match the dispatcher protocol or agents will bypass the sentinel and trigger routing errors.

Fan-out discuss examples — always use sentinel + outcome: discuss + route_to::

---
to: my-mesh/dispatch
from: my-mesh/reader-a
outcome: discuss
route_to: reader-b
msg-id: discuss-{timestamp}
headline: Question for reader-b
timestamp: {iso-timestamp}
---

Never write to: my-mesh/reader-b directly — this bypasses the dispatcher and the message gets dropped with a routing error nudge.

Completion examples — same pattern:

---
to: my-mesh/dispatch
from: my-mesh/reader-a
outcome: complete
msg-id: report-{timestamp}
headline: Domain report
timestamp: {iso-timestamp}
---

Write ONLY:

  • ✅ Agent role and mandate
  • ✅ Workflow steps (what to do, when)
  • ✅ Decision trees and logic
  • ✅ Domain-specific guidance
  • ✅ Quality gates and success criteria
# {Agent Name}

You are the {role} agent.

## Workflow
1. Read incoming task
2. {Work steps}
3. Signal completion when finished

Prompt Template Tokens

Prompts can embed {key} template tokens that are replaced with resolved values at runtime, before any section injection. This lets agents reference dynamic paths inline rather than relying on injected context sections.

Built-in tokens (always available when workspace is resolved):

  • {workspace} → absolute path to the resolved workspace directory

Example usage in prompt:

## Phase 0: Inventory
ls {workspace}/prose-draft.md
cat {workspace}/context.yaml

At runtime, if workspace resolves to /project/.ai/games/my-game/campaigns/campaign-1/turns/turn-35, the prompt becomes:

## Phase 0: Inventory
ls /project/.ai/games/my-game/campaigns/campaign-1/turns/turn-35/prose-draft.md
cat /project/.ai/games/my-game/campaigns/campaign-1/turns/turn-35/context.yaml

Rules:

  • Tokens that don't appear in the prompt are no-ops (safe for all meshes)
  • Unresolved tokens (no matching key) are left as-is (no silent failures)
  • Replacement happens via PromptInjector.replaceTemplateTokens() before workspace section injection
  • The injectWorkspace() method automatically replaces {workspace} — no caller changes needed

Dynamic workspace resolution via workspace.variables + workspace.locations:

When the workspace config declares variables and locations, the dispatcher resolves template variables from a source file (e.g., session.yaml) and uses the resolved workspace location as the workspace directory. This enables per-turn or per-session dynamic paths.

workspace:
  path: ".ai/games/"               # Static fallback
  variables:
    source: ".ai/tx/my-mesh/session.yaml"  # Fixed path (no chicken-and-egg)
    mapping:
      game-id: game_id             # {game-id} → session.game_id
      campaign-id: campaign_id     # {campaign-id} → session.campaign_id
      N: turn                      # {N} → session.turn
  locations:
    session: ".ai/tx/my-mesh"
    game: ".ai/games/{game-id}"
    campaign: ".ai/games/{game-id}/campaigns/{campaign-id}"
    workspace: ".ai/games/{game-id}/campaigns/{campaign-id}/turns/turn-{N}"

Resolution priority (dispatcher):

  1. FSM context $workspace variable (highest — gates use this)
  2. Resolved workspace location from manifest variables (per-turn path)
  3. Static workspace config (workspace.path)
  4. Default: .ai/tx/workspaces/<mesh-name>

Falls back gracefully: if the source file is missing or variables don't resolve, unresolved {tokens} remain and the static fallback is used instead.

Agent Boundaries (CRITICAL for Coordinators)

Haiku agents are eager helpers. Without explicit boundaries, they'll do work meant for other agents. Use <boundaries> blocks to constrain behavior.

Problem: A haiku coordinator sees domain context (file formats, workflow goals) and decides to "help" by doing the creative work itself instead of routing.

Solution: Explicit DO NOT / ONLY lists that name WHO does each task.

<role>
Route tasks. Validate state. Forward to specialists.
You are a ROUTER. You do NOT create content.
</role>

<boundaries>
DO NOT:
- Write output files (worker does that)
- Analyze input data (analyst does that)
- Make domain decisions (specialist does that)
- Read file contents beyond checking existence

ONLY:
- Read session state for routing decisions
- Check file EXISTENCE (ls), never CONTENTS (cat)
- Write routing messages to other agents
- Write ask-human when blocked
</boundaries>

Key principles:

  • State WHO does the forbidden work: "(worker does that)"
  • Separate existence checks from content reads
  • Add "If you find yourself doing X, STOP" guardrails
  • Keep domain knowledge minimal - coordinators route, they don't understand

Phase Coordinators Pattern

For complex pipelines, use one haiku coordinator per phase instead of one monolithic coordinator.

Problem: A single coordinator managing many phases accumulates too much context and state. It becomes complex, error-prone, and harder to debug.

Solution: Split into discrete phase coordinators, each with single responsibility.

Before (monolithic):

agents:
  - name: coordinator
    model: haiku
    prompt: coordinator/prompt.md  # 400 lines, manages 6 phases

After (phase-based):

agents:
  - name: entry
    model: haiku
    prompt: coordinator/entry.md        # Routes based on state

  - name: init-coord
    model: haiku
    prompt: coordinator/init-coord.md   # Sets up workspace, routes to prep

  - name: prep-coord
    model: haiku
    prompt: coordinator/prep-coord.md   # Fan-out/fan-in for prep agents

  - name: work-coord
    model: haiku
    prompt: coordinator/work-coord.md   # Dispatches workers, routes to validate

Benefits:

  • Each coordinator has ~50-80 lines (vs 400+)
  • Single responsibility per agent
  • Easier to debug (which phase failed?)
  • State validation at phase boundaries
  • Boundaries are clearer per-phase

Pattern:

entry → phase-1-coord → phase-2-coord → ... → completion-coord
              ↓               ↓
         specialists     specialists

Each phase coordinator:

  1. Receives task from previous coordinator
  2. Does its ONE job (setup, dispatch, validate, etc.)
  3. Updates shared session state
  4. Routes to next coordinator

Shared state: Use session.yaml that all coordinators read/write. Each coordinator preserves ALL fields when updating.

Multi-Agent Routing

routing:
  agent-a:
    complete:
      agent-b: "Handoff reason"
    blocked:
      core: "Need intervention"

See docs/mesh-config.md for full routing reference.

Dispatcher Routing (Opt-in)

Centralized routing where agents write to a sentinel address and the dispatcher resolves targets from config.

routing_mode: dispatcher
routing:
  agent-a: agent-b               # linear — always routes to agent-b
  agent-b:                        # branch — outcome determines target
    approved: agent-c
    needs_work: agent-a
    default: agent-c
  # agent-c: (absent) = terminal agent → routes to core/core on complete

Fan-out / Fan-in: Array value with trailing options object for parallel dispatch:

routing_mode: dispatcher
routing:
  planner: [reviewer-a, reviewer-b, reviewer-c, { discuss: true, complete: synthesizer, fan_in: batch }]
  • complete: agent — join agent, gated until all fan-out members send outcome: complete
  • discuss: true — members can peer-message via outcome: discuss + route_to: peer
  • fan_in: batch|queue|drain — controls how messages are delivered to the join agent (default: batch)
  • transform: summarize — optional haiku pre-pass to compress responses before delivery
  • Fan-out members get implicit routing (no individual entries needed)
  • Members send outcome: complete to signal done, outcome: discuss + route_to: for peer chat

Free Routing Mode

Agents self-organize from full roster. No routing table — each agent sees all peers and decides where to route next.

routing_mode: free

agents:
  - name: planner
    prompt: planner.md
  - name: researcher
    prompt: researcher.md
  - name: writer
    prompt: writer.md

entry_point: planner          # Optional: omit to fan-out to all agents
completion_agents:            # Optional: omit to let any agent complete
  - writer
  • No routing: block needed (warned if present)
  • Agents set to: agent-name directly in frontmatter (same as agent mode)
  • completion_agents controls who sees core/core as destination. Omit = all agents can complete
  • entry_point omitted → all agents receive the initial task simultaneously
  • Not compatible with FSM (warned if present)

Static Routing Mode

Ordered agent chain — each agent runs sequentially, worker exit fires the next agent automatically. No message passing between agents.

# Static routing — sequential chain, no agent messaging
routing_mode: static
routing:
  - preprocessor
  - analyzer
  - reporter
# routing[0] = entry_point (preprocessor)
# routing[last] = completion agent (reporter)
# Each agent runs, exits, next fires automatically
# On error: chain halts, error surfaces to core/core
  • routing must be an ordered array of agent names
  • First agent in array is the entry point (receives initial task)
  • Last agent in array is the completion agent (routes to core/core on completion)
  • No entrypoint or completionagents needed (derived from routing chain)
  • Each agent's completion triggers the next agent's spawn automatically
  • Agents do NOT write messages to mesh addresses — chain advances on exit
  • Error in any agent halts the chain; error surfaces to core/core
  • Not compatible with FSM or free routing (warned if mixed)

Fan-in delivery modes (fan_in):

Mode Behavior
batch (default) Gate until all complete, deliver all responses in one combined message
queue Current OAOM serial delivery (N cold worker starts)
drain Deliver immediately; inject into running join worker via session resume

Transform (transform):

Value Behavior
summarize Haiku pre-pass compresses response(s) before delivery to join agent
fan_in transform Result
batch Gate until all complete, deliver all in one worker
batch summarize Gate, haiku-compress all responses into one, deliver
queue Serial OAOM (N cold starts)
queue summarize Each message haiku-compressed before its worker run
drain Inject each response into running join worker
drain summarize Each response haiku-compressed then injected

Agents receive prompt instructions to write to: mesh/dispatch with outcome: in frontmatter. Override with route_to: for explicit targeting. Reserved outcome: escalate routes to human.

Fan-out members with discuss: true receive a peer list in their prompt. They use outcome: discuss + route_to: peer-name for peer-to-peer messaging within the group.

Type detection: string value = linear, object value = branch, array value = fan-out, absent = terminal.

Common Patterns

Session reuse (default behavior): continuation: true is the default — sessions persist naturally. Set continuation: false to force cold starts (needed for checkpoint/fork_from isolation).

Completion agents: Define which agents sit at the mesh boundary and can message core/core. Accepts array form (preferred) or deprecated singular string:

# Preferred: array form
completion_agents:
  - reviewer
  - evaluator

# Deprecated: singular form (backward compatible, array takes precedence if both set)
completion_agent: reviewer

Persistent mesh (no shutdown on complete): For meshes that loop perpetually and report status without dying:

completion_agents:
  - weaver
stop_on_first_complete: false   # Completion signal is informational, mesh continues
check_queue_on_complete: true   # (default) Queue-aware for future use
stoponfirst_complete checkqueueon_complete Behavior
true (default) true (default) Stop on complete, wait for queue to drain first
true false Stop immediately on complete (legacy behavior)
false true Informational complete, mesh continues running
false false True daemon mode, mesh never stops on complete

MCP tools only: toolRestriction: mcp-only

Quality hooks: Use explicit lifecycle: hooks for quality evaluation:

lifecycle:
  pre:
    - quality:preflight
  post:
    - quality:checklist
    - quality:rubric

FSM state tracking: fsm: block for system-managed state variables and logic. Only use when routing depends on computed state, counters, or file presence — not agent judgment. If an orchestrator handles all routing anyway, FSM is redundant. See FSM decision guide below.

Parallel execution: parallelism: block for fork/join semantics (see Parallel Execution section below), or ensemble: { type: parallel } for FSM states

CRITICAL - FSM Entry Routing: Entry agents in FSM ensemble meshes MUST fan out to ALL ensemble workers. FSM observes these messages to track state, but explicit routing triggers the workers.

routing:
  entry:
    complete:
      worker-1: "Spawn worker 1"  # ✅ CORRECT - Fan out to all workers
      worker-2: "Spawn worker 2"
      worker-3: "Spawn worker 3"
      # core: "..."                # ❌ WRONG - Workers never spawn!

Parallel Mesh Instances: Spawn isolated, named instances of the same mesh for concurrent execution:

---
to: dev/worker
from: core/core
parallel: true
mesh-id: auth-system
---

Implement user authentication.
  • parallel: true — Spawn new instance or route to existing one
  • mesh-id: <name> — Unique identifier for this instance
  • Each instance is isolated with its own state and session
  • Use mesh-id in follow-up messages to route to the same instance
  • Instance marked complete when completion agent sends status: complete
  • View instances: tx status shows running and completed instances

Isolation guarantees:

  • Session isolation: Each instance gets unique session key (meshName:meshId) that persists across follow-up messages
  • State isolation: Independent worker tracking, metrics, and FSM state per instance
  • Workspace isolation: Agents see same filesystem but track state separately
  • Cross-instance communication: Not supported - instances cannot message each other directly

Cleanup and lifecycle:

  • Instances persist in SQLite (parallel_instances table) with status running or completed
  • No automatic garbage collection - completed instances remain queryable via tx status
  • No instance limit enforced (guardrails planned but not yet implemented)
  • Routing to completed instances returns error to sender

When to use:

  • Multiple features being built in parallel by the same mesh
  • Concurrent tasks that shouldn't share state
  • Same workflow applied to different inputs (e.g., dev mesh building feature-a and feature-b simultaneously)

Example workflow:

# Core sends task to dev with unique mesh-id
echo "---
to: dev/worker
from: core/core
parallel: true
mesh-id: feature-auth
---
Build authentication feature." > .ai/tx/msgs/$(date +%s)-core-core--dev-worker-$(date +%s%N | tail -c 6).md

# Later, send follow-up to the same instance
echo "---
to: dev/worker
from: core/core
mesh-id: feature-auth
---
Update authentication to use JWT." > .ai/tx/msgs/$(date +%s)-core-core--dev-worker-$(date +%s%N | tail -c 6).md

Original task injection: injectOriginalMessage: true - Injects original task into downstream agents

Design documentation: playbook_notes: - Embed architectural rationale in config (replaces separate READMEs)

Self-assessment metadata: rearmatter: - Agent outputs self-assessment fields (grade, confidence, status) for FSM routing decisions

Lifecycle hooks: Auto-commit, brain insights, quality gates

lifecycle:
  post:
    - commit:auto    # Auto-commit changes
    - brain-update   # Document insights

Available hooks: worktree:create, commit:auto, brain-update, quality:*. See docs/mesh-config.md.

File Preload

Dump files into agent context before execution. Useful for preloading context without manual reads.

agents:
  - name: preloader
    model: haiku        # Model defaults to haiku when load is set
    prompt: prompt.md
    load:
      - "package.json"  # Exact file
      - "*.md"          # Glob pattern
      - "src/**/*.ts"   # Recursive glob

Behavior:

  • Files matched by glob patterns are read and injected into system prompt
  • Files over 200KB are skipped with warning
  • node_modules/ and .git/ are auto-excluded
  • Model defaults to haiku when load is set (cheap preloaders)

Use cases:

  • Virtual "setup" agents that preload project context
  • Checkpoint entry points that establish shared context
  • Cheap haiku agents that read files before expensive opus agents work

Session Forking

Share conversation context between agents via checkpoints.

agents:
  - name: setup
    model: haiku
    prompt: setup.md
    load: ["package.json"]
    checkpoint: true      # Save session for forking

  - name: worker-a
    model: sonnet
    prompt: worker.md
    fork_from: setup      # Fork from setup's checkpoint

  - name: worker-b
    model: opus
    prompt: worker.md
    fork_from: setup      # Same checkpoint, different agent

Behavior:

  • checkpoint: true saves the agent's sessionId on completion
  • fork_from: agent-name loads that checkpoint as the starting session
  • Forked agents continue from the checkpoint's conversation history
  • Works across models (haiku checkpoint → opus fork)

Use cases:

  • Skip redundant prework (preload once, fork many)
  • Share established context across parallel workers
  • Model escalation with preserved context

Parallel Execution

Fork from entry, run agents concurrently, join at exit.

agents:
  - name: preload
    model: haiku
    prompt: preload.md
    load: ["package.json"]
    # checkpoint: true auto-added

  - name: analyst
    model: sonnet
    prompt: analyst.md
    # fork_from: preload auto-added

  - name: reviewer
    model: sonnet
    prompt: reviewer.md

  - name: critic
    model: sonnet
    prompt: critic.md

  - name: synthesizer
    model: sonnet
    prompt: synthesizer.md

parallelism:
  - agents: [analyst, reviewer, critic]
    entry: preload        # Fork point (gets checkpoint: true)
    exit: synthesizer     # Sync gate (waits for all)
    timeout: 300000       # Optional: 5 min timeout
    on_partial: continue  # continue | abort on partial failure

Flow:

preload (entry)
    │ checkpoint
    ├─────┼─────┐
    ▼     ▼     ▼
analyst reviewer critic  (parallel, forked from preload)
    │     │     │
    └─────┼─────┘
          ▼
    synthesizer (exit, gated until all complete)

Auto-wiring:

  • Entry agent gets checkpoint: true automatically
  • Parallel agents get fork_from: entry automatically
  • Exit agent is gated until ALL parallel agents complete

Routing: Parallel agents must route to exit agent:

routing:
  preload:
    complete:
      analyst: "Ready for analysis"
  analyst:
    complete:
      synthesizer: "Analysis done"
  reviewer:
    complete:
      synthesizer: "Review done"
  critic:
    complete:
      synthesizer: "Critique done"
  synthesizer:
    complete:
      core: "Synthesis complete"

vs FSM Ensemble:

Feature parallelism: FSM ensemble:
Fork context Yes (checkpoint) No
Result aggregation No (just sync) Yes (concat/vote/etc)
Gating Exit gated FSM state transition
Use case Parallel work, shared context Same task, multiple perspectives

FSM (State Tracking)

Add fsm: block to track state and provide context to agents.

IMPORTANT: If you use FSM, you must also define routing: configuration. Routes can exist without FSM, but FSM cannot exist without routes.

When to Use FSM — Decision Guide

Default assumption: do NOT use FSM. Pure message routing handles the majority of meshes cleanly. Only add FSM when the routing itself cannot be handled by agent judgment.

Use FSM when ALL of these are true:

  • Routing decisions depend on computed state, counters, or file presence — not agent judgment
  • State must survive between completely separate TX sessions (cold restart)
  • You need arithmetic operations to drive routing (e.g., turn: turn + 1, loop N times then exit)
  • Or you need deterministic gate conditions independent of agent output (e.g., "only proceed if file X exists")

Real examples that warrant FSM:

  • Narrative engine tracking turn number across days-long campaigns
  • Loop mesh that runs exactly N iterations before exiting (counter drives routing)
  • Ensemble with a gate that only fires when ALL N output files are present on disk
  • Multi-session workflow that must resume in the exact right state after a cold restart

Do NOT use FSM when:

  • An orchestrator agent already handles all routing decisions — if every state routes back to orchestrator anyway, the orchestrator IS the state machine. Drop the FSM.
  • The workflow is linear or near-linear: A → B → C → D
  • Loops can be tracked by the agent itself (validator counting its own attempts in the message body)
  • Fan-out/fan-in is the need — use dispatcher routing with fan-out array syntax instead
  • HITL is the need — use ask-human messages directly, no FSM state required
  • "What phase are we in?" can be answered by reading the last message — that's not a state machine problem

The key test: If you'd trust an orchestrator agent to route correctly based on incoming messages, you don't need FSM. FSM is for when routing must be mechanical and cannot rely on agent judgment.

Red flags that you're over-engineering with FSM:

  • Every FSM state has only one non-error exit that always fires (just use routing)
  • All states route back to an orchestrator agent anyway (orchestrator is doing the state management, FSM is redundant)
  • The FSM context tracks values the orchestrator could just pass in message bodies
  • You added FSM because the workflow "felt complex" — complexity alone is not a reason

Sequential workflow:

fsm:
  initial: init

  context:
    turn: 0
    workspace: null

  states:
    init:
      agents: [coordinator]
      entry:
        set:
          turn: "$((turn + 1))"
          workspace: "/path/to/turn-$turn"
      exit:
        default: awaiting_work

    awaiting_work:
      agents: [worker]
      exit:
        when:
          - condition: signal == "PASS"
            target: complete
        default: awaiting_work

  scripts: {}

Parallel workflow (ensemble):

routing:
  # Ensemble agents need explicit routing
  rev-1:
    complete:
      synthesizer: "Review 1 complete"
  rev-2:
    complete:
      synthesizer: "Review 2 complete"
  rev-3:
    complete:
      synthesizer: "Review 3 complete"

fsm:
  initial: parallel_review

  states:
    parallel_review:
      ensemble:
        type: parallel          # Required: type inside ensemble block
        agents: [rev-1, rev-2, rev-3]
        aggregation: concat
      exit:
        set:
          results: "$ENSEMBLE_OUTPUT"
        default: synthesize

  scripts: {}

Ensemble shorthand (agent + count): Instead of listing agents individually, spawn N copies of one agent. Supports variable references for dynamic parallelism:

fsm:
  context:
    parallelism: 3

  states:
    parallel_review:
      ensemble:
        type: parallel
        agent: reviewer           # Single agent template
        count: $parallelism       # Spawns 3 instances (variable reference)
        aggregation: concat

FSM context_descriptions: Document context variables for maintainability:

fsm:
  context:
    turn: 0
    workspace: null

  context_descriptions:
    turn: "Current iteration number, incremented each cycle"
    workspace: "Resolved workspace path for this turn"

Agents receive injected context:

## FSM Context
state: awaiting_work
turn: 5
workspace: /path/to/turn-5

See docs/mesh-fsm-config.md for:

  • Exit-based routing (when/run/default)
  • Ensemble states (parallel execution)
  • Self-loops and iteration tracking
  • Gates and validation

Documentation

playbook_notes in config.yaml (for maintainers)

  • Design rationale and architectural decisions
  • WHY the mesh is built this way
  • Alignment with methodologies/patterns
  • Not injected into prompts

Example:

playbook_notes: |
  This mesh implements the Ralph pattern from ClaytonFarr/ralph-playbook.
  Uses layered quality refinement: haiku drafts, sonnet reviews, opus finalizes.

Task Distribution Pattern

Alternative to ensemble for splitting work across agents:

task_distribution:
  spawner: coordinator                  # Required: agent that splits the task
  subagents: [worker-1, worker-2, worker-3]  # Required: agents that do the work
  reviewer: synthesizer                 # Required: agent that combines results
  distribution_strategy: equal          # Required: equal | weighted | adaptive | custom
  distribution_prompt: "..."            # Required when strategy is 'custom'
  subtask_count: 5                      # Optional fixed count
  timeout_ms: 300000                    # 5 minute timeout
  allow_partial_failure: true

When to use task_distribution vs ensemble:

Pattern Task Distribution Ensemble
Task Split into parts Same task
Agents Different subtasks Same analysis
Output Combined portions Aggregated views

Aggregation Strategies

For ensemble aggregation field:

Strategy Description Use Case
concat Join all outputs Comprehensive review
deduplicate Remove duplicate findings Code analysis
voting Majority opinion wins Consensus decisions
consensus Require agreement High-stakes choices
custom Use custom prompt Domain-specific

Deprecated Patterns

AVOID these patterns:

Pattern Replacement Reason
state.type: ensemble state.ensemble: { type: parallel } Old FSM syntax
state.subtask: true Explicit ensemble routing Implicit behavior
workspace: "string" workspace: { path: "..." } Object format preferred
completion_agent: "name" completion_agents: [name] Array form takes precedence if both set
routingfallback / routingretry_max guardrails.routing_error.* Moved to guardrail config

Agent Config Fields

Field Type Required Description
name string yes Agent identifier
model string yes* opus / sonnet / haiku (*defaults to haiku if load set, else sonnet)
prompt string one of prompt/command Path to prompt file
command string one of prompt/command Slash command (e.g., /know:build). Supports {key} interpolation from payload.
workspace object no Per-agent workspace config
mcpServers object no MCP server configurations
description string no Agent documentation
load array no Files to preload into context (globs supported)
checkpoint boolean / string no Save session state on completion for forking. true (normalized to 'start'), 'start', or 'end'.
fork_from string no Fork from another agent's checkpoint
thinking boolean / object no Extended thinking. true (default), false to disable, or { budget_tokens: number } to set token budget.
max_turns number no API round-trip limit per invocation. Also configurable via guardrails.max_turns with strict/warning modes.
max_messages number no Outbound message limit per invocation. Also configurable via guardrails.max_messages with strict/warning modes.
fragments array no Prompt fragment names to inject (string[]). Fragments are reusable prompt snippets shared across agents.
loadclaudemd boolean no Control CLAUDE.md injection into agent system prompt (default: true). Set false to prevent project instructions from leaking into mesh agents.
orchestrator boolean no Restrict to Read + Write(msgs only). For coordinator agents that route, not implement.
permissions object no Tool access control. See Permissions section below.
postconditions object no Tool call postconditions. See Postconditions section.
chrome boolean no Use claude --chrome CLI instead of Agent SDK. Enables browser access. Fire-and-forget: no HITL, no resume, no checkpoint.

Incompatible combinations:

  • forkfrom + continuation: true — fork requires cold start isolation. Set continuation: false explicitly when using forkfrom.

Chrome Agents (Browser Access)

Set chrome: true to spawn claude --chrome --print CLI instead of SDK. Fire-and-forget: no HITL, no resume, no checkpoint. SIGTERM → SIGKILL after 5s.

agents:
  - name: browser
    model: sonnet
    prompt: browser.md
    chrome: true

Full docs: docs/chrome-agents.md — behavior differences, incompatible fields, chrome vs Playwright decision matrix, troubleshooting.

Permissions (Tool Access Control)

Control which tools an agent can access using the permissions field.

Default behavior (no permissions block):

  • Allowed: Read, Write, Edit, Glob, Grep
  • Denied: Bash, Task (must be explicitly allowed)

Example: Allow Bash for an implementer

agents:
  - name: implementer
    model: sonnet
    prompt: implementer.md
    permissions:
      allowedTools:
        - Read
        - Write
        - Edit
        - Glob
        - Grep
        - Bash  # Explicitly allowed

Example: Read-only reviewer

agents:
  - name: reviewer
    model: sonnet
    prompt: reviewer.md
    permissions:
      allowedTools:
        - Read
        - Glob
        - Grep
      # No Write, Edit, or Bash

Available tools:

  • File operations: Read, Write, Edit, Glob, Grep
  • Execution: Bash (denied by default)
  • Advanced: Task (subagent spawning), TaskOutput, LSP, WebFetch, WebSearch, TodoWrite, NotebookEdit, Skill, EnterPlanMode, ExitPlanMode, KillShell
  • Never grant to mesh agents: AskUserQuestion (no user session — use messaging to core/core instead)

Preamble behavior: Multi-agent meshes tell agents "not the Task tool" by default to prevent subprocess chaos. When Task or TaskOutput appear in allowedTools, the preamble switches to encourage Task tool usage for parallel subprocesses within the session, while still routing cross-agent work via messages. The preamble also recognizes Agent as a legacy alias.

Security principle: Only grant tools an agent actually needs. Start restrictive, add permissions as required.

AskUserQuestion — never grant to mesh agents. Mesh agents have no interactive user session. AskUserQuestion is an SDK tool for CLI sessions only. When agents need human input, they write a message to core/core (which suspends the session until the human responds via core). Prompts that need HITL steps should instruct agents to send a message to core, not use AskUserQuestion.

God mode: Run tx start dev --god-mode to bypass all permissions (unrestricted tool access). Use only when you need it.

Postconditions

Validate that required tool calls occurred during agent execution. Prevents agents from hallucinating results instead of using tools.

Agents sometimes describe what they would do instead of doing it — generating output inline rather than calling Bash, Write, or Task tools. Postconditions catch this by checking the actual tool call record after the agent completes.

agents:
  - name: gravity
    model: sonnet
    prompt: gravity/prompt.md
    postconditions:
      tool_calls:
        - tool: Bash
          pattern: "campaign.sh"  # Substring match in command
          exit_code: 0            # Required exit code (default: 0)
          min_calls: 1            # Minimum matching calls (default: 1)
        - tool: Write
          pattern: "collisions.yaml"
          min_calls: 1

Fields per entry:

Field Type Default Description
tool string required Tool name: Bash, Write, Read, Edit, TaskOutput, etc.
pattern string Substring match in command (Bash), file_path (Write/Read/Edit), or any string input value (other tools). Omit to match any call to that tool.
exit_code number 0 Required exit code. Bash only. Omit to use default (0 = no error).
min_calls number 1 Minimum number of matching calls required.

Behavior:

  • Validation runs after agent completion, before routing to next agent
  • All postconditions must pass for the agent to complete successfully
  • Guardrail mode (strict/warning) controlled via guardrails.postcondition override chain
  • Strict mode: kills worker, routes error to core/core
  • Warning mode: injects corrective feedback into agent session
  • Disabled mode (strict: false, warning: false): skips validation entirely

Pattern matching:

  • Bash: substring match on input.command
  • Write/Read/Edit: substring match on input.file_path
  • Other tools: substring match on any string value in input
  • Omit pattern to match all calls to that tool type

Examples:

Ensure agent runs a script successfully:

postconditions:
  tool_calls:
    - tool: Bash
      pattern: "campaign.sh"
      exit_code: 0

Ensure agent uses parallel Tasks (not inline generation):

postconditions:
  tool_calls:
    - tool: TaskOutput
      min_calls: 3    # Must read results from at least 3 parallel Tasks

Ensure agent writes required output file:

postconditions:
  tool_calls:
    - tool: Write
      pattern: "report.md"

Override chain for mode (strict/warning):

guardrails:
  postcondition:
    strict: false
    warning: true
  meshes:
    narrative-engine:
      postcondition:
        strict: true    # Kill gravity if it hallucinates
      agents:
        gravity:
          postcondition:
            strict: true

Additional Config Fields

Field Type Description
dev_mode boolean Force all agents to haiku for cheap workflow testing. Remove before production.
brain boolean Inject brain access prompt into all agents. Agents learn they can message brain/brain for project questions (architecture, dependencies, design rationale). Skipped for the brain mesh itself.
capability object Structured capability declaration for catalog matching (domain, input, output, tools, interaction — enum arrays). See src/mesh/capability/schema.ts for valid values.
config object Custom mesh-specific settings
idletimeoutminutes number/false Idle timeout (false=disabled)
clear-before boolean Clear state before run
turn_workspace object Turn-based game workspace
parallelism array Parallel execution blocks (see Parallel Execution)
persistence boolean/array Session persistence across mesh runs
routing_fallback string DEPRECATED — use guardrails.routingerror.routingfallback
routingretrymax number DEPRECATED — use guardrails.routingerror.routingretry_max
manifest_enforcement object Artifact validation settings
maxmeshmessages number/object Mesh-wide message cap (guardrail)
max_invocations number/object Per-agent spawn cap — counts worker spawns, not messages. Caps iteration loops while allowing ask/respond (no new spawn). Same {strict, warning, limit} shape.
autoInjectManifestFiles boolean Auto-preload manifest reads (default: true)
loadclaudemd boolean Load project CLAUDE.md into agent system prompt (default: true)

Route Validation

Verify all ask relationships have matching ask-response routes back.

Rule: If agent A asks agent B, then B must have an ask-response route back to A.

Manual check:

  1. List all ask relationships: A → asks → B
  2. List all ask-response routes: B → responds-to → [X, Y, Z]
  3. For each ask, verify the target can respond to the sender

Common mistakes:

  • Coordinator asks worker, but worker only responds to a different coordinator
  • Agent added to ask list but ask-response not updated
  • Indirect flows (A → B → C → A) mistaken for direct flows

Example mismatch:

# validator asks fixer
validator:
  ask:
    fixer: "Fix issues"

# fixer responds to reviewer, NOT validator - BUG!
fixer:
  ask-response:
    reviewer: "Fixes complete"  # ⚠ validator missing!

Intentional indirection (not a bug):

# narrator → lint-coordinator → editor → narrator
# lint-coordinator responds to editor, not narrator (by design)

Document intentional indirections in playbook_notes.

Prompt-to-Config Validation

Verify all agent references in prompts match agents defined in config.yaml.

Rule: Every to: mesh/agent in prompt examples must reference an agent that exists in the mesh's config.yaml.

Manual check:

# Extract agents from config
yq '.agents[].name' meshes/{mesh}/config.yaml | sort > /tmp/agents.txt

# Extract to: targets from prompts
rg "to: {mesh}/[a-z-]+" meshes/{mesh} --type md -o --no-filename \
  | sed 's/to: {mesh}\///' | sort | uniq > /tmp/targets.txt

# Find mismatches
comm -23 /tmp/targets.txt /tmp/agents.txt

Common mistakes:

  • Generic coordinator when mesh has phase coordinators (init-coord, render-coord, etc.)
  • Outdated agent names after refactoring
  • Copy-paste from other meshes with different agent names

Architectural principle:

Prompts should reference responsibilities, not agent names. Routing decisions (who handles what) belong in config.yaml, not prompts.

Pattern Guidance
to: mesh/specific-agent in examples Acceptable for illustrating message format
to: {from: field} dynamic routing Preferred for ask-response patterns
Prose describing "send to agent X" Move WHO to config, keep WHAT in prompt

Anti-pattern:

# BAD: Hardcoded routing in prompt
When done, send ask-response to COORDINATOR.

Better:

# GOOD: Reference responsibility, config handles routing
When done, send ask-response to the coordinator that sent the ask.
# Config routing section defines which coordinator that is.

Guardrails

Unified runtime enforcement with strict/warning mode on every guardrail. Config: .ai/tx/data/config.yaml under guardrails:.

Mode (applies to all guardrails):

strict warning Result
false true Default — Allow + inject feedback
true true Block/kill + reason
true false Block/kill silently
false false Disabled
  • Write gate: Intercepts Write/Edit/NotebookEdit and Bash redirects to undeclared paths.
  • Read gate: Intercepts Read/Glob/Grep to undeclared paths.
  • Routing error: Corrective injection on bad targets (max retries: 3) + per-edge message caps (routingretrymax / routing_fallback).
  • Artifact validation: Pre/post validation of agent outputs. Default: enabled, 2 retries.
  • Max messages/turns: Global or per-agent caps. Accept bare number or {strict, warning, limit} object.
  • Max mesh messages: Mesh-wide cap on total messages across all agents in a mesh run.
  • Max invocations: Per-agent spawn cap. Counts how many times a worker is spawned for an agent in a mesh run. Ask/respond cycles don't count (worker stays alive). Prevents unbounded iteration loops. Warning at limit injects "final invocation" feedback. Strict past limit blocks spawn and notifies core.
  • Max turns (warning mode): SDK limit bypassed, turns tracked manually, event emitted at threshold.
  • Parity: Always-on, non-configurable.
guardrails:
  write_gate:
    strict: false
    warning: true
    kill_threshold: null
  read_gate:
    strict: false
    warning: true
    kill_threshold: null
  routing_error:
    strict: false
    warning: true
    max_retries: 3
  artifact:
    strict: false
    warning: true
    post_validation: true
    pre_validation: true
    max_retry: 2
  max_messages:
    strict: false
    warning: true
    limit: null
  max_turns:
    strict: false
    warning: true
    limit: null
  max_mesh_messages:
    strict: false
    warning: true
    limit: null
  max_invocations:
    strict: true
    warning: true
    limit: null
  meshes:
    my-mesh:
      write_gate:
        strict: true
        kill_threshold: 5
      agents:
        my-agent:
          write_gate:
            strict: false
            warning: true
            kill_threshold: 10

Override chain: agent > mesh > global > hardcoded default. strict and warning resolve independently.

Gates activate automatically when manifest entries exist — no additional mesh config needed.

Full reference: docs/guardrails.md

Debugging

tx status    # Workers, queue
tx trace     # Message trace viewer
tx spy       # Real-time activity
tx logs      # System logs