Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,6 @@ Provider and model configuration lives in JSON settings files. The global file h

`models` is always an array (single- and multi-model providers are uniform). `defaultModel` (or the first entry) is used when no model is selected. With exactly one provider configured, `defaultProvider` may be omitted.

Optional `maxConcurrentSubAgents` (integer ≥ 0, default **10**) caps how many `task`-tool sub-agent loops may run at once; **0** disables sub-agents entirely. Change it in **Settings → Sub-agents** or in this file. Applies only when `sessionMode` is **orchestrator**.

Optional `tools` block for the outer per-tool wall-clock budget:

```json
Expand Down
1 change: 0 additions & 1 deletion src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,6 @@ describe("buildProviderCatalog", () => {
hiddenCommands: ["help"],
onboarded: true,
compactionMode: "pruning",
maxConcurrentSubAgents: 3,
subagentMaxTurns: 40,
sessionMode: "orchestrator",
agentModelFallback: "none",
Expand Down
25 changes: 0 additions & 25 deletions src/config/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ export type Settings = {
// "llm" (default) generates a structured handoff summary via LLM call.
// "pruning" uses fast deterministic pruning with no LLM call.
compactionMode?: "llm" | "pruning";
maxConcurrentSubAgents?: number;
// Default inference-turn budget for leaf sub-agents (not the parent session limit).
subagentMaxTurns?: number;
// Primary session behavior: single agent does work in-session; orchestrator
Expand Down Expand Up @@ -252,20 +251,6 @@ export function shellEnvFromSettings(
return local?.env;
}

export const DEFAULT_MAX_CONCURRENT_SUB_AGENTS = 10;

export function clampMaxConcurrentSubAgents(value: number): number {
if (!Number.isFinite(value)) return DEFAULT_MAX_CONCURRENT_SUB_AGENTS;
return Math.max(0, Math.floor(value));
}

export function resolveMaxConcurrentSubAgents(settings?: Settings | null): number {
if (settings?.maxConcurrentSubAgents === undefined) {
return DEFAULT_MAX_CONCURRENT_SUB_AGENTS;
}
return clampMaxConcurrentSubAgents(settings.maxConcurrentSubAgents);
}

export const DEFAULT_SUBAGENT_MAX_TURNS = 30;
export const MAX_SUBAGENT_MAX_TURNS_CAP = 100;

Expand Down Expand Up @@ -466,7 +451,6 @@ const SettingsSchema = type({
"onboarded?": "boolean",
"lastChangelogVersion?": "string",
"compactionMode?": "'llm' | 'pruning'",
"maxConcurrentSubAgents?": "number",
"subagentMaxTurns?": "number",
"sessionMode?": "'single' | 'orchestrator'",
"agentModelFallback?": "'active' | 'none'",
Expand Down Expand Up @@ -522,10 +506,6 @@ export function isSettings(value: unknown): value is Settings {
if (!SettingsSchema.allows(value)) return false;
const s = value as Record<string, unknown>;
if (s.mcpServers !== undefined && normalizeMcpServers(s.mcpServers) === undefined) return false;
if (s.maxConcurrentSubAgents !== undefined) {
const n = s.maxConcurrentSubAgents;
if (typeof n !== "number" || !Number.isInteger(n) || n < 0) return false;
}
if (s.subagentMaxTurns !== undefined) {
const n = s.subagentMaxTurns;
if (
Expand Down Expand Up @@ -645,7 +625,6 @@ export const GLOBAL_SETTINGS_OPTIONAL_KEYS = [
"onboarded",
"lastChangelogVersion",
"compactionMode",
"maxConcurrentSubAgents",
"subagentMaxTurns",
"sessionMode",
"agentModelFallback",
Expand Down Expand Up @@ -753,10 +732,6 @@ export async function loadSettings(path: string): Promise<Settings | null> {
: undefined,
compactionMode:
s.compactionMode === "llm" || s.compactionMode === "pruning" ? s.compactionMode : undefined,
maxConcurrentSubAgents:
s.maxConcurrentSubAgents !== undefined
? clampMaxConcurrentSubAgents(s.maxConcurrentSubAgents as number)
: undefined,
subagentMaxTurns:
s.subagentMaxTurns !== undefined
? clampSubAgentMaxTurns(s.subagentMaxTurns as number)
Expand Down
5 changes: 0 additions & 5 deletions src/exec/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ import {
import {
loadLocalSettings,
localSettingsPath,
resolveMaxConcurrentSubAgents,
shellTimeoutFromSettings,
toolWatchdogFromSettings,
} from "../config/settings.js";
import { configureSubAgentConcurrency } from "../subagent/concurrency.js";
import { codexProfileFromProviderName } from "../config/codex-providers.js";
import { xaiProfileFromProviderName } from "../config/xai-providers.js";
import { createInferenceDependencies } from "../provider/inference-dependencies.js";
Expand Down Expand Up @@ -280,9 +278,6 @@ export async function runExec(config: Config): Promise<ExecResult> {
);
const sessionMode: SessionMode =
resolveSessionMode(config.settings, localSettingsForMode) ?? "orchestrator";
if (sessionMode === "orchestrator") {
configureSubAgentConcurrency(resolveMaxConcurrentSubAgents(config.settings));
}

const activeProviderModel = `${config.providerName}:${config.model}`;
const seededApprovals = await loadSeededApprovals(config.cwd, sessionId);
Expand Down
77 changes: 24 additions & 53 deletions src/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@ import {
saveGlobalSettings,
saveLocalSettings,
type Settings,
DEFAULT_MAX_CONCURRENT_SUB_AGENTS,
DEFAULT_SUBAGENT_MAX_TURNS,
MAX_SUBAGENT_MAX_TURNS_CAP,
resolveMaxConcurrentSubAgents,
clampMaxConcurrentSubAgents,
resolveDefaultSubAgentMaxTurns,
resolveSubAgentMaxTurns,
clampSubAgentMaxTurns,
Expand Down Expand Up @@ -764,57 +761,31 @@ describe("sessionMode", () => {
});
});

describe("maxConcurrentSubAgents", () => {
test("defaults to 10 when unset", () => {
expect(resolveMaxConcurrentSubAgents(null)).toBe(DEFAULT_MAX_CONCURRENT_SUB_AGENTS);
expect(resolveMaxConcurrentSubAgents({ providers: {} })).toBe(10);
});

test("loadSettings round-trips showPromptCost", async () => {
const dir = await mkdtemp(join(tmpdir(), "ic-settings-"));
try {
const path = join(dir, ".corbits", "settings.json");
await saveGlobalSettings(path, { ...firepass, showPromptCost: true });
expect(await loadSettings(path)).toEqual({ ...firepass, showPromptCost: true });
} finally {
await rm(dir, { recursive: true, force: true });
}
});

test("loadSettings round-trips maxConcurrentSubAgents", async () => {
const dir = await mkdtemp(join(tmpdir(), "ic-settings-"));
try {
const path = join(dir, ".corbits", "settings.json");
await saveGlobalSettings(path, { ...firepass, maxConcurrentSubAgents: 6 });
expect(await loadSettings(path)).toEqual({ ...firepass, maxConcurrentSubAgents: 6 });
} finally {
await rm(dir, { recursive: true, force: true });
}
});

test("accepts zero to disable sub-agents", () => {
expect(
isSettings({
providers: firepass.providers,
maxConcurrentSubAgents: 0,
}),
).toBe(true);
expect(clampMaxConcurrentSubAgents(0)).toBe(0);
});

test("rejects negative maxConcurrentSubAgents", () => {
expect(
isSettings({
providers: firepass.providers,
maxConcurrentSubAgents: -1,
}),
).toBe(false);
});
test("loadSettings round-trips showPromptCost", async () => {
const dir = await mkdtemp(join(tmpdir(), "ic-settings-"));
try {
const path = join(dir, ".corbits", "settings.json");
await saveGlobalSettings(path, { ...firepass, showPromptCost: true });
expect(await loadSettings(path)).toEqual({ ...firepass, showPromptCost: true });
} finally {
await rm(dir, { recursive: true, force: true });
}
});

test("clamp floors fractional values and negative numbers", () => {
expect(clampMaxConcurrentSubAgents(3.9)).toBe(3);
expect(clampMaxConcurrentSubAgents(-2)).toBe(0);
});
test("loadSettings tolerates a legacy maxConcurrentSubAgents key", async () => {
const dir = await mkdtemp(join(tmpdir(), "ic-settings-"));
try {
const path = join(dir, ".corbits", "settings.json");
await mkdir(join(dir, ".corbits"), { recursive: true });
await writeFile(
path,
JSON.stringify({ ...firepass, maxConcurrentSubAgents: 6 }, null, 2),
"utf8",
);
expect(await loadSettings(path)).toEqual(firepass);
} finally {
await rm(dir, { recursive: true, force: true });
}
});

describe("lastChangelogVersion", () => {
Expand Down
35 changes: 0 additions & 35 deletions src/subagent/__tests__/concurrency.test.ts

This file was deleted.

65 changes: 0 additions & 65 deletions src/subagent/concurrency.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/subagent/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import { gatherEnvironment } from "../agent/environment.js";
import { generateSessionId } from "../session/index.js";
import { consumeStream } from "../session/stream-consumer.js";
import { createCycleTextRecorder } from "../session/stream-journal.js";
import { withSubAgentSlot } from "./concurrency.js";
import {
detectRepetition,
REPETITION_CHECK_INTERVAL_CHARS,
Expand Down Expand Up @@ -213,12 +212,6 @@ export function createSubAgentRunController(
// gets its own posix tool instances and its own git-backed context store so
// the two loops never trample each other's state.
export async function runSubAgent(params: RunSubAgentParams): Promise<string> {
return withSubAgentSlot(() => runSubAgentInner(params), {
reentrant: params.nested === true,
});
}

async function runSubAgentInner(params: RunSubAgentParams): Promise<string> {
await seedPricingMetadataFromCache({
cachePath: defaultPricingCachePath(),
});
Expand Down
3 changes: 0 additions & 3 deletions src/subagent/task-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,9 +597,6 @@ export function createTaskTool(deps: TaskToolDeps): AgentTool {
...(orchestrator
? { orchestrator: true, nestedDispatch: nestedDispatch! }
: {}),
// Nested workers (installed by an orchestrator that already holds a
// slot) reuse the parent slot rather than acquiring their own.
...(deps.allowOrchestrator === false ? { nested: true } : {}),
maxTurns: resolvedMaxTurns,
...(deps.deadlineMs !== undefined ? { deadlineMs: deps.deadlineMs } : {}),
};
Expand Down
4 changes: 0 additions & 4 deletions src/subagent/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@ export type RunSubAgentParams = {
// Present only when orchestrator is true. Installs task + search_agents so
// the orchestrator can actually dispatch workers.
nestedDispatch?: NestedDispatchDeps;
// Set when this dispatch is a nested worker spawned by an orchestrator that
// already holds a concurrency slot. The nested run reuses the parent's slot
// (reentrant) instead of acquiring its own, which would deadlock the pool.
nested?: boolean;
/** Inference-turn budget for this worker only (not the parent session limit). */
maxTurns?: number;
/**
Expand Down
Loading
Loading