Named session snapshots for OpenClaw — save, load, rename, copy, and branch conversations from one unified sess command.
Install
openclaw plugins install clawhub:@detain/openclaw-sess🦞 sess
Named session snapshots for OpenClaw.
Save, load, rename, copy, and branch your conversations — one command.
Why sess?
Your OpenClaw conversations are valuable. The plan you brainstormed at 2am, the
debugging context you built up over three hours, the prompt chain that finally
gave you the right answer — all of it lives in one linear transcript that any
/new command blows away forever.
sess turns that transcript into something you can checkpoint, branch, and return to, the same way git turns a directory of files into something you can commit, branch, and return to.
sess save before-refactor # checkpoint
# …try a risky prompt direction…
sess load before-refactor # nope, roll back
sess copy before-refactor v2 # branch into a new line of thought
One command. Seven verbs. Zero surprises.
Features
| 💾 Named snapshots | save, load, list, delete, rename, copy, new — the seven verbs you'd expect and nothing you wouldn't |
| 🌿 Branching | sess copy main side-quest forks a conversation; explore without losing your place |
| 🛟 Auto-backup before risky ops | Every load and new first snapshots the live session as pre-load~<ts> — you can't accidentally clobber an hour of work |
| ⚡ Auto-save (opt-in) | Roll-forward autosave~ slot after every turn; turn your laptop off mid-thought and pick up exactly where you left off |
| 🔖 Tags & notes | sess save v1 --note="before the refactor" --tags=env=dev,ticket=OPS-142 |
| 🧹 Retention | Config cap + background pruner; auto-managed snapshots evict first, your named ones stay |
| 🛠️ Five surfaces, one dispatcher | Slash command · agent tool · CLI subcommand · HTTP API · gateway RPC |
| 🔒 Safe by default | 0600 files in a 0700 directory, strict name allowlist, atomic tmp → rename writes, never concatenates user input into paths |
| 🧩 Pure native plugin | Uses definePluginEntry, api.runtime.*, and typed registerTool / registerHttpRoute / registerService — no reach-ins, no private APIs |
Install
From ClawHub (recommended)
openclaw plugins install sess
From npm
openclaw plugins install @detain/openclaw-sess
From source (local dev)
git clone https://github.com/detain/openclaw-sess.git
openclaw plugins install ./openclaw-sess
Then enable it in your OpenClaw config:
{
"plugins": {
"entries": {
"sess": {
"enabled": true,
"config": {
"autoSave": true,
"maxSnapshots": 100,
"allowOverwrite": false
}
}
}
}
}
Restart the gateway and you're done:
openclaw gateway restart
openclaw plugins inspect sess # verify it loaded cleanly
Quick start
# Start a conversation with your agent, do some work, then:
sess save working-context
# List what you've saved
sess list
# NAME MSGS SIZE UPDATED MODEL
# ────────────────────────────────────────────────────────────
# working-context 23 14.2K 12s ago openai/gpt-4o
# Branch it to try something risky
sess copy working-context experiment
# …try the risky thing, doesn't pan out…
sess load working-context # back where you were, automatically
# Start fresh but keep the old context available
sess new --backup
Command reference
sess save <name>
Snapshot the active session. Fails if <name> already exists unless you pass
--overwrite or set allowOverwrite: true in config.
sess save v1
sess save v2 --note="after adding the cache layer"
sess save prod-debug --tags=env=prod,ticket=OPS-142
sess load <name>
Restore a snapshot into the active session. The live transcript is
automatically backed up as pre-load~<timestamp> first; pass --no-backup to
skip.
sess load v1
sess load v1 --no-backup
sess list
Show every snapshot, newest first.
sess list
sess list --prefix=v # only names starting with "v"
sess list --tag=env=prod # only snapshots tagged env=prod
sess list --limit=5 # cap output
sess list --include-auto # show autosave~, pre-load~*, etc.
sess list --json # machine-readable output
sess new [<name>]
Start fresh. Auto-backs up the current session first as pre-new~<ts>.
sess new # clear and go
sess new side-quest # clear and save the fresh state as "side-quest"
sess new forked --from=v1 # seed the new session from snapshot v1
sess new --no-backup # skip the pre-new backup
sess copy <src> <dst>
Duplicate a snapshot. The primary way to branch a conversation.
sess copy main experiment
sess copy v1 v1-try-openrouter --note="swapping providers"
sess rename <old> <new>
sess rename experiment v3-final
sess delete <name>
Remove a snapshot. Supports prefix-wildcard mass-delete with a required
--confirm:
sess delete dead-end
sess delete --prefix=pre-load~ --confirm # clean up auto-backups
Aliases
ls, show → list · restore, get → load · rm, remove, del →
delete · mv, move → rename · cp, clone, branch → copy ·
reset, fresh → new
Five ways to drive it
sess exposes one dispatcher through five surfaces so it fits whatever workflow you're already in.
1. Slash command (in-chat)
/sess save v1
/sess list
2. Agent tool (let the model manage its own memory)
When exposeTool: true (default), your agent gains a sess_manage tool it
can invoke during a turn:
"Save this as
debug-session-aug-5before we try the rewrite."→ model calls
sess_manage({ action: "save", name: "debug-session-aug-5" })
3. CLI
openclaw sess list
openclaw sess save v1 --note="works!"
openclaw sess load v1
4. HTTP API
All endpoints require gateway auth. See HTTP API below for the full shape.
curl -H "Authorization: Bearer $TOKEN" \
https://localhost:7777/sess/list
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"v1","note":"from cron"}' \
https://localhost:7777/sess/save
5. Gateway RPC (in-process)
From another plugin or a gateway client:
await gateway.call("sess.invoke", { action: "list" });
await gateway.call("sess.invoke", { action: "save", name: "v1" });
Configuration
All fields in plugins.entries.sess.config:
| Field | Type | Default | Description |
|---|---|---|---|
storageDir | string | "sessions" | Where snapshots live. Relative → resolved against the agent dir. Supports ~ expansion. |
autoSave | boolean | false | Snapshot the live session into autoSaveSlot after every turn. |
autoSaveSlot | string | "autosave~" | Name of the rolling auto-save snapshot (trailing ~ marks it auto-managed). |
maxSnapshots | integer | 50 | Hard cap. 0 disables pruning. |
allowOverwrite | boolean | false | Default behavior of sess save when a name already exists. |
exposeTool | boolean | true | Register sess_manage as an optional agent tool. |
httpRoutes | boolean | true | Mount /sess/* HTTP routes on the gateway. |
pruneIntervalMinutes | integer | 60 | Background retention tick. 0 disables. |
tags | object | {} | Default tags applied to every new snapshot. |
HTTP API
All requests require normal OpenClaw gateway auth.
| Method | Path | Body | Response |
|---|---|---|---|
GET | /sess/list | — query params: prefix, tag, limit, includeAuto | SnapshotMeta[] |
POST | /sess/save | { name, note?, tags?, overwrite? } | { text, data: SnapshotMeta } |
POST | /sess/load | { name, backup? } | { text, data: SnapshotMeta } |
POST | /sess/new | { name?, from?, backup?, note? } | { text, data } |
POST | /sess/copy | { from, to, overwrite?, note? } | { text, data: SnapshotMeta } |
POST | /sess/rename | { from, to, overwrite? } | { text, data: SnapshotMeta } |
POST | /sess/delete | { name } or { prefix, confirm: true } | { text, data } |
Errors come back with status 400 and an { error } or { text } body.
Architecture
┌──────────────────────── user surfaces ────────────────────────┐
│ slash command agent tool CLI HTTP routes gateway RPC │
└──────────┬──────────────────────────────────────────┬─────────┘
│ all five normalize into an argv[] │
▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ src/command.ts │
│ parseArgv + dispatch() → subcommand handlers │
└──────────────┬────────────────────────────────┬──────────────┘
│ │
▼ ▼
┌────────────────┐ ┌────────────────────┐
│ src/store.ts │ │ src/subcommands/* │
│ (filesystem) │ │ (business logic) │
└────────────────┘ └────────────────────┘
Design rules this plugin follows:
- Single dispatcher. Every surface (command / tool / CLI / HTTP / RPC) lowers
to the same
dispatch(rt, argv)call. One code path to test, one set of behaviors to document, no drift. - Strict name validation at the boundary. No user string touches the
filesystem without passing
NAME_PATTERN. Path traversal is structurally impossible. - Atomic writes.
<name>.sess.json.tmp→rename(). A crash mid-write can leave an orphan tmp file but can't corrupt the live snapshot. - Runtime via
createPluginRuntimeStore. No unguarded module-levelletstate — the runtime is registered on plugin init and throws a clear error if anything tries to reach it beforeregister(api)has run. - Narrow SDK imports.
openclaw/plugin-sdk/plugin-entryandopenclaw/plugin-sdk/runtime-storeonly — we never reach through the monolithic root barrel.
Development
git clone https://github.com/detain/openclaw-sess.git
cd openclaw-sess
npm install
npm test # run vitest
npm run typecheck # tsc --noEmit
Running the full suite
npm test -- --run # one-shot
npm run test:watch # watch mode
Installing locally for manual testing
openclaw plugins install ./
openclaw gateway restart
openclaw plugins inspect sess
Publishing to ClawHub
sess is a code-plugin, not a skill, so use the package-family-aware
publish command:
# dry run first
clawhub package publish ./ --family code-plugin --dry-run
# real publish
clawhub package publish ./ \
--family code-plugin \
--source-repo detain/openclaw-sess
The --family code-plugin flag is important. Plain clawhub publish /
clawhub skill publish will fail with "missing SKILL.md" (it's looking
for a skill) and "not a valid hook pack: package.json missing
openclaw.hooks" (it fell back to hook-pack detection). Native code plugins
don't need either of those — they declare their family explicitly via the
openclaw block in package.json.
Required metadata (already set in this package):
{
"openclaw": {
"family": "code-plugin",
"extensions": ["./index.ts"],
"compat": {
"pluginApi": ">=2026.4.15",
"minGatewayVersion": "2026.4.15"
},
"build": {
"openclawVersion": "2026.4.15",
"pluginSdkVersion": "2026.4.15"
}
}
}
Bump the compat / build fields whenever the plugin is tested against a
newer gateway or SDK release — ClawHub uses compat.pluginApi to gate
installs on incompatible hosts before the archive is even extracted, so
keeping them honest protects users from half-installed plugins.
FAQ
Where do snapshots actually live?
By default, <agentDir>/sessions/<name>.sess.json. Set storageDir in config
to point elsewhere. Files are chmod 0600, directory is 0700.
What happens if I have two OpenClaw agents running?
Because storageDir resolves relative paths against the active agent dir,
each agent gets its own snapshot pool by default. Set an absolute storageDir
if you want them to share.
Is `sess load` destructive?
Not by default — we snapshot the live session as pre-load~<timestamp> first.
Pass --no-backup to skip that, or sess delete --prefix=pre-load~ --confirm
to clean them up later.
What's the `~` suffix about?
A convention, not syntax. Names ending in ~ are "auto-managed" and evict
first when the retention cap kicks in. autosave~, pre-load~<ts>, and
pre-new~<ts> all use it. Your named snapshots stay safe.
Does this work with OpenClaw bundles (Claude / Codex / Cursor)?
No — sess is a full native plugin (registers capabilities, has a runtime). Bundles can't do that. But you can freely mix: install sess as a native plugin alongside any bundles you're using.
Can I disable any of the five surfaces?
Yes. exposeTool: false drops the agent tool, httpRoutes: false drops the
HTTP API. The slash command, CLI, and gateway RPC are always on because they
have no security surface beyond what OpenClaw itself enforces.
Contributing
PRs welcome. Please:
- Keep the single-dispatcher invariant — every new feature should lower to an
argv passed to
dispatch(). - Add tests. There's no config option for "how much coverage do you want" — there's just the coverage.
- Don't add new top-level SDK subpath imports without checking
scripts/lib/plugin-sdk-entrypoints.jsonin the OpenClaw repo.
License
MIT © Detain
Built with ☕ and too many conversations I wish I could go back to.
