macro-agent 0.1.8 → 0.1.10

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 (258) hide show
  1. package/CLAUDE.md +166 -33
  2. package/README.md +781 -131
  3. package/dist/acp/claude-code-replay.d.ts +11 -0
  4. package/dist/acp/claude-code-replay.d.ts.map +1 -0
  5. package/dist/acp/claude-code-replay.js +190 -0
  6. package/dist/acp/claude-code-replay.js.map +1 -0
  7. package/dist/acp/macro-agent.d.ts.map +1 -1
  8. package/dist/acp/macro-agent.js +155 -6
  9. package/dist/acp/macro-agent.js.map +1 -1
  10. package/dist/acp/types.d.ts +9 -0
  11. package/dist/acp/types.d.ts.map +1 -1
  12. package/dist/acp/types.js.map +1 -1
  13. package/dist/agent/agent-manager-v2.d.ts +21 -0
  14. package/dist/agent/agent-manager-v2.d.ts.map +1 -1
  15. package/dist/agent/agent-manager-v2.js +234 -43
  16. package/dist/agent/agent-manager-v2.js.map +1 -1
  17. package/dist/agent/agent-manager.d.ts +12 -0
  18. package/dist/agent/agent-manager.d.ts.map +1 -1
  19. package/dist/agent/agent-manager.js.map +1 -1
  20. package/dist/agent/types.d.ts +15 -2
  21. package/dist/agent/types.d.ts.map +1 -1
  22. package/dist/agent/types.js.map +1 -1
  23. package/dist/boot-v2.d.ts +41 -0
  24. package/dist/boot-v2.d.ts.map +1 -1
  25. package/dist/boot-v2.js +16 -1
  26. package/dist/boot-v2.js.map +1 -1
  27. package/dist/cli/index.js +56 -0
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
  30. package/dist/cognitive/macro-agent-backend.js +40 -22
  31. package/dist/cognitive/macro-agent-backend.js.map +1 -1
  32. package/dist/integrations/skilltree.d.ts.map +1 -1
  33. package/dist/integrations/skilltree.js +1 -0
  34. package/dist/integrations/skilltree.js.map +1 -1
  35. package/dist/lifecycle/cleanup.d.ts +33 -2
  36. package/dist/lifecycle/cleanup.d.ts.map +1 -1
  37. package/dist/lifecycle/cleanup.js +28 -6
  38. package/dist/lifecycle/cleanup.js.map +1 -1
  39. package/dist/lifecycle/handlers-v2.d.ts +7 -0
  40. package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
  41. package/dist/lifecycle/handlers-v2.js +28 -2
  42. package/dist/lifecycle/handlers-v2.js.map +1 -1
  43. package/dist/lifecycle/types.d.ts +11 -0
  44. package/dist/lifecycle/types.d.ts.map +1 -1
  45. package/dist/lifecycle/types.js.map +1 -1
  46. package/dist/map/acp-bridge.d.ts +9 -0
  47. package/dist/map/acp-bridge.d.ts.map +1 -1
  48. package/dist/map/acp-bridge.js +15 -2
  49. package/dist/map/acp-bridge.js.map +1 -1
  50. package/dist/map/cascade-bridge.d.ts +44 -0
  51. package/dist/map/cascade-bridge.d.ts.map +1 -0
  52. package/dist/map/cascade-bridge.js +257 -0
  53. package/dist/map/cascade-bridge.js.map +1 -0
  54. package/dist/map/lifecycle-bridge.d.ts +1 -1
  55. package/dist/map/lifecycle-bridge.d.ts.map +1 -1
  56. package/dist/map/lifecycle-bridge.js +58 -23
  57. package/dist/map/lifecycle-bridge.js.map +1 -1
  58. package/dist/map/server.d.ts.map +1 -1
  59. package/dist/map/server.js +47 -6
  60. package/dist/map/server.js.map +1 -1
  61. package/dist/map/sidecar.d.ts.map +1 -1
  62. package/dist/map/sidecar.js +33 -2
  63. package/dist/map/sidecar.js.map +1 -1
  64. package/dist/map/types.d.ts +20 -0
  65. package/dist/map/types.d.ts.map +1 -1
  66. package/dist/mcp/tools/done-v2.d.ts.map +1 -1
  67. package/dist/mcp/tools/done-v2.js +8 -0
  68. package/dist/mcp/tools/done-v2.js.map +1 -1
  69. package/dist/teams/team-manager-v2.d.ts.map +1 -1
  70. package/dist/teams/team-manager-v2.js +26 -0
  71. package/dist/teams/team-manager-v2.js.map +1 -1
  72. package/dist/teams/team-runtime-v2.d.ts.map +1 -1
  73. package/dist/teams/team-runtime-v2.js +16 -3
  74. package/dist/teams/team-runtime-v2.js.map +1 -1
  75. package/dist/workspace/config.d.ts +10 -10
  76. package/dist/workspace/config.d.ts.map +1 -1
  77. package/dist/workspace/config.js +4 -4
  78. package/dist/workspace/config.js.map +1 -1
  79. package/dist/workspace/git-cascade-adapter.d.ts +510 -0
  80. package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
  81. package/dist/workspace/git-cascade-adapter.js +908 -0
  82. package/dist/workspace/git-cascade-adapter.js.map +1 -0
  83. package/dist/workspace/index.d.ts +3 -3
  84. package/dist/workspace/index.d.ts.map +1 -1
  85. package/dist/workspace/index.js +4 -4
  86. package/dist/workspace/index.js.map +1 -1
  87. package/dist/workspace/landing/direct-push.d.ts +20 -0
  88. package/dist/workspace/landing/direct-push.d.ts.map +1 -0
  89. package/dist/workspace/landing/direct-push.js +74 -0
  90. package/dist/workspace/landing/direct-push.js.map +1 -0
  91. package/dist/workspace/landing/index.d.ts +29 -0
  92. package/dist/workspace/landing/index.d.ts.map +1 -0
  93. package/dist/workspace/landing/index.js +37 -0
  94. package/dist/workspace/landing/index.js.map +1 -0
  95. package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
  96. package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
  97. package/dist/workspace/landing/merge-to-parent.js +185 -0
  98. package/dist/workspace/landing/merge-to-parent.js.map +1 -0
  99. package/dist/workspace/landing/optimistic-push.d.ts +16 -0
  100. package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
  101. package/dist/workspace/landing/optimistic-push.js +27 -0
  102. package/dist/workspace/landing/optimistic-push.js.map +1 -0
  103. package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
  104. package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
  105. package/dist/workspace/landing/queue-to-branch.js +79 -0
  106. package/dist/workspace/landing/queue-to-branch.js.map +1 -0
  107. package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
  108. package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
  109. package/dist/workspace/merge-queue/merge-queue.js +10 -0
  110. package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
  111. package/dist/workspace/merge-queue/types.d.ts +16 -2
  112. package/dist/workspace/merge-queue/types.d.ts.map +1 -1
  113. package/dist/workspace/merge-queue/types.js +9 -0
  114. package/dist/workspace/merge-queue/types.js.map +1 -1
  115. package/dist/workspace/pool/types.d.ts +1 -0
  116. package/dist/workspace/pool/types.d.ts.map +1 -1
  117. package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
  118. package/dist/workspace/pool/worktree-pool.js +1 -0
  119. package/dist/workspace/pool/worktree-pool.js.map +1 -1
  120. package/dist/workspace/recovery/abandon.d.ts +15 -0
  121. package/dist/workspace/recovery/abandon.d.ts.map +1 -0
  122. package/dist/workspace/recovery/abandon.js +45 -0
  123. package/dist/workspace/recovery/abandon.js.map +1 -0
  124. package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
  125. package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
  126. package/dist/workspace/recovery/auto-resolve.js +99 -0
  127. package/dist/workspace/recovery/auto-resolve.js.map +1 -0
  128. package/dist/workspace/recovery/defer.d.ts +15 -0
  129. package/dist/workspace/recovery/defer.d.ts.map +1 -0
  130. package/dist/workspace/recovery/defer.js +16 -0
  131. package/dist/workspace/recovery/defer.js.map +1 -0
  132. package/dist/workspace/recovery/escalate.d.ts +16 -0
  133. package/dist/workspace/recovery/escalate.d.ts.map +1 -0
  134. package/dist/workspace/recovery/escalate.js +24 -0
  135. package/dist/workspace/recovery/escalate.js.map +1 -0
  136. package/dist/workspace/recovery/index.d.ts +32 -0
  137. package/dist/workspace/recovery/index.d.ts.map +1 -0
  138. package/dist/workspace/recovery/index.js +45 -0
  139. package/dist/workspace/recovery/index.js.map +1 -0
  140. package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
  141. package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
  142. package/dist/workspace/recovery/spawn-resolver.js +111 -0
  143. package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
  144. package/dist/workspace/recovery/types.d.ts +63 -0
  145. package/dist/workspace/recovery/types.d.ts.map +1 -0
  146. package/dist/workspace/recovery/types.js +12 -0
  147. package/dist/workspace/recovery/types.js.map +1 -0
  148. package/dist/workspace/topology/index.d.ts +9 -0
  149. package/dist/workspace/topology/index.d.ts.map +1 -0
  150. package/dist/workspace/topology/index.js +8 -0
  151. package/dist/workspace/topology/index.js.map +1 -0
  152. package/dist/workspace/topology/no-workspace.d.ts +18 -0
  153. package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
  154. package/dist/workspace/topology/no-workspace.js +25 -0
  155. package/dist/workspace/topology/no-workspace.js.map +1 -0
  156. package/dist/workspace/topology/types.d.ts +97 -0
  157. package/dist/workspace/topology/types.d.ts.map +1 -0
  158. package/dist/workspace/topology/types.js +20 -0
  159. package/dist/workspace/topology/types.js.map +1 -0
  160. package/dist/workspace/topology/yaml-driven.d.ts +69 -0
  161. package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
  162. package/dist/workspace/topology/yaml-driven.js +273 -0
  163. package/dist/workspace/topology/yaml-driven.js.map +1 -0
  164. package/dist/workspace/types-v3.d.ts +110 -0
  165. package/dist/workspace/types-v3.d.ts.map +1 -0
  166. package/dist/workspace/types-v3.js +20 -0
  167. package/dist/workspace/types-v3.js.map +1 -0
  168. package/dist/workspace/types.d.ts +145 -17
  169. package/dist/workspace/types.d.ts.map +1 -1
  170. package/dist/workspace/workspace-manager.d.ts +92 -13
  171. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  172. package/dist/workspace/workspace-manager.js +373 -13
  173. package/dist/workspace/workspace-manager.js.map +1 -1
  174. package/dist/workspace/yaml-schema.d.ts +254 -0
  175. package/dist/workspace/yaml-schema.d.ts.map +1 -0
  176. package/dist/workspace/yaml-schema.js +170 -0
  177. package/dist/workspace/yaml-schema.js.map +1 -0
  178. package/docs/conflict-recovery.md +472 -0
  179. package/docs/git-cascade-integration-gaps.md +678 -0
  180. package/docs/workspace-interfaces.md +731 -0
  181. package/docs/workspace-redesign-plan.md +302 -0
  182. package/package.json +4 -4
  183. package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
  184. package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
  185. package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
  186. package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
  187. package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
  188. package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
  189. package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
  190. package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
  191. package/src/acp/__tests__/macro-agent.test.ts +39 -1
  192. package/src/acp/claude-code-replay.ts +208 -0
  193. package/src/acp/macro-agent.ts +167 -9
  194. package/src/acp/types.ts +10 -0
  195. package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
  196. package/src/agent/__tests__/agent-manager-v2.test.ts +66 -0
  197. package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
  198. package/src/agent/agent-manager-v2.ts +293 -48
  199. package/src/agent/agent-manager.ts +14 -0
  200. package/src/agent/types.ts +16 -2
  201. package/src/boot-v2.ts +68 -1
  202. package/src/cli/index.ts +61 -0
  203. package/src/cognitive/macro-agent-backend.ts +45 -29
  204. package/src/integrations/skilltree.ts +1 -0
  205. package/src/lifecycle/cleanup.ts +52 -3
  206. package/src/lifecycle/handlers-v2.ts +40 -3
  207. package/src/lifecycle/types.ts +12 -0
  208. package/src/map/__tests__/cascade-bridge.test.ts +229 -0
  209. package/src/map/__tests__/lifecycle-bridge.test.ts +86 -10
  210. package/src/map/acp-bridge.ts +26 -3
  211. package/src/map/cascade-bridge.ts +301 -0
  212. package/src/map/lifecycle-bridge.ts +52 -17
  213. package/src/map/server.ts +47 -6
  214. package/src/map/sidecar.ts +31 -1
  215. package/src/map/types.ts +20 -0
  216. package/src/mcp/tools/done-v2.ts +9 -0
  217. package/src/teams/team-manager-v2.ts +37 -0
  218. package/src/teams/team-runtime-v2.ts +23 -3
  219. package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
  220. package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
  221. package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
  222. package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
  223. package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
  224. package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
  225. package/src/workspace/config.ts +11 -11
  226. package/src/workspace/git-cascade-adapter.ts +1186 -0
  227. package/src/workspace/index.ts +11 -11
  228. package/src/workspace/landing/__tests__/strategies.test.ts +142 -0
  229. package/src/workspace/landing/direct-push.ts +91 -0
  230. package/src/workspace/landing/index.ts +40 -0
  231. package/src/workspace/landing/merge-to-parent.ts +228 -0
  232. package/src/workspace/landing/optimistic-push.ts +36 -0
  233. package/src/workspace/landing/queue-to-branch.ts +108 -0
  234. package/src/workspace/merge-queue/merge-queue.ts +10 -0
  235. package/src/workspace/merge-queue/types.ts +16 -2
  236. package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
  237. package/src/workspace/pool/types.ts +1 -0
  238. package/src/workspace/pool/worktree-pool.ts +1 -0
  239. package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
  240. package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
  241. package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
  242. package/src/workspace/recovery/abandon.ts +51 -0
  243. package/src/workspace/recovery/auto-resolve.ts +119 -0
  244. package/src/workspace/recovery/defer.ts +23 -0
  245. package/src/workspace/recovery/escalate.ts +30 -0
  246. package/src/workspace/recovery/index.ts +58 -0
  247. package/src/workspace/recovery/spawn-resolver.ts +145 -0
  248. package/src/workspace/recovery/types.ts +54 -0
  249. package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
  250. package/src/workspace/topology/index.ts +18 -0
  251. package/src/workspace/topology/no-workspace.ts +39 -0
  252. package/src/workspace/topology/types.ts +116 -0
  253. package/src/workspace/topology/yaml-driven.ts +316 -0
  254. package/src/workspace/types-v3.ts +155 -0
  255. package/src/workspace/types.ts +191 -20
  256. package/src/workspace/workspace-manager.ts +474 -19
  257. package/src/workspace/yaml-schema.ts +216 -0
  258. package/src/workspace/dataplane-adapter.ts +0 -546
