Repository navigation
[Feature]: Multi-agent isolation protocol — process-level feature context without shared-state races #4128
Description
Problem Statement
When multiple AI agents run Spec Kit pipelines concurrently within the same repository checkout (e.g., Antigravity subagents, Claude Code sub-agents, or parallel Cursor Composer tabs), they share a single .specify/feature.json file as their feature context pointer. This creates a write-write race condition:
Timeline:
t0 Agent-A: SPECIFY_FEATURE_DIRECTORY="specs/003-auth" → setup-plan.sh
→ get_feature_paths() persists "specs/003-auth" to feature.json ✓
t1 Agent-B: SPECIFY_FEATURE_DIRECTORY="specs/004-perf" → setup-tasks.sh
→ get_feature_paths() persists "specs/004-perf" to feature.json ← overwrites A's value
t2 Agent-A: (new process, no env var) → check-prerequisites.sh
→ reads feature.json → resolves "specs/004-perf" ← WRONG FEATURE
The root issue: get_feature_paths() in common.sh (L191-192) always persists SPECIFY_FEATURE_DIRECTORY back to feature.json unless the caller explicitly passes --no-persist. Since most scripts (setup-plan.sh, setup-tasks.sh) call get_feature_paths without --no-persist, every agent invocation silently overwrites the shared singleton.
Impact
- Silent cross-contamination: Agent B's plan/tasks get written into Agent A's feature directory (or vice versa) without any error signal.
- Non-reproducible failures: The behavior depends on timing — sometimes it works, sometimes it doesn't, making debugging extremely difficult.
- Blocks multi-agent orchestration: Features like
/speckit-implement-waves([Feature]: /speckit-implement-waves => run each phase in a subagent to prevent context rot #3507), which propose running phases in parallel subagents, cannot work safely without solving this shared-state problem first.
Real-world reproduction
We encountered this in TTZip (a macOS archive utility, 525+ tests, 28 design patterns) while running Antigravity subagents to parallelize a sorting-bugfix TDD suite alongside a 7z compression optimization. Both agents used SPECIFY_FEATURE_DIRECTORY correctly in their own processes, but the persist-on-read side effect in get_feature_paths caused each agent to clobber the other's feature.json entry on every script call.
Root Cause Analysis
The feature resolution chain in common.sh get_feature_paths() (L163-231) has a correct read priority:
1. SPECIFY_FEATURE_DIRECTORY env var (explicit override)
2. .specify/feature.json (persisted fallback)
3. Error (no context)
But it has an unconditional write side effect on the env-var branch (L191-192):
if [[ "$no_persist" != true ]]; then
_persist_feature_json "$repo_root" "$SPECIFY_FEATURE_DIRECTORY"
fiThe --no-persist guard (added in #3025) is a function-level parameter, not an environment-level control. Scripts that are "just resolving paths" but don't know they should pass --no-persist (like setup-plan.sh, setup-tasks.sh) trigger the persist unconditionally.
What already works
Credit to the maintainers — the infrastructure for multi-agent isolation is already in place:
| Mechanism | Status | Issue |
|---|---|---|
SPECIFY_FEATURE_DIRECTORY env var priority |
✅ Working | — |
--no-persist read-only resolution |
✅ Working | #3025 |
CURRENT_BRANCH fallback from feature dir basename |
✅ Working | #3026 |
SPECIFY_INIT_DIR for monorepo project scoping |
✅ Working | — |
| Parser fallback chain (jq → python3 → grep/sed) | ✅ Working | #3304 |
What's missing is the guidance layer: documentation, agent skill instructions, and an environment-level no-persist toggle.
Proposed Solution
1. Official multi-agent documentation (docs/multi-agent.md)
A new document covering:
- The race condition scenario (as above)
- The Multi-Agent Isolation Protocol: always inject
SPECIFY_FEATURE_DIRECTORYper-process, never rely onfeature.jsonfor read - Integration-specific examples (Antigravity subagents, Claude Code sub-agents, Cursor multi-tab, CI matrix)
- FAQ: "Do I need git worktrees?" → No, env-var isolation is sufficient for same-checkout concurrency
2. Update agent skill templates to stop instructing direct feature.json writes
Currently, the specify command template (the upstream equivalent of speckit-specify/SKILL.md) instructs agents to:
Persist the resolved path to
.specify/feature.json:{"feature_directory": "<resolved feature dir>"}
This instruction should be replaced with:
Pass the resolved feature directory to downstream commands via
SPECIFY_FEATURE_DIRECTORYenvironment variable prefix. Example:SPECIFY_FEATURE_DIRECTORY="specs/003-auth" .specify/scripts/bash/setup-plan.sh --json
The feature.json persistence should remain as an automatic side effect of get_feature_paths() for single-agent backward compatibility, but agents should not be told to write it directly (which bypasses the script's own idempotency guards in _persist_feature_json).
3. (Optional) SPECIFY_NO_PERSIST environment variable
Add an environment-level equivalent of the --no-persist function parameter:
# In get_feature_paths(), after the --no-persist argument check (L167-171):
if [[ "${SPECIFY_NO_PERSIST:-}" == "1" || "${SPECIFY_NO_PERSIST:-}" == "true" ]]; then
no_persist=true
fiThis allows CI pipelines and agent orchestrators to set SPECIFY_NO_PERSIST=1 globally, ensuring that no script invocation can accidentally write feature.json — even scripts that don't pass --no-persist internally.
Backward Compatibility
This proposal is fully backward compatible:
| Scenario | Before | After |
|---|---|---|
| Single agent, no env var | Reads feature.json |
Identical behavior |
| Single agent, with env var | Reads env var, persists to feature.json |
Identical behavior |
| Multi-agent, each sets env var | Race on feature.json (bug) |
Each agent's reads are short-circuited by env var; persistence is harmless |
Multi-agent + SPECIFY_NO_PERSIST=1 |
N/A | No feature.json writes at all |
specify integration upgrade |
Overwrites managed files | docs/multi-agent.md is not in manifest; protocol rules live in user-space |
No existing scripts, templates, or workflows change behavior. The persist side effect is still there (it's a "last writer wins" overwrite that's harmless when every reader uses env vars). SPECIFY_NO_PERSIST is strictly additive.
Reference Implementation
We've been running this protocol in production at TTZip with Antigravity (Google DeepMind's agentic coding tool) subagents. Our implementation consists of:
- Project-level rule file (
.agents/rules/speckit-multiagent.md): Instructs all agents to injectSPECIFY_FEATURE_DIRECTORYper-process and never read/writefeature.jsondirectly. - Global user rule: Gates (hard state machine gating) that prevent any agent from writing production code before spec/plan/tasks artifacts exist under the declared feature directory.
- Concurrent verification: Validated that two agents operating on
specs/003-sorting-fix/andspecs/006-7z-conquest/simultaneously produce zero cross-contamination.
The protocol adds zero overhead to single-agent workflows and requires no upstream code changes to function — it's purely a documentation and guidance contribution. The optional SPECIFY_NO_PERSIST env var is a small, additive improvement to common.sh.
Related Issues
- [Feature]: /speckit-implement-waves => run each phase in a subagent to prevent context rot #3507 —
/speckit-implement-waves: Needs this isolation protocol as a prerequisite for safe parallel phase execution - Native
git worktreeSupport for Concurrent/Parallel Agent Execution #1476 — Git worktree isolation: Our approach is complementary (env-var isolation within a single checkout vs. filesystem isolation across worktrees) - [Bug]: get_feature_paths persists .specify/feature.json during read-only path resolution (check-prerequisites.sh --paths-only) #3025 —
--no-persistfor read-only resolution: Foundation we build on - [Bug]: get_feature_paths emits empty CURRENT_BRANCH when SPECIFY_FEATURE unset, despite feature context via SPECIFY_FEATURE_DIRECTORY/feature.json #3026 —
CURRENT_BRANCHfallback: Foundation we build on - Feature Request: Utilize Claude Code's Subagent Feature for Command Execution #752 — Claude Code subagent feature execution: Would benefit from this protocol
Component
Core scripts (common.sh), Documentation, Agent skill templates
Activity
wittkung commentedon Aug 14, 2026
Addendum: Why this matters beyond any single platform — universal multi-agent safety through the process environment
I want to highlight a design property of this proposal that I think deserves explicit attention: the isolation boundary is the UNIX/Win32 process environment — the most universal, zero-cost primitive available, and this makes the protocol work across all 30+ Spec Kit integrations without any platform-specific support.
The landscape of multi-agent isolation approaches
node_modules/venvbloat, disk I/OSPECIFY_FEATURE_DIRECTORYenv var (this proposal)The key insight: every AI coding agent, regardless of platform, eventually executes a shell command. Whether it's Claude Code running
bash, Cursor invoking a terminal, Copilot Workspace executing a script, or Antigravity callingrun_command— they all spawn a process. And every process has an environment.What this means in practice
A project maintainer can write one rule file (like our
.agents/rules/speckit-multiagent.md) that says:And this single instruction works identically across:
invoke_subagent/speckit.implementin parallel terminalsSPECIFY_NO_PERSIST=1)No platform needs to add any multi-agent primitives. No worktree management. No VM provisioning. The protocol is non-invasive — it rides on infrastructure that already exists everywhere.
Contrast with branch-based approaches
Many multi-agent systems default to "one branch per agent" for isolation. This introduces significant complexity:
SPECIFY_FEATURE_DIRECTORY→feature.json→ error resolution chain ([Bug]: get_feature_paths persists .specify/feature.json during read-only path resolution (check-prerequisites.sh --paths-only) #3025, [Bug]: get_feature_paths emits empty CURRENT_BRANCH when SPECIFY_FEATURE unset, despite feature context via SPECIFY_FEATURE_DIRECTORY/feature.json #3026) was explicitly designed to work without git branch context. Our proposal simply documents this capability and makes agents aware of it.The elegance of the env-var approach is that multiple agents can work on different features within the same branch (e.g.,
main), because the isolation is at the feature-directory level, not the branch level. Each agent writes to its ownspecs/<NNN-feature>/directory tree. There's no merge conflict because there's no branch divergence.Summary
This proposal turns an implicit capability of Spec Kit's existing architecture into an explicit, documented, cross-platform protocol. The upstream cost is near-zero (docs + template wording change + optional 3-line env var). The downstream value is that every Spec Kit user, on any platform, gets multi-agent safety without waiting for their specific platform to ship isolation features.
mnriem commentedon Aug 17, 2026
I can support a SPECIFY_FEATURE_NO_PERSIST for this use case
feat(scripts): add SPECIFY_FEATURE_NO_PERSIST env var to suppress fea…