Use this file to discover all available pages before exploring further.
Beyond the basics of casting and onboarding, Squad supports sophisticated multi-agent patterns ready for production — autonomous task pipelines, cost-aware model routing, governance hooks for PII and file guards, real-time token streaming, and pluggable storage backends that swap the local filesystem for Azure Blob Storage or SQLite. The samples in this section are working TypeScript programs you can run today.
The autonomous-pipeline sample is the comprehensive showcase of the Squad runtime. Four agents — Keyser (lead), McManus (developer), Fenster (tester), and Verbal (scribe) — autonomously pick up tasks from a queue, route follow-up work to teammates, record architectural decisions, and accumulate learnings, all while CostTracker and TelemetryCollector monitor the run.
import { CastingEngine, CostTracker, TelemetryCollector, SkillRegistry, selectResponseTier, StreamingPipeline, initSquadTelemetry, recordAgentSpawn, recordAgentDuration, recordTokenUsage,} from '@bradygaster/squad-sdk';// Cast the teamconst engine = new CastingEngine();const cast = engine.castTeam({ universe: 'usual-suspects', requiredRoles: ['lead', 'developer', 'tester', 'scribe'], teamSize: 4,});// Initialize monitoring componentsconst costTracker = new CostTracker();const telemetry = new TelemetryCollector({ enabled: true });const skillRegistry = new SkillRegistry();const streaming = new StreamingPipeline();// Build agent states with streaming sessionsconst agents = cast.map((member, i) => { const sessionId = `session-${member.name.toLowerCase()}-${i}`; streaming.attachToSession(sessionId); recordAgentSpawn(member.name); return { member, status: 'idle', tasksCompleted: 0, sessionId };});// Autonomous execution loop — agents pick up role-matched taskswhile (tasks.some((t) => t.status !== 'done')) { for (const agent of agents) { const task = findNextTask(tasks, agent.member.role); if (!task) continue; // Select the cheapest model tier that fits the task const tier = selectResponseTier(task.description, config, tierContext); // Process streaming deltas streaming.markMessageStart(agent.sessionId); await streaming.processEvent({ type: 'message_delta', ... }); // Record cost costTracker.recordUsage({ sessionId: agent.sessionId, agentName: agent.member.name, model: tier.modelTier, inputTokens: cost.inputTokens, outputTokens: cost.outputTokens, estimatedCost: cost.cost, }); // Agent coordination patterns maybeRouteFollowUp(task, agent, tasks, timeline); // squad_route maybeRecordDecision(task, agent, decisions, timeline); // squad_decide maybeRecordMemory(task, agent, memories, timeline); // squad_memory }}
Use cases: CI/CD automation, automated code review pipelines, scheduled documentation passes, and any workflow where agents should coordinate without a human in the loop.Key coordination patterns demonstrated:
Pattern
What it does
squad_route
An agent routes a follow-up task to a teammate (e.g., developer → tester after implementation)
squad_decide
An agent records an architectural decision to a shared log
squad_memory
An agent persists a learning for future sessions
Set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 before running to export traces and metrics to the .NET Aspire dashboard. The sample emits squad.agent.spawns, squad.agent.duration, squad.tokens.input, and squad.sessions.created metrics automatically.
The cost-aware-router sample demonstrates how Squad selects the cheapest model tier that satisfies task complexity requirements, tracks per-agent spending, and fires budget warnings before limits are reached.
How tier selection works:selectResponseTier() matches the task description against routing rules and complexity heuristics to pick one of four tiers:
Tier
Model class
Typical use
direct
No model
Simple cache lookups, trivial edits
lightweight
Fast / cheap
Docs updates, brief summaries
standard
Balanced
Feature implementation, test writing
full
Premium
Security audits, architecture reviews
CostTracker.getSummary() returns a breakdown by agent and session, making it easy to build cost dashboards or enforce per-agent budgets programmatically.
The hook-governance sample demonstrates HookPipeline — a composable middleware layer that enforces security and policy rules as deterministic TypeScript code rather than prompt instructions.
Blocks writes to paths outside allowedWritePaths globs — prevents agents from touching .env, credentials, or system files
PII scrubbing
Redacts email addresses (and other PII patterns) from PostToolUseContext.result before it reaches the agent
Reviewer lockout
After a reviewer rejects a change, the original author is locked out of that file — enforces code review decisions
Ask-user rate limit
Caps the number of times an agent can interrupt the user per session — prevents noisy or runaway agents
Hooks run on every tool invocation and are applied in pipeline order. Keep hook logic lightweight and synchronous where possible to avoid adding latency to high-frequency tool calls.
The streaming-chat sample shows how to route user messages to agents by keyword and surface token-by-token responses in real time. It uses StreamingPipeline together with SquadClientWithPool and EventBus.
import { CastingEngine, StreamingPipeline } from '@bradygaster/squad-sdk';import type { StreamDelta } from '@bradygaster/squad-sdk';import { SquadClientWithPool, EventBus } from '@bradygaster/squad-sdk/client';// Set up the streaming pipeline — writes tokens to stdout as they arriveconst pipeline = new StreamingPipeline();pipeline.onDelta((event) => { process.stdout.write(event.content);});// Create a session per agent and attach it to the pipelineconst client = new SquadClientWithPool({ pool: { maxConcurrent: 5 } });await client.connect();for (const agent of AGENTS) { const session = await client.createSession({ streaming: true, systemMessage: { mode: 'append', content: agent.systemPrompt }, }); agent.sessionId = session.sessionId; pipeline.attachToSession(session.sessionId);}// Route a message and stream the responsefunction routeMessage(message: string): AgentInfo { const lower = message.toLowerCase(); for (const agent of AGENTS) { if (agent.keywords.some((kw) => lower.includes(kw))) return agent; } return AGENTS[0]; // default to Backend}// Demo mode: simulate streaming without a live tokenasync function simulateStreaming( pipeline: StreamingPipeline, agent: AgentInfo,): Promise<void> { const sessionId = agent.sessionId ?? `demo-${agent.role.toLowerCase()}`; const words = getDemoResponse(agent.role).split(' '); pipeline.markMessageStart(sessionId); for (let i = 0; i < words.length; i++) { const delta: StreamDelta = { type: 'message_delta', sessionId, agentName: agent.name, content: (i === 0 ? '' : ' ') + words[i], index: i, timestamp: new Date(), }; await pipeline.processEvent(delta); await new Promise((r) => setTimeout(r, 40 + Math.random() * 40)); }}
Key streaming APIs:
API
Description
pipeline.attachToSession(sessionId)
Register a session so the pipeline routes its deltas to the right handlers
pipeline.onDelta(handler)
Subscribe to every StreamDelta event across all attached sessions
pipeline.markMessageStart(sessionId)
Signal the start of a new message — used to reset per-message state
pipeline.processEvent(event)
Push a StreamDelta or UsageEvent into the pipeline
pipeline.detachFromSession(sessionId)
Unsubscribe a session when it closes
pipeline.clear()
Remove all sessions and reset internal state
Set SQUAD_DEMO_MODE=true to run streaming-chat without a GitHub Copilot token. The sample delivers pre-written word-by-word responses with realistic timing so you can explore the streaming API without any external dependencies.
AzureBlobStorageProvider maps the 24-method StorageProvider interface to Azure Blob Storage. Blobs map 1:1 to virtual paths inside a single container. Directories are virtual — they exist as common prefixes in blob names.
When to use the Azure provider: multi-machine shared state, Azure Functions / Container Apps deployments, compliance requirements (RBAC, encryption at rest, audit logs).Run the sample with Azurite (no Azure account needed):
cd samples/storage-provider-azurenpm installnpm run azurite # Terminal 1 — starts local emulatornpm run demo:emulator # Terminal 2 — runs the demo
SQLiteStorageProvider (exported directly from @bradygaster/squad-sdk) uses sql.js — SQLite compiled to WebAssembly — for a portable, zero-native-dependency storage backend. Files are rows in a files(path, content, updated_at) table with forward-slash paths as virtual keys.
import { SQLiteStorageProvider } from '@bradygaster/squad-sdk';// Create and initialize the providerconst provider = new SQLiteStorageProvider('./squad-demo.db');await provider.init(); // required — loads sql.js asynchronously// Standard StorageProvider operationsawait provider.write('team.md', '# Squad Team\n...');await provider.write('agents/flight/charter.md', '# FLIGHT — Commander\n...');const content = await provider.read('team.md');// → '# Squad Team\n...'const entries = await provider.list('agents/');// → ['flight']// Persistence: create a second instance from the same fileconst provider2 = new SQLiteStorageProvider('./squad-demo.db');await provider2.init();const persisted = await provider2.read('team.md');// → same content — SQLite persists across instances
Key patterns:
Pattern
Why
await provider.init() is required
sql.js loads the WASM engine asynchronously before any operation
Paths are virtual keys
Files don’t touch the filesystem — ideal for sandboxed environments
Atomic persistence
Every write flushes the in-memory database to disk with write-then-rename for crash safety
No native binaries
sql.js WASM works on Windows, Linux, macOS, and macOS ARM without compilation
When to use SQLite: portable single-file storage, embedded apps, isolated test runs (swap in a fresh .db per test), environments with limited filesystem access.
The skill-discovery sample shows how agents load domain knowledge from SKILL.md files, score them against task descriptions, and discover new patterns at runtime.
import { SkillRegistry, loadSkillsFromDirectory, parseSkillFile,} from '@bradygaster/squad-sdk/skills';// Load all SKILL.md files from a directory (synchronous)const skills = loadSkillsFromDirectory(skillsDir);const registry = new SkillRegistry();for (const skill of skills) registry.registerSkill(skill);// Match skills to a task — scored by trigger keywords + role affinityconst matches = registry.matchSkills( 'Add TypeScript generics to the data layer', 'developer',);// → [{ skill: { id: 'typescript-patterns', ... }, score: 0.8, reason: 'triggers: typescript, types; role affinity: developer' }]// An agent discovers a new pattern at runtimeconst newSkillContent = `---name: Error Handling Patternsdomain: reliabilitytriggers: [error, exception, result, try, catch, handling]roles: [developer, backend, tester]confidence: low---## Error Handling PatternsUse Result<T, E> type pattern instead of throwing exceptions.Never swallow errors silently — at minimum, log and re-throw.`;const discovered = parseSkillFile('error-handling', newSkillContent);if (discovered) registry.registerSkill(discovered);
The SKILL.md format uses YAML frontmatter to declare metadata and a markdown body for the skill content:
---name: TypeScript Patternsdomain: developmenttriggers: [typescript, types, generics, inference, strict]roles: [developer, lead]confidence: low---## TypeScript PatternsPrefer `unknown` over `any` for type-safe narrowing.Use discriminated unions for state machines.
Confidence lifecycle:
Level
Icon
Meaning
low
🔴
First observation — pattern just noticed
medium
🟡
Confirmed across multiple sessions
high
🟢
Established team standard
Matching algorithm:SkillRegistry.matchSkills(task, role) awards +0.5 per trigger keyword found in the task description (capped at 0.7) and +0.3 if the agent’s role matches the skill’s roles list. Scores are clamped to [0, 1] and sorted descending.