β 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.
#Persistant sessions
1 messages Β· Page 1 of 1 (latest)
Checked against OpenClaw 2026.4.9.
-
Same
sessionKeyonapi.runtime.subagent.run()means a new run in the same session, not a fresh blank session.api.runtime.subagent.run()insrc/gateway/server-plugins.tsjust dispatches gateway method"agent".- In
src/gateway/server-methods/agent.ts, OpenClaw callsloadSessionEntry(requestedSessionKey). If that entry exists, it reusesentry.sessionId; if not, it creates a new one withrandomUUID(). - 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/modeloverrides are per-run, not sticky, andextraSystemPromptis per-call too.
-
agents.defaults.subagents.archiveAfterMinutesdoes not look like the thing controllingapi.runtime.subagent.run().
That archive path is implemented in the tracked subagent registry path, not the plain plugin runtimerun()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.tsdeletes 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()insrc/gateway/session-utils.tslooks at the live session store, not archived transcript artifacts. - transcript gets renamed to
-
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_spawnwiththread: true, mode: "session"- in
src/agents/subagent-registry-run-manager.ts,spawnMode === "session"leavesarchiveAtMsunset - confirmed by
src/agents/subagent-registry.archive.e2e.test.ts
So for long human gates, mode: "session" is the built-in persistent path.
-
compaction: { mode: "safeguard" }is automatic.
docs/concepts/compaction.mdsays auto-compaction runs when the session nears the model limit, or after a context overflow, and retries automatically.
You only need manual/compactif you want to force it early or steer the summary. -
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_spawnwiththread: true, mode: "session".docs/tools/subagents.mdexplicitly calls that the persistent thread-bound path.
- If your LangGraph sidecar owns orchestration and you just want stable memory per worker, keep using
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.
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:
-
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). -
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.
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.modesession.reset.atHoursession.reset.idleMinutessession.resetByType.directsession.resetByType.groupsession.resetByType.threadsession.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:
-
You send
/newor/resetin the message body
src/gateway/server-methods/agent.tsexplicitly handles that. -
The live session entry gets deleted/pruned
sessions.delete- manual cleanup
session.maintenanceif you run it in destructive mode / enforce cleanup- transcript/store loss/corruption
-
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.idleMinutessession.reset.idleMinutessession.resetByTypesession.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
/newor/reset - do not prune/delete that key
- optionally increase
session.maintenanceretention 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.
does canonical openclaw plugins install support plugins that import from ../../plugin-entry-*.js, or do user-scoped plugins need a
different entry mechanism?
Short version, no. For externally installed, user-scoped plugins, the supported path is:
- package-local entry files declared in
package.jsonunderopenclaw.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.mdsaysopenclaw.extensionsentries must stay inside the plugin directorysrc/plugins/install.tsexplicitly expectspackage.json openclaw.extensions(example:["./dist/index.js"])