fix(config): materialize subagent archive default (#81998)

This commit is contained in:
Gio Della-Libera
2026-05-16 15:19:36 -07:00
committed by GitHub
parent 7b38ac9749
commit 82ab8a8785
6 changed files with 59 additions and 4 deletions

View File

@@ -1,5 +1,6 @@
import fsSync, { promises as fs } from "node:fs";
import path from "node:path";
import { DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES } from "../config/agent-limits.js";
import { getRuntimeConfig } from "../config/config.js";
import {
loadSessionStore,
@@ -345,7 +346,9 @@ export function reconcileOrphanedRestoredRuns(params: {
export function resolveArchiveAfterMs(cfg?: OpenClawConfig) {
const config = cfg ?? getRuntimeConfig();
const minutes = config.agents?.defaults?.subagents?.archiveAfterMinutes ?? 60;
const minutes =
config.agents?.defaults?.subagents?.archiveAfterMinutes ??
DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES;
if (!Number.isFinite(minutes) || minutes < 0) {
return undefined;
}

View File

@@ -950,6 +950,26 @@ describe("config cli", () => {
expect(mockLog).toHaveBeenCalledWith("__OPENCLAW_REDACTED__");
});
it("prints materialized subagent archive default", async () => {
const resolved: OpenClawConfig = {};
const config: OpenClawConfig = {
agents: {
defaults: {
maxConcurrent: 4,
subagents: {
maxConcurrent: 8,
archiveAfterMinutes: 60,
},
},
},
};
setSnapshot(resolved, config);
await runConfigCommand(["config", "get", "agents.defaults.subagents.archiveAfterMinutes"]);
expect(mockLog).toHaveBeenCalledWith("60");
});
});
describe("config validate", () => {

View File

@@ -3,6 +3,7 @@ import type { OpenClawConfig } from "./types.js";
export const DEFAULT_AGENT_MAX_CONCURRENT = 4;
export const DEFAULT_SUBAGENT_MAX_CONCURRENT = 8;
export const DEFAULT_SUBAGENT_MAX_CHILDREN_PER_AGENT = 5;
export const DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES = 60;
// Keep depth-1 subagents as leaves unless config explicitly opts into nesting.
export const DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH = 1;

View File

@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import {
DEFAULT_AGENT_MAX_CONCURRENT,
DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES,
DEFAULT_SUBAGENT_MAX_CONCURRENT,
resolveAgentMaxConcurrent,
resolveSubagentMaxConcurrent,
@@ -48,5 +49,8 @@ describe("agent concurrency defaults", () => {
expect(cfg.agents?.defaults?.maxConcurrent).toBe(DEFAULT_AGENT_MAX_CONCURRENT);
expect(cfg.agents?.defaults?.subagents?.maxConcurrent).toBe(DEFAULT_SUBAGENT_MAX_CONCURRENT);
expect(cfg.agents?.defaults?.subagents?.archiveAfterMinutes).toBe(
DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES,
);
});
});

View File

@@ -1,5 +1,9 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
import {
DEFAULT_AGENT_MAX_CONCURRENT,
DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES,
DEFAULT_SUBAGENT_MAX_CONCURRENT,
} from "./agent-limits.js";
import {
applyAgentDefaults,
applyContextPruningDefaults,
@@ -112,5 +116,17 @@ describe("config defaults", () => {
expect(next.agents?.defaults?.maxConcurrent).toBe(DEFAULT_AGENT_MAX_CONCURRENT);
expect(next.agents?.defaults?.subagents?.maxConcurrent).toBe(DEFAULT_SUBAGENT_MAX_CONCURRENT);
expect(next.agents?.defaults?.subagents?.archiveAfterMinutes).toBe(
DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES,
);
});
it("preserves explicit subagent archive default", () => {
const next = applyAgentDefaults({
agents: { defaults: { subagents: { archiveAfterMinutes: 0 } } },
} as never);
expect(next.agents?.defaults?.subagents?.archiveAfterMinutes).toBe(0);
expect(next.agents?.defaults?.subagents?.maxConcurrent).toBe(DEFAULT_SUBAGENT_MAX_CONCURRENT);
});
});

View File

@@ -2,7 +2,11 @@ import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
import { normalizeConfiguredProviderCatalogModelId } from "../agents/model-ref-shared.js";
import { normalizeProviderId } from "../agents/provider-id.js";
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
import {
DEFAULT_AGENT_MAX_CONCURRENT,
DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES,
DEFAULT_SUBAGENT_MAX_CONCURRENT,
} from "./agent-limits.js";
import { normalizeAgentModelMapForConfig, normalizeAgentModelRefForConfig } from "./model-input.js";
import {
applyProviderConfigDefaultsForConfig,
@@ -394,7 +398,10 @@ export function applyAgentDefaults(cfg: OpenClawConfig): OpenClawConfig {
const hasSubMax =
typeof defaults?.subagents?.maxConcurrent === "number" &&
Number.isFinite(defaults.subagents.maxConcurrent);
if (hasMax && hasSubMax) {
const hasSubArchive =
typeof defaults?.subagents?.archiveAfterMinutes === "number" &&
Number.isFinite(defaults.subagents.archiveAfterMinutes);
if (hasMax && hasSubMax && hasSubArchive) {
return cfg;
}
@@ -410,6 +417,10 @@ export function applyAgentDefaults(cfg: OpenClawConfig): OpenClawConfig {
nextSubagents.maxConcurrent = DEFAULT_SUBAGENT_MAX_CONCURRENT;
mutated = true;
}
if (!hasSubArchive) {
nextSubagents.archiveAfterMinutes = DEFAULT_SUBAGENT_ARCHIVE_AFTER_MINUTES;
mutated = true;
}
if (!mutated) {
return cfg;