oh-my-opencode 4.16.1 → 4.16.2

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 (95) hide show
  1. package/dist/cli/index.js +13 -13
  2. package/dist/cli-node/index.js +13 -13
  3. package/dist/features/background-agent/parent-wake-dedupe.d.ts +1 -0
  4. package/dist/features/background-agent/parent-wake-flush-runner.d.ts +2 -0
  5. package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -0
  6. package/dist/features/builtin-commands/templates/refactor-sections/intro-and-analysis.d.ts +1 -1
  7. package/dist/features/builtin-commands/templates/remove-ai-slops.d.ts +1 -1
  8. package/dist/index.js +63 -32
  9. package/dist/skills/review-work/SKILL.md +3 -1
  10. package/dist/skills/start-work/SKILL.md +2 -0
  11. package/dist/skills/ulw-research/SKILL.md +3 -1
  12. package/package.json +13 -13
  13. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  14. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  15. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  16. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  17. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  18. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  19. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  20. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  21. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  22. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  23. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  24. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  25. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +1 -1
  26. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  27. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +1 -1
  29. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  30. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  31. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +1 -1
  32. package/packages/omo-codex/plugin/components/teammode/AGENTS.md +1 -1
  33. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  34. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +3 -3
  36. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  37. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/ultrawork/directive.md +1 -0
  39. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  40. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  41. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +1 -0
  42. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +2 -0
  43. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +2 -0
  44. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +1 -0
  45. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
  46. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +2 -0
  48. package/packages/omo-codex/plugin/components/ulw-loop/test/skill-contract.test.ts +1 -1
  49. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  70. package/packages/omo-codex/plugin/package.json +1 -1
  71. package/packages/omo-codex/plugin/scripts/auto-update.mjs +64 -17
  72. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +186 -20
  73. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +51 -3
  74. package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +33 -5
  75. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +1 -1
  76. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
  77. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +1 -1
  78. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
  79. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +1 -1
  80. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +1 -1
  81. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +3 -3
  82. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +1 -0
  83. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -0
  84. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +2 -0
  85. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +2 -0
  86. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +1 -1
  87. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +1 -1
  88. package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +1 -1
  89. package/packages/omo-codex/plugin/test/auto-update.test.mjs +35 -1
  90. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +275 -19
  91. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +33 -0
  92. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +4 -2
  93. package/packages/shared-skills/skills/review-work/SKILL.md +3 -1
  94. package/packages/shared-skills/skills/start-work/SKILL.md +2 -0
  95. package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -1
@@ -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
@@ -19,7 +19,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
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
 
@@ -19,7 +19,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
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
 
@@ -88,7 +88,7 @@ export function findSpawnAgentCallsWithoutForkContextFalse(content) {
88
88
  const regex = /spawn_agent\(([^)]*)\)/g;
