docs: document subagent registry lifecycle tests

This commit is contained in:
Peter Steinberger
2026-06-04 15:54:32 -04:00
parent 7af2673965
commit ec4a871f91
6 changed files with 24 additions and 14 deletions
@@ -1,3 +1,5 @@
// Subagent registry lifecycle tests cover completion, cleanup, announce retry,
// detached task status, and resource retirement around child-run endings.
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { CallGatewayOptions } from "../gateway/call.js";
import {
@@ -203,6 +205,8 @@ async function runNoReplyMirrorScenario(params: {
idempotencyKey?: string;
idempotencyKeyForEntry?: (entry: SubagentRunRecord) => string;
}): Promise<SubagentRunRecord> {
// A failed direct announce can still be mirrored from the requester history;
// the idempotency key prevents stale or unrelated assistant text from winning.
const entry = createRunEntry({
endedAt: 4_000,
expectsCompletionMessage: true,
+3 -1
View File
@@ -1,3 +1,5 @@
// Subagent registry query tests cover liveness, descendant counting, requester
// lookup, and stale-row handling for in-memory run snapshots.
import { describe, expect, it } from "vitest";
import {
countActiveRunsForSessionFromRuns,
@@ -223,7 +225,7 @@ describe("subagent registry query regressions", () => {
});
it("regression nested parallel counting, traversal includes child and grandchildren pending states", () => {
// Regression guard: nested fan-out once under-counted grandchildren and announced too early.
// Nested fan-out once under-counted grandchildren and announced too early.
const parentSessionKey = "agent:main:subagent:parent-nested";
const middleSessionKey = `${parentSessionKey}:subagent:middle`;
const runs = toRunMap([
@@ -1,3 +1,5 @@
// Subagent registry read-context tests cover the indexed snapshot used by hot
// prompt/control paths instead of repeatedly scanning the run map.
import { describe, expect, it } from "vitest";
import {
buildSubagentRunReadIndexFromRuns,
@@ -160,6 +162,8 @@ describe("subagent registry read index", () => {
});
it("keeps one snapshot stable for the lifetime of the context", () => {
// Read indexes are process-local snapshots; callers can reuse them through
// one prompt assembly without observing later registry mutations.
const root = "agent:main:main";
const runs = toRunMap([
makeRun({
@@ -1,14 +1,8 @@
// Announce loop-guard tests prove deferred subagent delivery eventually gives
// up instead of retrying forever after gateway delivery keeps returning false.
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import type { SubagentRunRecord } from "./subagent-registry.types.js";
/**
* Regression test for #18264: Gateway announcement delivery loop.
*
* When `runSubagentAnnounceFlow` repeatedly returns `false` (deferred),
* `finalizeSubagentCleanup` must eventually give up rather than retrying
* forever via the max-retry and expiration guards.
*/
const mocks = vi.hoisted(() => ({
getRuntimeConfig: vi.fn(() => ({
session: { store: "/tmp/test-store", mainKey: "main" },
@@ -200,7 +194,8 @@ describe("announce loop guard (#18264)", () => {
const entry = createEntry(Date.now());
mocks.loadSubagentRegistryFromSqlite.mockReturnValue(new Map([[entry.runId, entry]]));
// Initialization attempts resume once, then gives up for exhausted entries.
// Initialization attempts one resume, then relies on expiry/retry-budget
// guards so old pending rows do not loop after restart.
const beforeInit = Date.now();
registry.initSubagentRegistry();
await flushAsync();
@@ -1,3 +1,5 @@
// Lifecycle retry-grace e2e tests cover completion delivery retry behavior when
// lifecycle events race gateway waits or transient announce failures.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { testing as subagentAnnounceDeliveryTesting } from "./subagent-announce-delivery.js";
import { testing as subagentAnnounceOutputTesting } from "./subagent-announce-output.js";
@@ -220,6 +222,8 @@ describe("subagent registry lifecycle error grace", () => {
};
const waitForCleanupHandledFalse = async (runId: string) => {
// Cleanup can be released asynchronously after announce failure; poll fake
// time until the retry-grace state is observable.
for (let attempt = 0; attempt < 40; attempt += 1) {
const run = mod
.listSubagentRunsForRequester(MAIN_REQUESTER_SESSION_KEY)
@@ -1,3 +1,5 @@
// Nested subagent registry e2e tests cover requester/controller relationships
// across orchestrator and leaf child sessions.
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import "./subagent-registry.mocks.shared.js";
@@ -76,7 +78,7 @@ describe("subagent registry nested agent tracking", () => {
it("announce uses requesterSessionKey to route to the correct parent", () => {
const { registerSubagentRun } = subagentRegistry;
// Register a sub-sub-agent whose parent is a sub-agent
// Register a sub-sub-agent whose parent is a sub-agent.
registerSubagentRun({
runId: "run-subsub",
childSessionKey: "agent:main:subagent:orch:subagent:child",
@@ -87,9 +89,8 @@ describe("subagent registry nested agent tracking", () => {
label: "nested-leaf",
});
// When announce fires for the sub-sub-agent, it should target the sub-agent (depth-1),
// NOT the main session. The registry entry's requesterSessionKey ensures this.
// We verify the registry entry has the correct requesterSessionKey.
// Announce should target the depth-1 parent, not the main session. The
// registry entry's requesterSessionKey carries that routing boundary.
const { listSubagentRunsForRequester } = subagentRegistry;
const orchRuns = listSubagentRunsForRequester("agent:main:subagent:orch");
expect(orchRuns).toHaveLength(1);