ADR-078: Subagent Context Isolation Pattern
Status
Accepted - January 16, 2026
MoE Judges Score: 9.3/10 (APPROVED)
Context
Problem Statement
When CODITECT dispatches subagents for parallel task execution, several context-related issues occur:
- Context Pollution: Subagents inherit parent's full context, including unrelated tasks
- Task Confusion: Subagent sees all tasks, not just its assigned one
- Information Overload: Large parent context degrades subagent performance
- Cross-Task Leakage: Information from sibling tasks bleeds into execution
- Inconsistent Results: Same task may produce different results based on context state
Source Analysis: Superpowers Pattern
Analysis of submodules/superpowers reveals a critical isolation pattern:
Superpowers' Fresh-Context Protocol:
Controller (Parent) Subagent (Child)
┌───────────────────┐ ┌───────────────────┐
│ Has full plan │ │ Gets ONLY: │
│ Sees all tasks │ Dispatch │ • Single task │
│ Tracks progress │ ──────────────► │ • Arch context │
│ Manages state │ │ • NO sibling info │
└───────────────────┘ └───────────────────┘
Key Rules:
1. Controller reads plan ONCE at start
2. Extract ALL tasks to TodoWrite
3. Per task: Create FRESH subagent
4. Pass FULL TASK TEXT (not file refs)
5. Subagent is UNAWARE of other tasks
Key Insight: "Subagent should complete its task without knowing what other tasks exist."
Current CODITECT Limitation
- Subagents via
Task(subagent_type=...)inherit conversation context - No mechanism to specify "isolated" context
- File references passed instead of full content
- No standardized architectural context injection
Requirements
- Fresh Context: Each subagent starts with clean slate
- Task-Only Scope: Subagent receives only its assigned task
- Architectural Context: Essential project context provided
- No Sibling Visibility: Cannot see parallel tasks
- Full Content: Pass text content, not file references
- TDD Compliance: Tests written before implementation
Decision
Implement Subagent Context Isolation Pattern that ensures subagents receive clean, task-specific context.
1. Architecture Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ SUBAGENT CONTEXT ISOLATION ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ CONTROLLER AGENT │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Plan Ingestion (ONE TIME at start) │ │
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. Read PILOT plan │ │ │
│ │ │ 2. Extract all tasks to TodoWrite │ │ │
│ │ │ 3. Build architectural context document │ │ │
│ │ │ 4. Cache: task_id → task_details │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Per-Task Dispatch │ │
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
│ │ │ For each task: │ │ │
│ │ │ 1. Build IsolatedContext (task_details + arch_context) │ │ │
│ │ │ 2. Create FRESH subagent with isolated context │ │ │
│ │ │ 3. Subagent executes in isolation │ │ │
│ │ │ 4. Controller receives result │ │ │
│ │ │ 5. Mark task complete, move to next │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ISOLATED CONTEXT STRUCTURE │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ IsolatedContext │ │
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
│ │ │ task_id: "A.9.1.3" │ │ │
│ │ │ task_description: "Full task text (not file reference)" │ │ │
│ │ │ architectural_context: │ │ │
│ │ │ - Project structure │ │ │
│ │ │ - Key patterns │ │ │
│ │ │ - Relevant ADRs │ │ │
│ │ │ - Dependencies │ │ │
│ │ │ file_contents: { │ │ │
│ │ │ "path/to/file.py": "full content..." # NOT path reference │ │ │
│ │ │ } │ │ │
│ │ │ constraints: ["No changes to X", "Must use Y"] │ │ │
│ │ │ │ │ │
│ │ │ EXCLUDED: │ │ │
│ │ │ - Other task descriptions │ │ │
│ │ │ - Parent conversation history │ │ │
│ │ │ - Sibling task results │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ SUBAGENT EXECUTION │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Subagent sees ONLY: │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ 1. Its single task │ │ │
│ │ │ 2. Architectural context │ │ │
│ │ │ 3. Required file contents │ │ │
│ │ │ 4. Constraints │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ │ │
│ │ Subagent can: │ │
│ │ • Ask clarifying questions (via AskUserQuestion) │ │
│ │ • Request additional file contents │ │
│ │ • Complete self-review checklist │ │
│ │ • Submit for two-stage review (ADR-076) │ │
│ │ │ │
│ │ Subagent CANNOT: │ │
│ │ • See other tasks │ │
│ │ • Access parent conversation │ │
│ │ • Know how many total tasks exist │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2. Core Implementation
# scripts/core/context_isolation.py
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Any
from pathlib import Path
@dataclass
class ArchitecturalContext:
"""
Essential project context provided to all subagents.
This is the ONLY shared context across subagents.
"""
project_structure: str # Directory tree
key_patterns: List[str] # Design patterns used
relevant_adrs: List[str] # ADR summaries (not full text)
dependencies: Dict[str, str] # Key dependencies and versions
naming_conventions: str # Code style rules
track_nomenclature: str # Task ID format
@classmethod
def from_project(cls, project_root: Path) -> "ArchitecturalContext":
"""Build architectural context from project."""
return cls(
project_structure=cls._get_structure(project_root),
key_patterns=cls._extract_patterns(project_root),
relevant_adrs=cls._summarize_adrs(project_root),
dependencies=cls._get_dependencies(project_root),
naming_conventions=cls._get_conventions(project_root),
track_nomenclature="Format: Track.Section.Task[.Subtask] (e.g., A.9.1.3)"
)
@staticmethod
def _get_structure(root: Path) -> str:
"""Get project directory structure."""
# Implementation: tree-like output of key directories
pass
@staticmethod
def _extract_patterns(root: Path) -> List[str]:
"""Extract key patterns from codebase."""
pass
@staticmethod
def _summarize_adrs(root: Path) -> List[str]:
"""Get ADR titles and summaries."""
pass
@staticmethod
def _get_dependencies(root: Path) -> Dict[str, str]:
"""Get key dependencies."""
pass
@staticmethod
def _get_conventions(root: Path) -> str:
"""Get naming/style conventions."""
pass
@dataclass
class IsolatedContext:
"""
Context provided to a single subagent.
This context is ISOLATED - the subagent cannot see other tasks.
"""
task_id: str
task_description: str # Full text, NOT file reference
architectural_context: ArchitecturalContext
file_contents: Dict[str, str] = field(default_factory=dict) # path → content
constraints: List[str] = field(default_factory=list)
clarifications: List[str] = field(default_factory=list)
def to_prompt(self) -> str:
"""Convert to subagent prompt."""
files_section = "\n".join(
f"### {path}\n```\n{content}\n```"
for path, content in self.file_contents.items()
)
return f"""
## Your Task
**Task ID:** {self.task_id}
{self.task_description}
## Architectural Context
**Project Structure:**
{self.architectural_context.project_structure}
**Key Patterns:**
{chr(10).join(f"- {p}" for p in self.architectural_context.key_patterns)}
**Relevant ADRs:**
{chr(10).join(f"- {a}" for a in self.architectural_context.relevant_adrs)}
**Task ID Format:**
{self.architectural_context.track_nomenclature}
## File Contents
{files_section if files_section else "No files provided. Request if needed."}
## Constraints
{chr(10).join(f"- {c}" for c in self.constraints) if self.constraints else "None specified."}
## Important Notes
- You are working on ONE task only
- You do not need to know about other tasks
- Include task ID in all tool descriptions: `{self.task_id}: action`
- Ask clarifying questions if needed
- Complete self-review before submitting
"""
class ContextIsolationManager:
"""
Manages context isolation for subagent dispatch.
Implements Superpowers' fresh-context-per-task pattern.
"""
def __init__(self, project_root: Path):
self.project_root = project_root
self.arch_context = ArchitecturalContext.from_project(project_root)
self.task_cache: Dict[str, IsolatedContext] = {}
def create_isolated_context(
self,
task_id: str,
task_description: str,
required_files: Optional[List[str]] = None,
constraints: Optional[List[str]] = None
) -> IsolatedContext:
"""
Create isolated context for a subagent.
Args:
task_id: PILOT plan task ID (e.g., A.9.1.3)
task_description: Full task text (NOT file reference)
required_files: Paths to files subagent needs
constraints: Constraints for this task
Returns:
IsolatedContext ready for subagent dispatch
"""
# Load file contents (not references)
file_contents = {}
if required_files:
for path in required_files:
full_path = self.project_root / path
if full_path.exists():
file_contents[path] = full_path.read_text()
context = IsolatedContext(
task_id=task_id,
task_description=task_description,
architectural_context=self.arch_context,
file_contents=file_contents,
constraints=constraints or []
)
# Cache for potential re-dispatch
self.task_cache[task_id] = context
return context
def dispatch_subagent(
self,
context: IsolatedContext,
agent_type: str = "general"
) -> str:
"""
Generate Task tool invocation for isolated subagent.
Returns ready-to-execute Task() call.
"""
prompt = context.to_prompt()
# Escape for JSON
escaped_prompt = prompt.replace('"', '\\"').replace('\n', '\\n')
return f'''Task(
subagent_type="{agent_type}",
prompt="{escaped_prompt}",
description="{context.task_id}: Execute isolated task"
)'''
def add_clarification(
self,
task_id: str,
clarification: str
) -> None:
"""Add clarification to task context."""
if task_id in self.task_cache:
self.task_cache[task_id].clarifications.append(clarification)
def add_file_content(
self,
task_id: str,
file_path: str
) -> None:
"""Add file content to task context (subagent requested)."""
if task_id in self.task_cache:
full_path = self.project_root / file_path
if full_path.exists():
self.task_cache[task_id].file_contents[file_path] = full_path.read_text()
3. Controller Protocol Skill
# skills/subagent-context-isolation/SKILL.md
---
name: subagent-context-isolation
description: Protocol for dispatching subagents with isolated, task-specific context
---
# Subagent Context Isolation
## When to Use
- Dispatching multiple tasks from a plan
- Parallel agent execution
- Any subagent dispatch
## The Protocol
### Controller Responsibilities
1. **Read Plan Once**: At session start, read PILOT plan completely
2. **Extract Tasks**: Add all tasks to TodoWrite
3. **Build Arch Context**: Create shared architectural context
4. **Per-Task Dispatch**:
- Create IsolatedContext with task details
- Pass FULL TASK TEXT (not file references)
- Include required file CONTENTS
- Dispatch fresh subagent
### What Subagent Receives
✅ **INCLUDED:**
- Single task description (full text)
- Architectural context (project structure, patterns)
- Required file contents (actual code, not paths)
- Constraints specific to task
❌ **EXCLUDED:**
- Other task descriptions
- Parent conversation history
- Sibling task results
- How many total tasks exist
### Example Dispatch
```python
# Controller builds isolated context
context = isolation_manager.create_isolated_context(
task_id="A.9.1.3",
task_description="""
Implement the UserService class with the following methods:
- create_user(email, password) -> User
- get_user(user_id) -> Optional[User]
- update_user(user_id, **kwargs) -> User
Requirements:
- Use SQLAlchemy ORM
- Hash passwords with bcrypt
- Validate email format
""",
required_files=[
"models/user.py",
"services/base_service.py"
],
constraints=[
"Do not modify existing model fields",
"Use existing database session pattern"
]
)
# Dispatch subagent
Task(
subagent_type="backend-specialist",
prompt=context.to_prompt()
)
Subagent Can Ask Questions
Subagent may ask clarifying questions before or during work:
# Subagent asks
AskUserQuestion(questions=[{
"question": "Should create_user validate password strength?",
"header": "Password",
"options": [
{"label": "Yes, enforce rules", "description": "Min 8 chars, etc."},
{"label": "No, accept any", "description": "Caller responsible"}
]
}])
# Controller adds clarification to context
isolation_manager.add_clarification(
"A.9.1.3",
"Yes, enforce password strength: min 8 chars, 1 uppercase, 1 digit"
)
Benefits
- No Context Pollution: Subagent unaware of other tasks
- Focused Execution: Only relevant information provided
- Parallel Safety: Subagents cannot interfere
- Reproducible: Same context = same behavior
- Debuggable: Can inspect exact context provided
### 4. TDD Test Specifications
```python
# tests/core/test_context_isolation.py
import pytest
from pathlib import Path
from unittest.mock import patch, MagicMock
from scripts.core.context_isolation import (
IsolatedContext,
ArchitecturalContext,
ContextIsolationManager
)
class TestIsolatedContext:
"""TDD tests for IsolatedContext."""
def test_to_prompt_includes_task_id(self):
"""RED→GREEN: Prompt includes task ID."""
arch = MagicMock(spec=ArchitecturalContext)
arch.project_structure = "project/"
arch.key_patterns = ["MVC"]
arch.relevant_adrs = ["ADR-001"]
arch.track_nomenclature = "X.N.N"
ctx = IsolatedContext(
task_id="A.9.1.3",
task_description="Implement feature",
architectural_context=arch
)
prompt = ctx.to_prompt()
assert "A.9.1.3" in prompt
assert "Implement feature" in prompt
def test_to_prompt_excludes_other_tasks(self):
"""RED→GREEN: Prompt does not mention other tasks."""
arch = MagicMock(spec=ArchitecturalContext)
arch.project_structure = "project/"
arch.key_patterns = []
arch.relevant_adrs = []
arch.track_nomenclature = "X.N.N"
ctx = IsolatedContext(
task_id="A.1.1",
task_description="Task A",
architectural_context=arch
)
prompt = ctx.to_prompt()
# Should not contain references to other tasks
assert "other task" not in prompt.lower()
assert "A.1.2" not in prompt
assert "total tasks" not in prompt.lower()
def test_file_contents_included_not_paths(self):
"""RED→GREEN: File contents included, not just paths."""
arch = MagicMock(spec=ArchitecturalContext)
arch.project_structure = ""
arch.key_patterns = []
arch.relevant_adrs = []
arch.track_nomenclature = ""
ctx = IsolatedContext(
task_id="A.1.1",
task_description="Fix bug",
architectural_context=arch,
file_contents={
"src/module.py": "def foo(): return 42"
}
)
prompt = ctx.to_prompt()
assert "def foo(): return 42" in prompt
assert "src/module.py" in prompt
class TestContextIsolationManager:
"""TDD tests for ContextIsolationManager."""
@pytest.fixture
def manager(self, tmp_path):
# Create minimal project structure
(tmp_path / "src").mkdir()
(tmp_path / "src" / "module.py").write_text("class Foo: pass")
with patch.object(ArchitecturalContext, 'from_project') as mock:
mock.return_value = MagicMock(spec=ArchitecturalContext)
return ContextIsolationManager(tmp_path)
def test_creates_isolated_context(self, manager):
"""RED→GREEN: Creates IsolatedContext with task details."""
ctx = manager.create_isolated_context(
task_id="A.1.1",
task_description="Implement feature"
)
assert ctx.task_id == "A.1.1"
assert ctx.task_description == "Implement feature"
def test_loads_file_contents(self, manager, tmp_path):
"""RED→GREEN: Loads actual file contents."""
ctx = manager.create_isolated_context(
task_id="A.1.1",
task_description="Fix bug",
required_files=["src/module.py"]
)
assert "src/module.py" in ctx.file_contents
assert "class Foo: pass" in ctx.file_contents["src/module.py"]
def test_caches_context(self, manager):
"""RED→GREEN: Context cached for re-dispatch."""
ctx = manager.create_isolated_context(
task_id="A.1.1",
task_description="Task"
)
assert "A.1.1" in manager.task_cache
assert manager.task_cache["A.1.1"] is ctx
def test_add_clarification(self, manager):
"""RED→GREEN: Can add clarifications to cached context."""
manager.create_isolated_context(
task_id="A.1.1",
task_description="Task"
)
manager.add_clarification("A.1.1", "Use async methods")
assert "Use async methods" in manager.task_cache["A.1.1"].clarifications
def test_dispatch_generates_task_call(self, manager):
"""RED→GREEN: Generates valid Task() invocation."""
ctx = manager.create_isolated_context(
task_id="A.1.1",
task_description="Implement"
)
dispatch = manager.dispatch_subagent(ctx, "backend-specialist")
assert "Task(" in dispatch
assert "subagent_type=" in dispatch
assert "backend-specialist" in dispatch
Consequences
Positive
- No Pollution: Subagents work with clean context
- Focused Work: Only task-relevant information provided
- Parallel Safe: Tasks cannot interfere with each other
- Reproducible: Same input = same output
- Debuggable: Can inspect exact context per task
Negative
- Context Building: Overhead to build architectural context
- File Loading: Large files must be loaded into memory
- Cache Management: Task cache grows with task count
Mitigations
- Lazy Loading: Build arch context once, reuse
- File Limits: Cap file content size
- Cache Expiry: Clear cache after task completion
Implementation
Files to Create
| File | Purpose | LOC Est. |
|---|---|---|
scripts/core/context_isolation.py | Core isolation classes | ~300 |
skills/subagent-context-isolation/SKILL.md | Protocol documentation | ~200 |
tests/core/test_context_isolation.py | TDD tests | ~200 |
Files to Modify
| File | Changes |
|---|---|
scripts/core/agent_dispatcher.py | Integrate isolation manager |
agents/controller.md | Reference isolation pattern |
References
- Source Analysis:
submodules/superpowers/skills/subagent-driven-development/SKILL.md - Related ADR: ADR-076 (Two-Stage Review), ADR-077 (Recursive Chains)
- Superpowers Prompts:
implementer-prompt.md
Author: CODITECT Architecture Team Reviewers: Architecture Council Source Commit: Analysis of submodules/superpowers