oh-my-opencode 4.16.1 → 4.16.3

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 (151) hide show
  1. package/.agents/skills/pre-publish-review/SKILL.md +3 -0
  2. package/README.md +2 -2
  3. package/dist/agents/hephaestus/agent.d.ts +1 -1
  4. package/dist/agents/hephaestus/gpt-5-6.d.ts +2 -0
  5. package/dist/agents/momus-gpt-5-6.d.ts +1 -0
  6. package/dist/agents/types.d.ts +2 -0
  7. package/dist/cli/index.js +800 -536
  8. package/dist/cli-node/index.js +800 -536
  9. package/dist/features/background-agent/error-classifier.d.ts +5 -0
  10. package/dist/features/background-agent/parent-wake-dedupe.d.ts +1 -0
  11. package/dist/features/background-agent/parent-wake-flush-runner.d.ts +2 -0
  12. package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -0
  13. package/dist/features/builtin-commands/templates/refactor-sections/intro-and-analysis.d.ts +1 -1
  14. package/dist/features/builtin-commands/templates/remove-ai-slops.d.ts +1 -1
  15. package/dist/index.js +464 -54
  16. package/dist/shared/live-server-route.d.ts +1 -1
  17. package/dist/skills/review-work/SKILL.md +4 -2
  18. package/dist/skills/start-work/SKILL.md +2 -0
  19. package/dist/skills/ulw-plan/references/full-workflow.md +1 -1
  20. package/dist/skills/ulw-research/SKILL.md +5 -3
  21. package/dist/skills/visual-qa/SKILL.md +1 -1
  22. package/dist/tui.js +125 -13
  23. package/package.json +13 -13
  24. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  25. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +398 -246
  26. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  29. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  30. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  31. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  32. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  33. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  34. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  36. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/rules/bundled-rules/{hephaestus.md → hephaestus/gpt-5.5.md} +1 -1
  38. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +93 -0
  39. package/packages/omo-codex/plugin/components/rules/dist/cli.js +43 -24
  40. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  41. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  42. package/packages/omo-codex/plugin/components/rules/src/codex-hook.ts +2 -1
  43. package/packages/omo-codex/plugin/components/rules/src/dynamic-target-fingerprints.ts +15 -6
  44. package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +7 -2
  45. package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +2 -2
  46. package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +32 -0
  47. package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +106 -0
  48. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +1 -1
  49. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  50. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  51. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +1 -1
  52. package/packages/omo-codex/plugin/components/teammode/AGENTS.md +12 -9
  53. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  54. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  55. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +136 -76
  56. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-guide.mjs +94 -23
  57. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +94 -14
  58. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-transport.mjs +55 -0
  59. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +72 -18
  60. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +23 -0
  61. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  62. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  63. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +24 -41
  64. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +1 -1
  65. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +1 -1
  66. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-executor.toml +2 -2
  67. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
  68. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-qa-executor.toml +1 -1
  69. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +21 -49
  70. package/packages/omo-codex/plugin/components/ultrawork/agents/metis.toml +17 -29
  71. package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +20 -50
  72. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +18 -39
  73. package/packages/omo-codex/plugin/components/ultrawork/directive.md +1 -0
  74. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  75. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  76. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +1 -0
  77. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +2 -0
  78. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +3 -1
  79. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +1 -0
  80. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
  81. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  82. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +2 -0
  83. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
  84. package/packages/omo-codex/plugin/components/ulw-loop/test/skill-contract.test.ts +1 -1
  85. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  93. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  94. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  95. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  96. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  97. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  98. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  99. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  100. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  101. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  102. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  103. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  104. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  105. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  106. package/packages/omo-codex/plugin/package-lock.json +13 -13
  107. package/packages/omo-codex/plugin/package.json +1 -1
  108. package/packages/omo-codex/plugin/scripts/auto-update.mjs +73 -19
  109. package/packages/omo-codex/plugin/scripts/entry-guard.mjs +26 -0
  110. package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +3 -1
  111. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +221 -20
  112. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +71 -3
  113. package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +35 -7
  114. package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +2 -2
  115. package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +2 -2
  116. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +7 -5
  117. package/packages/omo-codex/plugin/scripts/sync-version.mjs +4 -2
  118. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +2 -2
  119. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +2 -2
  120. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +2 -2
  121. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +2 -2
  122. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +2 -2
  123. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +136 -76
  124. package/packages/omo-codex/plugin/skills/teammode/scripts/team-guide.mjs +94 -23
  125. package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +94 -14
  126. package/packages/omo-codex/plugin/skills/teammode/scripts/team-transport.mjs +55 -0
  127. package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +72 -18
  128. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +1 -0
  129. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -0
  130. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
  131. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +2 -0
  132. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +3 -1
  133. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +3 -3
  134. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +3 -3
  135. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +47 -1
  136. package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +1 -1
  137. package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +4 -2
  138. package/packages/omo-codex/plugin/test/auto-update-restart-notice.test.mjs +33 -0
  139. package/packages/omo-codex/plugin/test/auto-update.test.mjs +35 -1
  140. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +412 -19
  141. package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +71 -0
  142. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +65 -0
  143. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +13 -4
  144. package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +345 -0
  145. package/packages/omo-codex/scripts/install-dist/install-local.mjs +436 -284
  146. package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +111 -1
  147. package/packages/shared-skills/skills/review-work/SKILL.md +4 -2
  148. package/packages/shared-skills/skills/start-work/SKILL.md +2 -0
  149. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +1 -1
  150. package/packages/shared-skills/skills/ulw-research/SKILL.md +5 -3
  151. package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
