#Persistant sessions

1 messages Β· Page 1 of 1 (latest)

opaque token
#

β–Ž Hey Krill β€” I'm building a multi-agent coding workflow (LangGraph sidecar dispatching to OpenClaw agents via run_workflow_step). Right now every
β–Ž api.runtime.subagent.run({ sessionKey, ... }) call gets a fresh UUID for sessionKey, so agents have zero memory across dispatches. I want to give
β–Ž each agent one continuous session for the whole workflow (e.g. Kruppe opens the session on classify, same session carries through design /
β–Ž design_defense / later revisions). A few questions:
β–Ž
β–Ž 1. If I pass the same sessionKey to api.runtime.subagent.run() on a subsequent call, does it continue the existing session (accumulated messages,
β–Ž same system prompt, same working state), or does it start a new run in that session?
β–Ž 2. Workflows pause at human approval gates β€” potentially for hours or days. Default subagents.archiveAfterMinutes: 120 β€” what happens when a session
β–Ž is archived and I call run() again with its sessionKey? Does it rehydrate from the archived transcript, error, or silently create a fresh session?
β–Ž 3. Is there a way to bump the archive timeout per-session (for gate-paused sessions specifically), or is it purely a global config?
β–Ž 4. On context growth: with compaction: { mode: "safeguard" }, does the platform handle long sessions automatically, or do I need to trigger
β–Ž compaction explicitly?
β–Ž 5. Is sessions_spawn still the right tool for the initial dispatch if I want a persistent-session pattern, or is there a cleaner primitive I'm
β–Ž missing in the newer OpenClaw versions?
β–Ž
β–Ž Context: the current stateless model means I have to manually stuff prior-round findings, the design doc, branch name, etc. into every dispatch
β–Ž message. That works but it's brittle and the agents concede things they could challenge because they have no recollection of their own prior
β–Ž reasoning. Persistent sessions would eliminate a whole category of prompt-engineering hacks.

timber ravenBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> βœ… Mark Solution

crimson vineBOT
maiden pulsarBOT
#

Checked against OpenClaw 2026.4.9.

  1. Same sessionKey on api.runtime.subagent.run() means a new run in the same session, not a fresh blank session.

    • api.runtime.subagent.run() in src/gateway/server-plugins.ts just dispatches gateway method "agent".
    • In src/gateway/server-methods/agent.ts, OpenClaw calls loadSessionEntry(requestedSessionKey). If that entry exists, it reuses entry.sessionId; if not, it creates a new one with randomUUID().
    • So you keep accumulated transcript/history and session metadata for that key.
    • But it is not a suspended process. It is a fresh turn against the existing session.
    • Also, provider / model overrides are per-run, not sticky, and extraSystemPrompt is per-call too.
  2. agents.defaults.subagents.archiveAfterMinutes does not look like the thing controlling api.runtime.subagent.run().
    That archive path is implemented in the tracked subagent registry path, not the plain plugin runtime run() path:

    • src/agents/subagent-registry-helpers.ts β†’ resolveArchiveAfterMs()
    • used from src/agents/subagent-registry-run-manager.ts
    • that is the sessions_spawn / tracked-child lifecycle machinery
    • api.runtime.subagent.run() itself just hits "agent"

    If a session is archived via sessions.delete, the behavior is:

#
  • src/gateway/server-methods/sessions.ts deletes the live store entry

    • transcript gets renamed to *.deleted.<timestamp>
    • later run() with the same key will silently create a fresh live session for that key
    • it does not auto-rehydrate the archived transcript into the live session path

    The key reason is loadSessionEntry() in src/gateway/session-utils.ts looks at the live session store, not archived transcript artifacts.

  1. I did not find a per-session archive timeout.
    The knob I found is config-level:

    • agents.defaults.subagents.archiveAfterMinutes
    • schema in src/config/zod-schema.agent-defaults.ts
    • type in src/config/types.agent-defaults.ts

    For persistent child sessions, the newer path is different:

    • sessions_spawn with thread: true, mode: "session"
    • in src/agents/subagent-registry-run-manager.ts, spawnMode === "session" leaves archiveAtMs unset
    • confirmed by src/agents/subagent-registry.archive.e2e.test.ts
#

So for long human gates, mode: "session" is the built-in persistent path.

  1. compaction: { mode: "safeguard" } is automatic.
    docs/concepts/compaction.md says auto-compaction runs when the session nears the model limit, or after a context overflow, and retries automatically.
    You only need manual /compact if you want to force it early or steer the summary.

  2. Which primitive to use?
    My read is:

    • If your LangGraph sidecar owns orchestration and you just want stable memory per worker, keep using api.runtime.subagent.run({ sessionKey: stableKey, ... }). That is the cleaner low-level primitive.
    • If you want OpenClaw-managed child lifecycle + persistent thread-bound human follow-up, use sessions_spawn with thread: true, mode: "session". docs/tools/subagents.md explicitly calls that the persistent thread-bound path.

One extra gotcha for your hours/days approval gates: even with a stable sessionKey, session reset policy can still rotate the sessionId on the next message:

  • daily reset
  • session.reset.idleMinutes
  • legacy session.idleMinutes

That part is documented in https://docs.openclaw.ai/reference/session-management-compaction and handled in initSessionState() in src/auto-reply/reply/session.ts.

#

If you want, I can sketch the exact session-keying pattern I’d use for workflowId Γ— agentRole, plus which reset settings to disable so Kruppe really stays continuous.

opaque token
#

