Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/bradygaster/squad/llms.txt

Use this file to discover all available pages before exploring further.

Squad’s hooks system provides programmatic enforcement of governance policies. Hooks intercept agent tool calls before and after execution — letting you block dangerous operations, scrub PII from outputs, inject context at session start, and handle errors. Unlike prompt-level rules, hooks run as ordinary TypeScript functions: deterministic, unit-testable, and free from model interpretation.

Hook types

All hook types are exported from @bradygaster/squad-sdk/hooks.

onPreToolUse — intercept before execution

Pre-tool-use hooks run synchronously (or asynchronously) before a tool call reaches the agent. Every hook returns a PreToolUseResult that tells the pipeline whether to allow, block, or modify the call.
import type { PreToolUseContext, PreToolUseResult } from '@bradygaster/squad-sdk/hooks';

export interface PreToolUseContext {
  /** Name of the tool being called (e.g. 'edit', 'bash', 'ask_user') */
  toolName: string;
  /** Arguments passed to the tool */
  arguments: Record<string, unknown>;
  /** Agent name that invoked the tool */
  agentName: string;
  /** Session ID */
  sessionId: string;
}

export interface PreToolUseResult {
  action: HookAction;
  /** Modified arguments — only used when action === 'modify' */
  modifiedArguments?: Record<string, unknown>;
  /** Human-readable reason — only used when action === 'block' */
  reason?: string;
}

onPostToolUse — inspect results after execution

Post-tool-use hooks run after a tool call completes. They can modify or scrub the result before it is returned to the agent.
import type { PostToolUseContext, PostToolUseResult } from '@bradygaster/squad-sdk/hooks';

export interface PostToolUseContext {
  toolName: string;
  arguments: Record<string, unknown>;
  result: unknown;
  agentName: string;
  sessionId: string;
}

export interface PostToolUseResult {
  /** Scrubbed or otherwise modified result */
  result: unknown;
}

HookAction

Every PreToolUseResult must carry one of three action values:
ActionMeaning
'allow'Proceed normally — the tool call continues unchanged.
'block'Cancel the tool call — the agent receives the reason string as an error.
'modify'Replace the tool arguments with modifiedArguments before the call proceeds.
The pipeline runs hooks in registration order. As soon as one hook returns 'block', the pipeline short-circuits and no further hooks run. If a hook returns 'modify', the updated arguments are passed to subsequent hooks as the new context.

HookPipeline

The HookPipeline class is the concrete implementation. Instantiate it with a PolicyConfig to get automatic registration of common governance hooks:
import { HookPipeline } from '@bradygaster/squad-sdk/hooks';
import type { PolicyConfig } from '@bradygaster/squad-sdk/hooks';

const policy: PolicyConfig = {
  allowedWritePaths:   ['src/**', 'test/**', '.squad/**'],
  blockedCommands:     ['rm -rf', 'git push --force'],
  maxAskUserPerSession: 3,
  scrubPii:            true,
  reviewerLockout:     true,
};

const pipeline = new HookPipeline(policy);
You can also register custom hooks manually after construction:
pipeline.addPreToolHook(async (ctx) => {
  if (ctx.toolName === 'bash' && ctx.agentName === 'waingro') {
    return { action: 'block', reason: 'Adversarial agent blocked from shell access.' };
  }
  return { action: 'allow' };
});

Using hooks with defineHooks

When using SDK-first mode, declare your governance policy in squad.config.ts via defineHooks. The builder validates all fields at startup:
import { defineSquad, defineTeam, defineAgent, defineHooks } from '@bradygaster/squad-sdk';

export default defineSquad({
  team: defineTeam({ name: 'my-team', members: ['edie', 'fenster'] }),
  agents: [
    defineAgent({ name: 'edie',    role: 'TypeScript Engineer' }),
    defineAgent({ name: 'fenster', role: 'Core Dev'            }),
  ],
  hooks: defineHooks({
    allowedWritePaths:   ['src/**/*.ts', 'test/**/*.ts', '.squad/**'],
    blockedCommands:     ['rm -rf', 'git reset --hard', 'git push --force'],
    maxAskUser:          3,
    scrubPii:            true,
    reviewerLockout:     true,
  }),
});

File-write guards

The built-in file-write guard blocks any edit, create, write_file, or create_file tool call where the target path does not match one of the allowed glob patterns. Glob patterns support * (within a path segment) and ** (across segments).
import { HookPipeline } from '@bradygaster/squad-sdk/hooks';
import type { PreToolUseContext } from '@bradygaster/squad-sdk/hooks';

const pipeline = new HookPipeline({
  allowedWritePaths: ['src/**/*.ts', '.squad/**'],
});

// Allowed — matches src/**/*.ts
const safeCtx: PreToolUseContext = {
  toolName:  'create',
  arguments: { path: 'src/utils/helper.ts' },
  agentName: 'McManus',
  sessionId: 'session-001',
};
const safeResult = await pipeline.runPreToolHooks(safeCtx);
// → { action: 'allow' }

// Blocked — /etc/passwd does not match any allowed pattern
const dangerCtx: PreToolUseContext = {
  toolName:  'edit',
  arguments: { path: '/etc/passwd' },
  agentName: 'McManus',
  sessionId: 'session-001',
};
const dangerResult = await pipeline.runPreToolHooks(dangerCtx);
// → { action: 'block', reason: 'File write blocked: "/etc/passwd" does not match ...' }

PII scrubbing

