Skip to content

active-memory recall subagent can deadlock on the main lane inside before_prompt_build #79026

Description

@Hashinai

Summary

active-memory can self-block when it runs its recall subagent from the before_prompt_build hook. The recall call currently does not pass an explicit lane to runEmbeddedPiAgent, so the embedded runner defaults to the global main lane. When the hook itself is already executing as part of a user-visible agent turn on main, the recall subagent is queued behind the turn that is waiting for it. The result is a predictable hook timeout and delayed/failed chat replies.

This is related to the broader active-memory timeout reports such as #65309, #72015, #75843, and #76212, but the local reproduction below isolates a specific lane self-deadlock and a one-line mitigation.

Environment

  • OpenClaw: 2026.5.6 (c97b9f7)
  • macOS 26.4.1 arm64
  • Node service runtime: node 25.9.0
  • Gateway: LaunchAgent, local loopback
  • Channel used for canary: Telegram direct session
  • Main agent model: openai-codex/gpt-5.5
  • Active Memory model: openai-codex/gpt-5.4-mini

Active Memory config excerpt:

{
  "enabled": true,
  "agents": ["main"],
  "allowedChatTypes": ["direct"],
  "queryMode": "recent",
  "promptStyle": "balanced",
  "timeoutMs": 15000,
  "maxSummaryChars": 220,
  "persistTranscripts": false,
  "logging": true,
  "model": "openai-codex/gpt-5.4-mini"
}

Reproduction

  1. Enable active-memory for a direct chat session.
  2. Configure a valid explicit memory model, e.g. openai-codex/gpt-5.4-mini.
  3. Restart the gateway.
  4. Send a simple direct-session canary prompt.
  5. Observe that active-memory starts but the hook times out after 15s.

Evidence before local patch

2026-05-07T13:15:31.280-03:00 active-memory: agent=main session=agent:main:telegram:default:direct:<redacted> activeProvider=openai-codex activeModel=gpt-5.4-mini start timeoutMs=15000 queryChars=4381 searchQueryChars=480
2026-05-07T13:15:46.280-03:00 [hooks] before_prompt_build handler from active-memory failed: timed out after 15000ms
2026-05-07T13:15:46.953-03:00 active-memory: agent=main session=agent:main:telegram:default:direct:<redacted> activeProvider=openai-codex activeModel=gpt-5.4-mini done status=timeout elapsedMs=15005 summaryChars=0
2026-05-07T13:15:57.778-03:00 lane task error: lane=main durationMs=0 error="Error: active-memory timeout after 15000ms"

The model was valid and available; changing from the main model to gpt-5.4-mini did not fix the timeout.

Root cause hypothesis

In dist/extensions/active-memory/index.js, runRecallSubagent(...) calls:

params.api.runtime.agent.runEmbeddedPiAgent({
  sessionId: subagentSessionId,
  sessionKey: subagentSessionKey,
  agentId: params.agentId,
  ...
  prompt,
  provider: modelRef.provider,
  model: modelRef.model,
  timeoutMs: embeddedTimeoutMs,
  ...
});

No lane is passed.

In runEmbeddedPiAgent, the global lane is derived from params.lane; if omitted, it defaults to main:

const globalLane = resolveGlobalLane(params.lane);

resolveGlobalLane(undefined) returns main.

Because active-memory is invoked from before_prompt_build for the user-visible turn, the subagent can be enqueued on the same global main lane that the current turn is holding while waiting for the subagent result. That creates the observed self-block until the hook timeout fires.

Local mitigation tested

Adding a dedicated lane for the active-memory embedded recall avoids the self-block:

 params.api.runtime.agent.runEmbeddedPiAgent({
   sessionId: subagentSessionId,
   sessionKey: subagentSessionKey,
   agentId: params.agentId,
   ...
   prompt,
+  lane: "active-memory",
   provider: modelRef.provider,
   model: modelRef.model,
   timeoutMs: embeddedTimeoutMs,
   ...
 });

Evidence after local patch

After applying only that lane change and restarting the gateway, the same internal canary completed. Active-memory no longer timed out:

2026-05-07T13:23:36.260-03:00 active-memory: agent=main session=agent:main:telegram:default:direct:<redacted> activeProvider=openai-codex activeModel=gpt-5.4-mini start timeoutMs=15000 queryChars=533 searchQueryChars=119
2026-05-07T13:23:45.828-03:00 active-memory: agent=main session=agent:main:telegram:default:direct:<redacted> activeProvider=openai-codex activeModel=gpt-5.4-mini done status=empty elapsedMs=9569 summaryChars=0

The outer canary turn returned successfully:

OK_CANARY_LANE

Gateway health afterward was stable: Telegram OK, Discord OK, 0 active/queued/running tasks, event loop OK.

Expected behavior

The active-memory recall subagent should not enqueue itself onto a global lane that is already occupied by the hook waiting for it. It should either use a dedicated lane or otherwise avoid blocking the user-visible run that spawned it.

Actual behavior

Without an explicit lane, active-memory recall can use main, causing the hook to wait for a nested run that cannot start promptly because it is queued behind the parent turn.

Suggested fix

Pass an explicit non-main lane for the active-memory recall embedded run, e.g. lane: "active-memory", or make runEmbeddedPiAgent/hook context detect nested prompt-build use and select a nonblocking lane by default.

A separate follow-up may still be useful for making active-memory fail-open/nonblocking by default, but this lane assignment appears to fix the self-deadlock observed in this environment.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions