Squad Squad

SDK-First Squad Mode

Phase 1 — Type-safe team configuration with builder functions.

Squad now supports SDK-First Mode: define your team in TypeScript with full type safety, runtime validation, and editor autocomplete. Instead of manually maintaining markdown files in .squad/, you write clean TypeScript, and squad build generates the governance markdown.


What is SDK-First Mode?

In SDK-First Mode:

  1. You write a squad.config.ts (or squad/index.ts) with builder functions
  2. Squad validates your config at runtime with type guards
  3. squad build generates .squad/ markdown files automatically
  4. You version control only your TypeScript source — markdown is generated

This replaces manual .squad/team.md, .squad/routing.md, and agent charters with a single source of truth in code.


Which Mode Should I Use?

ScenarioCommandWhat You Get
New project, want simplicitysquad initMarkdown-only .squad/ directory
New project, want type safetysquad init --sdk.squad/ + squad.config.ts with typed builders
Existing squad, want to upgradesquad migrate --to sdkKeeps your team, generates typed config
SDK squad, want to simplifysquad migrate --to markdownRemoves config, keeps markdown

Start with markdown. It’s simpler, requires no build step, and works great for most projects. Upgrade to SDK when you want type-safe configuration, IDE autocomplete, and the ability to define skills and ceremonies in TypeScript.


Quick Start

1. Install the SDK

npm install @bradygaster/squad-sdk

2. Create squad.config.ts

import {
  defineSquad,
  defineTeam,
  defineAgent,
  defineRouting,
} from '@bradygaster/squad-sdk';

export default defineSquad({
  team: defineTeam({
    name: 'Core Squad',
    description: 'The main engineering team',
    members: ['@edie', '@mcmanus'],
  }),

  agents: [
    defineAgent({
      name: 'edie',
      role: 'TypeScript Engineer',
      model: 'claude-sonnet-4',
      tools: ['grep', 'edit', 'powershell'],
      capabilities: [{ name: 'type-system', level: 'expert' }],
    }),
    defineAgent({
      name: 'mcmanus',
      role: 'DevRel',
      model: 'claude-haiku-4.5',
      capabilities: [{ name: 'documentation', level: 'expert' }],
    }),
  ],

  routing: defineRouting({
    rules: [
      { pattern: 'feature-*', agents: ['@edie'], tier: 'standard' },
      { pattern: 'docs-*', agents: ['@mcmanus'], tier: 'lightweight' },
    ],
    defaultAgent: '@coordinator',
  }),
});

3. Run squad build

npx squad-cli build

This generates:

  • .squad/team.md — team roster and context
  • .squad/routing.md — routing rules
  • .squad/agents/{name}/charter.md — agent charters
  • .squad/ceremonies.md — (if ceremonies defined)

Starting a New SDK-First Project

squad init --sdk

This generates:

  • .squad/ directory with all standard markdown files (team.md, routing.md, agent charters, etc.)
  • squad.config.ts at your project root using the defineSquad() builder syntax

Your squad.config.ts is the source of truth. Edit it, then run squad build to regenerate .squad/.

What Gets Generated

import {
  defineSquad,
  defineTeam,
  defineAgent,
} from '@bradygaster/squad-sdk';

export default defineSquad({
  version: '1.0.0',
  team: defineTeam({
    name: 'my-project',
    members: ['scribe'],
  }),
  agents: [
    defineAgent({ name: 'scribe', role: 'scribe', description: 'Scribe', status: 'active' }),
  ],
});

Without --sdk, squad init creates a markdown-only squad — no config file, no build step needed.


Migrating an Existing Squad to SDK-First

squad migrate --to sdk        # generate squad.config.ts from existing .squad/
squad migrate --to sdk --dry-run  # preview without writing

The migrate command reads your existing .squad/ files (team.md, routing.md, agent charters) and generates a squad.config.ts that reproduces your current configuration using typed builders.

What Gets Migrated

