oh-my-opencode 4.4.0 → 4.5.0

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 (218) hide show
  1. package/.agents/command/get-unpublished-changes.md +148 -0
  2. package/.agents/command/omomomo.md +37 -0
  3. package/.agents/command/publish.md +376 -0
  4. package/.agents/command/remove-deadcode.md +221 -0
  5. package/.agents/command/security-research.md +16 -0
  6. package/.agents/skills/get-unpublished-changes/SKILL.md +24 -0
  7. package/.agents/skills/github-triage/SKILL.md +587 -0
  8. package/.agents/skills/github-triage/scripts/gh_fetch.py +398 -0
  9. package/.agents/skills/hyperplan/SKILL.md +450 -0
  10. package/.agents/skills/omomomo/SKILL.md +36 -0
  11. package/.agents/skills/pre-publish-review/SKILL.md +407 -0
  12. package/.agents/skills/publish/SKILL.md +428 -0
  13. package/.agents/skills/remove-deadcode/SKILL.md +216 -0
  14. package/.agents/skills/security-research/SKILL.md +204 -0
  15. package/.agents/skills/work-with-pr/SKILL.md +360 -0
  16. package/.agents/skills/work-with-pr-workspace/evals/evals.json +76 -0
  17. package/.agents/skills/work-with-pr-workspace/iteration-1/benchmark.json +138 -0
  18. package/.agents/skills/work-with-pr-workspace/iteration-1/benchmark.md +42 -0
  19. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/eval_metadata.json +57 -0
  20. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/grading.json +15 -0
  21. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/code-changes.md +454 -0
  22. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/execution-plan.md +136 -0
  23. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/pr-description.md +47 -0
  24. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/verification-strategy.md +163 -0
  25. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/timing.json +1 -0
  26. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/grading.json +15 -0
  27. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/code-changes.md +615 -0
  28. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/execution-plan.md +99 -0
  29. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/pr-description.md +50 -0
  30. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/verification-strategy.md +111 -0
  31. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/timing.json +1 -0
  32. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/eval_metadata.json +37 -0
  33. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/grading.json +11 -0
  34. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/code-changes.md +205 -0
  35. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/execution-plan.md +78 -0
  36. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/pr-description.md +42 -0
  37. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/verification-strategy.md +87 -0
  38. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/timing.json +1 -0
  39. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/grading.json +11 -0
  40. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/code-changes.md +334 -0
  41. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/execution-plan.md +86 -0
  42. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/pr-description.md +23 -0
  43. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/verification-strategy.md +119 -0
  44. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/timing.json +1 -0
  45. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/eval_metadata.json +32 -0
  46. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/grading.json +10 -0
  47. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/code-changes.md +221 -0
  48. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/execution-plan.md +104 -0
  49. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/pr-description.md +41 -0
  50. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/verification-strategy.md +84 -0
  51. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/timing.json +1 -0
  52. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/grading.json +10 -0
  53. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/code-changes.md +342 -0
  54. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/execution-plan.md +131 -0
  55. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/pr-description.md +39 -0
  56. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/verification-strategy.md +128 -0
  57. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/timing.json +1 -0
  58. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/eval_metadata.json +32 -0
  59. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/grading.json +10 -0
  60. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/code-changes.md +143 -0
  61. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/execution-plan.md +82 -0
  62. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/pr-description.md +51 -0
  63. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/verification-strategy.md +69 -0
  64. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/timing.json +1 -0
  65. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/grading.json +10 -0
  66. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/code-changes.md +252 -0
  67. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/execution-plan.md +83 -0
  68. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/pr-description.md +33 -0
  69. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/verification-strategy.md +101 -0
  70. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/timing.json +1 -0
  71. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/eval_metadata.json +32 -0
  72. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/grading.json +10 -0
  73. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/code-changes.md +387 -0
  74. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +112 -0
  75. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/pr-description.md +51 -0
  76. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/verification-strategy.md +75 -0
  77. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/timing.json +1 -0
  78. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/grading.json +10 -0
  79. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/code-changes.md +529 -0
  80. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/execution-plan.md +127 -0
  81. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/pr-description.md +42 -0
  82. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/verification-strategy.md +120 -0
  83. package/.agents/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/timing.json +1 -0
  84. package/.agents/skills/work-with-pr-workspace/iteration-1/review.html +1326 -0
  85. package/.opencode/command/get-unpublished-changes.md +148 -0
  86. package/.opencode/command/omomomo.md +37 -0
  87. package/.opencode/command/publish.md +376 -0
  88. package/.opencode/command/remove-deadcode.md +221 -0
  89. package/.opencode/command/security-research.md +16 -0
  90. package/.opencode/skills/github-triage/SKILL.md +587 -0
  91. package/.opencode/skills/github-triage/scripts/gh_fetch.py +398 -0
  92. package/.opencode/skills/hyperplan/SKILL.md +450 -0
  93. package/.opencode/skills/pre-publish-review/SKILL.md +407 -0
  94. package/.opencode/skills/work-with-pr/SKILL.md +360 -0
  95. package/.opencode/skills/work-with-pr-workspace/evals/evals.json +76 -0
  96. package/.opencode/skills/work-with-pr-workspace/iteration-1/benchmark.json +138 -0
  97. package/.opencode/skills/work-with-pr-workspace/iteration-1/benchmark.md +42 -0
  98. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/eval_metadata.json +57 -0
  99. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/grading.json +15 -0
  100. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/code-changes.md +454 -0
  101. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/execution-plan.md +136 -0
  102. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/pr-description.md +47 -0
  103. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/outputs/verification-strategy.md +163 -0
  104. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/with_skill/timing.json +1 -0
  105. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/grading.json +15 -0
  106. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/code-changes.md +615 -0
  107. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/execution-plan.md +99 -0
  108. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/pr-description.md +50 -0
  109. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/outputs/verification-strategy.md +111 -0
  110. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-1/without_skill/timing.json +1 -0
  111. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/eval_metadata.json +37 -0
  112. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/grading.json +11 -0
  113. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/code-changes.md +205 -0
  114. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/execution-plan.md +78 -0
  115. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/pr-description.md +42 -0
  116. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/outputs/verification-strategy.md +87 -0
  117. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/with_skill/timing.json +1 -0
  118. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/grading.json +11 -0
  119. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/code-changes.md +334 -0
  120. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/execution-plan.md +86 -0
  121. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/pr-description.md +23 -0
  122. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/outputs/verification-strategy.md +119 -0
  123. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-2/without_skill/timing.json +1 -0
  124. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/eval_metadata.json +32 -0
  125. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/grading.json +10 -0
  126. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/code-changes.md +221 -0
  127. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/execution-plan.md +104 -0
  128. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/pr-description.md +41 -0
  129. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/outputs/verification-strategy.md +84 -0
  130. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/with_skill/timing.json +1 -0
  131. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/grading.json +10 -0
  132. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/code-changes.md +342 -0
  133. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/execution-plan.md +131 -0
  134. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/pr-description.md +39 -0
  135. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/outputs/verification-strategy.md +128 -0
  136. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-3/without_skill/timing.json +1 -0
  137. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/eval_metadata.json +32 -0
  138. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/grading.json +10 -0
  139. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/code-changes.md +143 -0
  140. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/execution-plan.md +82 -0
  141. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/pr-description.md +51 -0
  142. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/outputs/verification-strategy.md +69 -0
  143. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/with_skill/timing.json +1 -0
  144. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/grading.json +10 -0
  145. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/code-changes.md +252 -0
  146. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/execution-plan.md +83 -0
  147. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/pr-description.md +33 -0
  148. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/outputs/verification-strategy.md +101 -0
  149. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-4/without_skill/timing.json +1 -0
  150. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/eval_metadata.json +32 -0
  151. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/grading.json +10 -0
  152. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/code-changes.md +387 -0
  153. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/execution-plan.md +112 -0
  154. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/pr-description.md +51 -0
  155. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/outputs/verification-strategy.md +75 -0
  156. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/with_skill/timing.json +1 -0
  157. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/grading.json +10 -0
  158. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/code-changes.md +529 -0
  159. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/execution-plan.md +127 -0
  160. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/pr-description.md +42 -0
  161. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/outputs/verification-strategy.md +120 -0
  162. package/.opencode/skills/work-with-pr-workspace/iteration-1/eval-5/without_skill/timing.json +1 -0
  163. package/.opencode/skills/work-with-pr-workspace/iteration-1/review.html +1326 -0
  164. package/README.ja.md +1 -1
  165. package/README.ko.md +1 -1
  166. package/README.md +1 -1
  167. package/README.ru.md +1 -1
  168. package/README.zh-cn.md +1 -1
  169. package/dist/agents/atlas/agent.d.ts +6 -6
  170. package/dist/agents/prometheus/gemini.d.ts +0 -11
  171. package/dist/agents/prometheus/gpt.d.ts +0 -10
  172. package/dist/agents/prometheus/system-prompt.d.ts +2 -20
  173. package/dist/agents/types.d.ts +1 -16
  174. package/dist/cli/index.js +50 -17
  175. package/dist/config/schema/agent-names.d.ts +3 -3
  176. package/dist/config/schema/agent-overrides.d.ts +208 -208
  177. package/dist/config/schema/categories.d.ts +28 -28
  178. package/dist/config/schema/fallback-models.d.ts +20 -20
  179. package/dist/config/schema/oh-my-opencode-config.d.ts +208 -208
  180. package/dist/features/background-agent/parent-wake-notifier.d.ts +8 -1
  181. package/dist/help/schema/acp.d.ts +95 -0
  182. package/dist/help/schema/doctor.d.ts +147 -0
  183. package/dist/help/schema/sandbox.d.ts +74 -0
  184. package/dist/help/schema/status.d.ts +139 -0
  185. package/dist/hooks/keyword-detector/analyze/default.d.ts +1 -1
  186. package/dist/hooks/keyword-detector/hyperplan/default.d.ts +1 -1
  187. package/dist/hooks/keyword-detector/search/default.d.ts +1 -1
  188. package/dist/hooks/keyword-detector/team/default.d.ts +2 -7
  189. package/dist/hooks/keyword-detector/ultrawork/default.d.ts +1 -9
  190. package/dist/hooks/keyword-detector/ultrawork/gemini.d.ts +1 -16
  191. package/dist/hooks/keyword-detector/ultrawork/gpt.d.ts +1 -10
  192. package/dist/hooks/keyword-detector/ultrawork/planner.d.ts +1 -5
  193. package/dist/hooks/ralph-loop/no-progress-turn-detector.d.ts +7 -0
  194. package/dist/hooks/ralph-loop/pending-verification-handler.d.ts +1 -0
  195. package/dist/hooks/ralph-loop/types.d.ts +1 -0
  196. package/dist/hooks/runtime-fallback/error-classifier.d.ts +1 -0
  197. package/dist/index.js +51910 -50310
  198. package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +1 -0
  199. package/dist/shared/prompt-async-gate/types.d.ts +4 -3
  200. package/package.json +19 -13
  201. package/dist/agents/atlas/default-prompt-sections.d.ts +0 -6
  202. package/dist/agents/atlas/default.d.ts +0 -2
  203. package/dist/agents/atlas/gemini-prompt-sections.d.ts +0 -6
  204. package/dist/agents/atlas/gemini.d.ts +0 -2
  205. package/dist/agents/atlas/gpt-prompt-sections.d.ts +0 -6
  206. package/dist/agents/atlas/gpt.d.ts +0 -2
  207. package/dist/agents/atlas/kimi-prompt-sections.d.ts +0 -6
  208. package/dist/agents/atlas/kimi.d.ts +0 -2
  209. package/dist/agents/atlas/opus-4-7-prompt-sections.d.ts +0 -6
  210. package/dist/agents/atlas/opus-4-7.d.ts +0 -2
  211. package/dist/agents/atlas/shared-prompt.d.ts +0 -9
  212. package/dist/agents/prometheus/behavioral-summary.d.ts +0 -6
  213. package/dist/agents/prometheus/high-accuracy-mode.d.ts +0 -6
  214. package/dist/agents/prometheus/identity-constraints.d.ts +0 -7
  215. package/dist/agents/prometheus/interview-mode.d.ts +0 -7
  216. package/dist/agents/prometheus/plan-generation.d.ts +0 -7
  217. package/dist/agents/prometheus/plan-template.d.ts +0 -7
  218. package/dist/agents/prometheus/spec-driven-mode.d.ts +0 -7
