Skip to content

feat: add atomic repository-scoped state creation (#1947) - #1948

Open
cyrusjamula wants to merge 2 commits into
bradygaster:devfrom
cyrusjamula:u/cyrusjamula/squad/1947-atomic-state-create
Open

feat: add atomic repository-scoped state creation (#1947)#1948
cyrusjamula wants to merge 2 commits into
bradygaster:devfrom
cyrusjamula:u/cyrusjamula/squad/1947-atomic-state-create

Conversation

@cyrusjamula

Copy link
Copy Markdown

Closes #1947

Why

State MCP currently exposes unconditional writes, so concurrent workflows cannot safely claim a canonical repository-scoped artifact without risking overwrite. This adds a true create-if-absent primitive that fails closed when identity or outcome cannot be proven.

Approach

  • Adds createIfAbsent to StorageProvider, its implementations, state backends, and the storage adapter.
  • Uses exclusive filesystem creation locally, conditional insert for SQLite, and snapshot-aware Git CAS loops for notes and orphan backends.
  • Exposes exported typed conflict and uncertainty errors; conflict is never returned as success.
  • Makes two-layer disagreement or secondary-layer failure uncertain rather than silently succeeding.
  • Registers squad_state_create_if_absent in ToolRegistry and state MCP so it appears in tools/list.
  • Preserves existing unconditional write behavior.
  • Updates public storage/state documentation, synchronized templates, the Azure Blob sample, and package changesets.

Validation

  • vitest run test/state-backend-create-if-absent.test.ts - 39/39 passed
  • vitest run test/cli/state-mcp.test.ts - 6/6 passed
  • npm run build - passed

Integration

After the release containing this changeset, consumers should update their exact @bradygaster/squad-cli pin, add squad_state_create_if_absent to the MCP allowlist, restart the MCP server, verify the tool appears in tools/list, and use it instead of squad_state_write for exclusive canonical artifact creation. The released version is intentionally determined by the release process rather than hardcoded in this PR.

Add true atomic create-if-absent semantics across all state backends
(local/WorktreeBackend, git-notes/GitNotesBackend, orphan-branch/
OrphanBranchBackend, two-layer/TwoLayerBackend), all StorageProvider
implementations (FSStorageProvider, InMemoryStorageProvider,
SQLiteStorageProvider), StateBackendStorageAdapter, ToolRegistry
(squad_state_create_if_absent), state-mcp MCP bridge, and Azure Blob
sample provider.

Key guarantees:
- Exactly one concurrent creator succeeds; all others receive
  StateKeyConflictError (typed, exported from squad-sdk)
- Existing content is never overwritten
- Repository scope is verified at operation time; invalid/ambiguous
  identity fails with StateBackendUncertaintyError (never silently widens)
- Two-layer disagreement/failure is fail-closed: orphan succeeds but notes
  disagrees => StateBackendUncertaintyError, never success-shaped conflict
- Local backend uses O_CREAT|O_EXCL (no process-local locking needed)
- Git-native backends use compare-and-swap loops with re-check after CAS
  loss; retry exhaustion is uncertainty, not conflict
- Repeated legitimate conflicts never trip the circuit breaker

Registered in: StorageProvider interface, every implementation/adapter,
ToolRegistry, state-mcp MCP_TOOL_ALIASES + tools/list, public docs,
changeset.

Tests: 39 createIfAbsent tests (WorktreeBackend, GitNotesBackend,
OrphanBranchBackend, TwoLayerBackend, FSStorageProvider,
InMemoryStorageProvider, SQLiteStorageProvider, ToolRegistry) + 6
state-mcp bridge tests including squad_state_create_if_absent MCP surface.

Closes bradygaster#1947

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit bdad2c5

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 4 item(s) to address before review

Status Check Details
Single commit 2 commits — consider squashing before review
Not in draft Ready for review
Branch up to date Up to date with dev
Copilot review No Copilot review yet — it may still be processing
Changeset present Changeset file found
Scope clean ⚠️ PR includes 1 .squad/ file(s) — ensure these are intentional
No merge conflicts No merge conflicts
Copilot threads resolved 2 unresolved Copilot thread(s) — fix and resolve before merging
CI passing 6 check(s) still running

Files Changed (26 files, +1323 −13)

File +/−
.changeset/1947-create-if-absent.md +13 −0
.squad-templates/scribe-charter.md +8 −1
.squad-templates/spawn-reference.md +5 −0
.squad/skills/coordinator-source-of-truth/SKILL.md +1 −1
docs/src/content/docs/features/state-backends.md +123 −0
docs/src/content/docs/features/storage-provider.md +16 −1
packages/squad-cli/src/cli/commands/state-mcp.ts +1 −0
packages/squad-cli/templates/scribe-charter.md +8 −1
packages/squad-cli/templates/skills/coordinator-source-of-truth/SKILL.md +1 −1
packages/squad-cli/templates/spawn-reference.md +5 −0
packages/squad-sdk/src/state-backend.ts +275 −1
packages/squad-sdk/src/storage/fs-storage-provider.ts +45 −2
packages/squad-sdk/src/storage/in-memory-storage-provider.ts +12 −0
packages/squad-sdk/src/storage/index.ts +1 −1
packages/squad-sdk/src/storage/sqlite-storage-provider.ts +15 −0
packages/squad-sdk/src/storage/storage-error.ts +43 −0
packages/squad-sdk/src/storage/storage-provider.ts +22 −0
packages/squad-sdk/src/tools/index.ts +68 −0
packages/squad-sdk/templates/scribe-charter.md +8 −1
packages/squad-sdk/templates/skills/coordinator-source-of-truth/SKILL.md +1 −1
packages/squad-sdk/templates/spawn-reference.md +5 −0
samples/storage-provider-azure/azure-blob-storage-provider.ts +32 −1
templates/scribe-charter.md +8 −1
templates/spawn-reference.md +5 −0
test/cli/state-mcp.test.ts +47 −0
test/state-backend-create-if-absent.test.ts +555 −0

Total: +1323 −13


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

@github-actions

Copy link
Copy Markdown
Contributor

🟠 Impact Analysis — PR #1948

Risk tier: 🟠 HIGH

📊 Summary

Metric Count
Files changed 26
Files added 2
Files modified 24
Files deleted 0
Modules touched 7
Critical files 2

🎯 Risk Factors

  • 26 files changed (21-50 → HIGH)
  • 7 modules touched (5-8 → HIGH)
  • Critical files touched: packages/squad-sdk/src/storage/index.ts, packages/squad-sdk/src/tools/index.ts

📦 Modules Affected

docs (2 files)
  • docs/src/content/docs/features/state-backends.md
  • docs/src/content/docs/features/storage-provider.md
root (4 files)
  • .changeset/1947-create-if-absent.md
  • samples/storage-provider-azure/azure-blob-storage-provider.ts
  • templates/scribe-charter.md
  • templates/spawn-reference.md
squad-cli (4 files)
  • packages/squad-cli/src/cli/commands/state-mcp.ts
  • packages/squad-cli/templates/scribe-charter.md
  • packages/squad-cli/templates/skills/coordinator-source-of-truth/SKILL.md
  • packages/squad-cli/templates/spawn-reference.md
squad-sdk (11 files)
  • packages/squad-sdk/src/state-backend.ts
  • packages/squad-sdk/src/storage/fs-storage-provider.ts
  • packages/squad-sdk/src/storage/in-memory-storage-provider.ts
  • packages/squad-sdk/src/storage/index.ts
  • packages/squad-sdk/src/storage/sqlite-storage-provider.ts
  • packages/squad-sdk/src/storage/storage-error.ts
  • packages/squad-sdk/src/storage/storage-provider.ts
  • packages/squad-sdk/src/tools/index.ts
  • packages/squad-sdk/templates/scribe-charter.md
  • packages/squad-sdk/templates/skills/coordinator-source-of-truth/SKILL.md
  • packages/squad-sdk/templates/spawn-reference.md
squad-state (1 file)
  • .squad/skills/coordinator-source-of-truth/SKILL.md
templates (2 files)
  • .squad-templates/scribe-charter.md
  • .squad-templates/spawn-reference.md
tests (2 files)
  • test/cli/state-mcp.test.ts
  • test/state-backend-create-if-absent.test.ts

⚠️ Critical Files

  • packages/squad-sdk/src/storage/index.ts
  • packages/squad-sdk/src/tools/index.ts

This report is generated automatically for every PR. See #733 for details.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Squad File Leakage Detected

The following .squad/ files were modified in this PR:

  • .squad/skills/coordinator-source-of-truth/SKILL.md

These files affect team routing, agent charters, and decisions.
If intentional, ensure approval from the team lead.

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Architectural Review

⚠️ Architectural review: 1 warning(s).

Severity Category Finding Files
🟡 warning sweeping-refactor This PR touches 26 files (26 modified/added, 0 deleted). Large PRs are harder to review — consider splitting if possible.

Automated architectural review — informational only.

@cyrusjamula
cyrusjamula marked this pull request as ready for review August 30, 2026 02:39
Copilot AI lite review requested due to automatic review settings August 30, 2026 02:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

There are correctness/contract gaps to address (notably around atomic local writes and typed uncertainty behavior), plus at least one concurrency test that doesn’t actually exercise concurrent contention.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 High severity · 2 Medium severity · 1 Low severity

New issues introduced by this change (4)
Severity Finding
High severity packages/​squad-sdk/​src/​state-backend.ts — WorktreeBackend.createIfAbsent uses fsWriteSync(fd, buf) once; writeSync can legally perform a…
Medium severity test/​state-backend-create-if-absent.test.ts — These "concurrent create" tests for git-backed/two-layer backends are sequential (a simple…
Low severity docs/​src/​content/​docs/​features/​state-backends.md — This docs section hardcodes the first shipping version as 0.14.0, but the PR description states the…
Medium severity packages/​squad-sdk/​src/​storage/​sqlite-storage-provider.ts — SQLiteStorageProvider.createIfAbsent calls this.persist() without translating persistence failures…
What changed in this PR

Adds an atomic, repository-scoped “create-if-absent” state primitive across Squad’s storage/state stack, exposing it via MCP as squad_state_create_if_absent with typed conflict vs uncertainty outcomes to prevent concurrent creators from overwriting canonical artifacts.

Changes:

  • Introduces createIfAbsent on StorageProvider and implements it for FS, in-memory, and SQLite providers; wires it through StateBackendStorageAdapter.
  • Implements backend-specific atomic creation semantics for local, git-notes, orphan-branch, and two-layer state backends (including fail-closed uncertainty on two-layer disagreement).
  • Registers the new MCP tool, adds tests, and updates docs/templates/samples to describe usage and failure shapes.
File Description
test/​state-backend-create-if-absent.test.ts Adds cross-backend tests for create-if-absent semantics, conflicts, isolation, and uncertainty.
test/​cli/​state-mcp.test.ts Verifies state-mcp exposes squad_state_create_if_absent and exercises canonical-key creation behavior.
templates/​spawn-reference.md Documents when to use squad_state_create_if_absent and how to handle conflict/uncertainty.
templates/​scribe-charter.md Updates Scribe guidance to prefer create-if-absent for exclusive canonical artifacts.
samples/​storage-provider-azure/​azure-blob-storage-provider.ts Adds an Azure Blob sample implementation using conditional upload for atomic create.
packages/​squad-sdk/​templates/​spawn-reference.md Syncs spawn-reference template with create-if-absent guidance.
packages/​squad-sdk/​templates/​skills/​coordinator-source-of-truth/​SKILL.md Updates state-tool list to include create-if-absent for mutable state.
packages/​squad-sdk/​templates/​scribe-charter.md Syncs Scribe charter template with exclusive-canonical-artifact guidance.
packages/​squad-sdk/​src/​tools/​index.ts Registers squad_state_create_if_absent tool with typed failure shapes.
packages/​squad-sdk/​src/​storage/​storage-provider.ts Extends StorageProvider interface with createIfAbsent and its error contract.
packages/​squad-sdk/​src/​storage/​storage-error.ts Adds exported typed errors: StateKeyConflictError and StateBackendUncertaintyError.
packages/​squad-sdk/​src/​storage/​sqlite-storage-provider.ts Implements createIfAbsent via INSERT OR IGNORE and conflict detection.
packages/​squad-sdk/​src/​storage/​index.ts Re-exports new typed storage errors from the SDK storage entrypoint.
packages/​squad-sdk/​src/​storage/​in-memory-storage-provider.ts Implements createIfAbsent for test-friendly in-memory provider.
packages/​squad-sdk/​src/​storage/​fs-storage-provider.ts Implements createIfAbsent using exclusive open (wx) with typed conflict/uncertainty.
packages/​squad-sdk/​src/​state-backend.ts Adds createIfAbsent to backends + repo-identity fail-closed checks for git-native backends.
packages/​squad-cli/​templates/​spawn-reference.md Syncs CLI template spawn-reference with create-if-absent guidance.
packages/​squad-cli/​templates/​skills/​coordinator-source-of-truth/​SKILL.md Syncs CLI skill template to include create-if-absent in state tools.
packages/​squad-cli/​templates/​scribe-charter.md Syncs CLI template Scribe charter with exclusive-canonical-artifact guidance.
packages/​squad-cli/​src/​cli/​commands/​state-mcp.ts Adds alias mapping so state-mcp bridge exposes the new tool name.
docs/​src/​content/​docs/​features/​storage-provider.md Documents createIfAbsent as a core StorageProvider method and its semantics.
docs/​src/​content/​docs/​features/​state-backends.md Documents atomic create-if-absent semantics, typed errors, and MCP tool surface.
.squad/​skills/​coordinator-source-of-truth/​SKILL.md Updates canonical skill content to include create-if-absent among state tools.
.squad-templates/​spawn-reference.md Syncs canonical spawn template with create-if-absent guidance.
.squad-templates/​scribe-charter.md Syncs canonical Scribe template with exclusive-canonical-artifact guidance.
.changeset/​1947-create-if-absent.md Declares a minor bump for sdk/cli and summarizes the new capability.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +423 to +426
try {
const buf = Buffer.from(content, 'utf-8');
fsWriteSync(fd, buf);
} catch (writeErr: unknown) {
Comment on lines +140 to +144
it('exactly one of two sequential instances succeeds (concurrent create)', () => {
const b1 = new GitNotesBackend(TMP);
const b2 = new GitNotesBackend(TMP);
let successes = 0;
let conflicts = 0;
Comment on lines +294 to +298
`createIfAbsent` and the `squad_state_create_if_absent` MCP tool first ship in
`@bradygaster/squad-sdk` and `@bradygaster/squad-cli` **0.14.0** (the next minor
release after 0.13.1). Consumers pinned to `0.12.0` or `0.13.x` must upgrade the
pin to `0.14.0`, add `squad_state_create_if_absent` to their MCP tool allowlist,
and restart the state MCP server before the tool appears in `tools/list`.
Comment on lines +149 to +153
if (db.getRowsModified() === 0) {
throw new StateKeyConflictError(filePath);
}
this.persist();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add atomic repository-scoped create-if-absent state operation

3 participants