Skip to content

PRD: SDK-First Squad Mode — TypeScript-based team definitions with telemetry, type safety, and programmatic routing #194

Closed

Description

@bradygaster

Overview

SDK-First Squad Mode introduces an opt-in TypeScript-based workflow for defining squads, agents, routing, ceremonies, and hooks — with compile-time validation and full observability. Instead of hand-editing markdown files in .squad/, power users define their team in TypeScript using typed builder functions (defineTeam(), defineAgent(), defineRouting(), etc.), then run squad build to generate the standard .squad/ directory for Copilot CLI compatibility.

Who it's for: Developer teams who want type safety, programmatic routing, testable configurations, and — critically — telemetry. SDK mode activates the existing OpenTelemetry infrastructure that is currently dead code in markdown-only mode.

Why it matters: Squad already has 6+ OTel runtime modules (otel-init, otel-metrics, otel-bridge, event-bus, squad-observer, cost-tracker) that cannot fire in markdown-only mode because Copilot CLI owns the runtime loop. SDK mode flips this: the SDK owns the runtime, EventBus fires on every event, and OTel bridge captures spans. This is the activation path for observability that the team has been building toward since Wave A.

Design principle: Dual-mode, not replacement. Markdown stays the default — zero-dependency, works everywhere, non-developer accessible. SDK is the power-user upgrade path. The bridge (squad build: TS → markdown) lets both modes coexist. This is the Next.js pattern: simple pages for beginners, full framework for power users.


Problem Statement

1. Telemetry Blindness

Squad runs inside Copilot CLI's sandbox. We have no hook into session creation, tool calls, routing decisions, or token usage. The OTel infrastructure (initSquadTelemetry(), EventBus.subscribe(), SquadObserver, metric recorders) exists but has no activation path. Teams running Squad in production cannot answer: "How many tokens did Fenster use on that feature?" or "Why did the coordinator route to Verbal instead of Edie?"

2. No Type Safety for Team Definitions

team.md, routing.md, and agent charters are free-form markdown. A typo in an agent name in routing.md silently routes to nobody. A ceremony referencing a non-existent agent fails at runtime. There is no compile-time validation that the team definition is internally consistent.

3. No Programmatic Routing

Routing rules in routing.md are markdown tables parsed at runtime. Complex routing logic (e.g., "route security issues to Baer, unless Baer is overloaded, then fall back to Kujan with context about the security domain") requires markdown gymnastics. Functions are the natural unit for routing logic.

4. No Testable Configuration

You cannot unit test that your routing rules are correct, that your hook pipeline blocks the right commands, or that your ceremony definitions are well-formed. With TypeScript definitions, you can import your squad config into Vitest and assert against it.

5. No Composability

Multi-squad configurations (e.g., importing agents from a shared library squad into a project squad) require manual copy-paste of markdown files. TypeScript imports solve this naturally.


Proposed Solution: Dual-Mode Architecture with Bridge

┌─────────────────────────────────────────────────────┐
│  SDK Mode (TypeScript source of truth)              │
│                                                     │
│  squad/                                             │
│  ├── team.ts        → defineTeam()                  │
│  ├── agents/                                        │
│  │   ├── keaton.ts  → defineAgent()                 │
│  │   └── fenster.ts → defineAgent()                 │
│  ├── routing.ts     → defineRouting()               │
│  ├── ceremonies.ts  → defineCeremony()              │
│  ├── hooks.ts       → defineHooks()                 │
│  └── index.ts       → export { team, agents, ... }  │
│                                                     │
│  squad build                                        │
│  ─────────── ↓ generates ↓ ──────────               │
│                                                     │
│  .squad/              (standard markdown format)    │
│  ├── team.md          (generated, do not edit)      │
│  ├── routing.md       (generated, do not edit)      │
│  ├── agents/                                        │
│  │   ├── keaton/charter.md  (generated)             │
│  │   └── fenster/charter.md (generated)             │
│  ├── ceremonies.md    (generated)                   │
│  ├── decisions.md     (PRESERVED — user-owned)      │
│  └── agents/*/history.md (PRESERVED — user-owned)   │
│                                                     │
│  .squad/decisions.md stays markdown (append-only)   │
│  .squad/agents/*/history.md stays markdown           │
│  .squad/orchestration-log/ stays markdown            │
└─────────────────────────────────────────────────────┘