89
89
  for (const match of content.matchAll(regex)) {
90
90
  const call = match[0];
91
- if (!/"fork_context"\s*:\s*false|fork_context:\s*false|fork_context=false/.test(call)) {
91
+ if (!/"fork_context"\s*:\s*false|fork_context:\s*false|fork_context=false|"fork_turns"\s*:\s*"none"|fork_turns:\s*"none"|fork_turns="none"/.test(call)) {
92
92
  missingForkContext.push(call);
93
93
  }
94
94
  }
@@ -2,9 +2,10 @@ import assert from "node:assert/strict";
2
2
  import { mkdir, mkdtemp, readFile, symlink, utimes, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
+ import { PassThrough } from "node:stream";
5
6
  import test from "node:test";
6
7
 
7
- import { resolveAutoUpdatePlan, resolveLazyCodexUpdatePlan, runAutoUpdateCheck } from "../scripts/auto-update.mjs";
8
+ import { readSessionModelFromStdin, resolveAutoUpdatePlan, resolveLazyCodexUpdatePlan, runAutoUpdateCheck } from "../scripts/auto-update.mjs";
8
9
  import { detectInstallFlow } from "../scripts/install-flow.mjs";
9
10
  import { resolveSpawnInvocation } from "../scripts/spawn-command.mjs";
10
11
 
@@ -20,6 +21,12 @@ function autoUpdateEnv(root, extra = {}) {
20
21
  };
21
22
  }
22
23
 
24
+ function stdinFrom(payload) {
25
+ const stream = new PassThrough();
26
+ stream.end(payload);
27
+ return stream;
28
+ }
29
+
23
30
  test("#given auto update is disabled #when resolving plan #then no command is scheduled", () => {
24
31
  const plan = resolveAutoUpdatePlan({
25
32
  env: { LAZYCODEX_AUTO_UPDATE_DISABLED: "1" },
@@ -31,6 +38,33 @@ test("#given auto update is disabled #when resolving plan #then no command is sc
31
38
  assert.equal(plan.reason, "disabled");
32
39
  });
33
40
 
41
+ test("#given SessionStart payload with model #when reading stdin #then returns the model", async () => {
42
+ const payload = JSON.stringify({
43
+ hook_event_name: "SessionStart",
44
+ session_id: "s-1",
45
+ cwd: "/tmp",
46
+ model: "gpt-5.6-terra",
47
+ permission_mode: "default",
48
+ source: "startup",
49
+ });
50
+
51
+ assert.equal(await readSessionModelFromStdin(stdinFrom(payload)), "gpt-5.6-terra");
52
+ });
53
+
54
+ test("#given empty or malformed stdin #when reading session model #then returns null", async () => {
55
+ assert.equal(await readSessionModelFromStdin(stdinFrom("")), null);
56
+ assert.equal(await readSessionModelFromStdin(stdinFrom("not json")), null);
57
+ assert.equal(await readSessionModelFromStdin(stdinFrom(JSON.stringify({ model: " " }))), null);
58
+ assert.equal(await readSessionModelFromStdin(stdinFrom(JSON.stringify({ session_id: "s-1" }))), null);
59
+ assert.equal(await readSessionModelFromStdin(null), null);
60
+ });
61
+
62
+ test("#given a TTY stdin #when reading session model #then returns null without waiting", async () => {
63
+ const stream = stdinFrom("{}");
64
+ stream.isTTY = true;
65
+ assert.equal(await readSessionModelFromStdin(stream), null);
66
+ });
67
+
34
68
  test("#given stale state #when resolving plan #then installer update command is scheduled", () => {
35
69
  const plan = resolveAutoUpdatePlan({
36
70
  env: { LAZYCODEX_CURRENT_VERSION: "1.0.0", LAZYCODEX_LATEST_VERSION: "1.0.1" },
@@ -493,7 +493,7 @@ test("#given multi_agent_v2 enabled #when forcing disable #then flips the flag t
493
493
  "",
494
494
  ].join("\n");
495
495
 
496
- const result = forceDisableMultiAgentV2(config);
496
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
497
497
 
498
498
  assert.match(result, /enabled = false/);
499
499
  assert.doesNotMatch(result, /enabled = true/);
@@ -510,7 +510,7 @@ test("#given no multi_agent_v2 section #when forcing disable #then appends a dis
510
510
  "",
511
511
  ].join("\n");
512
512
 
513
- const result = forceDisableMultiAgentV2(config);
513
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
514
514
 
515
515
  assert.match(result, /\[features\.multi_agent_v2\]\nenabled = false\n/);
516
516
  assert.match(result, /plugins = true/);
@@ -525,7 +525,7 @@ test("#given multi_agent_v2 section without enabled key #when forcing disable #t
525
525
  "",
526
526
  ].join("\n");
527
527
 
528
- const result = forceDisableMultiAgentV2(config);
528
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
529
529
 
530
530
  assert.match(result, /\[features\.multi_agent_v2\]\nenabled = false\n/);
531
531
  assert.match(result, /max_concurrent_threads_per_session = 10000/);
@@ -541,7 +541,7 @@ test("#given [features] boolean shorthand multi_agent_v2 = true #when forcing di
541
541
  "",
542
542
  ].join("\n");
543
543
 
544
- const result = forceDisableMultiAgentV2(config);
544
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
545
545
 
546
546
  assert.doesNotMatch(result, /^multi_agent_v2\s*=/m);
547
547
  assert.match(result, /\[features\.multi_agent_v2\]\nenabled = false\n/);
@@ -557,7 +557,7 @@ test("#given multi_agent_v2 enabled #when forcing disable #then annotates the ma
557
557
  "",
558
558
  ].join("\n");
559
559
 
560
- const result = forceDisableMultiAgentV2(config);
560
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
561
561
 
562
562
  assert.match(result, /openai\/codex#26753/);
563
563
  assert.match(result, /LAZYCODEX_CONFIG_MIGRATION_DISABLED=1/);
@@ -567,7 +567,7 @@ test("#given multi_agent_v2 enabled #when forcing disable #then annotates the ma
567
567
  test("#given no multi_agent_v2 section #when forcing disable #then annotates the appended section", () => {
568
568
  const config = ['model = "gpt-5.5"', ""].join("\n");
569
569
 
570
- const result = forceDisableMultiAgentV2(config);
570
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
571
571
 
572
572
  assert.match(result, /openai\/codex#26753/);
573
573
  assert.match(result, /^#[^\n]*\n(?:#[^\n]*\n)*\[features\.multi_agent_v2\]\nenabled = false\n/m);
@@ -576,8 +576,8 @@ test("#given no multi_agent_v2 section #when forcing disable #then annotates the
576
576
  test("#given an annotated managed section #when forcing disable runs again #then does not duplicate the comment", () => {
577
577
  const config = ['model = "gpt-5.5"', "", "[features.multi_agent_v2]", "enabled = true", ""].join("\n");
578
578
 
579
- const annotated = forceDisableMultiAgentV2(config);
580
- const rerun = forceDisableMultiAgentV2(`${annotated.replace("enabled = false", "enabled = true")}`);
579
+ const annotated = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
580
+ const rerun = forceDisableMultiAgentV2(`${annotated.replace("enabled = false", "enabled = true")}`, { multiAgentVersion: null });
581
581
 
582
582
  const markers = rerun.match(/openai\/codex#26753/g) ?? [];
583
583
  assert.equal(markers.length, 1);
@@ -594,7 +594,7 @@ test("#given [features] boolean shorthand multi_agent_v2 = false #when forcing d
594
594
  "",
595
595
  ].join("\n");
596
596
 
597
- const result = forceDisableMultiAgentV2(config);
597
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
598
598
  const parsed = parseTomlWithPython(result);
599
599
 
600
600
  assert.doesNotMatch(result, /^\s*multi_agent_v2\s*=/m);
@@ -614,7 +614,7 @@ test("#given multi_agent_v2 already disabled #when forcing disable #then returns
614
614
  "",
615
615
  ].join("\n");
616
616
 
617
- const result = forceDisableMultiAgentV2(config);
617
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
618
618
 
619
619
  assert.equal(result, config);
620
620
  });
@@ -756,7 +756,7 @@ test("#given global config with forced multi_agent_v2 #when full migration runs
756
756
  test("#given enabled = true with an inline comment #when forcing disable #then flips to false and preserves the comment", () => {
757
757
  const config = ["[features.multi_agent_v2]", "enabled = true # tuned by me", ""].join("\n");
758
758
 
759
- const result = forceDisableMultiAgentV2(config);
759
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
760
760
 
761
761
  assert.match(result, /^enabled = false # tuned by me$/m);
762
762
  assert.doesNotMatch(result, /enabled = true/);
@@ -767,7 +767,7 @@ test("#given enabled = true with an inline comment #when forcing disable #then f
767
767
  test("#given a section header with an inline comment #when forcing disable #then patches in place without duplicating the table", () => {
768
768
  const config = ["[features.multi_agent_v2] # pinned by me", "enabled = true", ""].join("\n");
769
769
 
770
- const result = forceDisableMultiAgentV2(config);
770
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
771
771
 
772
772
  assert.equal((result.match(/\[features\.multi_agent_v2\]/g) ?? []).length, 1);
773
773
  assert.match(result, /enabled = false/);
@@ -779,19 +779,19 @@ test("#given an already-guarded commented config #when re-running #then output i
779
779
  const configA = ["[features.multi_agent_v2]", "enabled = true # tuned by me", ""].join("\n");
780
780
  const configB = ["[features.multi_agent_v2] # pinned by me", "enabled = true", ""].join("\n");
781
781
 
782
- const firstA = forceDisableMultiAgentV2(configA);
783
- const rerunA = forceDisableMultiAgentV2(firstA);
782
+ const firstA = forceDisableMultiAgentV2(configA, { multiAgentVersion: null });
783
+ const rerunA = forceDisableMultiAgentV2(firstA, { multiAgentVersion: null });
784
784
  assert.equal(rerunA, firstA);
785
785
 
786
- const firstB = forceDisableMultiAgentV2(configB);
787
- const rerunB = forceDisableMultiAgentV2(firstB);
786
+ const firstB = forceDisableMultiAgentV2(configB, { multiAgentVersion: null });
787
+ const rerunB = forceDisableMultiAgentV2(firstB, { multiAgentVersion: null });
788
788
  assert.equal(rerunB, firstB);
789
789
  });
790
790
 
791
791
  test("#given user-disabled with an inline comment #when forcing disable #then returns config unchanged", () => {
792
792
  const config = ["[features.multi_agent_v2]", "enabled = false # I turned this off myself", ""].join("\n");
793
793
 
794
- const result = forceDisableMultiAgentV2(config);
794
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
795
795
 
796
796
  assert.equal(result, config);
797
797
  });
@@ -799,7 +799,7 @@ test("#given user-disabled with an inline comment #when forcing disable #then re
799
799
  test("#given [features] shorthand true with an inline comment #when forcing disable #then removes the shorthand and appends one disabled table", () => {
800
800
  const config = ["[features]", "plugins = true", "multi_agent_v2 = true # legacy", ""].join("\n");
801
801
 
802
- const result = forceDisableMultiAgentV2(config);
802
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
803
803
 
804
804
  assert.doesNotMatch(result, /^\s*multi_agent_v2\s*=/m);
805
805
  assert.equal((result.match(/\[features\.multi_agent_v2\]/g) ?? []).length, 1);
@@ -816,12 +816,268 @@ test("#given a following section header with an inline comment #when inserting e
816
816
  "",
817
817
  ].join("\n");
818
818
 
819
- const result = forceDisableMultiAgentV2(config);
819
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null });
820
820
 
821
821
  assert.match(result, /\[features\.multi_agent_v2\]\nenabled = false\n/);
822
822
  assert.match(result, /\[mcp_servers\.x\][^\n]*\nenabled = true/);
823
823
  });
824
824
 
825
+ test("#given gpt-5.6-terra with managed disable #when model catalog prefers v2 #then clears enabled=false and managed comments", () => {
826
+ const config = [
827
+ 'model = "gpt-5.6-terra"',
828
+ "",
829
+ "# Managed by LazyCodex: multi_agent_v2 is re-disabled on every Codex session start",
830
+ "# because enabling it fails every turn with HTTP 400 (openai/codex#26753).",
831
+ "# Opt out: LAZYCODEX_CONFIG_MIGRATION_DISABLED=1 (or OMO_CODEX_CONFIG_MIGRATION_DISABLED=1).",
832
+ "",
833
+ "[features.multi_agent_v2]",
834
+ "enabled = false",
835
+ "max_concurrent_threads_per_session = 1000",
836
+ "",
837
+ ].join("\n");
838
+
839
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v2" });
840
+
841
+ assert.doesNotMatch(result, /^\s*enabled\s*=\s*false/m);
842
+ assert.doesNotMatch(result, /openai\/codex#26753/);
843
+ assert.doesNotMatch(result, /Managed by LazyCodex: multi_agent_v2/);
844
+ assert.match(result, /\[features\.multi_agent_v2\]/);
845
+ assert.match(result, /max_concurrent_threads_per_session = 1000/);
846
+ });
847
+
848
+ test("#given gpt-5.6 v2 model with hide_spawn_agent_metadata=false #when clearing #then removes the stale metadata override", () => {
849
+ const config = [
850
+ 'model = "gpt-5.6-sol"',
851
+ "",
852
+ "[features.multi_agent_v2]",
853
+ "enabled = false",
854
+ "hide_spawn_agent_metadata = false",
855
+ "max_concurrent_threads_per_session = 1000",
856
+ "",
857
+ ].join("\n");
858
+
859
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v2" });
860
+
861
+ assert.doesNotMatch(result, /^\s*enabled\s*=\s*false/m);
862
+ assert.doesNotMatch(result, /^\s*hide_spawn_agent_metadata\s*=/m);
863
+ assert.match(result, /max_concurrent_threads_per_session = 1000/);
864
+ });
865
+
866
+ test("#given gpt-5.6 v2 model with hide_spawn_agent_metadata=true #when clearing #then leaves the default-matching value alone", () => {
867
+ const config = [
868
+ 'model = "gpt-5.6-sol"',
869
+ "",
870
+ "[features.multi_agent_v2]",
871
+ "enabled = false",
872
+ "hide_spawn_agent_metadata = true",
873
+ "max_concurrent_threads_per_session = 1000",
874
+ "",
875
+ ].join("\n");
876
+
877
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v2" });
878
+
879
+ assert.doesNotMatch(result, /^\s*enabled\s*=\s*false/m);
880
+ assert.match(result, /hide_spawn_agent_metadata = true/);
881
+ });
882
+
883
+ test("#given v1 model with hide_spawn_agent_metadata=false #when forcing disable #then keeps the metadata override", () => {
884
+ const config = [
885
+ 'model = "gpt-5.5"',
886
+ "",
887
+ "[features.multi_agent_v2]",
888
+ "hide_spawn_agent_metadata = false",
889
+ "max_concurrent_threads_per_session = 1000",
890
+ "",
891
+ ].join("\n");
892
+
893
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v1" });
894
+
895
+ assert.match(result, /hide_spawn_agent_metadata = false/);
896
+ assert.match(result, /enabled = false/);
897
+ });
898
+
899
+ test("#given gpt-5.6-sol with no multi_agent_v2 section #when model catalog prefers v2 #then does not append a disable", () => {
900
+ const config = ['model = "gpt-5.6-sol"', "", "[features]", "plugins = true", ""].join("\n");
901
+
902
+ const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v2" });
903
+
904
+ assert.doesNotMatch(result, /\[features\.multi_agent_v2\]/);
905
+ assert.doesNotMatch(result, /enabled = false/);
906
+ assert.match(result, /plugins = true/);
907
+ });
908
+
909
+ test("#given gpt-5.6-terra managed disable #when full migration sees models_cache v2 #then clears disable and drops agents.max_threads", async () => {
910
+ const root = await mkdtemp(join(tmpdir(), "lazycodex-multi-agent-v2-gpt56-"));
911
+ const codexHome = join(root, "codex-home");
912
+ await mkdir(codexHome, { recursive: true });
913
+ const configPath = join(codexHome, "config.toml");
914
+ await writeFile(
915
+ configPath,
916
+ [
917
+ 'model = "gpt-5.6-terra"',
918
+ 'model_reasoning_effort = "xhigh"',
919
+ "",
920
+ "# Managed by LazyCodex: multi_agent_v2 is re-disabled on every Codex session start",
921
+ "# because enabling it fails every turn with HTTP 400 (openai/codex#26753).",
922
+ "",
923
+ "[agents]",
924
+ "max_threads = 1000",
925
+ "max_depth = 2",
926
+ "",
927
+ "[features.multi_agent_v2]",
928
+ "enabled = false",
929
+ "max_concurrent_threads_per_session = 1000",
930
+ "",
931
+ ].join("\n"),
932
+ );
933
+ await writeFile(
934
+ join(codexHome, "models_cache.json"),
935
+ JSON.stringify({
936
+ models: [{ slug: "gpt-5.6-terra", multi_agent_version: "v2" }],
937
+ }),
938
+ );
939
+
940
+ const result = await migrateCodexConfig({
941
+ env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
942
+ cwd: root,
943
+ });
944
+
945
+ assert.deepEqual(result.changed, [configPath]);
946
+ const content = await readFile(configPath, "utf8");
947
+ assert.doesNotMatch(content, /^\s*enabled\s*=\s*false/m);
948
+ assert.doesNotMatch(content, /openai\/codex#26753/);
949
+ assert.doesNotMatch(content, /^\s*max_threads\s*=/m);
950
+ assert.match(content, /max_depth = 2/);
951
+ assert.match(content, /max_concurrent_threads_per_session = 1000/);
952
+ });
953
+
954
+ test("#given config default gpt-5.5 #when SessionStart model is gpt-5.6-terra #then prefers session model and clears disable", () => {
955
+ const config = [
956
+ 'model = "gpt-5.5"',
957
+ "",
958
+ "[features.multi_agent_v2]",
959
+ "enabled = false",
960
+ "max_concurrent_threads_per_session = 1000",
961
+ "",
962
+ ].join("\n");
963
+
964
+ const result = forceDisableMultiAgentV2(config, {
965
+ sessionModel: "gpt-5.6-terra",
966
+ multiAgentVersion: "v2",
967
+ });
968
+
969
+ assert.doesNotMatch(result, /^\s*enabled\s*=\s*false/m);
970
+ assert.match(result, /max_concurrent_threads_per_session = 1000/);
971
+ });
972
+
973
+ test("#given SessionStart without model #when requireSessionModel is set #then skips legacy force-disable", () => {
974
+ const config = ['model = "gpt-5.5"', "", "[features]", "plugins = true", ""].join("\n");
975
+
976
+ const result = forceDisableMultiAgentV2(config, {
977
+ multiAgentVersion: null,
978
+ requireSessionModel: true,
979
+ sessionModel: null,
980
+ });
981
+
982
+ assert.equal(result, config);
983
+ assert.doesNotMatch(result, /\[features\.multi_agent_v2\]/);
984
+ });
985
+
986
+ test("#given legacy [features] shorthand #when requireSessionModel skips force-disable #then still removes the shorthand", () => {
987
+ const config = ['model = "gpt-5.5"', "", "[features]", "plugins = true", "multi_agent_v2 = true", ""].join("\n");
988
+
989
+ const result = forceDisableMultiAgentV2(config, {
990
+ multiAgentVersion: null,
991
+ requireSessionModel: true,
992
+ sessionModel: null,
993
+ });
994
+
995
+ assert.doesNotMatch(result, /^\s*multi_agent_v2\s*=/m);
996
+ assert.match(result, /plugins = true/);
997
+ assert.doesNotMatch(result, /\[features\.multi_agent_v2\]/);
998
+ });
999
+
1000
+ test("#given legacy [features] shorthand #when session model has no catalog entry #then still removes the shorthand", () => {
1001
+ const config = ['model = "gpt-5.5"', "", "[features]", "multi_agent_v2 = false", ""].join("\n");
1002
+
1003
+ const result = forceDisableMultiAgentV2(config, {
1004
+ multiAgentVersion: null,
1005
+ sessionModel: "gpt-5.7-nova",
1006
+ });
1007
+
1008
+ assert.doesNotMatch(result, /^\s*multi_agent_v2\s*=/m);
1009
+ assert.doesNotMatch(result, /\[features\.multi_agent_v2\]/);
1010
+ });
1011
+
1012
+ test("#given legacy shorthand and no session model on hook path #when full migration runs #then output stays valid TOML", async () => {
1013
+ const root = await mkdtemp(join(tmpdir(), "lazycodex-multi-agent-v2-shorthand-hook-"));
1014
+ const codexHome = join(root, "codex-home");
1015
+ await mkdir(codexHome, { recursive: true });
1016
+ const configPath = join(codexHome, "config.toml");
1017
+ await writeFile(
1018
+ configPath,
1019
+ ['model = "gpt-5.5"', 'model_reasoning_effort = "high"', "", "[features]", "plugins = true", "multi_agent_v2 = true", ""].join("\n"),
1020
+ );
1021
+
1022
+ await migrateCodexConfig({
1023
+ env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
1024
+ cwd: root,
1025
+ sessionModel: null,
1026
+ requireSessionModel: true,
1027
+ });
1028
+
1029
+ const content = await readFile(configPath, "utf8");
1030
+ const parsed = parseTomlWithPython(content);
1031
+ assert.doesNotMatch(content, /^\s*multi_agent_v2\s*=\s*(?:true|false)/m);
1032
+ assert.equal(parsed.features.plugins, true);
1033
+ assert.equal(parsed.features.multi_agent_v2.max_concurrent_threads_per_session, 1000);
1034
+ assert.equal("enabled" in parsed.features.multi_agent_v2, false);
1035
+ });
1036
+
1037
+ test("#given config default gpt-5.5 #when full migration gets SessionStart gpt-5.6-terra #then clears disable using session model", async () => {
1038
+ const root = await mkdtemp(join(tmpdir(), "lazycodex-multi-agent-v2-session-model-"));
1039
+ const codexHome = join(root, "codex-home");
1040
+ await mkdir(codexHome, { recursive: true });
1041
+ const configPath = join(codexHome, "config.toml");
1042
+ await writeFile(
1043
+ configPath,
1044
+ [
1045
+ 'model = "gpt-5.5"',
1046
+ 'model_reasoning_effort = "high"',
1047
+ "",
1048
+ "[agents]",
1049
+ "max_threads = 1000",
1050
+ "",
1051
+ "[features.multi_agent_v2]",
1052
+ "enabled = false",
1053
+ "max_concurrent_threads_per_session = 1000",
1054
+ "",
1055
+ ].join("\n"),
1056
+ );
1057
+ await writeFile(
1058
+ join(codexHome, "models_cache.json"),
1059
+ JSON.stringify({
1060
+ models: [
1061
+ { slug: "gpt-5.5", multi_agent_version: "v1" },
1062
+ { slug: "gpt-5.6-terra", multi_agent_version: "v2" },
1063
+ ],
1064
+ }),
1065
+ );
1066
+
1067
+ const result = await migrateCodexConfig({
1068
+ env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
1069
+ cwd: root,
1070
+ sessionModel: "gpt-5.6-terra",
1071
+ requireSessionModel: true,
1072
+ });
1073
+
1074
+ assert.deepEqual(result.changed, [configPath]);
1075
+ const content = await readFile(configPath, "utf8");
1076
+ assert.doesNotMatch(content, /^\s*enabled\s*=\s*false/m);
1077
+ assert.doesNotMatch(content, /^\s*max_threads\s*=/m);
1078
+ assert.match(content, /max_concurrent_threads_per_session = 1000/);
1079
+ });
1080
+
825
1081
  async function canCreateSymlink(type) {
826
1082
  const root = await mkdtemp(join(tmpdir(), "lazycodex-symlink-capability-"));
827
1083
  const target = join(root, "target");
@@ -42,3 +42,36 @@ test("#given SessionStart config migration sees a low subagent cap #when migrati
42
42
  assert.match(content, /max_concurrent_threads_per_session = 1000/);
43
43
  assert.doesNotMatch(content, /^max_threads\s*=\s*6$/m);
44
44
  });
45
+
46
+ test("#given gpt-5.6 session model with no models_cache #when migrating #then does not write agents.max_threads", async () => {
47
+ const root = await mkdtemp(join(tmpdir(), "lazycodex-subagent-limit-gpt56-nocache-"));
48
+ const configPath = join(root, "config.toml");
49
+ await writeFile(
50
+ configPath,
51
+ [
52
+ 'model = "gpt-5.6-sol"',
53
+ 'model_reasoning_effort = "xhigh"',
54
+ "",
55
+ "[agents]",
56
+ "max_threads = 1000",
57
+ "max_depth = 4",
58
+ "",
59
+ "[features.multi_agent_v2]",
60
+ "enabled = false",
61
+ "max_concurrent_threads_per_session = 1000",
62
+ "",
63
+ ].join("\n"),
64
+ );
65
+
66
+ const result = await migrateConfigFile(configPath, {
67
+ env: { CODEX_HOME: root },
68
+ sessionModel: "gpt-5.6-sol",
69
+ });
70
+
71
+ const content = await readFile(configPath, "utf8");
72
+ assert.equal(result.changed, true);
73
+ assert.doesNotMatch(content, /^\s*max_threads\s*=/m);
74
+ assert.doesNotMatch(content, /^\s*enabled\s*=\s*false/m);
75
+ assert.match(content, /max_depth = 4/);
76
+ assert.match(content, /max_concurrent_threads_per_session = 1000/);
77
+ });
@@ -29,7 +29,7 @@ function patternFromParts(parts, flags) {
29
29
  }
30
30
 
31
31
  const multiAgentV2RoleGuidance =
32
- "On `multi_agent_v2` sessions the same `agent_type` applies (the OMO installer exposes it) with `fork_turns` instead of `fork_context`.";
32
+ "If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example:";
33
33
  const loadSkillsGuidance =
34
34
  "When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`.";
35
35
 
@@ -148,7 +148,9 @@ call_omo_agent(subagent_type="explore", prompt="inspect")
148
148
  assert.match(adapted, /fork_context":false/);
149
149
  assert.match(adapted, /"agent_type":"explorer"/);
150
150
  assert.match(adapted, /multi_agent_v1\.wait_agent/);
151
- assert.doesNotMatch(adapted, /task_name/);
151
+ assert.match(adapted, /"task_name":"<lowercase_digits_underscores>"/);
152
+ assert.match(adapted, /fork_turns/);
153
+ assert.doesNotMatch(adapted, /\| `spawn_agent\({"task_name"/);
152
154
  assert.doesNotMatch(adapted, /Obsolete generated compatibility prose/);
153
155
  });
154
156
 
@@ -16,7 +16,9 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
16
16
  | `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
17
17
  | `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
18
 
19
- 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. On `multi_agent_v2` sessions the same `agent_type` applies (the OMO installer exposes it) with `fork_turns` instead of `fork_context`. 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.
19
+ 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.
20
+
21
+ 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.
20
22
 
21
23
  For work likely to exceed one wait cycle, require the child to send `WORKING: <task> - <current phase>` before long passes and `BLOCKED: <reason>` only when progress stops. A `multi_agent_v1.wait_agent` timeout only means no new mailbox update arrived. Treat a running child as alive. Fallback only when the child is completed without the deliverable, ack-only after followup, explicitly `BLOCKED:`, or no longer running.
22
24
 
@@ -20,6 +20,8 @@ Translate any OpenCode-only tool name in an inherited example to its Codex equiv
20
20
 
21
21
  When translating `load_skills=[...]`, name the skills inside the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
22
22
 
23
+ 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.
24
+
23
25
  ## Codex Subagent Reliability
24
26
 
25
27
  Every `multi_agent_v1.spawn_agent` message is a self-contained executable assignment: `TASK: <imperative assignment>`, then `DELIVERABLE`, `SCOPE`, and `VERIFY`, with role instructions inside `message`. Use `fork_context: false` unless full history is truly required; paste only the context the child needs.
@@ -17,7 +17,9 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
17
17
  | `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
18
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` |
19
19
 
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. On `multi_agent_v2` sessions the same `agent_type` applies (the OMO installer exposes it) with `fork_turns` instead of `fork_context`. 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.
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
+
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.
21
23
 
22
24
  For work likely to exceed one wait cycle, require the child to send `WORKING: <task> - <current phase>` before long passes and `BLOCKED: <reason>` only when progress stops. A `multi_agent_v1.wait_agent` timeout only means no new mailbox update arrived. Treat a running child as alive. Fallback only when the child is completed without the deliverable, ack-only after followup, explicitly `BLOCKED:`, or no longer running.
23
25