@@ -0,0 +1,472 @@
1
+ # Conflict Recovery Design (Draft)
2
+
3
+ Parallel design to `docs/workspace-interfaces.md`. Defines how macro-agent handles merge/rebase/cascade conflicts produced by `WorkspaceManager` operations.
4
+
5
+ **Status**: draft for iteration. Not yet implemented.
6
+
7
+ **Relationship to other docs:**
8
+ - `git-cascade-integration-gaps.md` — narrative, established that conflict recovery is its own concern (§9 Open).
9
+ - `workspace-interfaces.md` — defines `ConflictRecord`, `WorkspaceManager.resolveConflict()`. This doc defines how recovery is *driven*.
10
+
11
+ ---
12
+
13
+ ## 1. Problem statement
14
+
15
+ Conflicts arise from four operations:
16
+
17
+ - `mergeStream(source → target)` — merge conflict between branches
18
+ - `syncWithParent(stream)` — rebase conflict when parent advanced
19
+ - `rebaseOntoStream(stream → target)` — same
20
+ - `cascadeRebase({ rootStreamId })` — one or more dependent streams fail to rebase
21
+
22
+ git-cascade records each via `createConflict()`; the stream is marked `conflicted`. Today, macro-agent has no handling — strategies use raw `execSync('git rebase')` which fails hard, and the event is lost.
23
+
24
+ The redesign needs a **pluggable recovery mechanism** that sits between detection (git-cascade's conflict model) and resolution (which might be git strategies, a spawned resolver agent, a human, or abandonment). The mechanism must work for all four sources.
25
+
26
+ ---
27
+
28
+ ## 2. When conflicts happen — lifecycle
29
+
30
+ ```
31
+ Landing / sync / cascade operation
32
+
33
+ ├─ Success: MergeResult.success = true
34
+ │ stream.committed event emitted
35
+
36
+ └─ Conflict: ConflictRecord created in git-cascade
37
+ stream marked 'conflicted'
38
+ stream.conflicted event emitted
39
+ MergeResult.success = false, conflictId returned
40
+
41
+ ├─ LandingStrategy / operation caller receives result
42
+
43
+ └─ workspaceManager.recoverConflict({ conflictId, strategyName? })
44
+
45
+ └─ Recovery strategy dispatches
46
+
47
+ ├─ Sync resolution → returns ConflictResolution immediately
48
+ └─ Async resolution → returns pending; events fire on completion
49
+ ```
50
+
51
+ **Key separation**: LandingStrategy decides *how to land*; ConflictRecoveryStrategy decides *what to do when landing fails*. They're parallel concerns, not nested.
52
+
53
+ ---
54
+
55
+ ## 3. Core types
56
+
57
+ ```ts
58
+ export interface ConflictContext {
59
+ conflictId: string;
60
+ streamId: StreamId; // the conflicted stream
61
+ sourceCommit?: string;
62
+ targetCommit?: string;
63
+ targetStreamId?: StreamId; // where we were trying to land (if applicable)
64
+ paths: string[]; // conflicting file paths
65
+ operation: "merge" | "sync" | "rebase" | "cascade";
66
+ landingAgentId?: AgentId; // who was trying to land (may be terminated)
67
+ recoveryDepth: number; // for bounded recursion
68
+ strategyConfig?: Record<string, unknown>;
69
+ workspaceManager: WorkspaceManager;
70
+ agentManager: AgentManager;
71
+ inboxAdapter: InboxAdapter;
72
+ }
73
+
74
+ export type ConflictResolution =
75
+ | { kind: "resolved"; resolutionCommit: string }
76
+ | { kind: "deferred"; reason: string }
77
+ | { kind: "abandoned"; streamId: StreamId; reason: string }
78
+ | { kind: "escalated"; escalatedTo: AgentId | "human" }
79
+ | { kind: "retry-after"; backoffMs: number; reason: string }
80
+ | { kind: "failed"; error: string };
81
+
82
+ export type ConflictResolutionMode = "sync" | "async";
83
+
84
+ export interface ConflictRecoveryStrategy {
85
+ readonly name: string;
86
+ readonly mode: ConflictResolutionMode;
87
+
88
+ canHandle?(ctx: ConflictContext): boolean;
89
+ recover(ctx: ConflictContext): Promise<ConflictResolution>;
90
+
91
+ initialize?(): Promise<void>;
92
+ close?(): Promise<void>;
93
+ }
94
+ ```
95
+
96
+ **Rationale:**
97
+ - `recoveryDepth` prevents infinite recursion if a resolver creates new conflicts.
98
+ - `mode` is declarative — callers can decide whether to await or fire-and-forget based on it.
99
+ - `operation` tells strategies what produced the conflict (merge-specific resolution differs from rebase-specific).
100
+ - `landingAgentId` may be null or already terminated; strategies can't rely on it being alive.
101
+
102
+ ---
103
+
104
+ ## 4. Built-in strategies
105
+
106
+ Five registered by default. Teams pick via YAML `on_conflict:` directive.
107
+
108
+ ### 4.1 `auto-resolve` — sync, git-native strategies
109
+
110
+ ```ts
111
+ // strategyConfig: { strategy: "ours" | "theirs" | "union" }
112
+ // Runs raw git merge with -X <strategy>; commits the resolution.
113
+ // Returns: { kind: "resolved", resolutionCommit } or { kind: "failed", ... }
114
+ ```
115
+
116
+ Use cases: low-risk conflicts where one side is authoritative. Example: a reviewer branch that should always defer to coder's changes.
117
+
118
+ **Constraint**: Only applicable to `operation: "merge"`. Rebase conflicts need different handling.
119
+
120
+ ### 4.2 `defer` — sync, no-op
121
+
122
+ ```ts
123
+ // Creates no new state; conflict record persists, stream stays 'conflicted'.
124
+ // Returns: { kind: "deferred", reason: "no strategy configured" }
125
+ ```
126
+
127
+ Use cases: the team doesn't want automated recovery. Something else (human, external process) will deal with it.
128
+
129
+ ### 4.3 `spawn-resolver` — async, LLM-driven
130
+
131
+ ```ts
132
+ // strategyConfig: {
133
+ // role: string; // e.g., "resolver"
134
+ // max_concurrent?: number; // default 2
135
+ // timeout_ms?: number; // default 1800000 (30 min)
136
+ // prompt_template?: string; // optional prompt override
137
+ // }
138
+ //
139
+ // 1. Emit conflict.recovery.started event
140
+ // 2. AgentManager.spawn({ role, options: { conflictId, streamId, paths } })
141
+ // 3. Resolver's topology places it on the conflicted stream via
142
+ // workspace: new_stream, stream_lineage: track_existing_branch,
143
+ // track_branch: <conflicted_branch>.
144
+ // 4. Resolver has capabilities: [workspace.commit, workspace.resolve, workspace.read]
145
+ // 5. Resolver reads conflict markers, edits, commits, calls resolve_conflict MCP tool.
146
+ // 6. resolve_conflict → workspaceManager.resolveConflict({ conflictId, resolutionCommit })
147
+ // → emits conflict.resolved event
148
+ // → stream status back to 'active'
149
+ // 7. Original operation retried (if requested via config).
150
+ //
151
+ // Returns: { kind: "resolved", resolutionCommit } on success
152
+ // { kind: "escalated", escalatedTo: "human" } on timeout
153
+ ```
154
+
155
+ Use cases: default for teams with LLM budget. Handles most real conflicts via LLM judgment.
156
+
157
+ **Recursion bound**: if resolver's `resolve_conflict` call produces a new conflict, `recoveryDepth` increments. Default max = 3; beyond that, falls back to `escalate`.
158
+
159
+ ### 4.4 `abandon` — sync, give up
160
+
161
+ ```ts
162
+ // Calls workspaceManager.abandonStream(streamId, { reason }).
163
+ // If operation was 'merge', source stream is abandoned; target untouched.
164
+ // If operation was 'sync' / 'rebase' / 'cascade', conflicted stream is abandoned
165
+ // (cascade: just the failing stream; siblings continue per cascade strategy).
166
+ // Returns: { kind: "abandoned", streamId, reason }
167
+ ```
168
+
169
+ Use cases: throwaway exploration streams; CI-driven teams where broken work is discarded.
170
+
171
+ ### 4.5 `escalate` — async, human-in-the-loop
172
+
173
+ ```ts
174
+ // strategyConfig: { notify: AgentId[] | "team-root" | "external-channel" }
175
+ //
176
+ // 1. Create inbox message with high importance to configured recipients
177
+ // 2. Pause stream via workspaceManager.pauseStream(streamId, "awaiting human")
178
+ // 3. Emit conflict.recovery.escalated event
179
+ // 4. Wait indefinitely for external resolve_conflict call
180
+ // 5. On external resolution: resume stream, emit conflict.resolved
181
+ //
182
+ // Returns: { kind: "escalated", escalatedTo }
183
+ ```
184
+
185
+ Use cases: high-stakes merges; teams with human reviewers; fallback from `spawn-resolver` timeout.
186
+
187
+ ---
188
+
189
+ ## 5. Integration with LandingStrategy
190
+
191
+ Landing strategies detect conflicts but don't handle them directly. Contract:
192
+
193
+ ```ts
194
+ // Inside a LandingStrategy.land(ctx):
195
+
196
+ const mergeResult = await ctx.workspaceManager.mergeStream({ ... });
197
+ if (!mergeResult.success && mergeResult.conflictId) {
198
+ // Do NOT call recoverConflict from inside the strategy.
199
+ // Return the conflict up; the caller (agent's done flow) handles it.
200
+ return {
201
+ success: false,
202
+ conflictId: mergeResult.conflictId,
203
+ };
204
+ }
205
+ ```
206
+
207
+ **Why not in-strategy recovery?** Three reasons:
208
+ 1. Strategies can be registered without recovery knowledge. Separation of concerns.
209
+ 2. Recovery policy is role-level (`on_conflict:` YAML), not strategy-level.
210
+ 3. Async recovery means strategy would have to block on recovery resolution — fragile.
211
+
212
+ Instead, the agent's `done()` flow owns recovery dispatch:
213
+
214
+ ```ts
215
+ // In AgentManagerV2's done handler (or land MCP tool handler):
216
+
217
+ const landingResult = await workspaceManager.land({ ... });
218
+ if (!landingResult.success && landingResult.conflictId) {
219
+ const roleConfig = topologyPolicy.getRoleConfig(agent.role);
220
+ const recoveryStrategy = roleConfig.on_conflict ?? teamDefault.conflict_recovery.default_strategy;
221
+
222
+ const resolution = await workspaceManager.recoverConflict({
223
+ conflictId: landingResult.conflictId,
224
+ strategyName: recoveryStrategy,
225
+ strategyConfig: roleConfig.conflict_recovery_config,
226
+ operation: "merge",
227
+ landingAgentId: agent.id,
228
+ });
229
+
230
+ // Handle resolution result — may be async; if so, agent can terminate
231
+ // and let recovery run in background.
232
+ }
233
+ ```
234
+
235
+ ---
236
+
237
+ ## 6. `WorkspaceManager` additions
238
+
239
+ Extend the interface from `workspace-interfaces.md` §5:
240
+
241
+ ```ts
242
+ interface WorkspaceManager {
243
+ // ... existing methods ...
244
+
245
+ // Conflict recovery registry
246
+ registerConflictRecoveryStrategy(strategy: ConflictRecoveryStrategy): void;
247
+ unregisterConflictRecoveryStrategy(name: string): void;
248
+ getConflictRecoveryStrategy(name: string): ConflictRecoveryStrategy | null;
249
+
250
+ // Recovery dispatch
251
+ recoverConflict(opts: {
252
+ conflictId: string;
253
+ strategyName?: string; // defaults to team default
254
+ strategyConfig?: Record<string, unknown>;
255
+ operation?: "merge" | "sync" | "rebase" | "cascade";
256
+ landingAgentId?: AgentId;
257
+ }): Promise<ConflictResolution>;
258
+
259
+ // Existing method, formalized
260
+ resolveConflict(opts: {
261
+ conflictId: string;
262
+ resolvedBy: Principal;
263
+ resolutionCommit?: string;
264
+ }): void; // Called by resolver agents via MCP tool.
265
+ // Emits conflict.resolved event.
266
+
267
+ // Listing / inspection
268
+ listConflicts(filter?: {
269
+ streamId?: StreamId;
270
+ resolved?: boolean;
271
+ }): ConflictRecord[];
272
+
273
+ getConflict(conflictId: string): ConflictRecord | null;
274
+ }
275
+ ```
276
+
277
+ New events (extend `WorkspaceEvent` union):
278
+
279
+ ```ts
280
+ | { kind: "conflict.created"; conflictId: string; streamId: StreamId; operation: string }
281
+ | { kind: "conflict.recovery.started"; conflictId: string; strategyName: string }
282
+ | { kind: "conflict.recovery.escalated"; conflictId: string; escalatedTo: string }
283
+ | { kind: "conflict.recovery.timed-out"; conflictId: string; strategyName: string }
284
+ | { kind: "conflict.resolved"; conflictId: string; resolutionCommit?: string; resolvedBy: Principal }
285
+ | { kind: "conflict.abandoned"; conflictId: string; reason: string }
286
+ ```
287
+
288
+ ---
289
+
290
+ ## 7. YAML configuration
291
+
292
+ ```yaml
293
+ macro_agent:
294
+ workspace:
295
+ roles:
296
+ worker:
297
+ on_conflict: defer # strategy name
298
+ coder:
299
+ on_conflict: spawn-resolver
300
+ conflict_recovery_config:
301
+ role: resolver
302
+ timeout_ms: 1200000
303
+ resolver:
304
+ workspace: new_stream
305
+ stream_lineage: track_existing_branch
306
+ # track_branch is set at spawn time from conflict context
307
+ landing: none # resolver doesn't land itself
308
+ capabilities:
309
+ - workspace.commit
310
+ - workspace.resolve
311
+ - workspace.read
312
+
313
+ # Team-level defaults
314
+ conflict_recovery:
315
+ default_strategy: spawn-resolver # used when role has no on_conflict
316
+ default_config:
317
+ role: resolver
318
+ max_concurrent: 2
319
+ timeout_ms: 1800000
320
+ escalation_target: team-root # for escalate strategy
321
+ max_recovery_depth: 3 # recursion bound
322
+ ```
323
+
324
+ **Resolution order for strategy selection:**
325
+ 1. Role-level `on_conflict` (most specific)
326
+ 2. Team-level `conflict_recovery.default_strategy`
327
+ 3. Hardcoded fallback: `defer` (safest default)
328
+
329
+ ---
330
+
331
+ ## 8. MCP tool surface
332
+
333
+ New capability: `workspace.resolve`. Registered tools:
334
+
335
+ ```ts
336
+ // ── Capability: workspace.resolve ────────────────────────────────
337
+ export const resolveConflictToolInput = z.object({
338
+ conflictId: z.string(),
339
+ resolutionCommit: z.string().optional(), // inferred from current HEAD if omitted
340
+ });
341
+ // Handler: workspaceManager.resolveConflict({ conflictId, resolvedBy: agentId, resolutionCommit })
342
+ // → emits conflict.resolved event
343
+ // → returns { resolved: true, conflictId, resolutionCommit }
344
+
345
+ export const listConflictsToolInput = z.object({
346
+ streamId: z.string().optional(),
347
+ resolvedOnly: z.boolean().default(false),
348
+ });
349
+ // Handler: workspaceManager.listConflicts({ ... })
350
+
351
+ export const getConflictToolInput = z.object({
352
+ conflictId: z.string(),
353
+ });
354
+ // Handler: workspaceManager.getConflict(conflictId)
355
+ ```
356
+
357
+ **Capability → tool mapping:**
358
+
359
+ | Capability | Tools |
360
+ |---|---|
361
+ | `workspace.resolve` | `resolve_conflict`, `list_conflicts`, `get_conflict` |
362
+
363
+ Resolver roles need `workspace.resolve` + `workspace.commit` + `workspace.read`.
364
+
365
+ ---
366
+
367
+ ## 9. End-to-end flow: `spawn-resolver`
368
+
369
+ ```
370
+ [Coder agent] lands via queue-to-branch strategy
371
+ └─ queue-to-branch.land(ctx):
372
+ workspaceManager.addToMergeQueue({ streamId: coderStream, targetBranch })
373
+ returns { success: true, queuedAt: ... }
374
+
375
+ [Integrator agent] drains queue
376
+ └─ integrator.processNextMergeRequest():
377
+ next = workspaceManager.getNextToMerge(targetBranch)
378
+ result = workspaceManager.mergeStream({ sourceStreamId: next.streamId, ... })
379
+ → CONFLICT: result.success = false, result.conflictId = "c-abc"
380
+ workspaceManager.markMergeQueueReady(next.id, { status: "conflicted" })
381
+
382
+ [Integrator's landing flow sees conflict in result]
383
+ └─ done handler:
384
+ roleConfig.on_conflict = "spawn-resolver" (from YAML)
385
+ resolution = await workspaceManager.recoverConflict({
386
+ conflictId: "c-abc",
387
+ strategyName: "spawn-resolver",
388
+ strategyConfig: { role: "resolver", timeout_ms: 1200000 },
389
+ operation: "merge",
390
+ landingAgentId: integratorId,
391
+ })
392
+
393
+ [SpawnResolverStrategy.recover(ctx)]
394
+ emit conflict.recovery.started
395
+ agentId = await agentManager.spawn({
396
+ role: "resolver",
397
+ parent: ctx.landingAgentId,
398
+ task: `Resolve conflict ${ctx.conflictId} on stream ${ctx.streamId}`,
399
+ options: {
400
+ conflictId: ctx.conflictId,
401
+ streamId: ctx.streamId,
402
+ paths: ctx.paths,
403
+ },
404
+ })
405
+ // Topology places resolver on conflicted stream via track_existing_branch
406
+ // Resolver wakes up, reads conflict markers in worktree, edits files,
407
+ // calls `commit` MCP tool, then `resolve_conflict` MCP tool.
408
+
409
+ [Resolver agent]
410
+ calls commit({ message: "resolve: merge conflict in auth/middleware.ts" })
411
+ → commitChanges returns { commit: "def456", changeId: "Change-I..." }
412
+ calls resolve_conflict({ conflictId: "c-abc" })
413
+ → workspaceManager.resolveConflict({ conflictId, resolvedBy, resolutionCommit: "def456" })
414
+ → emits conflict.resolved
415
+ → stream status → 'active'
416
+
417
+ [SpawnResolverStrategy] subscribed to conflict.resolved
418
+ returns { kind: "resolved", resolutionCommit: "def456" }
419
+
420
+ [Integrator's done handler]
421
+ resolution.kind === "resolved"
422
+ → retry mergeStream(coderStream → targetBranch) — this time it succeeds
423
+ → mark queue entry merged
424
+ → done
425
+ ```
426
+
427
+ **Timing**: spawn-resolver is async from the caller's POV but the caller awaits the returned Promise. For truly fire-and-forget, caller doesn't await — just subscribes to `conflict.resolved` event.
428
+
429
+ ---
430
+
431
+ ## 10. Edge cases
432
+
433
+ **E1: Resolver creates a new conflict.**
434
+ Resolver's `commit` succeeds; but when the retry of the original merge runs, it conflicts again (e.g., rebase moved the target). `recoveryDepth` increments; strategy re-enters. Capped at `max_recovery_depth`; beyond that, falls back to `escalate`.
435
+
436
+ **E2: Multiple conflicts on the same stream.**
437
+ git-cascade allows multiple `ConflictRecord` entries per stream. Recovery serializes — `recoverConflict` for conflict B waits if conflict A is in-progress. Implementation: per-stream recovery lock.
438
+
439
+ **E3: Resolver crashes / times out.**
440
+ `spawn-resolver` has a `timeout_ms`. On timeout, strategy returns `{ kind: "escalated", escalatedTo: "human" }` — escalation happens via inbox message. Original agent sees escalation in its resolution result.
441
+
442
+ **E4: Landing agent already terminated when resolution completes.**
443
+ `landingAgentId` is advisory, not required. The retry logic in the agent's done handler is gone; so who retries? Options:
444
+ - (a) Recovery strategy doesn't trigger retry; caller subscribes to event and handles
445
+ - (b) Recovery strategy itself triggers retry via a "retry" phase
446
+
447
+ Proposal: **(a)**. The resolver's `resolve_conflict` tool does not retry the original operation. Downstream retry is a separate concern handled by the integrator/queue drainer, which should pick up the now-resolved stream on next iteration. Avoids complex state in strategies.
448
+
449
+ **E5: Conflict on `cascadeRebase`.**
450
+ Cascade produces N potential conflicts across dependents. `CascadeResult.failed[]` lists each with a conflictId. Caller iterates and calls `recoverConflict` per failed stream, typically in parallel. Cascade strategy `defer_conflicts` lets the cascade complete past failures; recovery kicks in per stream.
451
+
452
+ **E6: Abandoning a stream mid-recovery.**
453
+ If a human abandons a conflicted stream while recovery is in-progress, the resolver agent's subsequent `resolve_conflict` call fails with `stream_abandoned`. Resolver handles gracefully (reports failure, terminates). Recovery strategy emits `conflict.recovery.failed` with reason.
454
+
455
+ ---
456
+
457
+ ## 11. Open questions
458
+
459
+ - **Retry ownership** (E4). Proposed (a): caller subscribes to `conflict.resolved` and retries if it still cares. Downside: lost retries if no one subscribes. Alternative: retry hook on the original LandingStrategy. TBD.
460
+ - **Recovery for read-only operations.** `stream_status` tool returning stale data after a conflict — not a recovery concern, but affects UX. Documented as a non-goal.
461
+ - **Cross-team conflicts.** If two teams' streams conflict during merge to shared branch, which team's recovery policy applies? Propose: conflict is attached to the stream being merged; the *owning team's* policy runs. For federated scenarios, escalates to human by default.
462
+ - **Observability.** Recovery can take minutes; need a structured progress channel. Proposal: resolver agents emit `RECOVERY_PROGRESS` signals on a team channel; subscribers can watch. Optional, enabled via YAML.
463
+ - **Cost control for LLM-driven recovery.** Runaway recursion + expensive LLM calls are a risk. Hard cap: `max_recovery_depth` + `max_concurrent` per strategy config. Tokens should be tracked separately.
464
+
465
+ ---
466
+
467
+ ## 12. What's not defined here
468
+
469
+ - Implementation of resolver role prompt / skills.
470
+ - Conflict resolution heuristics (e.g., how an LLM resolver approaches different conflict types) — belongs in resolver role definition, not the strategy infrastructure.
471
+ - UI / dashboard for human-escalated conflicts.
472
+ - Cross-process conflict coordination (multi-instance macro-agent sharing a repo).