Key invariant: squad build NEVER overwrites decisions.md, history.md, or orchestration-log/. These are append-only, human-readable, merge-driver-compatible files that stay markdown regardless of mode.


CLI Commands

squad init --sdk

Scaffolds an SDK-mode squad project.

squad init --sdk

What it creates:

squad/
├── team.ts           # Team definition with defineTeam()
├── agents/
│   └── index.ts      # Agent definitions with defineAgent()
├── routing.ts        # Routing rules with defineRouting()
├── ceremonies.ts     # Ceremony definitions with defineCeremony()
├── hooks.ts          # Hook pipeline with defineHooks()
├── index.ts          # Barrel export
└── tsconfig.json     # TypeScript config (extends root)

.squad/               # Generated (gitignored except decisions/history)
├── team.md
├── routing.md
├── decisions.md      # User-owned, NOT generated
└── agents/*/history.md  # User-owned, NOT generated

Flags:

  • --sdk — Enable SDK mode (TypeScript definitions)
  • --template <name> — Starter template (minimal, full, monorepo)
  • --telemetry — Pre-wire OTel configuration (adds telemetry.ts with Aspire/OTLP defaults)
  • --no-build — Skip initial squad build after scaffolding

Behavior:

  1. Detects project type (detectProjectType()) for smart defaults
  2. Scaffolds squad/ directory with typed definitions
  3. Adds @bradygaster/squad-sdk as a devDependency
  4. Runs squad build to generate initial .squad/ directory
  5. Adds .squad/team.md and .squad/routing.md to .gitignore (generated files)
  6. Preserves .squad/decisions.md and .squad/agents/*/history.md in git

squad build

Compiles TypeScript squad definitions into .squad/ markdown format.

squad build [--watch] [--check] [--dry-run]

Flags:

  • --watch — Rebuild on file changes (uses SquadObserver.classifyFile())
  • --check — Validate without writing (CI mode)
  • --dry-run — Show what would be generated without writing

