fix: dedupe active child session counts

This commit is contained in:
Tak Hoffman
2026-03-24 16:45:41 -05:00
parent eb40f0b961
commit e24704d5eb
3 changed files with 96 additions and 1 deletions

View File

@@ -227,6 +227,62 @@ describe("sessions_spawn depth + child limits", () => {
});
});
it("does not double-count restarted child sessions toward maxChildrenPerAgent", async () => {
configOverride = {
session: createPerSenderSessionConfig({ store: storeTemplatePath }),
agents: {
defaults: {
subagents: {
maxSpawnDepth: 2,
maxChildrenPerAgent: 2,
},
},
},
};
const childSessionKey = "agent:main:subagent:restarted-child";
addSubagentRunForTests({
runId: "existing-old-run",
childSessionKey,
requesterSessionKey: "agent:main:subagent:parent",
requesterDisplayKey: "agent:main:subagent:parent",
task: "old orchestration run",
cleanup: "keep",
createdAt: Date.now() - 30_000,
startedAt: Date.now() - 30_000,
endedAt: Date.now() - 20_000,
cleanupCompletedAt: undefined,
});
addSubagentRunForTests({
runId: "existing-current-run",
childSessionKey,
requesterSessionKey: "agent:main:subagent:parent",
requesterDisplayKey: "agent:main:subagent:parent",
task: "current orchestration run",
cleanup: "keep",
createdAt: Date.now() - 10_000,
startedAt: Date.now() - 10_000,
});
addSubagentRunForTests({
runId: "existing-descendant-run",
childSessionKey: `${childSessionKey}:subagent:leaf`,
requesterSessionKey: childSessionKey,
requesterDisplayKey: childSessionKey,
task: "descendant still running",
cleanup: "keep",
createdAt: Date.now() - 5_000,
startedAt: Date.now() - 5_000,
});
const tool = createSessionsSpawnTool({ agentSessionKey: "agent:main:subagent:parent" });
const result = await tool.execute("call-max-children-dedupe", { task: "hello" });
expect(result.details).toMatchObject({
status: "accepted",
runId: "run-depth",
});
});
it("does not use subagent maxConcurrent as a per-parent spawn gate", async () => {
configOverride = {
session: createPerSenderSessionConfig({ store: storeTemplatePath }),

View File

@@ -175,6 +175,37 @@ describe("subagent registry query regressions", () => {
expect(countActiveRunsForSessionFromRuns(runs, "agent:main:main")).toBe(0);
});
it("dedupes stale and current rows for the same child session when counting active runs", () => {
const childSessionKey = "agent:main:subagent:orch-restarted";
const runs = toRunMap([
makeRun({
runId: "run-old",
childSessionKey,
requesterSessionKey: "agent:main:main",
createdAt: 100,
startedAt: 100,
endedAt: 150,
cleanupCompletedAt: undefined,
}),
makeRun({
runId: "run-current",
childSessionKey,
requesterSessionKey: "agent:main:main",
createdAt: 200,
startedAt: 200,
}),
makeRun({
runId: "run-descendant-active",
childSessionKey: `${childSessionKey}:subagent:child`,
requesterSessionKey: childSessionKey,
createdAt: 210,
startedAt: 210,
}),
]);
expect(countActiveRunsForSessionFromRuns(runs, "agent:main:main")).toBe(1);
});
it("scopes direct child listings to the requester run window when requesterRunId is provided", () => {
const requesterSessionKey = "agent:main:subagent:orchestrator";
const runs = toRunMap([

View File

@@ -136,11 +136,19 @@ export function countActiveRunsForSessionFromRuns(
return pending;
};
let count = 0;
const latestByChildSessionKey = new Map<string, SubagentRunRecord>();
for (const entry of runs.values()) {
if (resolveControllerSessionKey(entry) !== key) {
continue;
}
const existing = latestByChildSessionKey.get(entry.childSessionKey);
if (!existing || entry.createdAt > existing.createdAt) {
latestByChildSessionKey.set(entry.childSessionKey, entry);
}
}
let count = 0;
for (const entry of latestByChildSessionKey.values()) {
if (typeof entry.endedAt !== "number") {
count += 1;
continue;