SourceGenerated
.squad/team.md rosterdefineTeam({ members: [...] })
.squad/agents/*/charter.mddefineAgent({ name, role, ... }) per agent
.squad/routing.md rulesdefineRouting({ rules: [...] })
.squad/ceremonies.mddefineCeremony() entries
.squad/casting/policy.jsondefineCasting() block

What’s Preserved (Not Migrated)

  • decisions.md — append-only ledger, stays as-is
  • agents/*/history.md — personal knowledge, stays as-is
  • orchestration-log/, log/ — append-only archives

Reverting to Markdown

squad migrate --to markdown

This runs squad build to ensure .squad/ is current, then removes squad.config.ts.

Legacy Migration

squad migrate --from ai-team   # rename .ai-team/ → .squad/

This replaces the old squad upgrade --migrate-directory command.


Builder Functions

Each builder accepts a configuration object, validates it at runtime, and returns the typed value. The pattern mirrors defineConfig() — identity-passthrough with runtime safety.

defineTeam(config)

Define team metadata, project context, and member roster.

const team = defineTeam({
  name: 'Core Squad',
  description: 'The main engineering team',
  projectContext: 'Building a React/Node recipe app...',
  members: ['@edie', '@fenster', '@hockney'],
});

Type:

interface TeamDefinition {
  readonly name: string;
  readonly description?: string;
  readonly projectContext?: string;
  readonly members: readonly string[];
}
FieldTypeRequiredNotes
namestringTeam name (non-empty)
descriptionstringOne-liner
projectContextstringInjected into agent prompts; describe the codebase, tech stack, conventions
membersstring[]Agent refs ("@edie" or "edie"); order matters for routing fallback

defineAgent(config)

Define a single agent with role, charter, model preference, tools, and capability profile.

const edie = defineAgent({
  name: 'edie',
  role: 'TypeScript Engineer',
  charter: 'Expert in type systems and test-driven development',
  model: 'claude-sonnet-4',
  tools: ['grep', 'edit', 'powershell', 'view'],
  capabilities: [
    { name: 'type-system', level: 'expert' },
    { name: 'testing', level: 'proficient' },
  ],
  status: 'active',
});

Type:

interface AgentDefinition {
  readonly name: string;
  readonly role: string;
  readonly charter?: string;
  readonly model?: string;
  readonly tools?: readonly string[];
  readonly capabilities?: readonly AgentCapability[];
  readonly status?: 'active' | 'inactive' | 'retired';
}

interface AgentCapability {
  readonly name: string;
  readonly level: 'expert' | 'proficient' | 'basic';
}
FieldTypeRequiredNotes
namestringUnique identifier (kebab-case, no @)
rolestringHuman-readable title
charterstringCharacter description or link to charter
modelstringModel preference (e.g., "claude-sonnet-4", "claude-haiku-4.5")
toolsstring[]Allowed tools (e.g., ["grep", "edit", "view"])
capabilitiesobject[]Capability list with proficiency levels
statusenumLifecycle: 'active' (default), 'inactive', 'retired'

Capability Levels:

  • expert — core competency
  • proficient — strong knowledge
  • basic — foundational

defineRouting(config)

Define typed routing rules with pattern matching, priority, and tier.

const routing = defineRouting({
  rules: [
    { pattern: 'feature-*', agents: ['@edie'], tier: 'standard', priority: 1 },
    { pattern: 'docs-*', agents: ['@mcmanus'], tier: 'lightweight' },
    { pattern: 'test-*', agents: ['@edie', '@fenster'], tier: 'full' },
  ],
  defaultAgent: '@coordinator',
  fallback: 'coordinator',
});

Type:

interface RoutingDefinition {
  readonly rules: readonly RoutingRule[];
  readonly defaultAgent?: string;
  readonly fallback?: 'ask' | 'default-agent' | 'coordinator';
}

interface RoutingRule {
  readonly pattern: string;
  readonly agents: readonly string[];
  readonly tier?: 'direct' | 'lightweight' | 'standard' | 'full';
  readonly priority?: number;
}

Routing Tiers:

  • direct — skip ceremony, execute immediately
  • lightweight — quick handoff, minimal overhead
  • standard — normal workflow with decision checkpoints
  • full — maximum governance, review gates