What it does:

  1. Loads squad/index.ts (or squad.config.ts for hybrid mode)
  2. Validates the typed config at compile time (TypeScript) and runtime (Zod schemas)
  3. Generates .squad/team.md from defineTeam() output
  4. Generates .squad/routing.md from defineRouting() output
  5. Generates .squad/agents/*/charter.md from defineAgent() outputs
  6. Generates .squad/ceremonies.md from defineCeremony() outputs
  7. Stamps generated files with <!-- Generated by squad build. Do not edit. --> header
  8. SKIPS decisions.md, history.md, orchestration-log/ (user-owned)

Exit codes:

  • 0 — Success
  • 1 — Validation error (type mismatch, missing agent reference, etc.)
  • 2 — File system error

squad run (SDK Runtime Mode)

Launches the squad with SDK owning the runtime loop — activating telemetry.

squad run [--telemetry] [--otel-endpoint <url>]

Flags:

  • --telemetry — Activate OTel pipeline (equivalent to SQUAD_TELEMETRY=1)
  • --otel-endpoint <url> — OTLP endpoint (default: http://localhost:4317)
  • --aspire — Use Aspire dashboard defaults

What it does:

  1. Calls initSquadTelemetry() from otel-init.ts
  2. Creates EventBus instance and subscribes SquadObserver
  3. Starts HookPipeline with policy config from defineHooks()
  4. Launches coordinator with CopilotSession from SDK client
  5. Routes work through typed routing functions (not markdown table parsing)
  6. Fires events on every lifecycle transition (session:created, session:message, coordinator:routing, session:tool_call, etc.)
  7. OTel bridge captures spans for every event

squad migrate --to-sdk

Converts an existing markdown-only squad to SDK mode.

squad migrate --to-sdk [--dry-run] [--preserve-markdown]

Flags:

  • --to-sdk — Convert markdown → TypeScript
  • --dry-run — Show migration plan without executing
  • --preserve-markdown — Keep original .squad/ files as backup

What it does:

  1. Reads .squad/team.md → generates squad/team.ts with defineTeam()
  2. Reads .squad/routing.md → generates squad/routing.ts with defineRouting()
  3. Reads .squad/agents/*/charter.md → generates squad/agents/*.ts with defineAgent()
  4. Reads .squad/ceremonies.md → generates squad/ceremonies.ts with defineCeremony()
  5. Creates squad/index.ts barrel
  6. Runs squad build --check to validate round-trip fidelity
  7. Updates .gitignore for generated files
  8. Preserves decisions.md, history.md, orchestration-log/ untouched

Coordinator Prompt Changes

The coordinator (squad.agent.md / copilot-instructions.md) needs to detect and adapt to SDK mode.

Detection

The coordinator checks for SDK mode presence:

## Mode Detection

If the file `squad/index.ts` exists in the project root:
  → This is an SDK-mode squad. TypeScript definitions in `squad/` are the source of truth.
  → `.squad/` markdown files are generated artifacts. Do NOT edit them directly.
  → Direct users to edit `squad/*.ts` files and run `squad build`.

If `squad/index.ts` does NOT exist:
  → This is a markdown-mode squad. `.squad/` files are the source of truth.
  → Edit `.squad/*.md` files directly as usual.

Behavioral Changes in SDK Mode

  1. Routing awareness: The coordinator reads routing logic from the compiled config (loaded via loadConfig()) rather than parsing routing.md tables. This enables the coordinator to understand programmatic routing rules that can't be expressed in markdown tables.

  2. Agent discovery: Agent capabilities are read from the typed defineAgent() definitions (compiled and available via squad build output), giving the coordinator richer metadata (tool lists, model tier preferences, capability tags) than markdown charters alone.

  3. Edit redirection: When a user says "add a new agent" or "change the routing," the coordinator routes to the appropriate squad member with instructions to modify squad/*.ts files, NOT .squad/*.md files.

  4. Build reminder: After any structural change (new agent, routing update, ceremony change), the coordinator reminds the user to run squad build to regenerate .squad/ artifacts.


Type Surface

Core Builder Functions

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

// Team definition
export const team = defineTeam({
  name: 'squad-sdk',
  description: 'The programmable multi-agent runtime for GitHub Copilot',
  project: {
    stack: ['TypeScript', 'Node.js', 'Vitest'],
    repository: 'bradygaster/squad',
  },
  agents: [keaton, fenster, verbal, edie /* ... */],
  coordinator: {
    routingMode: 'eager',       // 'eager' | 'conservative' | 'custom'
    scribeAutoRuns: false,
    allowRecursiveSpawn: false,
  },
});

Agent Builder

export const keaton = defineAgent({
  name: 'Keaton',
  role: 'Lead',
  expertise: ['architecture', 'code-review', 'trade-offs'],
  style: 'Decisive. Opinionated when it matters.',
  owns: ['Product direction', 'Architectural decisions', 'Quality gates'],
  model: { tier: 'premium', preferred: 'claude-opus-4.6' },
  tools: ['github-mcp-server', 'code-review'],
  capabilities: {
    canWrite: ['.squad/decisions/**', 'docs/proposals/**'],
    canRead: ['**/*'],
  },
});

Routing Builder

export const routing = defineRouting({
  rules: [
    {
      match: (task) => task.workType === 'architecture',
      route: keaton,
      confidence: 'high',
    },
    {
      match: (task) => task.workType === 'core-runtime',
      route: fenster,
      confidence: 'high',
    },
    {
      match: (task) => task.files?.some(f => f.endsWith('.test.ts')),
      route: hockney,
      confidence: 'medium',
    },
    {
      // Complex routing: security with fallback
      match: (task) => task.workType === 'security',
      route: (task, context) => {
        if (context.agentLoad('baer') > 0.8) {
          return { agent: kujan, context: 'Security fallback — Baer at capacity' };
        }
        return { agent: baer };
      },
      confidence: 'high',
    },
  ],
  moduleOwnership: {
    'src/adapter/': { primary: fenster, secondary: kujan },
    'src/agents/': { primary: verbal, secondary: fenster },
    'src/runtime/': { primary: fortier, secondary: fenster },
  },
  fanOut: {
    trigger: /^team,?\s/i,     // "Team, ..." messages fan out
    parallel: true,
  },
});

