Skip to content

fix(state): transactional governed-memory migration and source-branch casting ownership (#2229, #2425) - #2439

Merged
tamirdresher merged 3 commits into
devfrom
tamirdresher-microsoft-data-fixing-memory-backend-contracts
Nov 9, 2026
Merged

tamirdresher merged 3 commits into
devfrom
tamirdresher-microsoft-data-fixing-memory-backend-contracts

Conversation

@tamirdresher

Copy link
Copy Markdown
Collaborator

Summary

Resolves #2229 and #2425 across the state backend, casting, hook, and governed-memory contracts:

1. Casting state (casting/*.json) is source-branch-owned (Fixes #2229)

  • Pre-commit hook (install-hooks.ts): Excludes .squad/casting/*.json from the git-notes state-backend pre-commit block (alongside config.json, eam.md,
    outing.md, ceremonies.md, identity/, emplates/, skills/, plugins/).
  • Worktree .gitignore (gitignore-state.ts): Stops ignoring .squad/casting/ under the worktree backend and automatically strips legacy .squad/casting/ ignore entries during �nsureStateGitignore upgrades.
  • State MCP server (packages/squad-sdk/src/tools/index.ts): Rejects casting/* reads, writes, appends, and deletes through squad_state_* with explicit error code CASTING_STATE_SOURCE_OWNED and remediation guidance to use file tools on the working tree.
  • Coordinator templates & docs: Aligns all four squad.agent.md templates (.github/agents/squad.agent.md, .squad-templates/squad.agent.md, packages/squad-cli/templates/squad.agent.md.template, packages/squad-sdk/templates/squad.agent.md.template, emplates/squad.agent.md.template) and docs/src/content/docs/features/state-backends.md so casting state is documented and operated as source-branch-owned in all backends.

2. Transactional, byte-preserving governed-memory & decision-inbox backend migration (Fixes #2425)

  • Full governed-memory & inbox discovery (migrate-backend.ts): Discovers and migrates arbitrary nested governed-memory files (memory/{agent}/{tier}/), normalizes legacy memories/{agent}/{tier}/ paths into canonical memory/{agent}/{tier}/** keys (preferring canonical entries when both exist), and includes decisions/inbox/*.md.
  • Byte-preserving migration: Replaces lossy �ppend() calls with exact write() transfers so existing content is preserved byte-for-byte without separator injection.
  • Transactional destination rollback: Snapshots pre-existing destination keys before writing and restores or deletes written keys if any destination write fails mid-migration, leaving source files untouched on error.
  • Clean source pruning: Removes migrated files and prunes empty parent directories under memory/, memories/, and decisions/inbox/ after verified destination writes when migrating away from git or worktree.

Validation

  • Targeted Vitest suites pass ( est/gitignore-state.test.ts, est/install-hooks-wi1.test.ts, est/upgrade-state-backend.test.ts, est/state-backend.test.ts, est/tools.test.ts, est/cli/state-mcp.test.ts — 184/184 tests passing).
  • Changesets and CHANGELOG.md updated.

Fixes #2229
Fixes #2425

… casting ownership

- Align casting state (casting/*.json) as source-branch-owned across git-notes pre-commit guard, worktree gitignore rules, state MCP tool rejection (CASTING_STATE_SOURCE_OWNED), coordinator templates, and docs (Fixes #2229)
- Migrate governed memory (memory/{agent}/{tier}/**) and decision inbox (decisions/inbox/*.md) transactionally with byte-preserving writes, legacy memories/ normalization, destination rollback on failure, and clean source pruning (Fixes #2425)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings November 9, 2026 16:35
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

🟠 Impact Analysis — PR #2439

Risk tier: 🟠 HIGH

📊 Summary

Metric Count
Files changed 20
Files added 2
Files modified 18
Files deleted 0
Modules touched 7
Critical files 1

🎯 Risk Factors

  • 20 files changed (6-20 → MEDIUM)
  • 7 modules touched (5-8 → HIGH)
  • Critical files touched: packages/squad-sdk/src/tools/index.ts

📦 Modules Affected

ci-workflows (1 file)
  • .github/agents/squad.agent.md
docs (1 file)
  • docs/src/content/docs/features/state-backends.md
root (4 files)
  • .changeset/casting-source-branch-ownership.md
  • .changeset/governed-memory-backend-migration.md
  • CHANGELOG.md
  • templates/squad.agent.md.template
squad-cli (4 files)
  • packages/squad-cli/src/cli/commands/doctor.ts
  • packages/squad-cli/src/cli/commands/install-hooks.ts
  • packages/squad-cli/src/cli/commands/migrate-backend.ts
  • packages/squad-cli/templates/squad.agent.md.template
squad-sdk (3 files)
  • packages/squad-sdk/src/config/gitignore-state.ts
  • packages/squad-sdk/src/tools/index.ts
  • packages/squad-sdk/templates/squad.agent.md.template
templates (1 file)
  • .squad-templates/squad.agent.md
tests (6 files)
  • test/cli/state-mcp.test.ts
  • test/gitignore-state.test.ts
  • test/install-hooks-wi1.test.ts
  • test/state-backend.test.ts
  • test/tools.test.ts
  • test/upgrade-state-backend.test.ts

⚠️ Critical Files

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

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

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

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

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 3 item(s) to address before review

Status Check Details
❌ Single commit 3 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 No .squad/ or docs/proposals/ files
✅ No merge conflicts No merge conflicts
❌ Copilot threads resolved 8 unresolved Copilot thread(s) — fix and resolve before merging
✅ CI passing All checks passing

Files Changed (20 files, +504 −169)

File +/−
.changeset/casting-source-branch-ownership.md +6 −0
.changeset/governed-memory-backend-migration.md +5 −0
.github/agents/squad.agent.md +10 −9
.squad-templates/squad.agent.md +10 −9
CHANGELOG.md +6 −0
docs/src/content/docs/features/state-backends.md +8 −6
packages/squad-cli/src/cli/commands/doctor.ts +10 −2
packages/squad-cli/src/cli/commands/install-hooks.ts +16 −11
packages/squad-cli/src/cli/commands/migrate-backend.ts +137 −81
packages/squad-cli/templates/squad.agent.md.template +10 −9
packages/squad-sdk/src/config/gitignore-state.ts +18 −8
packages/squad-sdk/src/tools/index.ts +1 −2
packages/squad-sdk/templates/squad.agent.md.template +10 −9
templates/squad.agent.md.template +10 −9
test/cli/state-mcp.test.ts +2 −2
test/gitignore-state.test.ts +32 −0
test/install-hooks-wi1.test.ts +75 −1
test/state-backend.test.ts +2 −6
test/tools.test.ts +2 −2
test/upgrade-state-backend.test.ts +134 −3

Total: +504 −169


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

@tamirdresher
tamirdresher enabled auto-merge (squash) November 9, 2026 16:41
…inked team roots (#2107, #2229)

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

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.

🟡 Changes recommended

Unresolved migration defects can omit identity files from the state backend and read or delete files outside .squad, while an existing doctor test also fails.

8 open issues
What changed in this PR

Updates Squad’s migration and casting ownership contracts for #2229 and #2425, preserving governed memory when moving from filesystem storage to Git-backed state.

Changes:

  • Adds byte-preserving memory and decision-inbox imports, destination conflict checks, and config/source safeguards.
  • Refreshes hook and ignore rules, while removing casting policy from runtime state mutations.
  • Updates coordinator guidance, documentation, release metadata, and regression coverage.
File Description
test/​upgrade-state-backend.test.ts Covers memory migration, retries, conflicts, and init lifting.
test/​tools.test.ts Updates casting mutation allowlist expectations.
test/​state-backend.test.ts Checks rejection of casting configuration mutations.
test/​install-hooks-wi1.test.ts Covers hook refresh, casting commits, and state protections.
test/​gitignore-state.test.ts Covers ignore-block upgrades and malformed markers.
test/​cli/​state-mcp.test.ts Updates casting-policy rejection expectations.
templates/​squad.agent.md.template Documents source-owned casting and runtime-state boundaries.
packages/​squad-sdk/​templates/​squad.agent.md.template Mirrors the updated coordinator contract for SDK consumers.
packages/​squad-sdk/​src/​tools/​index.ts Removes casting policy from mutable state keys.
packages/​squad-sdk/​src/​config/​gitignore-state.ts Adds memory/identity ignore rules and marker validation.
packages/​squad-cli/​templates/​squad.agent.md.template Mirrors the updated coordinator contract for CLI consumers.
packages/​squad-cli/​src/​cli/​commands/​migrate-backend.ts Imports governed state before switching authority and removing sources.
packages/​squad-cli/​src/​cli/​commands/​install-hooks.ts Refreshes managed hook sections and adjusts protected paths.
docs/​src/​content/​docs/​features/​state-backends.md Explains migration safeguards and casting ownership.
CHANGELOG.md Clarifies stable versus preview fix availability.
.squad-templates/​squad.agent.md Updates canonical coordinator ownership guidance.
.github/​agents/​squad.agent.md Aligns the repository coordinator with the updated guidance.
.changeset/​governed-memory-backend-migration.md Records the CLI migration patch.
.changeset/​casting-source-branch-ownership.md Records CLI/SDK casting ownership changes.

Review effort: Max (auto)

Comment on lines +105 to +107
if (fs.lstatSync(full).isSymbolicLink()) {
throw new Error(`Cannot migrate symbolic link .squad/${relative}; source was retained.`);
}
}
};
collectDirectory('memory');
collectDirectory('decisions/inbox');
Comment on lines 315 to 316
{ root: 'workstreams', kind: 'dir', pattern: /^workstreams\/active\/[A-Za-z0-9._-]+\/decisions\/inbox\/[^/]+$/ },
{ root: 'casting/policy.json', kind: 'file' },
{ root: 'agents', kind: 'dir', pattern: /^agents\/[a-zA-Z0-9_-]+\/history\.md$/ },
# Installed by: squad init / squad upgrade --state-backend (two-layer/orphan)
if [ -z "$SQUAD_SYNC_ACTIVE" ]; then
STAGED=$(git diff --cached --name-only 2>/dev/null | grep -E '^\\.squad/(decisions\\.md|agents/.+/history\\.md|casting/|routing/)' || true)
STAGED=$(git diff --cached --diff-filter=ACMRT --name-only 2>/dev/null | grep -E '^\\.squad/(decisions\\.md$|agents/[^/]+/history\\.md$|memory/|identity/)' || true)
Comment on lines +186 to +192
// Legacy sections end at the unindented outer `fi`. Remove every
// previously appended section, preserving user content on either side.
const cleaned = existing.replace(/^# --- squad-sync-hook ---\r?\n[\s\S]*?^fi(?:\r?\n|$)/gm, '').trimEnd();
if (cleaned.includes(SQUAD_HOOK_MARKER)) {
throw new Error(`Cannot safely replace squad section in ${hookPath}; repair the hook manually.`);
}
fs.writeFileSync(hookPath, cleaned + '\n\n' + content.split('\n').slice(1).join('\n'), { mode: 0o755 });
Comment on lines +156 to +159
const content = execFileSync('git', ['show', `${parentSha}:${f.relPath}`], {
cwd: dest, stdio: ['pipe', 'pipe', 'pipe'],
});
if (!content.equals(f.content)) {
Comment on lines +231 to 236
const current = config['stateBackend'] ?? 'local';
if (typeof current !== 'string' || !VALID_TARGETS.includes(current)) {
throw new Error(`Unsupported source backend '${String(current)}'; backend was not switched.`);
}

if (current === target) {
if (!isMutableStateKey(key)) {
throw new Error(
'State mutations are limited to mutable runtime state (decisions, inbox, casting policy, logs, sessions, scratch files, agent history, and identity). The casting registry/history pair must only be changed through the atomic casting protocol. Static config such as config.json, team.md, routing.md, charters, templates, and skills must not be changed with state tools.',
'State mutations are limited to mutable runtime state (decisions, inbox, logs, sessions, scratch files, agent history, and identity). Casting is source-branch configuration; the casting registry/history pair must only be changed through the atomic casting protocol on disk. Static config such as casting policy, config.json, team.md, routing.md, charters, templates, and skills must not be changed with state tools.',
# Installed by: squad init / squad upgrade --state-backend (two-layer/orphan)
if [ -z "$SQUAD_SYNC_ACTIVE" ]; then
STAGED=$(git diff --cached --name-only 2>/dev/null | grep -E '^\\.squad/(decisions\\.md|agents/.+/history\\.md|casting/|routing/)' || true)
STAGED=$(git diff --cached --diff-filter=ACMRT --name-only 2>/dev/null | grep -E '^\\.squad/(decisions\\.md$|agents/[^/]+/history\\.md$|memory/|identity/)' || true)
@tamirdresher
tamirdresher merged commit 9ee909e into dev Nov 9, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants