agentic-orchestrator 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/AGENTS.md +2 -2
  2. package/CLAUDE.md +2 -2
  3. package/README.md +47 -14
  4. package/agentic/orchestrator/agents.yaml +13 -0
  5. package/agentic/orchestrator/policy.yaml +3 -0
  6. package/agentic/orchestrator/schemas/agents.schema.json +76 -0
  7. package/agentic/orchestrator/schemas/policy.schema.json +16 -0
  8. package/agentic/orchestrator/schemas/policy.user.schema.json +16 -0
  9. package/agentic/orchestrator/schemas/state.schema.json +53 -0
  10. package/apps/control-plane/src/application/configuration-service.ts +181 -0
  11. package/apps/control-plane/src/application/kernel-tool-wiring.ts +292 -0
  12. package/apps/control-plane/src/application/services/checkpoint-service.ts +523 -0
  13. package/apps/control-plane/src/application/services/feature-send-message-service.ts +132 -0
  14. package/apps/control-plane/src/application/services/patch-service.ts +29 -5
  15. package/apps/control-plane/src/application/services/repo-operations-service.ts +276 -0
  16. package/apps/control-plane/src/application/services/worktree-watchdog-service.ts +156 -0
  17. package/apps/control-plane/src/cli/cli-argument-parser.ts +12 -0
  18. package/apps/control-plane/src/cli/help-command-handler.ts +17 -0
  19. package/apps/control-plane/src/cli/init-command-handler.ts +31 -0
  20. package/apps/control-plane/src/cli/resume-command-handler.ts +31 -4
  21. package/apps/control-plane/src/cli/rollback-command-handler.ts +217 -0
  22. package/apps/control-plane/src/cli/run-command-handler.ts +8 -0
  23. package/apps/control-plane/src/cli/types.ts +3 -0
  24. package/apps/control-plane/src/core/kernel-types.ts +55 -0
  25. package/apps/control-plane/src/core/kernel.ts +61 -878
  26. package/apps/control-plane/src/core/tool-caller.ts +10 -0
  27. package/apps/control-plane/src/core/utils/field-readers.ts +38 -0
  28. package/apps/control-plane/src/core/utils/index-normalizer.ts +119 -0
  29. package/apps/control-plane/src/core/utils/path-normalizers.ts +22 -0
  30. package/apps/control-plane/src/interfaces/cli/bootstrap.ts +15 -0
  31. package/apps/control-plane/src/providers/api-worker-provider.ts +14 -12
  32. package/apps/control-plane/src/providers/cli-worker-provider.ts +82 -12
  33. package/apps/control-plane/src/providers/providers.ts +45 -24
  34. package/apps/control-plane/src/providers/worker-provider-factory.ts +36 -1
  35. package/apps/control-plane/src/supervisor/run-coordinator.ts +91 -36
  36. package/apps/control-plane/src/supervisor/runtime.ts +107 -1
  37. package/apps/control-plane/src/supervisor/types.ts +9 -0
  38. package/apps/control-plane/src/supervisor/worker-decision-loop.ts +253 -14
  39. package/apps/control-plane/test/checkpoint-service.spec.ts +537 -0
  40. package/apps/control-plane/test/cli-helpers.spec.ts +28 -0
  41. package/apps/control-plane/test/cli.unit.spec.ts +52 -0
  42. package/apps/control-plane/test/configuration-service.spec.ts +466 -0
  43. package/apps/control-plane/test/dashboard-api.integration.spec.ts +537 -0
  44. package/apps/control-plane/test/dashboard-client.spec.ts +233 -0
  45. package/apps/control-plane/test/feature-send-message-service.spec.ts +314 -0
  46. package/apps/control-plane/test/init-wizard.spec.ts +35 -0
  47. package/apps/control-plane/test/path-normalizers.spec.ts +41 -0
  48. package/apps/control-plane/test/repo-operations-service.spec.ts +339 -0
  49. package/apps/control-plane/test/resume-command.spec.ts +33 -0
  50. package/apps/control-plane/test/review-workspace-logic.spec.ts +130 -0
  51. package/apps/control-plane/test/rollback-command.spec.ts +208 -0
  52. package/apps/control-plane/test/run-coordinator.spec.ts +119 -0
  53. package/apps/control-plane/test/worker-decision-loop.spec.ts +209 -0
  54. package/apps/control-plane/test/worker-provider-adapters.spec.ts +102 -0
  55. package/apps/control-plane/test/worker-provider-factory.spec.ts +14 -0
  56. package/apps/control-plane/test/worktree-watchdog-service.spec.ts +147 -0
  57. package/config/agentic/orchestrator/agents.yaml +13 -0
  58. package/dist/apps/control-plane/application/configuration-service.d.ts +19 -0
  59. package/dist/apps/control-plane/application/configuration-service.js +123 -0
  60. package/dist/apps/control-plane/application/configuration-service.js.map +1 -0
  61. package/dist/apps/control-plane/application/kernel-tool-wiring.d.ts +39 -0
  62. package/dist/apps/control-plane/application/kernel-tool-wiring.js +38 -0
  63. package/dist/apps/control-plane/application/kernel-tool-wiring.js.map +1 -0
  64. package/dist/apps/control-plane/application/services/checkpoint-service.d.ts +84 -0
  65. package/dist/apps/control-plane/application/services/checkpoint-service.js +367 -0
  66. package/dist/apps/control-plane/application/services/checkpoint-service.js.map +1 -0
  67. package/dist/apps/control-plane/application/services/feature-send-message-service.d.ts +25 -0
  68. package/dist/apps/control-plane/application/services/feature-send-message-service.js +105 -0
  69. package/dist/apps/control-plane/application/services/feature-send-message-service.js.map +1 -0
  70. package/dist/apps/control-plane/application/services/patch-service.d.ts +6 -0
  71. package/dist/apps/control-plane/application/services/patch-service.js +11 -2
  72. package/dist/apps/control-plane/application/services/patch-service.js.map +1 -1
  73. package/dist/apps/control-plane/application/services/repo-operations-service.d.ts +70 -0
  74. package/dist/apps/control-plane/application/services/repo-operations-service.js +213 -0
  75. package/dist/apps/control-plane/application/services/repo-operations-service.js.map +1 -0
  76. package/dist/apps/control-plane/application/services/worktree-watchdog-service.d.ts +23 -0
  77. package/dist/apps/control-plane/application/services/worktree-watchdog-service.js +119 -0
  78. package/dist/apps/control-plane/application/services/worktree-watchdog-service.js.map +1 -0
  79. package/dist/apps/control-plane/cli/cli-argument-parser.js +12 -0
  80. package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
  81. package/dist/apps/control-plane/cli/help-command-handler.js +17 -0
  82. package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -1
  83. package/dist/apps/control-plane/cli/init-command-handler.js +23 -0
  84. package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -1
  85. package/dist/apps/control-plane/cli/resume-command-handler.js +25 -5
  86. package/dist/apps/control-plane/cli/resume-command-handler.js.map +1 -1
  87. package/dist/apps/control-plane/cli/rollback-command-handler.d.ts +6 -0
  88. package/dist/apps/control-plane/cli/rollback-command-handler.js +177 -0
  89. package/dist/apps/control-plane/cli/rollback-command-handler.js.map +1 -0
  90. package/dist/apps/control-plane/cli/run-command-handler.js +7 -1
  91. package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
  92. package/dist/apps/control-plane/cli/types.d.ts +3 -0
  93. package/dist/apps/control-plane/cli/types.js +1 -0
  94. package/dist/apps/control-plane/cli/types.js.map +1 -1
  95. package/dist/apps/control-plane/core/configuration-service.d.ts +25 -0
  96. package/dist/apps/control-plane/core/configuration-service.js +130 -0
  97. package/dist/apps/control-plane/core/configuration-service.js.map +1 -0
  98. package/dist/apps/control-plane/core/kernel-tool-wiring.d.ts +50 -0
  99. package/dist/apps/control-plane/core/kernel-tool-wiring.js +44 -0
  100. package/dist/apps/control-plane/core/kernel-tool-wiring.js.map +1 -0
  101. package/dist/apps/control-plane/core/kernel-types.d.ts +48 -0
  102. package/dist/apps/control-plane/core/kernel-types.js +2 -0
  103. package/dist/apps/control-plane/core/kernel-types.js.map +1 -0
  104. package/dist/apps/control-plane/core/kernel.d.ts +17 -48
  105. package/dist/apps/control-plane/core/kernel.js +44 -539
  106. package/dist/apps/control-plane/core/kernel.js.map +1 -1
  107. package/dist/apps/control-plane/core/tool-caller.d.ts +10 -0
  108. package/dist/apps/control-plane/core/utils/error-normalizer.d.ts +2 -0
  109. package/dist/apps/control-plane/core/utils/error-normalizer.js +51 -0
  110. package/dist/apps/control-plane/core/utils/error-normalizer.js.map +1 -0
  111. package/dist/apps/control-plane/core/utils/field-readers.d.ts +9 -0
  112. package/dist/apps/control-plane/core/utils/field-readers.js +30 -0
  113. package/dist/apps/control-plane/core/utils/field-readers.js.map +1 -0
  114. package/dist/apps/control-plane/core/utils/index-normalizer.d.ts +7 -0
  115. package/dist/apps/control-plane/core/utils/index-normalizer.js +92 -0
  116. package/dist/apps/control-plane/core/utils/index-normalizer.js.map +1 -0
  117. package/dist/apps/control-plane/core/utils/path-normalizers.d.ts +2 -0
  118. package/dist/apps/control-plane/core/utils/path-normalizers.js +17 -0
  119. package/dist/apps/control-plane/core/utils/path-normalizers.js.map +1 -0
  120. package/dist/apps/control-plane/interfaces/cli/bootstrap.js +13 -1
  121. package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
  122. package/dist/apps/control-plane/providers/api-worker-provider.d.ts +4 -13
  123. package/dist/apps/control-plane/providers/api-worker-provider.js +10 -0
  124. package/dist/apps/control-plane/providers/api-worker-provider.js.map +1 -1
  125. package/dist/apps/control-plane/providers/cli-worker-provider.d.ts +11 -13
  126. package/dist/apps/control-plane/providers/cli-worker-provider.js +64 -0
  127. package/dist/apps/control-plane/providers/cli-worker-provider.js.map +1 -1
  128. package/dist/apps/control-plane/providers/providers.d.ts +31 -24
  129. package/dist/apps/control-plane/providers/providers.js +10 -0
  130. package/dist/apps/control-plane/providers/providers.js.map +1 -1
  131. package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +11 -0
  132. package/dist/apps/control-plane/providers/worker-provider-factory.js +20 -1
  133. package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
  134. package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +3 -0
  135. package/dist/apps/control-plane/supervisor/run-coordinator.js +81 -33
  136. package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
  137. package/dist/apps/control-plane/supervisor/runtime.d.ts +8 -1
  138. package/dist/apps/control-plane/supervisor/runtime.js +90 -0
  139. package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
  140. package/dist/apps/control-plane/supervisor/types.d.ts +11 -0
  141. package/dist/apps/control-plane/supervisor/types.js.map +1 -1
  142. package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +21 -1
  143. package/dist/apps/control-plane/supervisor/worker-decision-loop.js +207 -13
  144. package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
  145. package/package.json +1 -1
  146. package/packages/web-dashboard/package.json +2 -0
  147. package/packages/web-dashboard/src/app/analytics/page.tsx +83 -2
  148. package/packages/web-dashboard/src/app/api/actions/route.ts +92 -1
  149. package/packages/web-dashboard/src/app/api/analytics/route.ts +5 -2
  150. package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/[checkpointId]/diff/route.ts +43 -0
  151. package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/compare/route.ts +45 -0
  152. package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/stream/route.ts +170 -0
  153. package/packages/web-dashboard/src/app/api/features/[id]/file-diff/route.ts +144 -0
  154. package/packages/web-dashboard/src/app/api/features/[id]/log-stream/route.ts +167 -0
  155. package/packages/web-dashboard/src/app/api/features/[id]/raw-logs/[filename]/route.ts +65 -0
  156. package/packages/web-dashboard/src/app/api/features/[id]/raw-logs/route.ts +63 -0
  157. package/packages/web-dashboard/src/app/api/features/[id]/timeline/route.ts +60 -0
  158. package/packages/web-dashboard/src/app/feature/[id]/page.tsx +32 -11
  159. package/packages/web-dashboard/src/app/globals.css +2 -0
  160. package/packages/web-dashboard/src/components/detail-panel.tsx +483 -0
  161. package/packages/web-dashboard/src/components/review-workspace.tsx +1162 -0
  162. package/packages/web-dashboard/src/lib/aop-client.ts +725 -0
  163. package/packages/web-dashboard/src/lib/review-contracts.ts +182 -0
  164. package/packages/web-dashboard/src/lib/review-workspace-logic.ts +64 -0
  165. package/packages/web-dashboard/src/lib/types.ts +131 -0
  166. package/packages/web-dashboard/src/styles/dashboard.module.css +333 -0
  167. package/spec-files/completed/agentic_orchestrator_execution_mode_spec.md +1905 -0
  168. package/spec-files/outstanding/agentic_orchestrator_runtime_inspection_spec.md +940 -0
  169. package/spec-files/outstanding/execution_mode_critical_review.md +355 -0
  170. package/spec-files/outstanding/shadow_workspace_implementation_spec.md +1271 -0
  171. package/spec-files/outstanding/shadow_workspace_spec_summary.md +222 -0
  172. package/spec-files/progress.md +269 -1