The built-in PII scrubber is a post-tool-use hook that replaces email addresses with [EMAIL_REDACTED]. It handles plain string results and recursively scrubs nested objects:
import { HookPipeline } from '@bradygaster/squad-sdk/hooks';
import type { PostToolUseContext } from '@bradygaster/squad-sdk/hooks';

const piiPipeline = new HookPipeline({ scrubPii: true });

const ctx: PostToolUseContext = {
  toolName:  'bash',
  arguments: { command: 'git log --oneline' },
  result:    'Deploy fix by brady@example.com — cc: alice@company.io',
  agentName: 'Verbal',
  sessionId: 'session-002',
};

const { result } = await piiPipeline.runPostToolHooks(ctx);
// → 'Deploy fix by [EMAIL_REDACTED] — cc: [EMAIL_REDACTED]'

Reviewer lockout

The reviewer lockout hook prevents an agent from editing a file after it has been flagged by a reviewer. This enforces the principle that the original author cannot override a review decision:
import { HookPipeline } from '@bradygaster/squad-sdk/hooks';

const lockoutPipeline = new HookPipeline({ reviewerLockout: true });
const lockout = lockoutPipeline.getReviewerLockout();

// Reviewer rejects the auth module — lock out the original author
lockout.lockout('src/auth.ts', 'Backend');

// Backend attempts to edit — blocked
const result = await lockoutPipeline.runPreToolHooks({
  toolName:  'edit',
  arguments: { path: 'src/auth.ts' },
  agentName: 'Backend',
  sessionId: 'session-003',
});
// → { action: 'block', reason: 'Reviewer lockout: Agent "Backend" is locked out ...' }

// Frontend is not locked out — allowed
const frontendResult = await lockoutPipeline.runPreToolHooks({
  toolName:  'edit',
  arguments: { path: 'src/auth.ts' },
  agentName: 'Frontend',
  sessionId: 'session-003',
});
// → { action: 'allow' }

// Inspect the registry
lockout.getLockedAgents('src/auth.ts'); // → ['Backend']

// Clear when the review cycle ends
lockout.clearLockout('src/auth.ts');
The ReviewerLockoutHook class is also exported directly if you need to manage lockout state independently of a HookPipeline.

Ask-user rate limiter

Cap how many times agents can interrupt the user per session. After the limit is reached, ask_user calls are automatically blocked:
import { HookPipeline } from '@bradygaster/squad-sdk/hooks';

// From samples/hook-governance/index.ts
const ratePipeline = new HookPipeline({ maxAskUserPerSession: 3 });

for (let i = 1; i <= 5; i++) {
  const result = await ratePipeline.runPreToolHooks({
    toolName:  'ask_user',
    arguments: { question: `Clarification #${i}?` },
    agentName: 'Fenster',
    sessionId: 'session-004',
  });
  // Calls 1–3 → allow ✅
  // Calls 4–5 → block 🚫  (rate limit exceeded)
}

Custom tools

Squad’s custom tools are the typed orchestration primitives agents use to coordinate work. They are available on the server-side tool registry and exposed to the Copilot SDK session. Their full type signatures live in @bradygaster/squad-sdk/tools.

squad_route

Route a task to another agent via the session pool.
export interface RouteRequest {
  /** Target agent name */
  targetAgent: string;
  /** Task description for the target agent */
  task: string;
  /** Priority level */
  priority?: 'low' | 'normal' | 'high' | 'critical';
  /** Context to pass to the target session */
  context?: string;
}

squad_decide

Write a typed decision to the team inbox drop-box.
export interface DecisionRecord {
  /** Decision author (agent name) */
  author: string;
  /** Decision summary */
  summary: string;
  /** Full decision body */
  body: string;
  /** Related agents or PRDs */
  references?: string[];
}

squad_memory

Append a learning, decision, or context entry to agent history.
export interface MemoryEntry {
  /** Agent name */
  agent: string;
  /** Section to append to */
  section: 'learnings' | 'updates' | 'sessions';
  /** Content to append */
  content: string;
}

squad_status

Query the current session pool state for active and waiting sessions.
export interface StatusQuery {
  /** Filter by agent name */
  agentName?: string;
  /** Filter by session status */
  status?: string;
  /** Include detailed session metadata */
  verbose?: boolean;
}

squad_skill

Read or write agent skill definitions.
export interface SkillRequest {
  /** Skill name (maps to .copilot/skills/{name}/SKILL.md) */
  skillName: string;
  /** Operation: read the skill or write/update it */
  operation: 'read' | 'write';
  /** Skill content (required for write) */
  content?: string;
  /** Confidence level (required for write) */
  confidence?: 'low' | 'medium' | 'high';
}

SquadTool and SquadToolResult

The base types for all Squad custom tools, imported from @bradygaster/squad-sdk/adapter:
import type { SquadTool, SquadToolResult } from '@bradygaster/squad-sdk/adapter';
These types align with the @github/copilot-sdk tool registration API and are used internally when registering squad_route, squad_decide, squad_memory, squad_status, and squad_skill with the session.
All tool arguments are sanitized before being recorded as OpenTelemetry span attributes. Fields matching content, query, token, secret, password, key, or auth patterns are replaced with [REDACTED].

What’s next

SDK-First Mode

Register hooks via defineHooks in your squad.config.ts and generate the full .squad/ config from TypeScript.

Observability

Wire hooks into OpenTelemetry to get span-level visibility into every tool call your agents make.

Build docs developers (and LLMs) love