Key Types

interface TeamDefinition {
  name: string;
  description: string;
  project: ProjectContext;
  agents: AgentDefinition[];
  coordinator: CoordinatorConfig;
}

interface AgentDefinition {
  name: string;
  role: string;
  expertise: string[];
  style: string;
  owns: string[];
  model?: ModelPreference;
  tools?: string[];
  capabilities?: AgentCapabilities;
  hooks?: AgentHooks;
}

interface RoutingRule {
  match: (task: TaskContext) => boolean;
  route: AgentDefinition | RoutingFunction;
  confidence: 'high' | 'medium' | 'low';
  description?: string;
}

interface RoutingFunction {
  (task: TaskContext, context: RoutingContext): RoutingDecision;
}

interface TaskContext {
  workType: WorkType;
  description: string;
  files?: string[];
  labels?: string[];
  priority?: Priority;
}

interface RoutingContext {
  agentLoad: (name: string) => number;
  recentRouting: RoutingDecision[];
  activeAgents: string[];
}

type WorkType =
  | 'feature-dev' | 'bug-fix' | 'testing' | 'documentation'
  | 'refactoring' | 'architecture' | 'research' | 'triage'
  | 'security' | 'meta' | string;  // extensible

Import/Export

squad export with SDK Mode

When exporting an SDK-mode squad, squad-export.json includes both the compiled markdown AND the TypeScript source:

{
  "version": "2.0",
  "exported_at": "2026-03-06T...",
  "squad_version": "0.9.0",
  "mode": "sdk",
  "sdk_sources": {
    "squad/team.ts": "export const team = defineTeam({ ... })",
    "squad/agents/keaton.ts": "export const keaton = defineAgent({ ... })",
    "squad/routing.ts": "export const routing = defineRouting({ ... })",
    "squad/ceremonies.ts": "...",
    "squad/hooks.ts": "...",
    "squad/index.ts": "..."
  },
  "compiled": {
    "casting": { "registry": { ... }, "policy": { ... }, "history": { ... } },
    "agents": {
      "keaton": { "charter": "...", "history": "..." }
    },
    "skills": ["..."]
  }
}

