diff --git a/docs/concepts/context-engine.md b/docs/concepts/context-engine.md
index f251db9cef7..21c07af5e00 100644
--- a/docs/concepts/context-engine.md
+++ b/docs/concepts/context-engine.md
@@ -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:
- 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.
Clean up when a subagent session completes or is swept.
diff --git a/src/agents/subagent-spawn.context.test.ts b/src/agents/subagent-spawn.context.test.ts
index 373083cf7fa..78b79d8f285 100644
--- a/src/agents/subagent-spawn.context.test.ts
+++ b/src/agents/subagent-spawn.context.test.ts
@@ -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 },
diff --git a/src/agents/subagent-spawn.ts b/src/agents/subagent-spawn.ts
index c5a58b20e0c..72ee94184b6 100644
--- a/src/agents/subagent-spawn.ts
+++ b/src/agents/subagent-spawn.ts
@@ -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,