@@ -13,6 +13,16 @@ import { lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
13
13
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
14
14
  import { setTimeout as delay } from "node:timers/promises";
15
15
 
16
+ import {
17
+ agentPathForTaskName,
18
+ assertTransport,
19
+ isMultiAgentV2,
20
+ LEADER_AGENT_PATH,
21
+ parseTaskName,
22
+ parseTeamTransport,
23
+ TEAM_SCHEMA_VERSION,
24
+ } from "./team-transport.mjs";
25
+
16
26
  export const LENSES = ["area", "ownership", "perspective"];
17
27
  export const MEMBER_STATUSES = ["pending", "active", "reported", "blocked", "archived"];
18
28
  // A team is never a single member: two or more distinct slices is the minimum. One isolated
@@ -32,22 +42,25 @@ function isoNow(now) {
32
42
  return now ?? new Date().toISOString();
33
43
  }
34
44
 
35
- export function buildTeam({ teamName, sessionName, sessionId = null, dir = null, worktreeEnabled = false, baseBranch = "dev", now }) {
45
+ export function buildTeam({ teamName, sessionName, sessionId = null, dir = null, worktreeEnabled = false, baseBranch = "dev", transport, now }) {
36
46
  if (!teamName?.trim()) throw new Error("team name is required");
37
47
  if (!sessionName?.trim()) throw new Error("session name is required");
48
+ const teamTransport = parseTeamTransport(transport);
49
+ const v2 = teamTransport === "multi_agent_v2";
38
50
  const ts = isoNow(now);
39
51
  return {
40
- schemaVersion: 2,
52
+ schemaVersion: TEAM_SCHEMA_VERSION,
53
+ transport: teamTransport,
41
54
  teamId: randomUUID(),
42
55
  teamName: teamName.trim(),
43
56
  sessionName: sessionName.trim(),
44
57
  sessionId,
45
- threadTitleConvention: `[${teamName.trim()}] <member name>`,
58
+ threadTitleConvention: v2 ? null : `[${teamName.trim()}] <member name>`,
46
59
  status: "active",
47
60
  createdAt: ts,
48
61
  updatedAt: ts,
49
62
  archivedAt: null,
50
- leader: { kind: "main-session", sessionId },
63
+ leader: { kind: "main-session", sessionId: v2 ? null : sessionId, agentPath: v2 ? LEADER_AGENT_PATH : null },
51
64
  communication: { memberLanguage: "english", replyToUserInUserLanguage: true },
52
65
  worktree: { enabled: Boolean(worktreeEnabled), baseBranch, root: dir ? join(dir, "worktrees") : null },
53
66
  paths: dir
@@ -124,21 +137,44 @@ function assertUniqueMemberThreadTitle(team) {
124
137
  }
125
138
  }
126
139
 
127
- function assertTeamReadyForThreadBinding(team) {
140
+ function assertUniqueMemberTaskName(team) {
141
+ const seen = new Map();
142
+ for (const member of team.members) {
143
+ const taskName = parseTaskName(member.taskName);
144
+ const previous = seen.get(taskName);
145
+ if (previous) {
146
+ throw new Error(`member taskName "${taskName}" duplicates "${previous.taskName}" (no two members may share an agent path)`);
147
+ }
148
+ if (member.agentPath !== agentPathForTaskName(taskName)) {
149
+ throw new Error(`member "${member.id}" agentPath "${member.agentPath}" does not match task name "${taskName}"`);
150
+ }
151
+ seen.set(taskName, member);
152
+ }
153
+ }
154
+
155
+ function assertUniqueTransportIdentity(team) {
156
+ if (isMultiAgentV2(team)) assertUniqueMemberTaskName(team);
157
+ else assertUniqueMemberThreadTitle(team);
158
+ }
159
+
160
+ function assertTeamReadyForBinding(team) {
128
161
  if (isUnderstaffed(team)) {
129
162
  throw new Error(
130
- `cannot bind member threads until the team has at least ${MIN_MEMBERS} distinct members; current count is ${team.members.length}`,
163
+ `cannot bind members until the team has at least ${MIN_MEMBERS} distinct members; current count is ${team.members.length}`,
131
164
  );
132
165
  }
133
166
  assertUniqueMemberFocus(team);
134
167
  assertUniqueMemberName(team);
135
- assertUniqueMemberThreadTitle(team);
168
+ assertUniqueTransportIdentity(team);
136
169
  }
137
170
 
138
- export function addMember(team, { id, focus, lens, deliverable = "", branch = null, name = null }) {
171
+ export function addMember(team, { id, focus, lens, deliverable = "", branch = null, name = null, taskName = null }) {
139
172
  if (!id?.trim()) throw new Error("member id is required");
140
173
  if (!focus?.trim()) throw new Error("member focus is required - a concrete part, ownership area, or perspective");
141
174
  if (!LENSES.includes(lens)) throw new Error(`invalid lens "${lens}" - use one of: ${LENSES.join(", ")}`);
175
+ const v2 = isMultiAgentV2(team);
176
+ if (!v2 && taskName !== null) throw new Error("--task-name is only valid on multi_agent_v2 teams");
177
+ const memberTaskName = v2 ? parseTaskName(taskName) : null;
142
178
  const memberId = id.trim();
143
179
  const memberFocus = focus.trim();
144
180
  // The member name is the short role label that titles this member's thread. Fall back to the
@@ -151,14 +187,20 @@ export function addMember(team, { id, focus, lens, deliverable = "", branch = nu
151
187
  if (duplicateName) {
152
188
  throw new Error(`member name "${memberName}" duplicates "${duplicateName.name ?? duplicateName.focus}" (no two members may produce the same thread title)`);
153
189
  }
190
+ if (v2) {
191
+ const duplicateTask = team.members.find((m) => m.taskName === memberTaskName);
192
+ if (duplicateTask) throw new Error(`member taskName "${memberTaskName}" duplicates "${duplicateTask.taskName}" (no two members may share an agent path)`);
193
+ }
154
194
  team.members.push({
155
195
  id: memberId,
156
196
  name: memberName,
157
197
  focus: memberFocus,
158
198
  lens,
159
199
  deliverable: deliverable.trim(),
200
+ taskName: memberTaskName,
201
+ agentPath: v2 ? agentPathForTaskName(memberTaskName) : null,
160
202
  threadId: null,
161
- threadTitle: `[${team.teamName}] ${memberName}`,
203
+ threadTitle: v2 ? null : `[${team.teamName}] ${memberName}`,
162
204
  cwd: null,
163
205
  worktree: { path: null, branch: branch ?? null },
164
206
  status: "pending",
@@ -167,8 +209,9 @@ export function addMember(team, { id, focus, lens, deliverable = "", branch = nu
167
209
  }
168
210
 
169
211
  export function bindThread(team, { id, threadId, cwd = null, worktreePath = null }) {
212
+ assertTransport(team, "codex_app", "bind-thread");
170
213
  if (!threadId?.trim()) throw new Error("thread id is required");
171
- assertTeamReadyForThreadBinding(team);
214
+ assertTeamReadyForBinding(team);
172
215
  const m = memberById(team, id);
173
216
  m.threadId = threadId.trim();
174
217
  m.status = "active";
@@ -177,6 +220,21 @@ export function bindThread(team, { id, threadId, cwd = null, worktreePath = null
177
220
  return touch(team, "bind-thread", `member ${id} -> thread ${threadId.trim()}`);
178
221
  }
179
222
 
223
+ export function bindAgent(team, { id, agentPath, cwd = null, worktreePath = null }) {
224
+ assertTransport(team, "multi_agent_v2", "bind-agent");
225
+ if (!agentPath?.trim()) throw new Error("agent path is required");
226
+ assertTeamReadyForBinding(team);
227
+ const m = memberById(team, id);
228
+ const boundPath = agentPath.trim();
229
+ if (boundPath !== m.agentPath) {
230
+ throw new Error(`agent path mismatch for member ${m.id}: expected "${m.agentPath}" (task name "${m.taskName}"), got "${boundPath}"`);
231
+ }
232
+ m.status = "active";
233
+ if (cwd) m.cwd = cwd;
234
+ if (team.worktree.enabled) m.worktree.path = worktreePath ?? cwd ?? m.worktree.path;
235
+ return touch(team, "bind-agent", `member ${id} -> agent ${boundPath}`);
236
+ }
237
+
180
238
  export function setMemberStatus(team, { id, status, note = "" }) {
181
239
  if (!MEMBER_STATUSES.includes(status)) throw new Error(`invalid status "${status}" - use one of: ${MEMBER_STATUSES.join(", ")}`);
182
240
  memberById(team, id).status = status;
@@ -204,22 +262,44 @@ export function clearMemberWorktree(team, { id }) {
204
262
  export function archive(team, { id = null, note = "" } = {}) {
205
263
  if (id) {
206
264
  memberById(team, id).status = "archived";
207
- return touch(team, "archive-member", `member ${id}${note ? `: ${note}` : ""}`);
265
+ const memberQualifier = isMultiAgentV2(team) ? " (team state only; V2 exposes no runtime archive operation)" : "";
266
+ return touch(team, "archive-member", `member ${id}${memberQualifier}${note ? `: ${note}` : ""}`);
208
267
  }
209
268
  for (const m of team.members) m.status = "archived";
210
269
  team.status = "archived";
211
270
  team.archivedAt = isoNow();
212
- return touch(team, "archive", note || "team archived; all members closed");
271
+ // The default detail must never claim a runtime closure that did not happen: V2 exposes no
272
+ // runtime archive operation, so only the durable team state is archived there.
273
+ const defaultDetail = isMultiAgentV2(team)
274
+ ? "team archived; V2 exposes no runtime archive operation - durable team state is the archive"
275
+ : "team archived; all members closed";
276
+ return touch(team, "archive", note || defaultDetail);
277
+ }
278
+
279
+ // schemaVersion 2 predates transports; that skill allowed ONLY Codex App threads as members,
280
+ // so a legacy team is migrated in memory as codex_app and persisted on the next mutation.
281
+ function migrateLegacyTeam(team) {
282
+ if (team?.schemaVersion !== 2) return team;
283
+ team.schemaVersion = TEAM_SCHEMA_VERSION;
284
+ team.transport = "codex_app";
285
+ if (team.leader && typeof team.leader === "object") team.leader.agentPath = null;
286
+ for (const member of team.members ?? []) {
287
+ member.taskName = null;
288
+ member.agentPath = null;
289
+ }
290
+ return team;
213
291
  }
214
292
 
215
293
  export function validateTeam(team) {
216
- if (team?.schemaVersion !== 2) throw new Error("invalid team: schemaVersion must be 2");
294
+ migrateLegacyTeam(team);
295
+ if (team?.schemaVersion !== TEAM_SCHEMA_VERSION) throw new Error(`invalid team: schemaVersion must be ${TEAM_SCHEMA_VERSION}`);
296
+ parseTeamTransport(team.transport);
217
297
  if (!team.teamId || !team.teamName) throw new Error("invalid team: teamId and teamName are required");
218
298
  if (team.leader?.kind !== "main-session") throw new Error("invalid team: leader.kind must be main-session");
219
299
  if (!Array.isArray(team.members)) throw new Error("invalid team: members must be an array");
220
300
  assertUniqueMemberFocus(team);
221
301
  assertUniqueMemberName(team);
222
- assertUniqueMemberThreadTitle(team);
302
+ assertUniqueTransportIdentity(team);
223
303
  return team;
224
304
  }
225
305
 
@@ -0,0 +1,55 @@
1
+ // team-transport.mjs - the single source of transport identity for teammode state.
2
+ //
3
+ // A team runs on exactly ONE transport, chosen at init and immutable afterwards:
4
+ // - "multi_agent_v2": members are native Codex MultiAgentV2 agents addressed by task
5
+ // name / canonical agent path (`/root/<task_name>`), messaged with flat `send_message`
6
+ // and `followup_task`.
7
+ // - "codex_app": members are durable Codex App threads addressed by thread id, messaged
8
+ // with `codex_app.send_message_to_thread` (the fallback when flat V2 tools are absent).
9
+ //
10
+ // Zero external dependencies (node builtins only), same portability contract as the
11
+ // sibling scripts.
12
+
13
+ export const TEAM_SCHEMA_VERSION = 3;
14
+ export const TEAM_TRANSPORTS = ["multi_agent_v2", "codex_app"];
15
+ export const DEFAULT_TEAM_TRANSPORT = "codex_app";
16
+ export const LEADER_AGENT_PATH = "/root";
17
+
18
+ // Codex validates V2 task names as lowercase letters, digits, and underscores; "root" is
19
+ // the leader's own path segment and can never name a member.
20
+ const TASK_NAME_PATTERN = /^[a-z0-9_]+$/;
21
+
22
+ export function parseTeamTransport(value = DEFAULT_TEAM_TRANSPORT) {
23
+ if (!TEAM_TRANSPORTS.includes(value)) {
24
+ throw new Error(`invalid transport "${value}" - use one of: ${TEAM_TRANSPORTS.join(", ")}`);
25
+ }
26
+ return value;
27
+ }
28
+
29
+ export function isMultiAgentV2(team) {
30
+ return team?.transport === "multi_agent_v2";
31
+ }
32
+
33
+ export function isCodexApp(team) {
34
+ return team?.transport === "codex_app";
35
+ }
36
+
37
+ export function assertTransport(team, transport, operation) {
38
+ if (team?.transport !== transport) {
39
+ throw new Error(`${operation} is only valid on ${transport} teams; this team's transport is "${team?.transport}"`);
40
+ }
41
+ }
42
+
43
+ export function parseTaskName(taskName) {
44
+ const trimmed = typeof taskName === "string" ? taskName.trim() : "";
45
+ if (!trimmed) throw new Error("member task name is required on multi_agent_v2 teams (--task-name)");
46
+ if (!TASK_NAME_PATTERN.test(trimmed)) {
47
+ throw new Error(`invalid task name "${trimmed}" - use lowercase letters, digits, and underscores`);
48
+ }
49
+ if (trimmed === "root") throw new Error('invalid task name "root" - it names the leader, never a member');
50
+ return trimmed;
51
+ }
52
+
53
+ export function agentPathForTaskName(taskName) {
54
+ return `${LEADER_AGENT_PATH}/${taskName}`;
55
+ }
@@ -6,9 +6,10 @@
6
6
  // Zero external dependencies (node builtins only): runs identically under node and bun on
7
7
  // macOS, Linux, and Windows.
8
8
  //
9
- // node "<skill-root>/scripts/team.mjs" init --name "<team>" --session-name "<sess>" [--session <id>] [--worktree] [--base-branch dev]
10
- // node "<skill-root>/scripts/team.mjs" add-member --team <id> --id A --name "<short role>" --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>" [--branch <b>]
11
- // node "<skill-root>/scripts/team.mjs" bind-thread --team <id> --id A --thread <thread-id> [--cwd <path>]
9
+ // node "<skill-root>/scripts/team.mjs" init --name "<team>" --session-name "<sess>" [--transport multi_agent_v2|codex_app] [--session <id>] [--worktree] [--base-branch dev]
10
+ // node "<skill-root>/scripts/team.mjs" add-member --team <id> --id A --name "<short role>" --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>" [--task-name <v2 task name>] [--branch <b>]
11
+ // node "<skill-root>/scripts/team.mjs" bind-agent --team <id> --id A --agent-path /root/<task_name> [--cwd <path>] (multi_agent_v2 teams)
12
+ // node "<skill-root>/scripts/team.mjs" bind-thread --team <id> --id A --thread <thread-id> [--cwd <path>] (codex_app teams)
12
13
  // node "<skill-root>/scripts/team.mjs" member-prompt --team <id> --id A
13
14
  // node "<skill-root>/scripts/team.mjs" set-status --team <id> --id A --status reported|blocked|active|archived [--note "<...>"]
14
15
  // node "<skill-root>/scripts/team.mjs" archive --team <id> [--id A] [--note "<...>"]
@@ -25,6 +26,7 @@ import {
25
26
  addMember,
26
27
  archive,
27
28
  assertSafeTeamDir,
29
+ bindAgent,
28
30
  bindThread,
29
31
  buildTeam,
30
32
  clearMemberWorktree,
@@ -39,6 +41,7 @@ import {
39
41
  writeGuideAtomic,
40
42
  writeTeamAtomic,
41
43
  } from "./team-state.mjs";
44
+ import { isMultiAgentV2, parseTeamTransport } from "./team-transport.mjs";
42
45
  import { addMemberWorktree, integrateMemberBranch, removeMemberWorktree } from "./team-worktree.mjs";
43
46
 
44
47
  function parseFlags(args) {
@@ -95,11 +98,20 @@ const handlers = {
95
98
  async init(cwd, flags) {
96
99
  const teamName = requireFlag(flags, "name");
97
100
  const sessionName = requireFlag(flags, "session-name");
101
+ // Validate the transport BEFORE any directory exists so a bad value leaves no state behind.
102
+ // A valueless `--transport` parses as `true` and must fail loudly, never default silently.
103
+ const transport = parseTeamTransport(flags.transport === undefined ? undefined : flags.transport);
98
104
  const sessionId = typeof flags.session === "string" ? flags.session : `team-${randomUUID().slice(0, 8)}`;
99
105
  const dir = await ensureTeamDir(cwd, sessionId);
100
106
  await withTeamLock(dir, "init", async () => {
101
107
  if (await teamExists(dir)) {
102
- process.stdout.write(`exists: ${dir} (left untouched; re-init is a safe no-op)\n`);
108
+ const existing = await readTeam(dir);
109
+ if (flags.transport !== undefined && existing.transport !== transport) {
110
+ throw new Error(
111
+ `team "${sessionId}" already exists with transport "${existing.transport}" - transport is immutable; keep using it or delete the team first`,
112
+ );
113
+ }
114
+ process.stdout.write(`exists: ${dir} (transport: ${existing.transport}; left untouched; re-init is a safe no-op)\n`);
103
115
  return;
104
116
  }
105
117
  const team = buildTeam({
@@ -107,13 +119,15 @@ const handlers = {
107
119
  sessionName,
108
120
  sessionId,
109
121
  dir,
122
+ transport,
110
123
  worktreeEnabled: flags.worktree === true,
111
124
  baseBranch: typeof flags["base-branch"] === "string" ? flags["base-branch"] : "dev",
112
125
  });
113
126
  await persist(team, dir);
114
- process.stdout.write(`created: ${dir}\n`);
127
+ const taskNameFlag = isMultiAgentV2(team) ? ' --task-name "<lowercase_digits_underscores>"' : "";
128
+ process.stdout.write(`created: ${dir} (transport: ${team.transport})\n`);
115
129
  process.stdout.write(`team.json + guide.md written; artifacts/ ready. session id: ${sessionId}\n`);
116
- process.stdout.write(`next: add-member --team ${sessionId} --id A --name "<short role>" --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>"\n`);
130
+ process.stdout.write(`next: add-member --team ${sessionId} --id A --name "<short role>"${taskNameFlag} --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>"\n`);
117
131
  });
118
132
  },
119
133
 
@@ -127,13 +141,17 @@ const handlers = {
127
141
  lens: requireFlag(flags, "lens"),
128
142
  deliverable: typeof flags.deliverable === "string" ? flags.deliverable : "",
129
143
  branch: typeof flags.branch === "string" ? flags.branch : null,
144
+ taskName: typeof flags["task-name"] === "string" ? flags["task-name"] : null,
130
145
  };
131
146
  const { team, member } = await mutateTeam(cwd, sessionId, "add-member", async (team, dir) => {
132
147
  addMember(team, memberInput);
133
148
  await persist(team, dir);
134
149
  return { team, member: team.members.find((m) => m.id === memberId) };
135
150
  });
136
- process.stdout.write(`added member ${memberId} to team ${sessionId}.\n\nSend this as the new thread's first message (title the thread "${member.threadTitle}"):\n---\n${buildMemberPrompt(team, memberId)}\n---\n`);
151
+ const delivery = isMultiAgentV2(team)
152
+ ? `Send this as spawn_agent message (task_name "${member.taskName}", fork_turns "none"), then bind-agent --agent-path "${member.agentPath}"`
153
+ : `Send this as the new thread's first message (title the thread "${member.threadTitle}")`;
154
+ process.stdout.write(`added member ${memberId} to team ${sessionId}.\n\n${delivery}:\n---\n${buildMemberPrompt(team, memberId)}\n---\n`);
137
155
  },
138
156
 
139
157
  async "bind-thread"(cwd, flags) {
@@ -151,6 +169,21 @@ const handlers = {
151
169
  process.stdout.write(`bound member ${flags.id} to thread ${flags.thread}.\n`);
152
170
  },
153
171
 
172
+ async "bind-agent"(cwd, flags) {
173
+ const sessionId = requireFlag(flags, "team");
174
+ const input = {
175
+ id: requireFlag(flags, "id"),
176
+ agentPath: requireFlag(flags, "agent-path"),
177
+ cwd: typeof flags.cwd === "string" ? flags.cwd : null,
178
+ worktreePath: typeof flags["worktree-path"] === "string" ? flags["worktree-path"] : null,
179
+ };
180
+ await mutateTeam(cwd, sessionId, "bind-agent", async (team, dir) => {
181
+ bindAgent(team, input);
182
+ await persist(team, dir);
183
+ });
184
+ process.stdout.write(`bound member ${flags.id} to agent ${flags["agent-path"]}.\n`);
185
+ },
186
+
154
187
  async "member-prompt"(cwd, flags) {
155
188
  const sessionId = requireFlag(flags, "team");
156
189
  const { team } = await loadTeam(cwd, sessionId);
@@ -175,19 +208,24 @@ const handlers = {
175
208
  const sessionId = requireFlag(flags, "team");
176
209
  const memberId = requireFlag(flags, "id");
177
210
  const baseBranch = typeof flags["base-branch"] === "string" ? flags["base-branch"] : null;
178
- const { member, result } = await mutateTeam(cwd, sessionId, "worktree-add", async (team, dir) => {
211
+ const { team, member, result } = await mutateTeam(cwd, sessionId, "worktree-add", async (team, dir) => {
179
212
  const member = memberOrThrow(team, memberId);
180
213
  const result = addMemberWorktree(cwd, team, member, { baseBranch });
181
214
  setMemberWorktree(team, { id: member.id, path: result.path, branch: result.branch });
182
215
  await persist(team, dir);
183
- return { member, result };
216
+ return { team, member, result };
184
217
  });
185
218
  const note = result.created ? "" : " (already exists)";
186
- const thread = member.threadId
187
- ? `\nMember thread: ${codexThreadLink(member.threadId)}\nTell that member to: cd "${result.path}"`
188
- : "\nMember thread is not bound yet; wait for the real Codex thread id, then bind-thread before sending bootstrap. After binding, send the member this worktree path.";
219
+ const v2 = isMultiAgentV2(team);
220
+ const followUp = v2
221
+ ? member.status === "active"
222
+ ? `\nMember agent: ${member.agentPath}\nSend it a followup_task to: cd "${result.path}"`
223
+ : `\nMember agent is not spawned yet; include this worktree path in its spawn_agent message, then bind-agent.`
224
+ : member.threadId
225
+ ? `\nMember thread: ${codexThreadLink(member.threadId)}\nTell that member to: cd "${result.path}"`
226
+ : "\nMember thread is not bound yet; wait for the real Codex thread id, then bind-thread before sending bootstrap. After binding, send the member this worktree path.";
189
227
  process.stdout.write(
190
- `worktree for member ${member.id}${note}: ${result.path} on branch ${result.branch} (off ${result.base}).${thread}\n`,
228
+ `worktree for member ${member.id}${note}: ${result.path} on branch ${result.branch} (off ${result.base}).${followUp}\n`,
191
229
  );
192
230
  },
193
231
 
@@ -235,11 +273,19 @@ const handlers = {
235
273
  id: typeof flags.id === "string" ? flags.id : null,
236
274
  note: typeof flags.note === "string" ? flags.note : "",
237
275
  };
238
- await mutateTeam(cwd, sessionId, "archive", async (team, dir) => {
276
+ const team = await mutateTeam(cwd, sessionId, "archive", async (team, dir) => {
239
277
  archive(team, input);
240
278
  await persist(team, dir);
279
+ return team;
241
280
  });
242
- process.stdout.write(flags.id ? `archived member ${flags.id}\n` : `archived team ${sessionId} and closed all members\n`);
281
+ const v2 = isMultiAgentV2(team);
282
+ const teamSummary = v2
283
+ ? `archived team ${sessionId}; V2 has no runtime archive operation - the durable team state is the archive (interrupt_agent any member still mid-turn)\n`
284
+ : `archived team ${sessionId} and closed all members\n`;
285
+ const memberSummary = v2
286
+ ? `archived member ${flags.id} in team state; V2 has no runtime archive operation (interrupt_agent it if still mid-turn)\n`
287
+ : `archived member ${flags.id}\n`;
288
+ process.stdout.write(flags.id ? memberSummary : teamSummary);
243
289
  },
244
290
 
245
291
  async delete(cwd, flags) {
@@ -261,10 +307,18 @@ const handlers = {
261
307
  async status(cwd, flags) {
262
308
  const sessionId = requireFlag(flags, "team");
263
309
  const { team } = await loadTeam(cwd, sessionId);
264
- process.stdout.write(`Team ${team.teamName} [${team.status}] - leader: main session - ${team.members.length} member(s)\n`);
310
+ process.stdout.write(
311
+ `Team ${team.teamName} [${team.status}] - transport=${team.transport} - leader: main session - ${team.members.length} member(s)\n`,
312
+ );
265
313
  for (const m of team.members) {
266
- const thread = m.threadId ? ` thread=${m.threadId} link=${codexThreadLink(m.threadId)}` : "";
267
- process.stdout.write(` ${m.id} (${m.lens}) ${m.focus} -> ${m.deliverable || "(no deliverable)"} [${m.status}]${thread}${m.cwd ? ` cwd=${m.cwd}` : ""}\n`);
314
+ const endpoint = isMultiAgentV2(team)
315
+ ? m.agentPath
316
+ ? ` agent=${m.agentPath}`
317
+ : ""
318
+ : m.threadId
319
+ ? ` thread=${m.threadId} link=${codexThreadLink(m.threadId)}`
320
+ : "";
321
+ process.stdout.write(` ${m.id} (${m.lens}) ${m.focus} -> ${m.deliverable || "(no deliverable)"} [${m.status}]${endpoint}${m.cwd ? ` cwd=${m.cwd}` : ""}\n`);
268
322
  }
269
323
  if (isUnderstaffed(team)) {
270
324
  process.stdout.write(
@@ -266,6 +266,7 @@ Every `multi_agent_v1.spawn_agent` message is self-contained and starts with
266
266
  handoff. Use `fork_context: false` unless full history is truly
267
267
  required; paste only the context the child needs. Full-history forks can
268
268
  make the child continue old parent context instead of the delegated task.
269
+ If your tool list has a flat `spawn_agent` with a required `task_name` instead of `multi_agent_v1.*` (`multi_agent_v2`), rewrite: `fork_context: false` becomes `fork_turns: "none"`, `send_input` becomes `send_message`, finished agents end on their own (no `close_agent`; `followup_task` re-tasks, `interrupt_agent` stops), and `wait_agent` takes only `timeout_ms`, returning on any child mailbox activity.
269
270
 
270
271
  # TOML-backed subagent routing compatibility
271
272
  Treat TOML-backed role routing as **routing-unverified**. The
@@ -46,4 +46,6 @@ The full workflow may mention OpenCode-style orchestration examples. In Codex, t
46
46
  | Wait for background result | `multi_agent_v1.wait_agent(...)` |
47
47
  | Clean up finished worker | `multi_agent_v1.close_agent(...)` |
48
48
 
49
+ Flat `spawn_agent` requiring `task_name` instead (`multi_agent_v2`)? Rewrite rows: add `"task_name"`, `"fork_context":false` → `"fork_turns":"none"`, `wait_agent` takes only `timeout_ms`, no `close_agent` — finished agents end on their own.
50
+
49
51
  When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`.
@@ -161,7 +161,7 @@ Loop per goal. Cap at 5 cycles per goal. Cap identical same-criterion failures a
161
161
  2. Register atomic todos via `update_plan` — one ultra-granular step per action, `path: <action> for <criterion> - verify by <check>`. Call `update_plan` on every transition (start → `in_progress`, finish → `completed`); exactly one `in_progress`, mark completed immediately, never batch, never let the rendered plan lag behind reality.
162
162
  3. DELEGATE-IN-PARALLEL: dispatch every independent task in the wave at once via right-sized `multi_agent_v1.spawn_agent` workers (Delegation table). Each worker captures evidence failing-first: when the task touches EXISTING behavior, PIN it FIRST — a characterization test that asserts the current observable behavior and PASSES on the unchanged code, as rigorous as the new-behavior scenario (exact inputs, exact observable, exact assertion). Then RED through the cheapest faithful channel — a unit test where a seam exists, an integration/e2e test where the behavior lives in wiring, or the criterion's scenario captured failing when no test seam exists — failing for the RIGHT reason (no syntax/import error). A test that mirrors its implementation (mock-call assertions, pinned constants, cannot fail under plausible regression) is not evidence; use the scenario as the failing proof instead. Then the SMALLEST GREEN change; before GREEN work that depends on external review, PR, issue, or branch state, refresh current branch/PR/issue state, preserve existing ordering/policy, and separate compatibility detection from policy changes unless the goal explicitly asks to change policy. A GREEN far larger than the criterion implies means the proof was too coarse — instruct a split. Serialize only on a NAMED dependency.
163
163
  4. INTEGRATE + CRITICAL SELF-QA + GIT CHECKPOINT (EVERY WORKER RETURN): do NOT trust the worker's report. Read the diff yourself, re-run its tests, and run LSP diagnostics on the changed files. Treat "done" as a claim to disprove. If the diff drifts, the test is hollow, or evidence is missing, RESPAWN the worker with the specific failure context. Once the work unit is verified, use `git-master` before staging: inspect recent repository commits and touched-path history to infer commit language, Conventional Commit scope, message shape, and unit size. Stage only that unit's files and commit in the observed style; do not carry verified work forward into a later omnibus commit. If no git-tracked files changed or committing is unsafe, record the no-commit reason as evidence. Forward every finding/learning to subsequent workers.
164
- 5. EXECUTE-AS-SCENARIO: ACTUALLY run the Manual-QA scenario the criterion named (channel table above). Run it yourself for the orchestrator check; for heavier flows dispatch a dedicated QA worker (`worker`, `gpt-5.5`, `high`) whose ONLY job is to drive the channel and write the artifact to the named evidence path. If the scenario FAILS, respawn the implementing worker with the captured failure — do not hand-patch around it.
164
+ 5. EXECUTE-AS-SCENARIO: ACTUALLY run the Manual-QA scenario the criterion named (channel table above). Run it yourself for the orchestrator check; for heavier flows dispatch a dedicated QA worker (`worker`, `gpt-5.6-sol`, `xhigh`) whose ONLY job is to drive the channel and write the artifact to the named evidence path. If the scenario FAILS, respawn the implementing worker with the captured failure — do not hand-patch around it.
165
165
  6. CAPTURE: collect the observable artifact path: transcript, stdout, screenshot, assertion, status+body, diff, or parsed dump. No artifact written at the evidence path — not done; record BLOCKED and respawn QA.
166
166
  7. CLEAN (PAIRED, NEVER SKIP): tear down every runtime artifact step 5 spawned BEFORE recording — server PIDs (`kill`, verify `kill -0` fails), `tmux` sessions (`tmux kill-session -t ulw-qa-<criterion>`; confirm `tmux ls`), browser / Playwright contexts (`.close()`), containers (`docker rm -f`), bound ports (`lsof -i :<port>` empty), temp sockets / files / dirs (`rm -rf` the `mktemp` paths), QA-only env vars, AND `multi_agent_v1.close_agent` on every finished worker. Register each teardown as its own todo the moment the QA spawns the resource (scripts, tmux assets, browsers / agent-browser sessions, PIDs, ports) so none is forgotten. Embed a one-line cleanup receipt in the evidence string, e.g. `cleanup: killed 12345; tmux kill-session ulw-qa-foo; rm -rf /tmp/ulw.aB12cD; multi_agent_v1.close_agent w-3`. Missing receipt → record BLOCKED, not PASS.
167
167
  8. RECORD exactly one result:
@@ -67,6 +67,8 @@ Fan out read-only research before deciding. Every spawn names DELIVERABLE / SCOP
67
67
  multi_agent_v1.spawn_agent({"message":"TASK: act as an explorer. DELIVERABLE: ... SCOPE: ... VERIFY: ...","agent_type":"explorer","fork_context":false})
68
68
  ```
69
69
 
70
+ If your tool list has a flat `spawn_agent` with a required `task_name` instead of `multi_agent_v1.*` (`multi_agent_v2`), rewrite: add `"task_name":"<lowercase_digits_underscores>"`, replace `"fork_context":false` with `"fork_turns":"none"`, and `wait_agent` takes only `timeout_ms`, returning on any child mailbox activity (finished agents end on their own).
71
+
70
72
  Spawn every independent child for the current wave first. After the wave
71
73
  is launched, use `multi_agent_v1.wait_agent` for each child until each
72
74
  reaches terminal status. A timeout is not terminal status. Do not start dependent planning, drafting, approval-gate work, or final handoff until each child result is integrated or recorded as inconclusive.
@@ -85,7 +85,7 @@ Runs in parallel; ALL must APPROVE; surface results and wait for the user's expl
85
85
  - UNCLEAR: run the high-accuracy review AUTOMATICALLY before presenting (unless Classify=Trivial), then present a brief that LEADS with the derived approach and the adopted defaults; still wait for the user's explicit okay.
86
86
 
87
87
  ### High-accuracy review (dual review)
88
- The high-accuracy review is DUAL and both passes must return OKAY before handoff: (1) the native `momus` reviewer subagent, and (2) an independent Codex CLI review on gpt-5.5 at xhigh reasoning, run in a disposable isolated workspace and `CODEX_HOME` with the harness's normal approval and sandbox policy. Do not add flags that disable approvals or sandboxing. One round = exactly ONE `momus` + ONE independent review, dispatched together against the COMPLETE plan file (todos + TL;DR filled) - never a second `momus` while a round is in flight. After both verdicts return, fix every cited issue and resubmit both fresh until each approves. CLEAR: runs when the user opts in or `review_required: true`. UNCLEAR: runs automatically unless Classify=Trivial.
88
+ The high-accuracy review is DUAL and both passes must return OKAY before handoff: (1) the native `momus` reviewer subagent, and (2) an independent Codex CLI review on gpt-5.6-sol at xhigh reasoning, run in a disposable isolated workspace and `CODEX_HOME` with the harness's normal approval and sandbox policy. Do not add flags that disable approvals or sandboxing. Momus runs at Ultra and may take substantially longer than other agents. One round = exactly ONE `momus` + ONE independent review, dispatched together against the COMPLETE plan file (todos + TL;DR filled). Keep Momus in flight and wait for its terminal result: elapsed time alone never justifies cancelling, duplicating, replacing, or treating it as failed. After both verdicts return, fix every cited issue and resubmit both fresh until each approves. CLEAR: runs when the user opts in or `review_required: true`. UNCLEAR: runs automatically unless Classify=Trivial.
89
89
 
90
90
  The draft must record the native Momus session/result, the independent Codex CLI review command/result, and the fix/retry summary. Do not say "high-accuracy review completed" unless both receipts exist and both final verdicts are unconditional approval.
91
91
 
@@ -96,6 +96,8 @@ Every spawn starts with `TASK:`, then DELIVERABLE / SCOPE / VERIFY inside `messa
96
96
  multi_agent_v1.spawn_agent({"message":"TASK: act as an explorer. DELIVERABLE: ... SCOPE: ... VERIFY: ...","agent_type":"explorer","fork_context":false})
97
97
  ```
98
98
 
99
+ If your tool list has a flat `spawn_agent` with a required `task_name` instead of `multi_agent_v1.*` (`multi_agent_v2`), rewrite: add `"task_name":"<lowercase_digits_underscores>"`, replace `"fork_context":false` with `"fork_turns":"none"`, and `wait_agent` takes only `timeout_ms`, returning on any child mailbox activity (finished agents end on their own — skip the close step).
100
+
99
101
  Roles: `explorer`, `librarian`, `metis`, `momus`. Spawn long plan/reviewer agents in the background and poll with short waits; require the child to send `WORKING: <task> - <phase>` before long passes and `BLOCKED: <reason>` only when progress stops. A wait timeout only means no new mailbox update arrived; treat a running child as alive. Fall back only when the child completed without the deliverable, is ack-only after followup, explicitly `BLOCKED:`, or no longer running; then respawn a smaller `fork_context: false` job. Close each agent after integrating its result.
100
102
 
101
103
  ## Stop rules
@@ -15,11 +15,11 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
15
15
  | `task(subagent_type="oracle", ...)` for final verification | `multi_agent_v1.spawn_agent({"message":"TASK: act as a rigorous reviewer. ...","agent_type":"lazycodex-gate-reviewer","fork_context":false})` |
16
16
  | `task(category="...", ...)` for implementation or QA | `multi_agent_v1.spawn_agent({"message":"TASK: act as an implementation or QA worker. ...","fork_context":false})` |
17
17
  | `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
18
- | `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent`, `multi_agent_v1.send_input`, `multi_agent_v1.wait_agent`, and `multi_agent_v1.close_agent` |
18
+ | `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent` and `multi_agent_v1.wait_agent`; use `multi_agent_v1.send_input` and `multi_agent_v1.close_agent` only when exposed in the active tools list |
19
19
 
20
20
  Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` - pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
21
21
 
22
- On `multi_agent_v2` sessions the same `agent_type` applies (the OMO installer exposes it) with `fork_turns` instead of `fork_context`. If a code block below conflicts with this section, this section wins.
22
+ Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
23
23
 
24
24
  When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
25
25
 
@@ -69,7 +69,7 @@ Observation candidates and claim candidates travel back from workers as message
69
69
 
70
70
  ## Run the swarm as a cooperating team
71
71
 
72
- Saturation research defaults to teammode, not isolated fire-and-forget workers: a lead one worker surfaces almost always reshapes what another should search next. When your harness gives you real cooperating members — Codex: the `teammode` skill (`codex_app` threads); OpenCode: `team_mode` — run this swarm as a team. Fall back to the background-worker swarm below only when team mode is unavailable, or the axes are genuinely independent with no cross-pollination expected.
72
+ Saturation research defaults to teammode, not isolated fire-and-forget workers: a lead one worker surfaces almost always reshapes what another should search next. When your harness gives you real cooperating members — Codex: the `teammode` skill (MultiAgentV2 agents, or `codex_app` threads as its fallback); OpenCode: `team_mode` — run this swarm as a team. Fall back to the background-worker swarm below only when team mode is unavailable, or the axes are genuinely independent with no cross-pollination expected.
73
73
 
74
74
  - **One member per axis — by part, ownership, or perspective, never a job title.** Each Phase 0 axis is one member owning one concrete slice: a codebase part, a source territory, or a question lens. No two members share an angle. "Backend researcher" or "the web person" gives no real boundary and invites overlap — name what the member owns.
75
75
  - **Many teammates by default.** Prefer a larger roster, usually 5-8 teammates, whenever the axes can be made distinct. Add at least one skeptic or red-team perspective for hyperdebate/ultradebate: cross-critique claims, evidence quality, synthesis structure, and visual-report choices before they reach the final deliverable.
@@ -15,11 +15,11 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
15
15
  | `task(subagent_type="oracle", ...)` for final verification | `multi_agent_v1.spawn_agent({"message":"TASK: act as a rigorous reviewer. ...","agent_type":"lazycodex-gate-reviewer","fork_context":false})` |
16
16
  | `task(category="...", ...)` for implementation or QA | `multi_agent_v1.spawn_agent({"message":"TASK: act as an implementation or QA worker. ...","fork_context":false})` |
17
17
  | `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
18
- | `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent`, `multi_agent_v1.send_input`, `multi_agent_v1.wait_agent`, and `multi_agent_v1.close_agent` |
18
+ | `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent` and `multi_agent_v1.wait_agent`; use `multi_agent_v1.send_input` and `multi_agent_v1.close_agent` only when exposed in the active tools list |
19
19
 
20
20
  Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` - pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
21
21
 
22
- On `multi_agent_v2` sessions the same `agent_type` applies (the OMO installer exposes it) with `fork_turns` instead of `fork_context`. If a code block below conflicts with this section, this section wins.
22
+ Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
23
23
 
24
24
  When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
25
25
 
@@ -271,7 +271,7 @@ If any page fails, you are not done: fix it, re-capture the full set, re-dispatc
271
271
 
272
272
  Run this step IN ADDITION to Steps 1-4 when the original user task has a concrete visual target: "clone this site", "move this Figma design to code", "rebuild this screen", "make it look exactly like X", or "build this Imagen/Stitch/generated mockup and overview". For these tasks the normal dual-oracle is necessary but NOT sufficient. After it returns, run the following TWO additional MANDATORY verifications and LOOP until BOTH pass.
273
273
 
274
- 1. Pixel-perfect design-compare subagent (visual oracle). Dispatch a focused, read-only design-compare reviewer (recommend `gpt-5.5` with medium reasoning). It must crop/zoom BOTH the reference (target / Figma export / source-site screenshot / generated page snapshot) and the ACTUAL screenshot into matching regions and read them **pixel-by-pixel** - header, nav, each card, spacing, type ramp, color tokens - not at a glance. It must also compare the overview text or annotations against the rendered content and DOM text. Anchor every claim with the bundled tool:
274
+ 1. Pixel-perfect design-compare subagent (visual oracle). Dispatch a focused, read-only design-compare reviewer (recommend `gpt-5.6-sol` with xhigh reasoning). It must crop/zoom BOTH the reference (target / Figma export / source-site screenshot / generated page snapshot) and the ACTUAL screenshot into matching regions and read them **pixel-by-pixel** - header, nav, each card, spacing, type ramp, color tokens - not at a glance. It must also compare the overview text or annotations against the rendered content and DOM text. Anchor every claim with the bundled tool:
275
275
 
276
276
  ```
277
277
  node "$SKILL_DIR/scripts/visual-qa.mjs" image-diff <reference.png> <actual.png>