Key decisions:

  • version: "2.0" — New schema version to distinguish from 1.0 (markdown-only)
  • mode: "sdk" | "markdown" — Explicit mode field
  • sdk_sources — Raw TypeScript source files for full round-trip fidelity
  • compiled — Standard 1.0-compatible structure for backward compatibility
  • History/decisions are always in compiled — never in sdk_sources (they're markdown regardless of mode)

squad import with SDK Mode

squad import squad-export.json [--mode sdk|markdown|auto]
  • --mode auto (default): Imports in the mode specified by the export file's mode field
  • --mode sdk: Forces SDK mode import even from a 1.0 export (generates TypeScript from markdown)
  • --mode markdown: Forces markdown import even from a 2.0 export (uses compiled block only)

Round-trip fidelity: squad export | squad import --mode sdk | squad build must produce identical .squad/ output (modulo timestamps).

Backward Compatibility

  • 2.0 exports always include compiled block → importable by older 1.0 tooling
  • 1.0 exports are importable into SDK mode via --mode sdk (generates TypeScript from parsed markdown)
  • squad-export.json remains the single portable format

Casting in SDK Mode

Casting (agent personality/universe assignments) moves from JSON config to typed definitions:

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

export const casting = defineCasting({
  universe: 'The Usual Suspects',
  allowlist: ['The Usual Suspects', 'Breaking Bad', 'The Wire', 'Firefly'],
  overflowStrategy: 'generic',  // when universe exhausted
  assignments: {
    keaton: { character: 'Keaton', persistent: true },
    fenster: { character: 'Fenster', persistent: true },
    verbal: { character: 'Verbal', persistent: true },
    // ... auto-assigned agents omitted, handled by CastingEngine
  },
  policy: {
    legacyNamed: false,        // no pre-existing name overrides
    recastOnConflict: 'skip',  // 'skip' | 'reassign' | 'error'
  },
});

What stays in JSON: casting-history.json (append-only log of casting decisions) stays as a JSON file managed by CastingEngine — same rationale as decisions.md staying markdown. It's operational history, not configuration.

Build output: squad build generates casting-registry.json and casting-policy.json from the typed defineCasting() output, maintaining compatibility with the current CastingEngine runtime.


Ceremonies in SDK Mode

Ceremonies (team rituals like standups, retros, reviews) get typed definitions:

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

export const standup = defineCeremony({
  name: 'standup',
  description: 'Daily sync — what each agent accomplished and plans next',
  trigger: 'manual',          // 'manual' | 'schedule' | 'event'
  participants: [keaton, fenster, verbal, edie, hockney],
  moderator: keaton,
  format: {
    each: (agent) => [
      `What ${agent.name} accomplished since last standup`,
      `What ${agent.name} plans to work on next`,
      `Any blockers`,
    ],
    output: 'summary',        // 'summary' | 'full-transcript' | 'decisions-only'
  },
  hooks: {
    afterCeremony: async (result) => {
      // Auto-append decisions to decisions.md
      if (result.decisions.length > 0) {
        await appendToDecisions(result.decisions);
      }
    },
  },
});

export const codeReview = defineCeremony({
  name: 'code-review',
  description: 'Structured review of proposed changes',
  trigger: { event: 'pull_request.opened' },
  participants: (pr) => {
    // Dynamic participant selection based on changed files
    const owners = resolveCodeOwners(pr.changedFiles);
    return [keaton, ...owners];     // Lead always participates
  },
  format: {
    phases: ['architecture', 'correctness', 'style', 'testing'],
    output: 'review-comments',
  },
});

Build output: squad build generates .squad/ceremonies.md with human-readable ceremony descriptions. The programmatic hooks and dynamic participant selection are noted as "SDK-only features" in the generated markdown.


Telemetry Activation

This is the killer feature. SDK mode finally activates the OTel infrastructure.

Current State (Dead Code)

The SDK already has:

Module What it does Status today
otel-init.ts initSquadTelemetry() — bootstraps OTel SDK ⚠️ Never called in markdown mode
otel-metrics.ts recordAgentSpawn(), recordAgentDuration(), etc. ⚠️ No runtime to trigger
otel-bridge.ts OTLP exporter to collector ⚠️ No connection
event-bus.ts EventBus with subscribe() API ⚠️ No subscribers
squad-observer.ts classifyFile(), file change detection ⚠️ No watcher active
cost-tracker.ts Token/cost per agent per session ⚠️ No session data

SDK Mode Activation

// squad/telemetry.ts
import { defineTelemetry } from '@bradygaster/squad-sdk';

export const telemetry = defineTelemetry({
  enabled: true,
  provider: 'otlp',                    // 'otlp' | 'aspire' | 'console' | 'custom'
  endpoint: 'http://localhost:4317',    // OTLP gRPC endpoint
  serviceName: 'squad-sdk',
  metrics: {
    agentSpawn: true,
    agentDuration: true,
    agentTokens: true,
    routingDecisions: true,
    toolCalls: true,
    sessionLifecycle: true,
  },
  traces: {
    sampleRate: 1.0,               // 100% in dev, lower in prod
    propagation: 'w3c',            // W3C TraceContext
  },
  costTracking: {
    enabled: true,
    budgetPerSession: null,         // null = unlimited
    alertThreshold: 10.00,          // USD — warn at $10/session
  },
});

What Lights Up

When squad run --telemetry executes:

  1. initSquadTelemetry() bootstraps the OTel SDK with the config from defineTelemetry()
  2. EventBus starts firing on every lifecycle event:
    • session:created / session:destroyed — session lifecycle spans
    • session:message — user message received (with token count)
    • coordinator:routing — routing decision made (which agent, why, confidence)
    • session:tool_call — tool invocation (which tool, duration, success/failure)
    • agent:milestone — agent completed a significant step
  3. otel-bridge exports spans to the configured OTLP endpoint
  4. otel-metrics records counters and histograms:
    • squad.agent.spawn_count (counter, by agent name)
    • squad.agent.duration_ms (histogram, by agent name)
    • squad.agent.tokens (counter, by agent name, input/output)
    • squad.routing.decision_count (counter, by route target)
    • squad.session.cost_usd (gauge, by session)
  5. cost-tracker aggregates token usage and cost per agent per session
  6. squad-observer watches for file changes and classifies them

Aspire Integration

For the Aspire dashboard (per Brady's directive):

squad run --aspire
# Equivalent to:
#   SQUAD_TELEMETRY=1
#   OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
#   OTEL_SERVICE_NAME=squad-sdk

This enables the Aspire dashboard to show:

  • Distributed traces across agent interactions
  • Token usage dashboards per agent
  • Routing decision flow visualization
  • Cost tracking over time

Migration Path

Markdown → SDK Upgrade

# Step 1: Preview what migration will do
squad migrate --to-sdk --dry-run

# Step 2: Execute migration
squad migrate --to-sdk

# Step 3: Validate round-trip
squad build --check

# Step 4: Run with telemetry
squad run --telemetry

What squad migrate --to-sdk Does

  1. Parses .squad/team.md → extracts team metadata, agent list
  2. Parses .squad/routing.md → extracts routing rules table, module ownership
  3. Parses .squad/agents/*/charter.md → extracts agent definitions
  4. Parses .squad/ceremonies.md → extracts ceremony definitions
  5. Generates squad/team.ts, squad/agents/*.ts, squad/routing.ts, squad/ceremonies.ts, squad/hooks.ts (empty scaffold), squad/index.ts
  6. Validates by running squad build --check to ensure generated markdown matches original
  7. Updates .gitignore to ignore generated .squad/ files (except decisions/history)
  8. Creates squad/tsconfig.json

Backward Compatibility Guarantees

  • Markdown mode is never removed. squad init (no --sdk) continues to work exactly as today.
  • SDK mode always generates .squad/. Any tool that reads .squad/ (Copilot CLI, VS Code, GitHub Actions) continues to work.
  • squad build is idempotent. Running it twice produces identical output.
  • No breaking changes to squad-export.json v1.0. The compiled block in v2.0 exports is identical to v1.0 format.
  • squad.config.ts continues to work. Existing typed config at project root is respected; SDK mode extends it, doesn't replace it.

What Stays Markdown

File Why it stays markdown
.squad/decisions.md Append-only semantics. merge=union git driver. Human-readable history. Multiple agents append concurrently. Moving to TS would lose merge-driver compatibility and add compile step to append operations.
.squad/agents/*/history.md Same rationale as decisions. Append-only, merge-driver-compatible, human-readable learning log.
.squad/orchestration-log/ Diagnostic output. Append-only. Not configuration. No benefit from typing.
.squad/log/ Session logs. Same as orchestration-log.
.squad/decisions/inbox/*.md Decision proposals. Written by agents asynchronously. Scribe merges into decisions.md. Markdown is the right format for prose proposals.

The principle: Configuration is TypeScript. History is markdown. If it's append-only and benefits from merge=union, it stays markdown.


Acceptance Criteria

Phase 1: squad build Bridge

  • squad build reads squad/index.ts and generates .squad/team.md, .squad/routing.md, .squad/agents/*/charter.md, .squad/ceremonies.md
  • Generated markdown files include <!-- Generated by squad build. Do not edit. --> header
  • squad build NEVER touches decisions.md, history.md, or orchestration-log/
  • squad build --check exits 0 if generated files match disk, 1 if drift detected
  • squad build --watch rebuilds on .ts file changes in squad/
  • squad build --dry-run prints diff without writing
  • Round-trip test: squad migrate --to-sdk && squad build produces markdown equivalent to original
  • All existing tests (2931+) continue to pass
  • defineTeam(), defineAgent(), defineRouting(), defineCeremony() builders export from @bradygaster/squad-sdk
  • Zod runtime validation catches invalid configs with actionable error messages

Phase 2: squad init --sdk

  • squad init --sdk scaffolds squad/ directory with typed definitions
  • squad init --sdk --template minimal creates minimal 1-agent squad
  • squad init --sdk --template full creates full squad with all current agents
  • squad init --sdk --telemetry adds squad/telemetry.ts with OTel defaults
  • Scaffolded project compiles with tsc --noEmit (zero type errors)
  • squad init --sdk runs squad build automatically (unless --no-build)
  • .gitignore updated correctly (generated .squad/ files ignored, decisions/history preserved)
  • squad doctor detects SDK mode and validates squad/ directory structure

Phase 3: squad run with SDK Runtime

  • squad run launches coordinator with SDK owning the runtime loop
  • squad run --telemetry calls initSquadTelemetry() and activates EventBus
  • OTel spans exported for: session:created, coordinator:routing, session:tool_call, agent:milestone
  • Metrics recorded: squad.agent.spawn_count, squad.agent.duration_ms, squad.agent.tokens
  • squad run --aspire configures Aspire-compatible OTLP endpoint
  • Cost tracker aggregates token usage per agent per session
  • Routing uses typed routing functions (not markdown table parsing)
  • Hook pipeline enforces policy config from defineHooks()
  • Ctrl+C gracefully shuts down, flushes pending telemetry

Phase 4: squad migrate --to-sdk

  • squad migrate --to-sdk converts existing .squad/ to squad/*.ts files
  • squad migrate --to-sdk --dry-run shows plan without executing
  • squad migrate --to-sdk --preserve-markdown keeps original files as backup
  • Migration validates round-trip with squad build --check
  • All agent charters, routing rules, and ceremonies survive migration
  • decisions.md and history.md untouched by migration

Import/Export

  • squad export in SDK mode produces version: "2.0" with sdk_sources and compiled blocks
  • squad import of 2.0 export restores both squad/ and .squad/ directories
  • squad import --mode markdown of 2.0 export restores only .squad/ (uses compiled block)
  • squad import --mode sdk of 1.0 export generates TypeScript from markdown
  • Backward compatibility: 2.0 export's compiled block is importable by 1.0 tooling

Phases

Phase 1: squad build Bridge (Foundation)

Dependencies: None
Estimated scope: Medium
Delivers: Builder functions, build pipeline, generated markdown, --check/--watch/--dry-run
Why first: Everything else depends on the TS → markdown bridge working correctly. This is the architectural foundation.

Phase 2: squad init --sdk (Scaffolding)

Dependencies: Phase 1
Estimated scope: Medium
Delivers: SDK mode project scaffolding, templates, doctor integration
Why second: Users need a way to create SDK-mode projects before they can use squad run.

Phase 3: squad run with SDK Runtime (Telemetry Goes Live)

Dependencies: Phase 1, Phase 2
Estimated scope: Large
Delivers: SDK-owned runtime loop, telemetry activation, programmatic routing, Aspire integration
Why third: This is the highest-value phase but depends on the build pipeline and scaffolding being solid.

Phase 4: squad migrate --to-sdk (Conversion)

Dependencies: Phase 1, Phase 2
Estimated scope: Medium
Delivers: Markdown → SDK migration, round-trip validation, backward compatibility
Why last: Migration is a convenience feature. Users can manually create SDK squads via init --sdk until this ships.


Risks & Mitigations

Risk Impact Likelihood Mitigation
Build step friction — SDK mode requires squad build before Copilot CLI can use the squad High High squad build --watch for dev. CI integration docs. squad run auto-builds.
Markdown generation drift — generated markdown diverges from hand-written quality Medium Medium Golden file tests. Round-trip validation in CI. Human-review of generated output.
TypeScript barrier — non-developer users can't use SDK mode Medium Low Markdown mode stays default. SDK mode is explicitly opt-in. Generated markdown is human-readable.
OTel overhead — telemetry adds latency to agent interactions Medium Low Telemetry is opt-in (--telemetry flag). Sample rate configurable. Console provider for zero-overhead dev mode.
Export format fragmentation — two schema versions to maintain Low Medium compiled block ensures backward compat. Version field enables graceful degradation.
Copilot CLI compatibility — Copilot CLI may not read generated .squad/ correctly High Low Generated markdown matches hand-written format exactly. Integration tests verify Copilot CLI reads generated files.
Config complexity — TypeScript definitions become too complex for casual users Medium Medium --template minimal for simple squads. Progressive complexity: start with defineTeam() only, add routing/ceremonies/hooks incrementally.

Open Questions

  1. Should squad build be automatic? When squad run or squad start is called in SDK mode, should it implicitly run squad build first? (Leaning yes — reduce friction.)

  2. How do we handle squad.config.ts vs squad/index.ts? Currently squad.config.ts exists at project root. SDK mode introduces squad/index.ts. Do they merge? Does one supersede the other? (Leaning: squad/index.ts imports from squad.config.ts for backward compat.)

  3. Should routing functions have access to conversation history? The RoutingContext type could include recent messages for context-aware routing. Privacy implications need consideration.

  4. What's the minimum viable squad build? Could Phase 1 ship with just defineTeam() + defineAgent(), deferring routing/ceremonies to Phase 2? (Leaning: include routing — it's the most valuable typed surface.)

  5. Plugin/marketplace interaction: How do SDK-mode squads interact with the plugin marketplace? Can plugins be imported as TypeScript modules? (Defer to Phase 3+.)

  6. Multi-squad composition: TypeScript imports enable import { securityTeam } from '@company/security-squad'. How does squad build handle external squad imports? (Defer — valuable but complex.)

  7. Hot reload in squad run: Should squad run --watch (or squad build --watch + auto-restart) support hot reload of routing functions without restarting the session? (Defer — nice-to-have.)

Activity

  1. added
    squadSquad triage inbox — Lead will assign to a member
    prdProduct Requirements Document
    enhancementNew feature or improvement
    on Mar 5, 2026
  2. changed the title [-]PRD: SDK-First Squad Mode ΓÇö TypeScript-based team definitions with telemetry, type safety, and programmatic routing[/-] [+]PRD: SDK-First Squad Mode - TypeScript-based team definitions with telemetry, type safety, and programmatic routing[/+] on Mar 5, 2026
  3. changed the title [-]PRD: SDK-First Squad Mode - TypeScript-based team definitions with telemetry, type safety, and programmatic routing[/-] [+]PRD: SDK-First Squad Mode — TypeScript-based team definitions with telemetry, type safety, and programmatic routing[/+] on Mar 5, 2026
  4. deleted a comment from github-actions on Mar 5, 2026
  5. added 2 commits that reference this issue on Mar 5, 2026
  6. bradygaster commented on Mar 5, 2026

    @bradygaster
    CollaboratorAuthor

    Ideally, a user who starts with markdown charters and histories could keep using those and the TS layer would sit atop it, read from the markdown, and use the markdown as the source. In fact, i like this as a principle - ideally the things the agents learn are always in - or primarily in - markdown and read from disk when the agent runs or is spun up. if there's a way the agents could stay in memory and we avoid ANY startup time on them, too? that would be utterly amazing. i think you're alluding to this with the bridge concept but just wanted to make sure first. a user should never not be able to change their agent by changing their markdown. this should be a thing.

    the user should never have to build. that HAS to be transparent each time the agents need to build. i don't think they need to build each time they learn; ideally my "markdown as the source of truth" rule above grounds this.

    you have --preserve-markdown as "keep original squad files as backup. this would need to change based on my principle above. the markdown need to still be the source of truth. think of the agent typescript "stuff" as a facade so we can use the underlying markdown with all the features. i'd prefer not move the content into the ts. and to that end, it almost feels like you could have a single ts "agent" file. like it would be like a programmatic interface exposing instances of that interface, for each agent, but read from the markdown.

    will feed this back to squad for thought and updates on the prd.

    also i adore with dance moves the idea about knowing how much each agent cost in terms of tokens per set of work in the UI. that's amazing and much-much desired.

  7. bradygaster commented on Mar 7, 2026

    @bradygaster
    CollaboratorAuthor

    ✅ CLOSED: SDK-First PRD has been implemented. This work is complete.

  8. added a commit that references this issue on Mar 7, 2026
    58e563c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or improvementgo:needs-researchNeeds investigationprdProduct Requirements DocumentsquadSquad triage inbox — Lead will assign to a member

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions