mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 14:24:07 +00:00
fix(agents): keep light isolated subagents lightweight
Keep native subagent spawns with `lightContext=true` and resolved isolated context out of context-engine pre-spawn preparation so they remain lightweight. The normal isolated and forked context-engine lifecycle stays intact, and docs now call out the lightweight isolated exception. Fixes #81214
This commit is contained in:
@@ -91,7 +91,7 @@ For the bundled non-ACP Codex harness, OpenClaw applies the same lifecycle by pr
|
||||
OpenClaw calls two optional subagent lifecycle hooks:
|
||||
|
||||
<ParamField path="prepareSubagentSpawn" type="method">
|
||||
Prepare shared context state before a child run starts. The hook receives parent/child session keys, `contextMode` (`isolated` or `fork`), available transcript ids/files, and optional TTL. If it returns a rollback handle, OpenClaw calls it when spawn fails after preparation succeeds.
|
||||
Prepare shared context state before a child run starts. The hook receives parent/child session keys, `contextMode` (`isolated` or `fork`), available transcript ids/files, and optional TTL. If it returns a rollback handle, OpenClaw calls it when spawn fails after preparation succeeds. Native subagent spawns that request `lightContext` and resolve to `contextMode="isolated"` intentionally skip this hook so the child starts from the lightweight bootstrap context without context-engine-managed pre-spawn state.
|
||||
</ParamField>
|
||||
<ParamField path="onSubagentEnded" type="method">
|
||||
Clean up when a subagent session completes or is swept.
|
||||
|
||||
@@ -154,6 +154,28 @@ describe("sessions_spawn context modes", () => {
|
||||
expect(prepareContext.contextMode).toBe("isolated");
|
||||
});
|
||||
|
||||
it("keeps lightContext isolated spawns out of context-engine preparation", async () => {
|
||||
const store: SessionStore = {
|
||||
main: { sessionId: "parent-session-id", updatedAt: 1 },
|
||||
};
|
||||
usePersistentStoreMock(store);
|
||||
const prepareSubagentSpawn = vi.fn(async () => undefined);
|
||||
resolveContextEngineMock.mockResolvedValue({ prepareSubagentSpawn });
|
||||
|
||||
const result = await spawnSubagentDirect(
|
||||
{ task: "clean worker", context: "isolated", lightContext: true },
|
||||
{ agentSessionKey: "main" },
|
||||
);
|
||||
|
||||
expect(result.status).toBe("accepted");
|
||||
expect(forkSessionFromParentMock).not.toHaveBeenCalled();
|
||||
expect(ensureContextEnginesInitializedMock).not.toHaveBeenCalled();
|
||||
expect(resolveContextEngineMock).not.toHaveBeenCalled();
|
||||
expect(prepareSubagentSpawn).not.toHaveBeenCalled();
|
||||
const agentRequest = requireGatewayRequest("agent");
|
||||
expect(agentRequest.params?.bootstrapContextMode).toBe("lightweight");
|
||||
});
|
||||
|
||||
it("caps oversized context engine subagent TTLs at the timer-safe ceiling", async () => {
|
||||
const store: SessionStore = {
|
||||
main: { sessionId: "parent-session-id", updatedAt: 1 },
|
||||
|
||||
@@ -1485,13 +1485,16 @@ export async function spawnSubagentDirect(
|
||||
childSessionKey,
|
||||
};
|
||||
}
|
||||
const contextEnginePrepareResult = await prepareContextEngineSubagentSpawn({
|
||||
cfg,
|
||||
context: preparedSpawnContext,
|
||||
requesterInternalKey,
|
||||
childSessionKey,
|
||||
runTimeoutSeconds,
|
||||
});
|
||||
const contextEnginePrepareResult =
|
||||
params.lightContext && preparedSpawnContext.mode === "isolated"
|
||||
? ({ status: "ok", preparation: undefined } as const)
|
||||
: await prepareContextEngineSubagentSpawn({
|
||||
cfg,
|
||||
context: preparedSpawnContext,
|
||||
requesterInternalKey,
|
||||
childSessionKey,
|
||||
runTimeoutSeconds,
|
||||
});
|
||||
if (contextEnginePrepareResult.status === "error") {
|
||||
await cleanupFailedSpawnBeforeAgentStart({
|
||||
childSessionKey,
|
||||
|
||||
Reference in New Issue
Block a user