Fallback Behavior:

  • ask — ask the user for routing direction
  • default-agent — use the defaultAgent
  • coordinator — delegate to the Squad coordinator

defineCeremony(config)

Define ceremonies (standups, retros, etc.) with schedule, participants, and agenda.

const standup = defineCeremony({
  name: 'standup',
  trigger: 'schedule',
  schedule: '0 9 * * 1-5',  // Cron: 9 AM weekdays
  participants: ['@edie', '@mcmanus', '@fenster'],
  agenda: 'Yesterday / Today / Blockers',
});

Type:

interface CeremonyDefinition {
  readonly name: string;
  readonly trigger?: string;
  readonly schedule?: string;
  readonly participants?: readonly string[];
  readonly agenda?: string;
  readonly hooks?: readonly string[];
}
FieldTypeRequiredNotes
namestringCeremony name
triggerstringEvent that triggers ceremony (e.g., "schedule", "pr-merged")
schedulestringCron expression or human-readable frequency
participantsstring[]Agent refs
agendastringFreeform agenda or template
hooksstring[]Hook names that fire during ceremony

defineHooks(config)

Define the governance hook pipeline — write paths, blocked commands, PII scrubbing, reviewer gates.

const hooks = defineHooks({
  allowedWritePaths: ['src/**', 'test/**', '.squad/**'],
  blockedCommands: ['rm -rf /', 'DROP TABLE', 'delete from'],
  maxAskUser: 3,
  scrubPii: true,
  reviewerLockout: true,
});

Type:

interface HooksDefinition {
  readonly allowedWritePaths?: readonly string[];
  readonly blockedCommands?: readonly string[];
  readonly maxAskUser?: number;
  readonly scrubPii?: boolean;
  readonly reviewerLockout?: boolean;
}
FieldTypeRequiredNotes
allowedWritePathsstring[]Glob patterns (e.g., ["src/**", "docs/**"])
blockedCommandsstring[]Dangerous commands to block
maxAskUsernumberMax user prompts per session
scrubPiibooleanAnonymize email/phone before logging
reviewerLockoutbooleanPrevent PR author from approving own PR

defineCasting(config)

Define casting configuration — universe allowlists and overflow strategy.

const casting = defineCasting({
  allowlistUniverses: ['The Usual Suspects', 'Breaking Bad'],
  overflowStrategy: 'generic',
  capacity: {
    'The Usual Suspects': 8,
    'Breaking Bad': 5,
  },
});

Type:

interface CastingDefinition {
  readonly allowlistUniverses?: readonly string[];
  readonly overflowStrategy?: 'reject' | 'generic' | 'rotate';
  readonly capacity?: Readonly<Record<string, number>>;
}

Overflow Strategies:

  • reject — refuse to cast if universe is at capacity
  • generic — use a generic persona
  • rotate — cycle through available universes

defineTelemetry(config)

Define OpenTelemetry configuration for observability.

const telemetry = defineTelemetry({
  enabled: true,
  endpoint: 'http://localhost:4317',
  serviceName: 'squad-prod',
  sampleRate: 1.0,
  aspireDefaults: true,
});

Type:

interface TelemetryDefinition {
  readonly enabled?: boolean;
  readonly endpoint?: string;
  readonly serviceName?: string;
  readonly sampleRate?: number;
  readonly aspireDefaults?: boolean;
}
FieldTypeRequiredNotes
enabledbooleanMaster on/off switch (default: false)
endpointstringOTLP receiver endpoint (e.g., "http://localhost:4317")
serviceNamestringOTel service name
sampleRatenumberTrace sample rate (0.0 – 1.0)
aspireDefaultsbooleanApply Aspire-compatible defaults for dashboard integration

defineSkill()

Define a reusable skill that agents can load on demand.

import { defineSkill } from '@bradygaster/squad-sdk';

