From 213d9b47b0194975ae9cbeb18452dd3149412ede Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 20 Jan 2026 10:27:58 +0000 Subject: [PATCH] refactor: centralize agent concurrency defaults --- CHANGELOG.md | 1 + src/config/agent-limits.ts | 2 ++ src/config/defaults.ts | 38 +++++++++++++++++++++++++++ src/config/io.ts | 15 ++++++++--- src/config/validation.ts | 6 +++-- src/gateway/server-lanes.ts | 14 ++++++++-- src/gateway/server-reload-handlers.ts | 11 ++++++-- src/telegram/monitor.ts | 3 ++- 8 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 src/config/agent-limits.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bda53398e..6b5ec756b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.clawd.bot ### Changes - Repo: remove the Peekaboo git submodule now that the SPM release is used. - Gateway: raise default lane concurrency for main and sub-agent runs. +- Config: centralize default agent concurrency limits. ### Fixes - Web search: infer Perplexity base URL from API key source (direct vs OpenRouter). diff --git a/src/config/agent-limits.ts b/src/config/agent-limits.ts new file mode 100644 index 000000000..0a1733c03 --- /dev/null +++ b/src/config/agent-limits.ts @@ -0,0 +1,2 @@ +export const DEFAULT_AGENT_MAX_CONCURRENT = 4; +export const DEFAULT_SUBAGENT_MAX_CONCURRENT = 8; diff --git a/src/config/defaults.ts b/src/config/defaults.ts index e9028f0fe..1d0208fc2 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -1,5 +1,6 @@ import { resolveTalkApiKey } from "./talk.js"; import type { ClawdbotConfig } from "./types.js"; +import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js"; type WarnState = { warned: boolean }; @@ -105,6 +106,43 @@ export function applyModelDefaults(cfg: ClawdbotConfig): ClawdbotConfig { }; } +export function applyAgentDefaults(cfg: ClawdbotConfig): ClawdbotConfig { + const agents = cfg.agents; + const defaults = agents?.defaults; + const hasMax = + typeof defaults?.maxConcurrent === "number" && Number.isFinite(defaults.maxConcurrent); + const hasSubMax = + typeof defaults?.subagents?.maxConcurrent === "number" && + Number.isFinite(defaults.subagents.maxConcurrent); + if (hasMax && hasSubMax) return cfg; + + let mutated = false; + const nextDefaults = defaults ? { ...defaults } : {}; + if (!hasMax) { + nextDefaults.maxConcurrent = DEFAULT_AGENT_MAX_CONCURRENT; + mutated = true; + } + + const nextSubagents = defaults?.subagents ? { ...defaults.subagents } : {}; + if (!hasSubMax) { + nextSubagents.maxConcurrent = DEFAULT_SUBAGENT_MAX_CONCURRENT; + mutated = true; + } + + if (!mutated) return cfg; + + return { + ...cfg, + agents: { + ...agents, + defaults: { + ...nextDefaults, + subagents: nextSubagents, + }, + }, + }; +} + export function applyLoggingDefaults(cfg: ClawdbotConfig): ClawdbotConfig { const logging = cfg.logging; if (!logging) return cfg; diff --git a/src/config/io.ts b/src/config/io.ts index 2d57bf0a7..6fdc5f16d 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -15,6 +15,7 @@ import { DuplicateAgentDirError, findDuplicateAgentDirs } from "./agent-dirs.js" import { applyCompactionDefaults, applyContextPruningDefaults, + applyAgentDefaults, applyLoggingDefaults, applyMessageDefaults, applyModelDefaults, @@ -244,8 +245,10 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { const cfg = applyModelDefaults( applyCompactionDefaults( applyContextPruningDefaults( - applySessionDefaults( - applyLoggingDefaults(applyMessageDefaults(validated.data as ClawdbotConfig)), + applyAgentDefaults( + applySessionDefaults( + applyLoggingDefaults(applyMessageDefaults(validated.data as ClawdbotConfig)), + ), ), ), ), @@ -291,7 +294,9 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { const config = applyTalkApiKey( applyModelDefaults( applyCompactionDefaults( - applyContextPruningDefaults(applySessionDefaults(applyMessageDefaults({}))), + applyContextPruningDefaults( + applyAgentDefaults(applySessionDefaults(applyMessageDefaults({}))), + ), ), ), ); @@ -402,7 +407,9 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { config: normalizeConfigPaths( applyTalkApiKey( applyModelDefaults( - applySessionDefaults(applyLoggingDefaults(applyMessageDefaults(validated.config))), + applyAgentDefaults( + applySessionDefaults(applyLoggingDefaults(applyMessageDefaults(validated.config))), + ), ), ), ), diff --git a/src/config/validation.ts b/src/config/validation.ts index 8bd8c2629..4b7108db0 100644 --- a/src/config/validation.ts +++ b/src/config/validation.ts @@ -1,5 +1,5 @@ import { findDuplicateAgentDirs, formatDuplicateAgentDirError } from "./agent-dirs.js"; -import { applyModelDefaults, applySessionDefaults } from "./defaults.js"; +import { applyAgentDefaults, applyModelDefaults, applySessionDefaults } from "./defaults.js"; import { findLegacyConfigIssues } from "./legacy.js"; import type { ClawdbotConfig, ConfigValidationIssue } from "./types.js"; import { ClawdbotSchema } from "./zod-schema.js"; @@ -41,6 +41,8 @@ export function validateConfigObject( } return { ok: true, - config: applyModelDefaults(applySessionDefaults(validated.data as ClawdbotConfig)), + config: applyModelDefaults( + applyAgentDefaults(applySessionDefaults(validated.data as ClawdbotConfig)), + ), }; } diff --git a/src/gateway/server-lanes.ts b/src/gateway/server-lanes.ts index d73167f05..95de0586f 100644 --- a/src/gateway/server-lanes.ts +++ b/src/gateway/server-lanes.ts @@ -1,8 +1,18 @@ import type { loadConfig } from "../config/config.js"; +import { + DEFAULT_AGENT_MAX_CONCURRENT, + DEFAULT_SUBAGENT_MAX_CONCURRENT, +} from "../config/agent-limits.js"; import { setCommandLaneConcurrency } from "../process/command-queue.js"; export function applyGatewayLaneConcurrency(cfg: ReturnType) { setCommandLaneConcurrency("cron", cfg.cron?.maxConcurrentRuns ?? 1); - setCommandLaneConcurrency("main", cfg.agents?.defaults?.maxConcurrent ?? 4); - setCommandLaneConcurrency("subagent", cfg.agents?.defaults?.subagents?.maxConcurrent ?? 8); + setCommandLaneConcurrency( + "main", + cfg.agents?.defaults?.maxConcurrent ?? DEFAULT_AGENT_MAX_CONCURRENT, + ); + setCommandLaneConcurrency( + "subagent", + cfg.agents?.defaults?.subagents?.maxConcurrent ?? DEFAULT_SUBAGENT_MAX_CONCURRENT, + ); } diff --git a/src/gateway/server-reload-handlers.ts b/src/gateway/server-reload-handlers.ts index 094a29640..31c643db8 100644 --- a/src/gateway/server-reload-handlers.ts +++ b/src/gateway/server-reload-handlers.ts @@ -8,6 +8,10 @@ import { setGatewaySigusr1RestartPolicy, } from "../infra/restart.js"; import { setCommandLaneConcurrency } from "../process/command-queue.js"; +import { + DEFAULT_AGENT_MAX_CONCURRENT, + DEFAULT_SUBAGENT_MAX_CONCURRENT, +} from "../config/agent-limits.js"; import { isTruthyEnvValue } from "../infra/env.js"; import type { ChannelKind, GatewayReloadPlan } from "./config-reload.js"; import { resolveHooksConfig } from "./hooks.js"; @@ -127,10 +131,13 @@ export function createGatewayReloadHandlers(params: { } setCommandLaneConcurrency("cron", nextConfig.cron?.maxConcurrentRuns ?? 1); - setCommandLaneConcurrency("main", nextConfig.agents?.defaults?.maxConcurrent ?? 4); + setCommandLaneConcurrency( + "main", + nextConfig.agents?.defaults?.maxConcurrent ?? DEFAULT_AGENT_MAX_CONCURRENT, + ); setCommandLaneConcurrency( "subagent", - nextConfig.agents?.defaults?.subagents?.maxConcurrent ?? 8, + nextConfig.agents?.defaults?.subagents?.maxConcurrent ?? DEFAULT_SUBAGENT_MAX_CONCURRENT, ); if (plan.hotReasons.length > 0) { diff --git a/src/telegram/monitor.ts b/src/telegram/monitor.ts index bb8d6adb0..be70c6a33 100644 --- a/src/telegram/monitor.ts +++ b/src/telegram/monitor.ts @@ -1,6 +1,7 @@ import { type RunOptions, run } from "@grammyjs/runner"; import type { ClawdbotConfig } from "../config/config.js"; import { loadConfig } from "../config/config.js"; +import { DEFAULT_AGENT_MAX_CONCURRENT } from "../config/agent-limits.js"; import { computeBackoff, sleepWithAbort } from "../infra/backoff.js"; import { formatDurationMs } from "../infra/format-duration.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -28,7 +29,7 @@ export type MonitorTelegramOpts = { export function createTelegramRunnerOptions(cfg: ClawdbotConfig): RunOptions { return { sink: { - concurrency: cfg.agents?.defaults?.maxConcurrent ?? 1, + concurrency: cfg.agents?.defaults?.maxConcurrent ?? DEFAULT_AGENT_MAX_CONCURRENT, }, runner: { fetch: {