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 watch mode (Ralph) continuously polls for issues and dispatches agents to handle them. To do this reliably, it needs to track which issues have been triaged, which agents are working, and what the current execution state is. By default this state lives in memory — fast and zero-setup, but lost when the watch process exits. Two git-native backends provide durability across restarts without requiring any external infrastructure.

Backend Comparison

BackendDurabilityBranchesBest for
local (default)Lost on restartNoneLocal development, short sessions
orphanSurvives restartsCreates one orphan branch (squad-state)Long-running teams, easy pruning
two-layerSurvives restartsCreates one orphan branch + git notesCI/CD pipelines, shared teams, audit trails
The git-notes backend name is deprecated. It is automatically migrated to two-layer, which combines git notes for commit-scoped annotations with an orphan branch for permanent state. If your config.json still references "stateBackend": "git-notes", update it to "two-layer" to suppress the migration warning.

In-Memory (Default)

The default backend stores everything in process memory. There is nothing to configure and no git operations are performed.
# Default — in-memory (no flag needed)
squad watch --execute
State is lost when the watch process exits. For quick local sessions where you will keep the process running, this is the simplest option. For overnight runs or CI pipelines where you need state to survive restarts, use one of the git-native backends.

Orphan Branch

The orphan backend stores state in a dedicated orphan branch named squad-state. This branch has no shared history with your main branch — it is isolated and can be pruned cleanly without affecting your codebase history.
squad watch --execute --state-backend orphan
The backend creates the squad-state branch automatically on first use. State is written as a git tree structure, with each state key stored as a file in the branch. The backend uses optimistic concurrency (compare-and-swap on update-ref) with jittered exponential backoff to handle concurrent writes safely. To prune all watch state:
git branch -D squad-state
The OrphanBranchBackend class from the SDK:
import { OrphanBranchBackend } from '@bradygaster/squad-sdk';

const backend = new OrphanBranchBackend('/path/to/repo', 'squad-state');

backend.write('triage/issue-42.json', JSON.stringify({ status: 'in-progress' }));
const state = backend.read('triage/issue-42.json');

Two-Layer Backend

The two-layer backend combines two storage strategies: an orphan branch for permanent state (same as the orphan backend above) and git notes for commit-scoped annotations. Permanent state like decisions, histories, and logs goes to the orphan branch; ephemeral commit-scoped context goes to git notes under refs/notes/squad.
squad watch --execute --state-backend two-layer
Use the two-layer backend for CI/CD scenarios and shared teams. It provides the durability of the orphan backend plus the ability to annotate commits with context (“why was this decision made here?”) that lives with the commit even after the squad-state branch is pruned. Push the notes ref with git push origin refs/notes/squad to share annotations with the team.
Git notes can be pushed to and pulled from a remote like any other ref:
# Push notes to remote
git push origin refs/notes/squad

# Pull notes from remote
git fetch origin refs/notes/squad:refs/notes/squad
The TwoLayerBackend also provides a promoteNotes method for moving ephemeral notes into permanent storage after a PR merges:
import { TwoLayerBackend } from '@bradygaster/squad-sdk';

const backend = new TwoLayerBackend('/path/to/repo');

// After a PR merges, promote flagged notes to permanent storage
const result = backend.promoteNotes('squad/my-feature');
console.log(`Promoted: ${result.promoted.length}, Archived: ${result.archived.length}`);
Notes flagged with "promote_to_permanent": true are moved to the orphan layer and removed from notes. Notes flagged with "archive_on_close": true are copied to the orphan layer but kept as notes.

SDK Usage

Use resolveStateBackend to get the appropriate backend based on configuration:
import {
  resolveStateBackend,
  verifyStateBackend,
  WorktreeBackend,
  OrphanBranchBackend,
  TwoLayerBackend,
  CircuitBreaker,
  StateBackendStorageAdapter,
} from '@bradygaster/squad-sdk';

const squadDir = '/path/to/project/.squad';
const repoRoot = '/path/to/project';

// Resolve from config.json or CLI override
const backend = resolveStateBackend(squadDir, repoRoot, 'two-layer');

// Verify the backend is accessible (read-only health check)
const health = verifyStateBackend(backend);
if (!health.ok) {
  console.error('Backend unhealthy:', health.error);
}

// Use as a StorageProvider adapter
const adapter = new StateBackendStorageAdapter(backend, squadDir);
await adapter.write('.squad/triage/issue-42.json', JSON.stringify({ status: 'in-progress' }));
All backends implement the StateBackend interface:
interface StateBackend {
  read(relativePath: string): string | undefined;
  write(relativePath: string, content: string): void;
  exists(relativePath: string): boolean;
  list(relativeDir: string): string[];
  delete(relativePath: string): boolean;
  append(relativePath: string, content: string): void;
  readonly name: string;
}
The CircuitBreaker wraps git operations in git-native backends. After 5 consecutive failures it opens the circuit and rejects operations for 30 seconds before retrying. This prevents a broken git state from causing cascading failures in a long-running watch process.

Configuring via config.json

Set the default backend for a project in .squad/config.json:
{
  "stateBackend": "two-layer"
}
The CLI --state-backend flag overrides this setting for a single run.

External State

squad externalize moves .squad/ state outside the working tree so it survives branch switches. This is useful when you are working across multiple branches and don’t want the squad state to differ between them.
# Move .squad/ to an external location, keyed by project name
squad externalize --key my-project

# Move it back into the working tree
squad internalize
Externalized state is stored outside the repository and referenced by a key. The squad status command shows whether the current squad is local or externalized and why.
squad status
# Squad active: .squad/ (local)
# or
# Squad active: ~/.squad/external/my-project (externalized)

Build docs developers (and LLMs) love