From e271b4a1b059b99f7c176736b5fac21067b53e26 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 17 Mar 2026 16:31:04 +0900 Subject: [PATCH] feat(config): add background task circuit breaker settings Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- assets/oh-my-opencode.schema.json | 26 ++++++++ .../background-task-circuit-breaker.test.ts | 59 +++++++++++++++++++ src/config/schema/background-task.ts | 7 +++ 3 files changed, 92 insertions(+) create mode 100644 src/config/schema/background-task-circuit-breaker.test.ts diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index 46babc30b..dfd7558bf 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -3699,6 +3699,32 @@ "syncPollTimeoutMs": { "type": "number", "minimum": 60000 + }, + "maxToolCalls": { + "type": "integer", + "minimum": 10, + "maximum": 9007199254740991 + }, + "circuitBreaker": { + "type": "object", + "properties": { + "maxToolCalls": { + "type": "integer", + "minimum": 10, + "maximum": 9007199254740991 + }, + "windowSize": { + "type": "integer", + "minimum": 5, + "maximum": 9007199254740991 + }, + "repetitionThresholdPercent": { + "type": "number", + "exclusiveMinimum": 0, + "maximum": 100 + } + }, + "additionalProperties": false } }, "additionalProperties": false diff --git a/src/config/schema/background-task-circuit-breaker.test.ts b/src/config/schema/background-task-circuit-breaker.test.ts new file mode 100644 index 000000000..e236d4012 --- /dev/null +++ b/src/config/schema/background-task-circuit-breaker.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test } from "bun:test" +import { ZodError } from "zod/v4" +import { BackgroundTaskConfigSchema } from "./background-task" + +describe("BackgroundTaskConfigSchema.circuitBreaker", () => { + describe("#given valid circuit breaker settings", () => { + test("#when parsed #then returns nested config", () => { + const result = BackgroundTaskConfigSchema.parse({ + circuitBreaker: { + maxToolCalls: 150, + windowSize: 10, + repetitionThresholdPercent: 70, + }, + }) + + expect(result.circuitBreaker).toEqual({ + maxToolCalls: 150, + windowSize: 10, + repetitionThresholdPercent: 70, + }) + }) + }) + + describe("#given windowSize below minimum", () => { + test("#when parsed #then throws ZodError", () => { + let thrownError: unknown + + try { + BackgroundTaskConfigSchema.parse({ + circuitBreaker: { + windowSize: 4, + }, + }) + } catch (error) { + thrownError = error + } + + expect(thrownError).toBeInstanceOf(ZodError) + }) + }) + + describe("#given repetitionThresholdPercent is zero", () => { + test("#when parsed #then throws ZodError", () => { + let thrownError: unknown + + try { + BackgroundTaskConfigSchema.parse({ + circuitBreaker: { + repetitionThresholdPercent: 0, + }, + }) + } catch (error) { + thrownError = error + } + + expect(thrownError).toBeInstanceOf(ZodError) + }) + }) +}) diff --git a/src/config/schema/background-task.ts b/src/config/schema/background-task.ts index f98040e2d..5bec5065e 100644 --- a/src/config/schema/background-task.ts +++ b/src/config/schema/background-task.ts @@ -1,5 +1,11 @@ import { z } from "zod" +const CircuitBreakerConfigSchema = z.object({ + maxToolCalls: z.number().int().min(10).optional(), + windowSize: z.number().int().min(5).optional(), + repetitionThresholdPercent: z.number().gt(0).max(100).optional(), +}) + export const BackgroundTaskConfigSchema = z.object({ defaultConcurrency: z.number().min(1).optional(), providerConcurrency: z.record(z.string(), z.number().min(0)).optional(), @@ -13,6 +19,7 @@ export const BackgroundTaskConfigSchema = z.object({ syncPollTimeoutMs: z.number().min(60000).optional(), /** Maximum tool calls per subagent task before circuit breaker triggers (default: 200, minimum: 10). Prevents runaway loops from burning unlimited tokens. */ maxToolCalls: z.number().int().min(10).optional(), + circuitBreaker: CircuitBreakerConfigSchema.optional(), }) export type BackgroundTaskConfig = z.infer