const gitWorkflow = defineSkill({
  name: 'git-workflow',
  description: 'Squad branching model and PR conventions',
  domain: 'workflow',
  confidence: 'high',
  source: 'manual',
  content: `
    ## Patterns
    - Branch from dev: squad/{issue-number}-{slug}
    - PRs target dev, not main
    - Three-branch model: dev → insiders → main
  `,
});
FieldTypeRequiredDescription
namestringUnique skill name (kebab-case)
descriptionstringWhat this skill teaches
domainstringCategory (e.g., ‘orchestration’, ‘testing’)
confidence'low' | 'medium' | 'high'Skill maturity level
source'manual' | 'observed' | 'earned' | 'extracted'How the skill was learned
contentstringThe skill body (patterns, examples)
toolsSkillTool[]MCP tools relevant to this skill

Skills defined in squad.config.ts are generated to .squad/skills/{name}/SKILL.md when you run squad build.


defineSquad(config)

Compose all builders into a single SDK config.

export default defineSquad({
  version: '1.0.0',
  team: defineTeam({ /* ... */ }),
  agents: [
    defineAgent({ /* ... */ }),
    defineAgent({ /* ... */ }),
  ],
  routing: defineRouting({ /* ... */ }),
  ceremonies: [defineCeremony({ /* ... */ })],
  hooks: defineHooks({ /* ... */ }),
  casting: defineCasting({ /* ... */ }),
  telemetry: defineTelemetry({ /* ... */ }),
});

Type:

interface SquadSDKConfig {
  readonly version?: string;
  readonly team: TeamDefinition;
  readonly agents: readonly AgentDefinition[];
  readonly routing?: RoutingDefinition;
  readonly ceremonies?: readonly CeremonyDefinition[];
  readonly hooks?: HooksDefinition;
  readonly casting?: CastingDefinition;
  readonly telemetry?: TelemetryDefinition;
}

squad build Command

Compile TypeScript Squad definitions into .squad/ markdown.

Usage

squad build [options]

Flags

FlagWhat it does
--checkValidate without writing; exit 0 if matches disk, exit 1 if drift
--dry-runShow what would be generated without writing
--watchRebuild on .ts file changes (coming soon)

Examples

Rebuild all generated files:

squad build

Validate that generated files match disk:

squad build --check

This is useful in CI/CD to ensure the config is in sync:

# .github/workflows/squad-check.yml
- name: Check Squad config
  run: squad build --check

Preview changes before writing:

squad build --dry-run

Output:

ℹ️ Dry run — would generate 6 file(s):

  create  .squad/team.md
  create  .squad/routing.md
  create  .squad/agents/edie/charter.md
  create  .squad/agents/mcmanus/charter.md
  create  .squad/ceremonies.md
  overwrite .squad/hooks.md

Generated Files

squad build generates:

FileConditionContains
.squad/team.mdAlwaysTeam roster, member list, project context
.squad/routing.mdIf routing definedRouting rules and default agent
.squad/agents/{name}/charter.mdFor each agentAgent role, model, tools, capabilities
.squad/ceremonies.mdIf ceremonies definedCeremony schedule and agenda