Hey Krill β€” follow-up on the persistent-session pattern. I landed the stable sessionKey (wf:<workflow_id>:agent:<role>) against
api.runtime.subagent.run(). You flagged that session reset policy can still rotate the sessionId on the next message β€” daily reset,
session.reset.idleMinutes, legacy session.idleMinutes, all handled in initSessionState().

Two questions:

  1. Which exact config keys do I set to turn those off for our workflow agents? Happy with per-agent overrides if supported, or I can just scope them
    under agents.defaults. Looking for the zod-schema paths (e.g. agents.defaults.session.reset.idleMinutes: null or 0 or whatever the "disabled" sentinel
    is).

  2. Our workflows pause at human gates for hours to days. With resets disabled, is there any OTHER mechanism that would silently rotate the sessionId
    for a long-idle session using api.runtime.subagent.run() (not sessions_spawn)? I want to be confident that if Kruppe opens his workflow session on
    Monday and the user approves the gate Friday, the next dispatch still lands in the same session.

If the answer to (1) involves a larger config restructure, that sketch you offered earlier for the session-keying pattern + reset-disable config block
would be great β€” paste it raw, I'll wire it into our openclaw.json.

maiden pulsarBOT
#

I checked the code again, and I need to correct one thing from my earlier note.

For api.runtime.subagent.run({ sessionKey, ... }), the normal chat-session reset policy is not the code path being used.

1. Exact config keys, and whether you need them here

There is no agents.defaults.session.* path. Session reset config is top-level:

  • session.reset.mode
  • session.reset.atHour
  • session.reset.idleMinutes
  • session.resetByType.direct
  • session.resetByType.group
  • session.resetByType.thread
  • session.resetByChannel.<channel>
  • legacy session.idleMinutes
#

Schema source:

  • src/config/zod-schema.session.ts
  • types in src/config/types.base.ts

But for your specific path, api.runtime.subagent.run() goes through gateway method "agent", and that handler in src/gateway/server-methods/agent.ts does not call:

  • initSessionState()
  • resolveSessionResetPolicy()
  • evaluateSessionFreshness()

Instead it just does:

  • loadSessionEntry(requestedSessionKey)
  • const sessionId = entry?.sessionId ?? randomUUID()

So for your workflow sessions, you do not need to disable daily/idle reset config to keep the same sessionId.

That reset logic lives in src/auto-reply/reply/session.ts, which is the chat-routing path, not the explicit "agent" RPC path behind api.runtime.subagent.run().

#

2. What would make Friday land in a new session?

For api.runtime.subagent.run({ sessionKey }), the main things that can break continuity are:

  1. You send /new or /reset in the message body
    src/gateway/server-methods/agent.ts explicitly handles that.

  2. The live session entry gets deleted/pruned

    • sessions.delete
    • manual cleanup
    • session.maintenance if you run it in destructive mode / enforce cleanup
    • transcript/store loss/corruption
  3. You change the sessionKey

What I do not see on this path is an automatic β€œit was idle for 5 days, so rotate sessionId” behavior.

#

So for your Monday β†’ Friday gate case, same sessionKey should keep the same sessionId, unless something explicitly reset/deleted/pruned that session.

Important nuance on disable sentinels

If you do want to disable chat-routed daily/idle resets globally, I would use:

{
  session: {
    reset: {
      mode: "idle",
    },
  },
}

And leave these unset:

#
  • session.idleMinutes
  • session.reset.idleMinutes
  • session.resetByType
  • session.resetByChannel

Reason: src/config/zod-schema.session.ts validates both session.idleMinutes and session.reset.idleMinutes as positive integers, so I would not rely on literal 0 in config there.

Raw sketch I’d use

{
  session: {
    // Optional, only affects chat-routed sessions.
    // Your api.runtime.subagent.run({ sessionKey }) workflow does not depend on this.
    reset: {
      mode: "idle",
#
    },

    // Optional hardening if you ever run session maintenance in enforce mode
    // and want long-lived workflow sessions to survive for a long time.
    maintenance: {
      mode: "warn",
      pruneAfter: "180d",
      maxEntries: 5000,
    },
  },
}

Recommendation

For your sidecar pattern, I’d treat the rule as:

#
  • continuity key = stable sessionKey
  • do not send /new or /reset
  • do not prune/delete that key
  • optionally increase session.maintenance retention if workflows can sit around for weeks

If you want, I can do one more pass and give you a tighter β€œsafe persistent workflow session” checklist with the exact failure modes from agent.ts and sessions.ts.

opaque token
#

does canonical openclaw plugins install support plugins that import from ../../plugin-entry-*.js, or do user-scoped plugins need a
different entry mechanism?

maiden pulsarBOT
#

Short version, no. For externally installed, user-scoped plugins, the supported path is:

  • package-local entry files declared in package.json under openclaw.extensions
  • optional openclaw.setupEntry
  • SDK imports like openclaw/plugin-sdk/plugin-entry, openclaw/plugin-sdk/channel-core, etc.

Example supported shape:

{
  "openclaw": {
    "extensions": ["./dist/index.js"],
    "setupEntry": "./dist/setup-entry.js"
  }
}
#
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";

export default definePluginEntry({
  id: "my-plugin",
  name: "My Plugin",
  description: "…",
  register(api) {
    // …
  },
});

A few exact refs:

  • docs/plugins/architecture.md says openclaw.extensions entries must stay inside the plugin directory
  • src/plugins/install.ts explicitly expects package.json openclaw.extensions (example: ["./dist/index.js"])