@@ -0,0 +1,1271 @@
1
+ # Shadow Workspace Implementation Specification
2
+
3
+ **Status:** Outstanding
4
+ **Created:** 2026-03-05
5
+ **Author:** System
6
+ **Scope:** Detailed implementation of shadow workspace strategy for interactive execution mode
7
+ **Parent Spec:** [Execution Mode Specification](../completed/agentic_orchestrator_execution_mode_spec.md)
8
+
9
+ ---
10
+
11
+ ## 0. Executive Summary
12
+
13
+ ### 0.1 Purpose
14
+
15
+ This specification details the **shadow workspace strategy** for interactive execution mode, where agents work in an isolated copy of the feature worktree. Changes are validated before promotion to the real worktree, providing validation-before-write guarantees while maintaining the benefits of direct file system access.
16
+
17
+ ### 0.2 Key Concepts
18
+
19
+ **Shadow Workspace:** An isolated copy of the feature worktree where the agent performs all file operations. Changes remain in the shadow until validated and promoted.
20
+
21
+ **Promotion:** The atomic operation of copying validated changes from shadow workspace to real worktree.
22
+
23
+ **Lifecycle:** Shadow creation → Agent execution → Checkpoint validation → Promotion (if valid) or Discard (if invalid) → Repeat
24
+
25
+ ### 0.3 Benefits Over Direct Worktree
26
+
27
+ | Aspect | Direct Worktree | Shadow Workspace |
28
+ | -------------------- | -------------------------- | ------------------------------- |
29
+ | Validation timing | After write (risky) | Before promotion (safe) |
30
+ | Rollback complexity | Destructive revert | Simple discard |
31
+ | Real worktree safety | Can be corrupted | Always valid |
32
+ | Race conditions | Possible during checkpoint | Eliminated (shadow is isolated) |
33
+ | Agent restart cost | Low | Medium (must recreate shadow) |
34
+ | Disk overhead | None | 2x worktree size |
35
+
36
+ ### 0.4 Trade-offs
37
+
38
+ **Pros:**
39
+
40
+ - Real worktree never corrupted
41
+ - Validation before promotion (safe)
42
+ - Easy rollback (discard shadow)
43
+ - No race conditions
44
+ - Atomic promotion guarantees
45
+
46
+ **Cons:**
47
+
48
+ - 2x disk space required
49
+ - Promotion overhead (copy operation)
50
+ - Agent must restart in fresh shadow after validation failure
51
+ - More complex implementation
52
+
53
+ ### 0.5 When to Use
54
+
55
+ **Use shadow workspace for:**
56
+
57
+ - High-risk features (contract changes, schema migrations, API modifications)
58
+ - Features with strict validation requirements
59
+ - Production environments where safety > performance
60
+ - Features modifying protected areas
61
+
62
+ **Use direct worktree for:**
63
+
64
+ - Low-risk features (documentation, tests, internal utilities)
65
+ - Development environments where iteration speed matters
66
+ - Features with simple validation rules
67
+ - Trusted agents with proven track record
68
+
69
+ ---
70
+
71
+ ## 1. Architecture Overview
72
+
73
+ ### 1.1 Component Diagram
74
+
75
+ ```
76
+ ┌─────────────────────────────────────────────────────────────┐
77
+ │ SupervisorRuntime │
78
+ │ ┌──────────────────────────────────────────────────────┐ │
79
+ │ │ InteractiveExecutionService │ │
80
+ │ │ ┌────────────────────────────────────────────────┐ │ │
81
+ │ │ │ ShadowWorkspaceManager │ │ │
82
+ │ │ │ - createShadow() │ │ │
83
+ │ │ │ - promoteShadow() │ │ │
84
+ │ │ │ - discardShadow() │ │ │
85
+ │ │ │ - syncShadow() │ │ │
86
+ │ │ └────────────────────────────────────────────────┘ │ │
87
+ │ │ │ │ │
88
+ │ │ ▼ │ │
89
+ │ │ ┌────────────────────────────────────────────────┐ │ │
90
+ │ │ │ WorktreeWatchdogService │ │ │
91
+ │ │ │ - startWatching(shadowPath) │ │ │
92
+ │ │ │ - getChangedFiles() │ │ │
93
+ │ │ └────────────────────────────────────────────────┘ │ │
94
+ │ │ │ │ │
95
+ │ │ ▼ │ │
96
+ │ │ ┌────────────────────────────────────────────────┐ │ │
97
+ │ │ │ CheckpointService │ │ │
98
+ │ │ │ - validateShadow() │ │ │
99
+ │ │ │ - computeShadowDiff() │ │ │
100
+ │ │ └────────────────────────────────────────────────┘ │ │
101
+ │ └──────────────────────────────────────────────────────┘ │
102
+ └─────────────────────────────────────────────────────────────┘
103
+
104
+
105
+ ┌────────────────────────┐
106
+ │ WorkerProvider │
107
+ │ (Agent runs in shadow)│
108
+ └────────────────────────┘
109
+ ```
110
+
111
+ ### 1.2 Directory Structure
112
+
113
+ ```
114
+ .worktrees/
115
+ ├── my_feature/ # Real worktree (always valid)
116
+ │ ├── src/
117
+ │ ├── tests/
118
+ │ └── ...
119
+ └── my_feature.shadow/ # Shadow workspace (agent works here)
120
+ ├── src/
121
+ ├── tests/
122
+ └── ...
123
+
124
+ .aop/features/my_feature/
125
+ ├── state.md
126
+ ├── plan.json
127
+ ├── checkpoints/
128
+ │ ├── ckpt-001.diff # Diff between real and shadow at checkpoint
129
+ │ └── ckpt-002.diff
130
+ └── shadow/
131
+ ├── metadata.json # Shadow lifecycle metadata
132
+ └── promotion-history.jsonl # Log of all promotions
133
+ ```
134
+
135
+ ---
136
+
137
+ ## 2. Shadow Workspace Lifecycle
138
+
139
+ ### 2.1 Phase 1: Shadow Creation
140
+
141
+ **Trigger:** Feature starts in interactive mode with shadow strategy enabled
142
+
143
+ **Steps:**
144
+
145
+ 1. Verify real worktree exists and is clean
146
+ 2. Create shadow directory (`.worktrees/<feature_id>.shadow`)
147
+ 3. Copy real worktree to shadow (fast copy with hardlinks where possible)
148
+ 4. Initialize shadow metadata
149
+ 5. Start watchdog on shadow directory
150
+ 6. Return shadow path to agent
151
+
152
+ **Implementation:**
153
+
154
+ ```typescript
155
+ interface ShadowWorkspaceManager {
156
+ createShadow(featureId: string): Promise<ShadowInfo>;
157
+ promoteShadow(featureId: string, validationResult: ValidationResult): Promise<PromotionResult>;
158
+ discardShadow(featureId: string, reason: string): Promise<void>;
159
+ syncShadow(featureId: string): Promise<void>;
160
+ getShadowInfo(featureId: string): Promise<ShadowInfo>;
161
+ }
162
+
163
+ interface ShadowInfo {
164
+ feature_id: string;
165
+ shadow_path: string;
166
+ real_worktree_path: string;
167
+ created_at: string;
168
+ last_sync_at: string;
169
+ generation: number; // Increments on each recreate
170
+ size_bytes: number;
171
+ file_count: number;
172
+ }
173
+
174
+ class ShadowWorkspaceManager {
175
+ async createShadow(featureId: string): Promise<ShadowInfo> {
176
+ const realPath = this.kernel.worktreePath(featureId);
177
+ const shadowPath = `${realPath}.shadow`;
178
+
179
+ // 1. Verify real worktree is clean
180
+ const status = await this.git.status(realPath);
181
+ if (status.modified.length > 0) {
182
+ throw new Error('Real worktree has uncommitted changes');
183
+ }
184
+
185
+ // 2. Remove existing shadow if present
186
+ if (await fs.pathExists(shadowPath)) {
187
+ await fs.remove(shadowPath);
188
+ }
189
+
190
+ // 3. Fast copy with hardlinks (copy-on-write)
191
+ await this.fastCopy(realPath, shadowPath);
192
+
193
+ // 4. Initialize metadata
194
+ const shadowInfo: ShadowInfo = {
195
+ feature_id: featureId,
196
+ shadow_path: shadowPath,
197
+ real_worktree_path: realPath,
198
+ created_at: new Date().toISOString(),
199
+ last_sync_at: new Date().toISOString(),
200
+ generation: await this.getNextGeneration(featureId),
201
+ size_bytes: await this.getDirectorySize(shadowPath),
202
+ file_count: await this.countFiles(shadowPath),
203
+ };
204
+
205
+ await this.persistShadowMetadata(featureId, shadowInfo);
206
+
207
+ return shadowInfo;
208
+ }
209
+
210
+ private async fastCopy(src: string, dest: string): Promise<void> {
211
+ // Use cp with reflink for copy-on-write (fast on modern filesystems)
212
+ await execa('cp', ['-r', '--reflink=auto', src, dest]);
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### 2.2 Phase 2: Agent Execution in Shadow
218
+
219
+ **Trigger:** Shadow created, agent ready to start
220
+
221
+ **Steps:**
222
+
223
+ 1. Spawn agent with `cwd` set to shadow path
224
+ 2. Agent performs file operations in shadow
225
+ 3. Watchdog monitors shadow for changes
226
+ 4. Track changed files for checkpoint
227
+
228
+ **Key Points:**
229
+
230
+ - Agent is unaware it's in a shadow (transparent)
231
+ - All file operations are scoped to shadow
232
+ - Real worktree remains untouched
233
+ - Watchdog only monitors shadow directory
234
+
235
+ **Implementation:**
236
+
237
+ ```typescript
238
+ class InteractiveExecutionService {
239
+ async runWithShadowWorkspace(featureId: string): Promise<void> {
240
+ // 1. Create shadow
241
+ const shadowInfo = await this.shadowManager.createShadow(featureId);
242
+
243
+ // 2. Start watchdog on shadow
244
+ await this.watchdog.startWatching(featureId, shadowInfo.shadow_path);
245
+
246
+ // 3. Start checkpoint loop
247
+ const checkpointTimer = setInterval(async () => {
248
+ await this.checkpointShadow(featureId);
249
+ }, this.config.checkpoint_interval_ms);
250
+
251
+ // Also trigger on change threshold
252
+ this.watchdog.on('changeThreshold', async (fid) => {
253
+ if (fid === featureId) {
254
+ await this.checkpointShadow(featureId);
255
+ }
256
+ });
257
+
258
+ try {
259
+ // 4. Run agent in shadow
260
+ const result = await this.provider.runWorker({
261
+ role: 'builder',
262
+ feature_id: featureId,
263
+ working_directory: shadowInfo.shadow_path, // Agent works in shadow
264
+ execution_mode: 'interactive',
265
+ });
266
+
267
+ // 5. Final checkpoint and promotion
268
+ await this.finalCheckpointAndPromote(featureId);
269
+ } finally {
270
+ clearInterval(checkpointTimer);
271
+ await this.watchdog.stopWatching(featureId);
272
+ }
273
+ }
274
+ }
275
+ ```
276
+
277
+ ### 2.3 Phase 3: Checkpoint Validation
278
+
279
+ **Trigger:** Checkpoint interval elapsed OR change threshold reached
280
+
281
+ **Steps:**
282
+
283
+ 1. Pause agent
284
+ 2. Compute diff between real worktree and shadow
285
+ 3. Validate diff against plan/policy/locks
286
+ 4. If valid: promote shadow changes to real worktree
287
+ 5. If invalid: notify agent, optionally discard shadow
288
+ 6. Resume agent (in shadow or fresh shadow)
289
+
290
+ **Implementation:**
291
+
292
+ ```typescript
293
+ class InteractiveExecutionService {
294
+ private async checkpointShadow(featureId: string): Promise<void> {
295
+ // 1. Pause agent
296
+ const pauseResult = await this.provider.pauseAgent(featureId, 10000);
297
+ if (!pauseResult.acknowledged) {
298
+ this.logger.warn(`Agent ${featureId} did not acknowledge pause`);
299
+ }
300
+
301
+ // 2. Wait for file writes to stabilize
302
+ await this.delay(100);
303
+
304
+ // 3. Compute diff between real and shadow
305
+ const shadowInfo = await this.shadowManager.getShadowInfo(featureId);
306
+ const diff = await this.computeShadowDiff(
307
+ shadowInfo.real_worktree_path,
308
+ shadowInfo.shadow_path,
309
+ );
310
+
311
+ // 4. Validate diff
312
+ const validation = await this.checkpointService.validateCheckpoint(featureId, diff);
313
+
314
+ // 5. Record checkpoint
315
+ await this.checkpointService.recordCheckpoint(featureId, {
316
+ checkpoint_id: this.generateCheckpointId(),
317
+ timestamp: new Date().toISOString(),
318
+ files_changed: this.parseDiffFiles(diff),
319
+ validation_status: validation.valid ? 'valid' : 'invalid',
320
+ violations: validation.violations,
321
+ severity: validation.severity,
322
+ diff_snapshot: await this.storeDiff(featureId, diff),
323
+ });
324
+
325
+ // 6. Handle validation result
326
+ if (validation.valid) {
327
+ await this.promoteShadowChanges(featureId, shadowInfo, diff);
328
+ } else {
329
+ await this.handleValidationFailure(featureId, validation);
330
+ }
331
+
332
+ // 7. Resume agent
333
+ await this.provider.resumeAgent(featureId);
334
+ }
335
+
336
+ private async computeShadowDiff(realPath: string, shadowPath: string): Promise<string> {
337
+ // Use git diff to compare real and shadow
338
+ // This captures all changes agent made in shadow
339
+ const result = await execa('git', ['diff', '--no-index', '--no-color', realPath, shadowPath], {
340
+ reject: false,
341
+ }); // Exit code 1 means differences found (expected)
342
+
343
+ return result.stdout;
344
+ }
345
+ }
346
+ ```
347
+
348
+ ### 2.4 Phase 4: Promotion (Valid Changes)
349
+
350
+ **Trigger:** Checkpoint validation passed
351
+
352
+ **Steps:**
353
+
354
+ 1. Verify real worktree is still clean (no external changes)
355
+ 2. Apply shadow diff to real worktree (atomic)
356
+ 3. Sync shadow with real worktree (reset shadow to match real)
357
+ 4. Log promotion in history
358
+ 5. Continue agent execution in shadow
359
+
360
+ **Promotion Strategies:**
361
+
362
+ #### Strategy A: Atomic Copy (Default)
363
+
364
+ Copy changed files from shadow to real worktree atomically.
365
+
366
+ ```typescript
367
+ class ShadowWorkspaceManager {
368
+ async promoteShadow(
369
+ featureId: string,
370
+ validationResult: ValidationResult,
371
+ ): Promise<PromotionResult> {
372
+ const shadowInfo = await this.getShadowInfo(featureId);
373
+ const changedFiles = validationResult.files_changed;
374
+
375
+ // 1. Verify real worktree is clean
376
+ const status = await this.git.status(shadowInfo.real_worktree_path);
377
+ if (status.modified.length > 0) {
378
+ throw new Error('Real worktree modified externally during shadow execution');
379
+ }
380
+
381
+ // 2. Atomic promotion: copy changed files
382
+ const tempDir = `${shadowInfo.real_worktree_path}.promotion-temp`;
383
+ await fs.ensureDir(tempDir);
384
+
385
+ try {
386
+ // Copy changed files to temp
387
+ for (const file of changedFiles) {
388
+ const shadowFile = path.join(shadowInfo.shadow_path, file);
389
+ const tempFile = path.join(tempDir, file);
390
+
391
+ if (await fs.pathExists(shadowFile)) {
392
+ await fs.copy(shadowFile, tempFile);
393
+ }
394
+ }
395
+
396
+ // Atomic move from temp to real
397
+ for (const file of changedFiles) {
398
+ const tempFile = path.join(tempDir, file);
399
+ const realFile = path.join(shadowInfo.real_worktree_path, file);
400
+
401
+ if (await fs.pathExists(tempFile)) {
402
+ await fs.move(tempFile, realFile, { overwrite: true });
403
+ } else {
404
+ // File was deleted in shadow
405
+ await fs.remove(realFile);
406
+ }
407
+ }
408
+
409
+ // 3. Sync shadow with real (reset shadow)
410
+ await this.syncShadow(featureId);
411
+
412
+ // 4. Log promotion
413
+ await this.logPromotion(featureId, {
414
+ timestamp: new Date().toISOString(),
415
+ files_promoted: changedFiles,
416
+ validation_result: validationResult,
417
+ });
418
+
419
+ return {
420
+ success: true,
421
+ files_promoted: changedFiles.length,
422
+ promotion_time_ms: Date.now() - startTime,
423
+ };
424
+ } finally {
425
+ await fs.remove(tempDir);
426
+ }
427
+ }
428
+
429
+ async syncShadow(featureId: string): Promise<void> {
430
+ const shadowInfo = await this.getShadowInfo(featureId);
431
+
432
+ // Remove shadow and recreate from real worktree
433
+ await fs.remove(shadowInfo.shadow_path);
434
+ await this.fastCopy(shadowInfo.real_worktree_path, shadowInfo.shadow_path);
435
+
436
+ // Update metadata
437
+ shadowInfo.last_sync_at = new Date().toISOString();
438
+ await this.persistShadowMetadata(featureId, shadowInfo);
439
+ }
440
+ }
441
+ ```
442
+
443
+ #### Strategy B: Incremental Sync
444
+
445
+ Only sync changed files (faster for large worktrees).
446
+
447
+ ```typescript
448
+ async syncShadowIncremental(featureId: string, changedFiles: string[]): Promise<void> {
449
+ const shadowInfo = await this.getShadowInfo(featureId);
450
+
451
+ // Copy only changed files from real to shadow
452
+ for (const file of changedFiles) {
453
+ const realFile = path.join(shadowInfo.real_worktree_path, file);
454
+ const shadowFile = path.join(shadowInfo.shadow_path, file);
455
+
456
+ if (await fs.pathExists(realFile)) {
457
+ await fs.copy(realFile, shadowFile, { overwrite: true });
458
+ } else {
459
+ await fs.remove(shadowFile);
460
+ }
461
+ }
462
+
463
+ shadowInfo.last_sync_at = new Date().toISOString();
464
+ await this.persistShadowMetadata(featureId, shadowInfo);
465
+ }
466
+ ```
467
+
468
+ ### 2.5 Phase 5: Discard (Invalid Changes)
469
+
470
+ **Trigger:** Checkpoint validation failed
471
+
472
+ **Steps:**
473
+
474
+ 1. Notify agent of violations
475
+ 2. Decide: discard shadow OR keep shadow for agent to fix
476
+ 3. If discard: remove shadow, recreate fresh from real worktree
477
+ 4. If keep: agent continues in shadow with violation feedback
478
+ 5. Resume agent
479
+
480
+ **Discard Strategies:**
481
+
482
+ #### Strategy A: Full Discard (Default for critical violations)
483
+
484
+ ```typescript
485
+ async discardShadow(featureId: string, reason: string): Promise<void> {
486
+ const shadowInfo = await this.getShadowInfo(featureId);
487
+
488
+ // 1. Log discard event
489
+ await this.logDiscard(featureId, {
490
+ timestamp: new Date().toISOString(),
491
+ reason,
492
+ generation: shadowInfo.generation,
493
+ });
494
+
495
+ // 2. Remove shadow
496
+ await fs.remove(shadowInfo.shadow_path);
497
+
498
+ // 3. Recreate fresh shadow from real worktree
499
+ await this.createShadow(featureId);
500
+
501
+ // 4. Notify agent
502
+ await this.provider.sendMessage(featureId, {
503
+ type: 'shadow_discarded',
504
+ severity: 'error',
505
+ content: `Shadow workspace discarded due to validation failure: ${reason}`,
506
+ structured_data: { reason, new_generation: shadowInfo.generation + 1 },
507
+ requires_acknowledgment: true,
508
+ });
509
+ }
510
+ ```
511
+
512
+ #### Strategy B: Partial Discard (Revert only violated files)
513
+
514
+ ```typescript
515
+ async discardViolatedFiles(
516
+ featureId: string,
517
+ violations: ValidationViolation[]
518
+ ): Promise<void> {
519
+ const shadowInfo = await this.getShadowInfo(featureId);
520
+ const violatedFiles = new Set(violations.map(v => v.file_path));
521
+
522
+ // Revert only violated files in shadow (copy from real worktree)
523
+ for (const file of violatedFiles) {
524
+ const realFile = path.join(shadowInfo.real_worktree_path, file);
525
+ const shadowFile = path.join(shadowInfo.shadow_path, file);
526
+
527
+ if (await fs.pathExists(realFile)) {
528
+ await fs.copy(realFile, shadowFile, { overwrite: true });
529
+ } else {
530
+ await fs.remove(shadowFile);
531
+ }
532
+ }
533
+
534
+ // Notify agent which files were reverted
535
+ await this.provider.sendMessage(featureId, {
536
+ type: 'partial_revert',
537
+ severity: 'warning',
538
+ content: `Reverted ${violatedFiles.size} files with violations`,
539
+ structured_data: {
540
+ reverted_files: Array.from(violatedFiles),
541
+ violations
542
+ },
543
+ requires_acknowledgment: true,
544
+ });
545
+ }
546
+ ```
547
+
548
+ ---
549
+
550
+ ## 3. Configuration
551
+
552
+ ### 3.1 Schema Changes
553
+
554
+ **File:** `agentic/orchestrator/schemas/agents.schema.json`
555
+
556
+ ```json
557
+ {
558
+ "runtime": {
559
+ "execution_mode": "interactive",
560
+ "interactive": {
561
+ "strategy": "shadow_workspace",
562
+ "shadow_workspace": {
563
+ "enabled": true,
564
+ "promotion_strategy": "atomic",
565
+ "sync_strategy": "full",
566
+ "discard_strategy": "full",
567
+ "cleanup_on_failure": true,
568
+ "max_shadow_size_mb": 2048,
569
+ "max_shadow_age_hours": 24,
570
+ "hardlink_optimization": true,
571
+ "violation_handling": {
572
+ "critical": "discard_full",
573
+ "error": "discard_violated_files",
574
+ "warning": "keep_shadow",
575
+ "info": "keep_shadow"
576
+ }
577
+ }
578
+ }
579
+ }
580
+ }
581
+ ```
582
+
583
+ ### 3.2 Configuration Options
584
+
585
+ | Option | Type | Default | Description |
586
+ | ----------------------- | ------- | --------- | ------------------------------------------------------------ |
587
+ | `enabled` | boolean | `false` | Enable shadow workspace strategy |
588
+ | `promotion_strategy` | enum | `atomic` | How to promote changes (`atomic` \| `incremental`) |
589
+ | `sync_strategy` | enum | `full` | How to sync shadow after promotion (`full` \| `incremental`) |
590
+ | `discard_strategy` | enum | `full` | How to discard invalid changes (`full` \| `partial`) |
591
+ | `cleanup_on_failure` | boolean | `true` | Delete shadow after validation failure |
592
+ | `max_shadow_size_mb` | number | `2048` | Maximum shadow workspace size (MB) |
593
+ | `max_shadow_age_hours` | number | `24` | Maximum shadow age before forced cleanup |
594
+ | `hardlink_optimization` | boolean | `true` | Use hardlinks for fast copy (copy-on-write) |
595
+ | `violation_handling` | object | See above | Per-severity discard strategy |
596
+
597
+ ### 3.3 Feature-Level Override
598
+
599
+ Features can override shadow strategy in `state.md`:
600
+
601
+ ```yaml
602
+ ---
603
+ feature_id: my_feature
604
+ execution_mode: interactive
605
+ shadow_workspace:
606
+ enabled: true
607
+ discard_strategy: partial # Override: keep valid changes
608
+ ---
609
+ ```
610
+
611
+ ---
612
+
613
+ ## 4. Performance Optimization
614
+
615
+ ### 4.1 Fast Copy with Hardlinks
616
+
617
+ **Problem:** Copying large worktrees (>1GB) is slow.
618
+
619
+ **Solution:** Use copy-on-write (reflink) or hardlinks.
620
+
621
+ ```typescript
622
+ async fastCopy(src: string, dest: string): Promise<void> {
623
+ if (this.config.hardlink_optimization) {
624
+ // Try reflink first (copy-on-write on btrfs, xfs, apfs)
625
+ try {
626
+ await execa('cp', ['-r', '--reflink=always', src, dest]);
627
+ return;
628
+ } catch {
629
+ // Reflink not supported, fall back to hardlinks
630
+ }
631
+
632
+ // Use hardlinks (fast, but files share inodes until modified)
633
+ await execa('cp', ['-rl', src, dest]);
634
+ } else {
635
+ // Standard copy (slow but safe)
636
+ await fs.copy(src, dest);
637
+ }
638
+ }
639
+ ```
640
+
641
+ **Benchmark:**
642
+
643
+ - Standard copy (1GB worktree): ~10s
644
+ - Hardlink copy (1GB worktree): ~500ms
645
+ - Reflink copy (1GB worktree): ~100ms
646
+
647
+ ### 4.2 Incremental Sync
648
+
649
+ **Problem:** Full shadow recreation after each promotion is slow.
650
+
651
+ **Solution:** Only sync changed files.
652
+
653
+ ```typescript
654
+ async syncShadowIncremental(featureId: string, promotedFiles: string[]): Promise<void> {
655
+ const shadowInfo = await this.getShadowInfo(featureId);
656
+
657
+ // Only copy promoted files from real to shadow
658
+ await pMap(promotedFiles, async (file) => {
659
+ const realFile = path.join(shadowInfo.real_worktree_path, file);
660
+ const shadowFile = path.join(shadowInfo.shadow_path, file);
661
+ await fs.copy(realFile, shadowFile, { overwrite: true });
662
+ }, { concurrency: 10 });
663
+ }
664
+ ```
665
+
666
+ **Benchmark:**
667
+
668
+ - Full sync (1GB worktree): ~10s
669
+ - Incremental sync (50 files): ~200ms
670
+
671
+ ### 4.3 Lazy Shadow Creation
672
+
673
+ **Problem:** Creating shadow upfront delays agent start.
674
+
675
+ **Solution:** Create shadow in background while agent initializes.
676
+
677
+ ```typescript
678
+ async runWithLazyShadow(featureId: string): Promise<void> {
679
+ // Start shadow creation in background
680
+ const shadowPromise = this.shadowManager.createShadow(featureId);
681
+
682
+ // Initialize agent in parallel
683
+ const agentPromise = this.provider.createSession('builder', featureId, systemPrompt);
684
+
685
+ // Wait for both
686
+ const [shadowInfo, session] = await Promise.all([shadowPromise, agentPromise]);
687
+
688
+ // Now run agent in shadow
689
+ await this.provider.runWorker({
690
+ working_directory: shadowInfo.shadow_path,
691
+ // ...
692
+ });
693
+ }
694
+ ```
695
+
696
+ ### 4.4 Shadow Pooling
697
+
698
+ **Problem:** Creating/destroying shadows repeatedly is wasteful.
699
+
700
+ **Solution:** Maintain pool of pre-created shadows.
701
+
702
+ ```typescript
703
+ class ShadowPool {
704
+ private pool = new Map<string, ShadowInfo[]>();
705
+ private readonly POOL_SIZE = 3;
706
+
707
+ async getOrCreateShadow(featureId: string): Promise<ShadowInfo> {
708
+ const available = this.pool.get(featureId) || [];
709
+
710
+ if (available.length > 0) {
711
+ return available.pop()!;
712
+ }
713
+
714
+ return await this.shadowManager.createShadow(featureId);
715
+ }
716
+
717
+ async returnShadow(featureId: string, shadowInfo: ShadowInfo): Promise<void> {
718
+ const pool = this.pool.get(featureId) || [];
719
+
720
+ if (pool.length < this.POOL_SIZE) {
721
+ // Clean shadow and return to pool
722
+ await this.cleanShadow(shadowInfo);
723
+ pool.push(shadowInfo);
724
+ this.pool.set(featureId, pool);
725
+ } else {
726
+ // Pool full, discard
727
+ await fs.remove(shadowInfo.shadow_path);
728
+ }
729
+ }
730
+ }
731
+ ```
732
+
733
+ ---
734
+
735
+ ## 5. Error Handling and Edge Cases
736
+
737
+ ### 5.1 Shadow Creation Failures
738
+
739
+ **Scenario:** Disk full, permission denied, or real worktree corrupted.
740
+
741
+ **Handling:**
742
+
743
+ ```typescript
744
+ async createShadow(featureId: string): Promise<ShadowInfo> {
745
+ try {
746
+ // Attempt shadow creation
747
+ return await this._createShadow(featureId);
748
+ } catch (error) {
749
+ if (error.code === 'ENOSPC') {
750
+ // Disk full: cleanup old shadows and retry
751
+ await this.cleanupOldShadows();
752
+ return await this._createShadow(featureId);
753
+ } else if (error.code === 'EACCES') {
754
+ // Permission denied: escalate to human
755
+ throw new Error('Shadow creation failed: permission denied');
756
+ } else {
757
+ // Unknown error: fall back to direct worktree mode
758
+ this.logger.error(`Shadow creation failed: ${error.message}`);
759
+ throw new Error('Shadow creation failed, falling back to direct worktree');
760
+ }
761
+ }
762
+ }
763
+ ```
764
+
765
+ ### 5.2 Promotion Failures
766
+
767
+ **Scenario:** Real worktree modified externally during shadow execution.
768
+
769
+ **Handling:**
770
+
771
+ ```typescript
772
+ async promoteShadow(featureId: string): Promise<PromotionResult> {
773
+ const shadowInfo = await this.getShadowInfo(featureId);
774
+
775
+ // Verify real worktree is clean
776
+ const status = await this.git.status(shadowInfo.real_worktree_path);
777
+
778
+ if (status.modified.length > 0) {
779
+ // Real worktree modified externally
780
+ this.logger.error(`Real worktree modified externally: ${status.modified}`);
781
+
782
+ // Options:
783
+ // 1. Abort promotion, notify human
784
+ // 2. Attempt 3-way merge
785
+ // 3. Force promotion (overwrite external changes)
786
+
787
+ // Default: abort and escalate
788
+ throw new Error('Promotion aborted: real worktree modified externally');
789
+ }
790
+
791
+ // Proceed with promotion
792
+ return await this._promoteShadow(featureId);
793
+ }
794
+ ```
795
+
796
+ ### 5.3 Shadow Corruption
797
+
798
+ **Scenario:** Agent corrupts shadow (e.g., deletes `.git`, creates infinite loop).
799
+
800
+ **Handling:**
801
+
802
+ ```typescript
803
+ async validateShadowIntegrity(featureId: string): Promise<boolean> {
804
+ const shadowInfo = await this.getShadowInfo(featureId);
805
+
806
+ // Check .git directory exists
807
+ const gitDir = path.join(shadowInfo.shadow_path, '.git');
808
+ if (!await fs.pathExists(gitDir)) {
809
+ this.logger.error(`Shadow corrupted: .git directory missing`);
810
+ return false;
811
+ }
812
+
813
+ // Check shadow size is reasonable
814
+ const size = await this.getDirectorySize(shadowInfo.shadow_path);
815
+ if (size > this.config.max_shadow_size_mb * 1024 * 1024) {
816
+ this.logger.error(`Shadow corrupted: size exceeds limit (${size} bytes)`);
817
+ return false;
818
+ }
819
+
820
+ // Check file count is reasonable
821
+ const fileCount = await this.countFiles(shadowInfo.shadow_path);
822
+ if (fileCount > 100000) {
823
+ this.logger.error(`Shadow corrupted: too many files (${fileCount})`);
824
+ return false;
825
+ }
826
+
827
+ return true;
828
+ }
829
+
830
+ async checkpointShadow(featureId: string): Promise<void> {
831
+ // Validate shadow integrity before checkpoint
832
+ const isValid = await this.validateShadowIntegrity(featureId);
833
+
834
+ if (!isValid) {
835
+ // Shadow corrupted: discard and recreate
836
+ await this.discardShadow(featureId, 'shadow_corrupted');
837
+ return;
838
+ }
839
+
840
+ // Proceed with normal checkpoint
841
+ // ...
842
+ }
843
+ ```
844
+
845
+ ### 5.4 Concurrent Promotions
846
+
847
+ **Scenario:** Multiple checkpoints trigger simultaneously (race condition).
848
+
849
+ **Handling:**
850
+
851
+ ```typescript
852
+ class ShadowWorkspaceManager {
853
+ private promotionLocks = new Map<string, Promise<PromotionResult>>();
854
+
855
+ async promoteShadow(featureId: string): Promise<PromotionResult> {
856
+ // Serialize promotions per feature
857
+ const existingPromotion = this.promotionLocks.get(featureId);
858
+ if (existingPromotion) {
859
+ this.logger.warn(`Promotion already in progress for ${featureId}, waiting...`);
860
+ return await existingPromotion;
861
+ }
862
+
863
+ const promotionPromise = this._promoteShadow(featureId);
864
+ this.promotionLocks.set(featureId, promotionPromise);
865
+
866
+ try {
867
+ return await promotionPromise;
868
+ } finally {
869
+ this.promotionLocks.delete(featureId);
870
+ }
871
+ }
872
+ }
873
+ ```
874
+
875
+ ---
876
+
877
+ ## 6. Monitoring and Observability
878
+
879
+ ### 6.1 Metrics
880
+
881
+ **Shadow lifecycle metrics:**
882
+
883
+ - Shadow creation time (p50, p95, p99)
884
+ - Shadow size distribution
885
+ - Promotion time (p50, p95, p99)
886
+ - Promotion success rate
887
+ - Discard rate (by severity)
888
+ - Shadow age distribution
889
+
890
+ **Resource metrics:**
891
+
892
+ - Total disk usage (all shadows)
893
+ - Disk usage per shadow
894
+ - Shadow count (active, pooled, orphaned)
895
+ - Hardlink effectiveness (% space saved)
896
+
897
+ **Validation metrics:**
898
+
899
+ - Validation pass rate (shadow vs direct worktree)
900
+ - Violations per checkpoint (shadow vs direct worktree)
901
+ - Time to first violation
902
+
903
+ ### 6.2 Logging
904
+
905
+ **Shadow events to log:**
906
+
907
+ ```typescript
908
+ interface ShadowEvent {
909
+ event_type: 'created' | 'promoted' | 'discarded' | 'synced' | 'corrupted';
910
+ feature_id: string;
911
+ timestamp: string;
912
+ generation: number;
913
+ details: Record<string, unknown>;
914
+ }
915
+
916
+ // Example log entries
917
+ {
918
+ "event_type": "created",
919
+ "feature_id": "my_feature",
920
+ "timestamp": "2026-03-05T17:45:00.000Z",
921
+ "generation": 1,
922
+ "details": {
923
+ "shadow_path": ".worktrees/my_feature.shadow",
924
+ "size_bytes": 1048576000,
925
+ "file_count": 5432,
926
+ "creation_time_ms": 523
927
+ }
928
+ }
929
+
930
+ {
931
+ "event_type": "promoted",
932
+ "feature_id": "my_feature",
933
+ "timestamp": "2026-03-05T17:46:30.000Z",
934
+ "generation": 1,
935
+ "details": {
936
+ "files_promoted": 12,
937
+ "promotion_time_ms": 145,
938
+ "validation_status": "valid"
939
+ }
940
+ }
941
+
942
+ {
943
+ "event_type": "discarded",
944
+ "feature_id": "my_feature",
945
+ "timestamp": "2026-03-05T17:47:15.000Z",
946
+ "generation": 1,
947
+ "details": {
948
+ "reason": "validation_failed",
949
+ "violations": ["Path 'src/config.ts' not in allowed_areas"],
950
+ "severity": "error"
951
+ }
952
+ }
953
+ ```
954
+
955
+ ### 6.3 Alerts
956
+
957
+ **Alert conditions:**
958
+
959
+ - Shadow creation time > 5s
960
+ - Promotion time > 2s
961
+ - Discard rate > 30%
962
+ - Total shadow disk usage > 80% of limit
963
+ - Shadow age > 24 hours (orphaned shadow)
964
+ - Shadow corruption detected
965
+
966
+ ---
967
+
968
+ ## 7. Testing Strategy
969
+
970
+ ### 7.1 Unit Tests
971
+
972
+ **ShadowWorkspaceManager:**
973
+
974
+ - `createShadow()` creates shadow with correct structure
975
+ - `promoteShadow()` copies only changed files
976
+ - `discardShadow()` removes shadow and recreates
977
+ - `syncShadow()` resets shadow to match real worktree
978
+ - Fast copy uses hardlinks when available
979
+ - Promotion is atomic (all or nothing)
980
+
981
+ **CheckpointService:**
982
+
983
+ - `computeShadowDiff()` correctly compares real and shadow
984
+ - `validateShadow()` enforces plan/policy/locks
985
+ - Validation failures trigger correct discard strategy
986
+
987
+ ### 7.2 Integration Tests
988
+
989
+ **Shadow lifecycle:**
990
+
991
+ 1. Create shadow → Agent modifies files → Checkpoint validates → Promote → Verify real worktree updated
992
+ 2. Create shadow → Agent violates policy → Checkpoint fails → Discard → Verify shadow recreated
993
+ 3. Create shadow → Agent corrupts shadow → Checkpoint detects → Discard → Verify recovery
994
+
995
+ **Concurrent features:**
996
+
997
+ 1. Start 3 features in shadow mode simultaneously
998
+ 2. Verify each has isolated shadow
999
+ 3. Verify promotions don't interfere
1000
+ 4. Verify disk usage is tracked correctly
1001
+
1002
+ ### 7.3 Performance Tests
1003
+
1004
+ **Benchmarks:**
1005
+
1006
+ - Shadow creation time vs worktree size (100MB, 1GB, 10GB)
1007
+ - Promotion time vs changed file count (10, 100, 1000 files)
1008
+ - Sync time (full vs incremental)
1009
+ - Disk space overhead (hardlinks vs standard copy)
1010
+
1011
+ **Load tests:**
1012
+
1013
+ - 10 concurrent features in shadow mode
1014
+ - 100 checkpoints per feature
1015
+ - Verify no memory leaks
1016
+ - Verify disk cleanup works
1017
+
1018
+ ---
1019
+
1020
+ ## 8. Migration Path
1021
+
1022
+ ### 8.1 Phase 1: Infrastructure (Week 1)
1023
+
1024
+ - Implement `ShadowWorkspaceManager` service
1025
+ - Add shadow configuration to `agents.schema.json`
1026
+ - Implement fast copy with hardlink optimization
1027
+ - Add shadow metadata persistence
1028
+ - Unit tests (>= 90% coverage)
1029
+
1030
+ ### 8.2 Phase 2: Lifecycle Integration (Week 2)
1031
+
1032
+ - Integrate shadow creation into `InteractiveExecutionService`
1033
+ - Implement checkpoint validation for shadow
1034
+ - Implement promotion logic (atomic strategy)
1035
+ - Implement discard logic (full strategy)
1036
+ - Integration tests
1037
+
1038
+ ### 8.3 Phase 3: Optimization (Week 3)
1039
+
1040
+ - Implement incremental sync
1041
+ - Implement partial discard
1042
+ - Add shadow pooling
1043
+ - Performance benchmarks
1044
+ - Load tests
1045
+
1046
+ ### 8.4 Phase 4: Monitoring and Rollout (Week 4)
1047
+
1048
+ - Add shadow metrics and logging
1049
+ - Add shadow alerts
1050
+ - Dashboard integration (shadow status in RuntimeInspector)
1051
+ - Documentation
1052
+ - Beta rollout (opt-in via config)
1053
+
1054
+ ---
1055
+
1056
+ ## 9. Acceptance Criteria
1057
+
1058
+ ### 9.1 Must Have
1059
+
1060
+ - [ ] `ShadowWorkspaceManager` implemented with all methods
1061
+ - [ ] Shadow creation uses hardlinks (< 1s for 1GB worktree)
1062
+ - [ ] Promotion is atomic (all files or none)
1063
+ - [ ] Discard recreates fresh shadow from real worktree
1064
+ - [ ] Validation enforces plan/policy/locks (same as direct worktree)
1065
+ - [ ] Real worktree never corrupted by agent
1066
+ - [ ] Shadow integrity checks detect corruption
1067
+ - [ ] Concurrent features have isolated shadows
1068
+ - [ ] All unit tests pass (>= 90% coverage)
1069
+ - [ ] All integration tests pass
1070
+
1071
+ ### 9.2 Should Have
1072
+
1073
+ - [ ] Incremental sync (< 500ms for 50 files)
1074
+ - [ ] Partial discard (revert only violated files)
1075
+ - [ ] Shadow pooling (reduce creation overhead)
1076
+ - [ ] Metrics and logging for all shadow events
1077
+ - [ ] Dashboard displays shadow status
1078
+ - [ ] Alerts for shadow issues
1079
+ - [ ] Performance benchmarks documented
1080
+
1081
+ ### 9.3 Nice to Have
1082
+
1083
+ - [ ] Shadow diff viewer in dashboard
1084
+ - [ ] Shadow history timeline
1085
+ - [ ] Manual shadow promotion command (`aop promote --feature-id <id>`)
1086
+ - [ ] Shadow inspection command (`aop shadow inspect --feature-id <id>`)
1087
+ - [ ] Shadow cleanup command (`aop shadow cleanup --all`)
1088
+
1089
+ ---
1090
+
1091
+ ## 10. Comparison with Direct Worktree
1092
+
1093
+ ### 10.1 Safety
1094
+
1095
+ | Aspect | Direct Worktree | Shadow Workspace |
1096
+ | ----------------------------- | ------------------ | ----------------------------- |
1097
+ | Real worktree corruption risk | High | None |
1098
+ | Validation timing | After write | Before promotion |
1099
+ | Rollback complexity | Destructive revert | Simple discard |
1100
+ | Race conditions | Possible | Eliminated |
1101
+ | Agent can escape worktree | Yes (via symlinks) | Yes (but doesn't affect real) |
1102
+
1103
+ **Winner:** Shadow Workspace (much safer)
1104
+
1105
+ ### 10.2 Performance
1106
+
1107
+ | Aspect | Direct Worktree | Shadow Workspace |
1108
+ | --------------------- | --------------- | -------------------------- |
1109
+ | Agent start time | Instant | +500ms (shadow creation) |
1110
+ | Checkpoint overhead | Low | Medium (diff + promotion) |
1111
+ | Disk usage | 1x | 2x (with hardlinks: ~1.1x) |
1112
+ | Validation speed | Same | Same |
1113
+ | Agent iteration speed | Fast | Fast (works in shadow) |
1114
+
1115
+ **Winner:** Direct Worktree (slightly faster)
1116
+
1117
+ ### 10.3 Complexity
1118
+
1119
+ | Aspect | Direct Worktree | Shadow Workspace |
1120
+ | ---------------------------- | --------------- | ---------------- |
1121
+ | Implementation lines of code | ~500 | ~1500 |
1122
+ | Service dependencies | 2 | 3 |
1123
+ | Configuration options | 5 | 12 |
1124
+ | Error scenarios | 8 | 15 |
1125
+ | Testing surface | Medium | Large |
1126
+
1127
+ **Winner:** Direct Worktree (simpler)
1128
+
1129
+ ### 10.4 Recommendation
1130
+
1131
+ **Use Shadow Workspace when:**
1132
+
1133
+ - Safety is paramount (production environments)
1134
+ - Features modify contracts, schemas, or migrations
1135
+ - Agent is untrusted or experimental
1136
+ - Validation failures are expected to be common
1137
+ - Rollback needs to be fast and reliable
1138
+
1139
+ **Use Direct Worktree when:**
1140
+
1141
+ - Iteration speed is critical (development environments)
1142
+ - Features are low-risk (docs, tests, internal utilities)
1143
+ - Agent is trusted and proven
1144
+ - Validation failures are rare
1145
+ - Disk space is constrained
1146
+
1147
+ **Hybrid approach:** Start with direct worktree, automatically switch to shadow workspace on first validation failure.
1148
+
1149
+ ---
1150
+
1151
+ ## 11. Future Enhancements
1152
+
1153
+ ### 11.1 Multi-Shadow Branching
1154
+
1155
+ **Concept:** Agent can create multiple shadow branches to explore alternatives.
1156
+
1157
+ **Use case:** Agent wants to try two different implementation approaches in parallel.
1158
+
1159
+ ```typescript
1160
+ interface ShadowBranch {
1161
+ branch_id: string;
1162
+ parent_shadow_id: string;
1163
+ created_at: string;
1164
+ description: string;
1165
+ }
1166
+
1167
+ async createShadowBranch(featureId: string, description: string): Promise<ShadowBranch> {
1168
+ const currentShadow = await this.getShadowInfo(featureId);
1169
+ const branchShadow = await this.copyShadow(currentShadow, `${featureId}.branch-${uuid()}`);
1170
+
1171
+ return {
1172
+ branch_id: branchShadow.shadow_path,
1173
+ parent_shadow_id: currentShadow.shadow_path,
1174
+ created_at: new Date().toISOString(),
1175
+ description,
1176
+ };
1177
+ }
1178
+ ```
1179
+
1180
+ ### 11.2 Shadow Snapshots
1181
+
1182
+ **Concept:** Create lightweight snapshots of shadow at any point for easy rollback.
1183
+
1184
+ **Use case:** Agent wants to experiment but be able to revert to a known-good state.
1185
+
1186
+ ```typescript
1187
+ async createShadowSnapshot(featureId: string, label: string): Promise<SnapshotInfo> {
1188
+ const shadowInfo = await this.getShadowInfo(featureId);
1189
+
1190
+ // Use git to create snapshot (lightweight)
1191
+ await this.git.commit(shadowInfo.shadow_path, `Snapshot: ${label}`);
1192
+ const commitHash = await this.git.getHeadCommit(shadowInfo.shadow_path);
1193
+
1194
+ return {
1195
+ snapshot_id: commitHash,
1196
+ label,
1197
+ created_at: new Date().toISOString(),
1198
+ };
1199
+ }
1200
+
1201
+ async rollbackToSnapshot(featureId: string, snapshotId: string): Promise<void> {
1202
+ const shadowInfo = await this.getShadowInfo(featureId);
1203
+ await this.git.reset(shadowInfo.shadow_path, snapshotId, { hard: true });
1204
+ }
1205
+ ```
1206
+
1207
+ ### 11.3 Shadow Diff Streaming
1208
+
1209
+ **Concept:** Stream shadow changes to dashboard in real-time.
1210
+
1211
+ **Use case:** Human observer wants to watch agent work live.
1212
+
1213
+ ```typescript
1214
+ async streamShadowChanges(featureId: string): AsyncIterableIterator<FileChange> {
1215
+ const shadowInfo = await this.getShadowInfo(featureId);
1216
+
1217
+ const watcher = chokidar.watch(shadowInfo.shadow_path);
1218
+
1219
+ for await (const event of watcher) {
1220
+ yield {
1221
+ event_type: event.type,
1222
+ file_path: event.path,
1223
+ timestamp: new Date().toISOString(),
1224
+ diff: await this.computeFileDiff(event.path),
1225
+ };
1226
+ }
1227
+ }
1228
+ ```
1229
+
1230
+ ### 11.4 Shadow Collaboration
1231
+
1232
+ **Concept:** Multiple agents work in same shadow with conflict resolution.
1233
+
1234
+ **Use case:** Planner and builder agents collaborate on same feature.
1235
+
1236
+ **Challenge:** Requires sophisticated merge logic and coordination.
1237
+
1238
+ ---
1239
+
1240
+ ## 12. References
1241
+
1242
+ ### 12.1 Related Specifications
1243
+
1244
+ - **Parent Spec:** [Execution Mode Specification](../completed/agentic_orchestrator_execution_mode_spec.md) - Overall architecture for deterministic vs interactive modes
1245
+ - **Related:** [Runtime Inspection Specification](./agentic_orchestrator_runtime_inspection_spec.md) - Dashboard integration for shadow monitoring
1246
+
1247
+ ### 12.2 External Resources
1248
+
1249
+ - **Copy-on-Write Filesystems:** [Btrfs reflink](https://btrfs.wiki.kernel.org/index.php/Reflink), [XFS reflink](https://www.kernel.org/doc/Documentation/filesystems/xfs.txt)
1250
+ - **Hardlinks:** [Linux hardlink documentation](https://man7.org/linux/man-pages/man2/link.2.html)
1251
+ - **File System Monitoring:** [chokidar](https://github.com/paulmillr/chokidar)
1252
+
1253
+ ### 12.3 Implementation Files
1254
+
1255
+ **New files to create:**
1256
+
1257
+ - `apps/control-plane/src/application/services/shadow-workspace-manager.ts`
1258
+ - `apps/control-plane/src/application/services/shadow-pool.ts`
1259
+ - `apps/control-plane/test/shadow-workspace-manager.spec.ts`
1260
+ - `apps/control-plane/test/shadow-pool.spec.ts`
1261
+
1262
+ **Files to modify:**
1263
+
1264
+ - `apps/control-plane/src/supervisor/worker-decision-loop.ts` - Add shadow mode execution path
1265
+ - `apps/control-plane/src/application/services/checkpoint-service.ts` - Add shadow diff computation
1266
+ - `agentic/orchestrator/schemas/agents.schema.json` - Add shadow configuration
1267
+ - `agentic/orchestrator/schemas/state.schema.json` - Add shadow metadata
1268
+
1269
+ ---
1270
+
1271
+ **End of Specification**