@@ -0,0 +1,615 @@
1
+ # Code Changes: `max_background_agents` Config Option
2
+
3
+ ## 1. Schema Change
4
+
5
+ **File:** `src/config/schema/background-task.ts`
6
+
7
+ ```typescript
8
+ import { z } from "zod"
9
+
10
+ export const BackgroundTaskConfigSchema = z.object({
11
+ defaultConcurrency: z.number().min(1).optional(),
12
+ providerConcurrency: z.record(z.string(), z.number().min(0)).optional(),
13
+ modelConcurrency: z.record(z.string(), z.number().min(0)).optional(),
14
+ maxDepth: z.number().int().min(1).optional(),
15
+ maxDescendants: z.number().int().min(1).optional(),
16
+ /** Maximum number of background agents that can run simultaneously across all models/providers (default: no global limit, only per-model limits apply) */
17
+ maxBackgroundAgents: z.number().int().min(1).optional(),
18
+ /** Stale timeout in milliseconds - interrupt tasks with no activity for this duration (default: 180000 = 3 minutes, minimum: 60000 = 1 minute) */
19
+ staleTimeoutMs: z.number().min(60000).optional(),
20
+ /** Timeout for tasks that never received any progress update, falling back to startedAt (default: 1800000 = 30 minutes, minimum: 60000 = 1 minute) */
21
+ messageStalenessTimeoutMs: z.number().min(60000).optional(),
22
+ syncPollTimeoutMs: z.number().min(60000).optional(),
23
+ })
24
+
25
+ export type BackgroundTaskConfig = z.infer<typeof BackgroundTaskConfigSchema>
26
+ ```
27
+
28
+ **What changed:** Added `maxBackgroundAgents` field after `maxDescendants` (grouped with other limit fields). Uses `z.number().int().min(1).optional()` matching the pattern of `maxDepth` and `maxDescendants`.
29
+
30
+ ---
31
+
32
+ ## 2. ConcurrencyManager Changes
33
+
34
+ **File:** `src/features/background-agent/concurrency.ts`
35
+
36
+ ```typescript
37
+ import type { BackgroundTaskConfig } from "../../config/schema"
38
+
39
+ /**
40
+ * Queue entry with settled-flag pattern to prevent double-resolution.
41
+ *
42
+ * The settled flag ensures that cancelWaiters() doesn't reject
43
+ * an entry that was already resolved by release().
44
+ */
45
+ interface QueueEntry {
46
+ resolve: () => void
47
+ rawReject: (error: Error) => void
48
+ settled: boolean
49
+ }
50
+
51
+ export class ConcurrencyManager {
52
+ private config?: BackgroundTaskConfig
53
+ private counts: Map<string, number> = new Map()
54
+ private queues: Map<string, QueueEntry[]> = new Map()
55
+ private globalCount = 0
56
+ private globalQueue: QueueEntry[] = []
57
+
58
+ constructor(config?: BackgroundTaskConfig) {
59
+ this.config = config
60
+ }
61
+
62
+ getGlobalLimit(): number {
63
+ const limit = this.config?.maxBackgroundAgents
64
+ if (limit === undefined) {
65
+ return Infinity
66
+ }
67
+ return limit
68
+ }
69
+
70
+ getConcurrencyLimit(model: string): number {
71
+ const modelLimit = this.config?.modelConcurrency?.[model]
72
+ if (modelLimit !== undefined) {
73
+ return modelLimit === 0 ? Infinity : modelLimit
74
+ }
75
+ const provider = model.split('/')[0]
76
+ const providerLimit = this.config?.providerConcurrency?.[provider]
77
+ if (providerLimit !== undefined) {
78
+ return providerLimit === 0 ? Infinity : providerLimit
79
+ }
80
+ const defaultLimit = this.config?.defaultConcurrency
81
+ if (defaultLimit !== undefined) {
82
+ return defaultLimit === 0 ? Infinity : defaultLimit
83
+ }
84
+ return 5
85
+ }
86
+
87
+ async acquire(model: string): Promise<void> {
88
+ const perModelLimit = this.getConcurrencyLimit(model)
89
+ const globalLimit = this.getGlobalLimit()
90
+
91
+ // Fast path: both limits have capacity
92
+ if (perModelLimit === Infinity && globalLimit === Infinity) {
93
+ return
94
+ }
95
+
96
+ const currentPerModel = this.counts.get(model) ?? 0
97
+
98
+ if (currentPerModel < perModelLimit && this.globalCount < globalLimit) {
99
+ this.counts.set(model, currentPerModel + 1)
100
+ this.globalCount++
101
+ return
102
+ }
103
+
104
+ return new Promise<void>((resolve, reject) => {
105
+ const entry: QueueEntry = {
106
+ resolve: () => {
107
+ if (entry.settled) return
108
+ entry.settled = true
109
+ resolve()
110
+ },
111
+ rawReject: reject,
112
+ settled: false,
113
+ }
114
+
115
+ // Queue on whichever limit is blocking
116
+ if (currentPerModel >= perModelLimit) {
117
+ const queue = this.queues.get(model) ?? []
118
+ queue.push(entry)
119
+ this.queues.set(model, queue)
120
+ } else {
121
+ this.globalQueue.push(entry)
122
+ }
123
+ })
124
+ }
125
+
126
+ release(model: string): void {
127
+ const perModelLimit = this.getConcurrencyLimit(model)
128
+ const globalLimit = this.getGlobalLimit()
129
+
130
+ if (perModelLimit === Infinity && globalLimit === Infinity) {
131
+ return
132
+ }
133
+
134
+ // Try per-model handoff first
135
+ const queue = this.queues.get(model)
136
+ while (queue && queue.length > 0) {
137
+ const next = queue.shift()!
138
+ if (!next.settled) {
139
+ // Hand off the slot to this waiter (counts stay the same)
140
+ next.resolve()
141
+ return
142
+ }
143
+ }
144
+
145
+ // No per-model handoff - decrement per-model count
146
+ const current = this.counts.get(model) ?? 0
147
+ if (current > 0) {
148
+ this.counts.set(model, current - 1)
149
+ }
150
+
151
+ // Try global handoff
152
+ while (this.globalQueue.length > 0) {
153
+ const next = this.globalQueue.shift()!
154
+ if (!next.settled) {
155
+ // Hand off the global slot - but the waiter still needs a per-model slot
156
+ // Since they were queued on global, their per-model had capacity
157
+ // Re-acquire per-model count for them
158
+ const waiterModel = this.findModelForGlobalWaiter()
159
+ if (waiterModel) {
160
+ const waiterCount = this.counts.get(waiterModel) ?? 0
161
+ this.counts.set(waiterModel, waiterCount + 1)
162
+ }
163
+ next.resolve()
164
+ return
165
+ }
166
+ }
167
+
168
+ // No handoff occurred - decrement global count
169
+ if (this.globalCount > 0) {
170
+ this.globalCount--
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Cancel all waiting acquires for a model. Used during cleanup.
176
+ */
177
+ cancelWaiters(model: string): void {
178
+ const queue = this.queues.get(model)
179
+ if (queue) {
180
+ for (const entry of queue) {
181
+ if (!entry.settled) {
182
+ entry.settled = true
183
+ entry.rawReject(new Error(`Concurrency queue cancelled for model: ${model}`))
184
+ }
185
+ }
186
+ this.queues.delete(model)
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Clear all state. Used during manager cleanup/shutdown.
192
+ * Cancels all pending waiters.
193
+ */
194
+ clear(): void {
195
+ for (const [model] of this.queues) {
196
+ this.cancelWaiters(model)
197
+ }
198
+ // Cancel global queue waiters
199
+ for (const entry of this.globalQueue) {
200
+ if (!entry.settled) {
201
+ entry.settled = true
202
+ entry.rawReject(new Error("Concurrency queue cancelled: manager shutdown"))
203
+ }
204
+ }
205
+ this.globalQueue = []
206
+ this.globalCount = 0
207
+ this.counts.clear()
208
+ this.queues.clear()
209
+ }
210
+
211
+ /**
212
+ * Get current count for a model (for testing/debugging)
213
+ */
214
+ getCount(model: string): number {
215
+ return this.counts.get(model) ?? 0
216
+ }
217
+
218
+ /**
219
+ * Get queue length for a model (for testing/debugging)
220
+ */
221
+ getQueueLength(model: string): number {
222
+ return this.queues.get(model)?.length ?? 0
223
+ }
224
+
225
+ /**
226
+ * Get current global count across all models (for testing/debugging)
227
+ */
228
+ getGlobalCount(): number {
229
+ return this.globalCount
230
+ }
231
+
232
+ /**
233
+ * Get global queue length (for testing/debugging)
234
+ */
235
+ getGlobalQueueLength(): number {
236
+ return this.globalQueue.length
237
+ }
238
+ }
239
+ ```
240
+
241
+ **What changed:**
242
+ - Added `globalCount` field to track total active agents across all keys
243
+ - Added `globalQueue` for tasks waiting on the global limit
244
+ - Added `getGlobalLimit()` method to read `maxBackgroundAgents` from config
245
+ - Modified `acquire()` to check both per-model AND global limits
246
+ - Modified `release()` to handle global queue handoff and decrement global count
247
+ - Modified `clear()` to reset global state
248
+ - Added `getGlobalCount()` and `getGlobalQueueLength()` for testing
249
+
250
+ **Important design note:** The `release()` implementation above is a simplified version. In practice, the global queue handoff is tricky because we need to know which model the global waiter was trying to acquire for. A cleaner approach would be to store the model key in the QueueEntry. Let me refine:
251
+
252
+ ### Refined approach (simpler, more correct)
253
+
254
+ Instead of a separate global queue, a simpler approach is to check the global limit inside `acquire()` and use a single queue per model. When global capacity frees up on `release()`, we try to drain any model's queue:
255
+
256
+ ```typescript
257
+ async acquire(model: string): Promise<void> {
258
+ const perModelLimit = this.getConcurrencyLimit(model)
259
+ const globalLimit = this.getGlobalLimit()
260
+
261
+ if (perModelLimit === Infinity && globalLimit === Infinity) {
262
+ return
263
+ }
264
+
265
+ const currentPerModel = this.counts.get(model) ?? 0
266
+
267
+ if (currentPerModel < perModelLimit && this.globalCount < globalLimit) {
268
+ this.counts.set(model, currentPerModel + 1)
269
+ if (globalLimit !== Infinity) {
270
+ this.globalCount++
271
+ }
272
+ return
273
+ }
274
+
275
+ return new Promise<void>((resolve, reject) => {
276
+ const queue = this.queues.get(model) ?? []
277
+
278
+ const entry: QueueEntry = {
279
+ resolve: () => {
280
+ if (entry.settled) return
281
+ entry.settled = true
282
+ resolve()
283
+ },
284
+ rawReject: reject,
285
+ settled: false,
286
+ }
287
+
288
+ queue.push(entry)
289
+ this.queues.set(model, queue)
290
+ })
291
+ }
292
+
293
+ release(model: string): void {
294
+ const perModelLimit = this.getConcurrencyLimit(model)
295
+ const globalLimit = this.getGlobalLimit()
296
+
297
+ if (perModelLimit === Infinity && globalLimit === Infinity) {
298
+ return
299
+ }
300
+
301
+ // Try per-model handoff first (same model queue)
302
+ const queue = this.queues.get(model)
303
+ while (queue && queue.length > 0) {
304
+ const next = queue.shift()!
305
+ if (!next.settled) {
306
+ // Hand off the slot to this waiter (per-model and global counts stay the same)
307
+ next.resolve()
308
+ return
309
+ }
310
+ }
311
+
312
+ // No per-model handoff - decrement per-model count
313
+ const current = this.counts.get(model) ?? 0
314
+ if (current > 0) {
315
+ this.counts.set(model, current - 1)
316
+ }
317
+
318
+ // Decrement global count
319
+ if (globalLimit !== Infinity && this.globalCount > 0) {
320
+ this.globalCount--
321
+ }
322
+
323
+ // Try to drain any other model's queue that was blocked by global limit
324
+ if (globalLimit !== Infinity) {
325
+ this.tryDrainGlobalWaiters()
326
+ }
327
+ }
328
+
329
+ private tryDrainGlobalWaiters(): void {
330
+ const globalLimit = this.getGlobalLimit()
331
+ if (this.globalCount >= globalLimit) return
332
+
333
+ for (const [model, queue] of this.queues) {
334
+ const perModelLimit = this.getConcurrencyLimit(model)
335
+ const currentPerModel = this.counts.get(model) ?? 0
336
+
337
+ if (currentPerModel >= perModelLimit) continue
338
+
339
+ while (queue.length > 0 && this.globalCount < globalLimit && currentPerModel < perModelLimit) {
340
+ const next = queue.shift()!
341
+ if (!next.settled) {
342
+ this.counts.set(model, (this.counts.get(model) ?? 0) + 1)
343
+ this.globalCount++
344
+ next.resolve()
345
+ return
346
+ }
347
+ }
348
+ }
349
+ }
350
+ ```
351
+
352
+ This refined approach keeps all waiters in per-model queues (no separate global queue), and on release, tries to drain waiters from any model queue that was blocked by the global limit.
353
+
354
+ ---
355
+
356
+ ## 3. Schema Test Changes
357
+
358
+ **File:** `src/config/schema/background-task.test.ts`
359
+
360
+ Add after the `syncPollTimeoutMs` describe block:
361
+
362
+ ```typescript
363
+ describe("maxBackgroundAgents", () => {
364
+ describe("#given valid maxBackgroundAgents (10)", () => {
365
+ test("#when parsed #then returns correct value", () => {
366
+ const result = BackgroundTaskConfigSchema.parse({ maxBackgroundAgents: 10 })
367
+
368
+ expect(result.maxBackgroundAgents).toBe(10)
369
+ })
370
+ })
371
+
372
+ describe("#given maxBackgroundAgents of 1 (minimum)", () => {
373
+ test("#when parsed #then returns correct value", () => {
374
+ const result = BackgroundTaskConfigSchema.parse({ maxBackgroundAgents: 1 })
375
+
376
+ expect(result.maxBackgroundAgents).toBe(1)
377
+ })
378
+ })
379
+
380
+ describe("#given maxBackgroundAgents below minimum (0)", () => {
381
+ test("#when parsed #then throws ZodError", () => {
382
+ let thrownError: unknown
383
+
384
+ try {
385
+ BackgroundTaskConfigSchema.parse({ maxBackgroundAgents: 0 })
386
+ } catch (error) {
387
+ thrownError = error
388
+ }
389
+
390
+ expect(thrownError).toBeInstanceOf(ZodError)
391
+ })
392
+ })
393
+
394
+ describe("#given maxBackgroundAgents is negative (-1)", () => {
395
+ test("#when parsed #then throws ZodError", () => {
396
+ let thrownError: unknown
397
+
398
+ try {
399
+ BackgroundTaskConfigSchema.parse({ maxBackgroundAgents: -1 })
400
+ } catch (error) {
401
+ thrownError = error
402
+ }
403
+
404
+ expect(thrownError).toBeInstanceOf(ZodError)
405
+ })
406
+ })
407
+
408
+ describe("#given maxBackgroundAgents is non-integer (2.5)", () => {
409
+ test("#when parsed #then throws ZodError", () => {
410
+ let thrownError: unknown
411
+
412
+ try {
413
+ BackgroundTaskConfigSchema.parse({ maxBackgroundAgents: 2.5 })
414
+ } catch (error) {
415
+ thrownError = error
416
+ }
417
+
418
+ expect(thrownError).toBeInstanceOf(ZodError)
419
+ })
420
+ })
421
+
422
+ describe("#given maxBackgroundAgents not provided", () => {
423
+ test("#when parsed #then field is undefined", () => {
424
+ const result = BackgroundTaskConfigSchema.parse({})
425
+
426
+ expect(result.maxBackgroundAgents).toBeUndefined()
427
+ })
428
+ })
429
+ })
430
+ ```
431
+
432
+ ---
433
+
434
+ ## 4. ConcurrencyManager Test Changes
435
+
436
+ **File:** `src/features/background-agent/concurrency.test.ts`
437
+
438
+ Add new describe block:
439
+
440
+ ```typescript
441
+ describe("ConcurrencyManager.globalLimit (maxBackgroundAgents)", () => {
442
+ test("should return Infinity when maxBackgroundAgents is not set", () => {
443
+ // given
444
+ const manager = new ConcurrencyManager()
445
+
446
+ // when
447
+ const limit = manager.getGlobalLimit()
448
+
449
+ // then
450
+ expect(limit).toBe(Infinity)
451
+ })
452
+
453
+ test("should return configured maxBackgroundAgents", () => {
454
+ // given
455
+ const config: BackgroundTaskConfig = { maxBackgroundAgents: 3 }
456
+ const manager = new ConcurrencyManager(config)
457
+
458
+ // when
459
+ const limit = manager.getGlobalLimit()
460
+
461
+ // then
462
+ expect(limit).toBe(3)
463
+ })
464
+
465
+ test("should enforce global limit across different models", async () => {
466
+ // given
467
+ const config: BackgroundTaskConfig = {
468
+ maxBackgroundAgents: 2,
469
+ defaultConcurrency: 5,
470
+ }
471
+ const manager = new ConcurrencyManager(config)
472
+ await manager.acquire("model-a")
473
+ await manager.acquire("model-b")
474
+
475
+ // when
476
+ let resolved = false
477
+ const waitPromise = manager.acquire("model-c").then(() => { resolved = true })
478
+ await Promise.resolve()
479
+
480
+ // then - should be blocked by global limit even though per-model has capacity
481
+ expect(resolved).toBe(false)
482
+ expect(manager.getGlobalCount()).toBe(2)
483
+
484
+ // cleanup
485
+ manager.release("model-a")
486
+ await waitPromise
487
+ expect(resolved).toBe(true)
488
+ })
489
+
490
+ test("should allow tasks when global limit not reached", async () => {
491
+ // given
492
+ const config: BackgroundTaskConfig = {
493
+ maxBackgroundAgents: 3,
494
+ defaultConcurrency: 5,
495
+ }
496
+ const manager = new ConcurrencyManager(config)
497
+
498
+ // when
499
+ await manager.acquire("model-a")
500
+ await manager.acquire("model-b")
501
+ await manager.acquire("model-c")
502
+
503
+ // then
504
+ expect(manager.getGlobalCount()).toBe(3)
505
+ expect(manager.getCount("model-a")).toBe(1)
506
+ expect(manager.getCount("model-b")).toBe(1)
507
+ expect(manager.getCount("model-c")).toBe(1)
508
+ })
509
+
510
+ test("should respect both per-model and global limits", async () => {
511
+ // given - per-model limit of 1, global limit of 3
512
+ const config: BackgroundTaskConfig = {
513
+ maxBackgroundAgents: 3,
514
+ defaultConcurrency: 1,
515
+ }
516
+ const manager = new ConcurrencyManager(config)
517
+ await manager.acquire("model-a")
518
+
519
+ // when - try second acquire on same model
520
+ let resolved = false
521
+ const waitPromise = manager.acquire("model-a").then(() => { resolved = true })
522
+ await Promise.resolve()
523
+
524
+ // then - blocked by per-model limit, not global
525
+ expect(resolved).toBe(false)
526
+ expect(manager.getGlobalCount()).toBe(1)
527
+
528
+ // cleanup
529
+ manager.release("model-a")
530
+ await waitPromise
531
+ })
532
+
533
+ test("should release global slot and unblock waiting tasks", async () => {
534
+ // given
535
+ const config: BackgroundTaskConfig = {
536
+ maxBackgroundAgents: 1,
537
+ defaultConcurrency: 5,
538
+ }
539
+ const manager = new ConcurrencyManager(config)
540
+ await manager.acquire("model-a")
541
+
542
+ // when
543
+ let resolved = false
544
+ const waitPromise = manager.acquire("model-b").then(() => { resolved = true })
545
+ await Promise.resolve()
546
+ expect(resolved).toBe(false)
547
+
548
+ manager.release("model-a")
549
+ await waitPromise
550
+
551
+ // then
552
+ expect(resolved).toBe(true)
553
+ expect(manager.getGlobalCount()).toBe(1)
554
+ expect(manager.getCount("model-a")).toBe(0)
555
+ expect(manager.getCount("model-b")).toBe(1)
556
+ })
557
+
558
+ test("should not enforce global limit when not configured", async () => {
559
+ // given - no maxBackgroundAgents set
560
+ const config: BackgroundTaskConfig = { defaultConcurrency: 5 }
561
+ const manager = new ConcurrencyManager(config)
562
+
563
+ // when - acquire many across different models
564
+ await manager.acquire("model-a")
565
+ await manager.acquire("model-b")
566
+ await manager.acquire("model-c")
567
+ await manager.acquire("model-d")
568
+ await manager.acquire("model-e")
569
+ await manager.acquire("model-f")
570
+
571
+ // then - all should succeed (no global limit)
572
+ expect(manager.getCount("model-a")).toBe(1)
573
+ expect(manager.getCount("model-f")).toBe(1)
574
+ })
575
+
576
+ test("should reset global count on clear", async () => {
577
+ // given
578
+ const config: BackgroundTaskConfig = { maxBackgroundAgents: 5 }
579
+ const manager = new ConcurrencyManager(config)
580
+ await manager.acquire("model-a")
581
+ await manager.acquire("model-b")
582
+
583
+ // when
584
+ manager.clear()
585
+
586
+ // then
587
+ expect(manager.getGlobalCount()).toBe(0)
588
+ })
589
+ })
590
+ ```
591
+
592
+ ---
593
+
594
+ ## Config Usage Example
595
+
596
+ User's `.opencode/oh-my-opencode.jsonc`:
597
+
598
+ ```jsonc
599
+ {
600
+ "background_task": {
601
+ // Global limit: max 5 background agents total
602
+ "maxBackgroundAgents": 5,
603
+ // Per-model limits still apply independently
604
+ "defaultConcurrency": 3,
605
+ "providerConcurrency": {
606
+ "anthropic": 2
607
+ }
608
+ }
609
+ }
610
+ ```
611
+
612
+ With this config:
613
+ - Max 5 background agents running simultaneously across all models
614
+ - Max 3 per model (default), max 2 for any Anthropic model
615
+ - If 2 Anthropic + 3 OpenAI agents are running (5 total), no more can start regardless of per-model capacity
@@ -0,0 +1,99 @@
1
+ # Execution Plan: Add `max_background_agents` Config Option
2
+
3
+ ## Overview
4
+
5
+ Add a `max_background_agents` config option to oh-my-opencode that limits total simultaneous background agents across all models/providers. Currently, concurrency is only limited per-model/provider key (default 5 per key). This new option adds a **global ceiling** on total running background agents.
6
+
7
+ ## Step-by-Step Plan
8
+
9
+ ### Step 1: Create feature branch
10
+
11
+ ```bash
12
+ git checkout -b feat/max-background-agents dev
13
+ ```
14
+
15
+ ### Step 2: Add `max_background_agents` to BackgroundTaskConfigSchema
16
+
17
+ **File:** `src/config/schema/background-task.ts`
18
+
19
+ - Add `maxBackgroundAgents` field to the Zod schema with `z.number().int().min(1).optional()`
20
+ - This follows the existing pattern of `maxDepth` and `maxDescendants` (integer, min 1, optional)
21
+ - The field name uses camelCase to match existing schema fields (`defaultConcurrency`, `maxDepth`, `maxDescendants`)
22
+ - No `.default()` needed since the hardcoded fallback of 5 lives in `ConcurrencyManager`
23
+
24
+ ### Step 3: Modify `ConcurrencyManager` to enforce global limit
25
+
26
+ **File:** `src/features/background-agent/concurrency.ts`
27
+
28
+ - Add a `globalCount` field tracking total active agents across all keys
29
+ - Modify `acquire()` to check global count against `maxBackgroundAgents` before granting a slot
30
+ - Modify `release()` to decrement global count
31
+ - Modify `clear()` to reset global count
32
+ - Add `getGlobalCount()` for testing/debugging (follows existing `getCount()`/`getQueueLength()` pattern)
33
+
34
+ The global limit check happens **in addition to** the per-model limit. Both must have capacity for a task to proceed.
35
+
36
+ ### Step 4: Add tests for the new config schema field
37
+
38
+ **File:** `src/config/schema/background-task.test.ts`
39
+
40
+ - Add test cases following the existing given/when/then pattern with nested describes
41
+ - Test valid value, below-minimum value, undefined (not provided), non-number type
42
+
43
+ ### Step 5: Add tests for ConcurrencyManager global limit
44
+
45
+ **File:** `src/features/background-agent/concurrency.test.ts`
46
+
47
+ - Test that global limit is enforced across different model keys
48
+ - Test that tasks queue when global limit reached even if per-model limit has capacity
49
+ - Test that releasing a slot from one model allows a queued task from another model to proceed
50
+ - Test default behavior (5) when no config provided
51
+ - Test interaction between global and per-model limits
52
+
53
+ ### Step 6: Run typecheck and tests
54
+
55
+ ```bash
56
+ bun run typecheck
57
+ bun test src/config/schema/background-task.test.ts
58
+ bun test src/features/background-agent/concurrency.test.ts
59
+ ```
60
+
61
+ ### Step 7: Verify LSP diagnostics clean
62
+
63
+ Check `src/config/schema/background-task.ts` and `src/features/background-agent/concurrency.ts` for errors.
64
+
65
+ ### Step 8: Create PR
66
+
67
+ - Push branch to remote
68
+ - Create PR with structured description via `gh pr create`
69
+
70
+ ## Files Modified (4 files)
71
+
72
+ | File | Change |
73
+ |------|--------|
74
+ | `src/config/schema/background-task.ts` | Add `maxBackgroundAgents` field |
75
+ | `src/features/background-agent/concurrency.ts` | Add global count tracking + enforcement |
76
+ | `src/config/schema/background-task.test.ts` | Add schema validation tests |
77
+ | `src/features/background-agent/concurrency.test.ts` | Add global limit enforcement tests |
78
+
79
+ ## Files NOT Modified (intentional)
80
+
81
+ | File | Reason |
82
+ |------|--------|
83
+ | `src/config/schema/oh-my-opencode-config.ts` | No change needed - `BackgroundTaskConfigSchema` is already composed into root schema via `background_task` field |
84
+ | `src/create-managers.ts` | No change needed - `pluginConfig.background_task` already passed to `BackgroundManager` constructor |
85
+ | `src/features/background-agent/manager.ts` | No change needed - already passes config to `ConcurrencyManager` |
86
+ | `src/plugin-config.ts` | No change needed - `background_task` is a simple object field, uses default override merge |
87
+ | `src/config/schema.ts` | No change needed - barrel already exports `BackgroundTaskConfigSchema` |
88
+
89
+ ## Design Decisions
90
+
91
+ 1. **Field name `maxBackgroundAgents`** - camelCase to match existing schema fields (`maxDepth`, `maxDescendants`, `defaultConcurrency`). The user-facing JSONC config key is also camelCase per existing convention in `background_task` section.
92
+
93
+ 2. **Global limit vs per-model limit** - The global limit is a ceiling across ALL concurrency keys. Per-model limits still apply independently. A task needs both a per-model slot AND a global slot to proceed.
94
+
95
+ 3. **Default of 5** - Matches the existing hardcoded default in `getConcurrencyLimit()`. When `maxBackgroundAgents` is not set, no global limit is enforced (only per-model limits apply), preserving backward compatibility.
96
+
97
+ 4. **Queue behavior** - When global limit is reached, tasks wait in the same FIFO queue mechanism. The global check happens inside `acquire()` before the per-model check.
98
+
99
+ 5. **0 means Infinity** - Following the existing pattern where `defaultConcurrency: 0` means unlimited, `maxBackgroundAgents: 0` would also mean no global limit.