Protected files — never overwritten:

  • .squad/decisions.md / .squad/decisions-archive.md
  • .squad/agents/*/history.md
  • .squad/orchestration-log/*

Config Discovery

squad build discovers your config in this order:

  1. squad/index.ts — SDK-First config
  2. squad.config.ts — Alternative location
  3. squad.config.js — JavaScript fallback

The config must export one of:

  • export default config — default export
  • export { config } — named export
  • export { squadConfig } — alias

Validation

All builders perform runtime validation with typed error messages.

If validation fails:

defineAgent({
  name: 'edie',
  // Missing required 'role' field
});
// BuilderValidationError: [defineAgent] "role" must be a non-empty string

Validation is:

  • Runtime — catches errors at build time, not runtime
  • Typed — assertions narrow TypeScript types
  • Descriptive — error messages include field path and expected type
  • No dependencies — no Zod, JSON Schema, or external validators

Migration Guide: From Manual to SDK-First

Before (manual .squad/team.md)

# Squad Team — Core Squad

## Members

| Name | Role | Charter |
|------|------|---------|
| Edie | TypeScript Engineer | `.squad/agents/edie/charter.md` |

You manually maintain this file and agent charters.

After (SDK-First)

export default defineSquad({
  team: defineTeam({
    name: 'Core Squad',
    members: ['@edie'],
  }),
  agents: [
    defineAgent({
      name: 'edie',
      role: 'TypeScript Engineer',
    }),
  ],
});

Run squad build and the markdown is generated. Version control your TypeScript, not the markdown.


Best Practices

  1. Keep squad.config.ts at project root — easier to discover
  2. Use defineSquad() for composition — ensures all sections are validated together
  3. Add capabilities — helps the coordinator understand agent expertise
  4. Document project context — inject into agent prompts via TeamDefinition.projectContext
  5. Use consistent tier names — team members should understand routing tiers
  6. Validate in CI — add squad build --check to your CI pipeline
  7. Don’t edit generated markdown — it will be overwritten; edit squad.config.ts instead

Examples

Full SDK-First Config

import {
  defineSquad,
  defineTeam,
  defineAgent,
  defineRouting,
  defineHooks,
  defineCasting,
  defineTelemetry,
} from '@bradygaster/squad-sdk';

export default defineSquad({
  version: '1.0.0',

  team: defineTeam({
    name: 'Platform Squad',
    description: 'Full-stack platform engineering team',
    projectContext: `
      React/Node monorepo. TypeScript strict mode.
      Uses Vitest for testing, ESLint for linting.
      Deploys to Vercel (frontend) and AWS Lambda (backend).
    `,
    members: ['@edie', '@mcmanus', '@fenster', '@hockney'],
  }),

  agents: [
    defineAgent({
      name: 'edie',
      role: 'TypeScript Engineer',
      model: 'claude-sonnet-4',
      tools: ['grep', 'edit', 'powershell', 'view'],
      capabilities: [
        { name: 'type-system', level: 'expert' },
        { name: 'testing', level: 'proficient' },
        { name: 'architecture', level: 'proficient' },
      ],
    }),

    defineAgent({
      name: 'mcmanus',
      role: 'DevRel',
      model: 'claude-haiku-4.5',
      tools: ['grep', 'view', 'edit'],
      capabilities: [
        { name: 'documentation', level: 'expert' },
        { name: 'developer-experience', level: 'expert' },
      ],
    }),

    defineAgent({
      name: 'fenster',
      role: 'Test Lead',
      model: 'claude-sonnet-4',
      capabilities: [
        { name: 'testing', level: 'expert' },
        { name: 'qa', level: 'proficient' },
      ],
    }),

    defineAgent({
      name: 'hockney',
      role: 'Frontend Specialist',
      model: 'claude-opus-4.6',
      capabilities: [
        { name: 'frontend', level: 'expert' },
        { name: 'ui-ux', level: 'proficient' },
      ],
    }),
  ],

  routing: defineRouting({
    rules: [
      {
        pattern: 'feature-*',
        agents: ['@edie', '@hockney'],
        tier: 'standard',
        priority: 1,
      },
      {
        pattern: 'docs-*',
        agents: ['@mcmanus'],
        tier: 'lightweight',
      },
      {
        pattern: 'test-*',
        agents: ['@fenster'],
        tier: 'standard',
      },
      {
        pattern: 'bug-*',
        agents: ['@edie', '@fenster'],
        tier: 'full',
        priority: 0,
      },
    ],
    defaultAgent: '@coordinator',
    fallback: 'coordinator',
  }),

  hooks: defineHooks({
    allowedWritePaths: [
      'src/**',
      'test/**',
      'docs/**',
      '.squad/**',
      'package.json',
    ],
    blockedCommands: ['rm -rf /', 'DROP TABLE'],
    maxAskUser: 3,
    scrubPii: true,
    reviewerLockout: true,
  }),

  casting: defineCasting({
    allowlistUniverses: ['The Usual Suspects', 'Breaking Bad'],
    overflowStrategy: 'generic',
    capacity: {
      'The Usual Suspects': 8,
    },
  }),

  telemetry: defineTelemetry({
    enabled: true,
    endpoint: 'http://localhost:4317',
    serviceName: 'squad-platform',
    sampleRate: 1.0,
    aspireDefaults: true,
  }),
});

See Also