Hooks: persist session memory on /reset

This commit is contained in:
Vignesh Natarajan 2026-02-20 20:19:29 -08:00
parent 544c213d42
commit d583399c92
8 changed files with 39 additions and 19 deletions

View File

@ -45,6 +45,7 @@ Docs: https://docs.openclaw.ai
- Gateway/Pairing: clear persisted paired-device state when the gateway client closes with `device token mismatch` (`1008`) so reconnect flows can cleanly re-enter pairing. (#22071) Thanks @mbelinky.
- Memory/QMD: respect per-agent `memorySearch.enabled=false` during gateway QMD startup initialization, split multi-collection QMD searches into per-collection queries (`search`/`vsearch`/`query`) to avoid sparse-term drops, prefer collection-hinted doc resolution to avoid stale-hash collisions, retry boot updates on transient lock/timeout failures, skip `qmd embed` in BM25-only `search` mode (including `memory index --force`), and serialize embed runs globally with failure backoff to prevent CPU storms on multi-agent hosts. (#20581, #21590, #20513, #20001, #21266, #21583, #20346, #19493) Thanks @danielrevivo, @zanderkrause, @sunyan034-cmd, @tilleulenspiegel, @dae-oss, @adamlongcreativellc, @jonathanadams96, and @kiliansitel.
- Memory/Builtin: prevent automatic sync races with manager shutdown by skipping post-close sync starts and waiting for in-flight sync before closing SQLite, so `onSearch`/`onSessionStart` no longer fail with `database is not open` in ephemeral CLI flows. (#20556, #7464) Thanks @FuzzyTG and @henrybottter.
- Hooks/Session memory: trigger bundled `session-memory` persistence on both `/new` and `/reset` so reset flows no longer skip markdown transcript capture before archival. (#21382) Thanks @mofesolapaul.
- Signal/Outbound: preserve case for Base64 group IDs during outbound target normalization so cross-context routing and policy checks no longer break when group IDs include uppercase characters. (#5578) Thanks @heyhudson.
- Providers/Copilot: drop persisted assistant `thinking` blocks for Claude models (while preserving turn structure/tool blocks) so follow-up requests no longer fail on invalid `thinkingSignature` payloads. (#19459) Thanks @jackheuberger.
- Providers/Copilot: add `claude-sonnet-4.6` and `claude-sonnet-4.5` to the default GitHub Copilot model catalog and add coverage for model-list/definition helpers. (#20270, fixes #20091) Thanks @Clawborn.

View File

@ -86,13 +86,13 @@ describe("onboard-hooks", () => {
createMockHook(
{
name: "session-memory",
description: "Save session context to memory when /new command is issued",
description: "Save session context to memory when /new or /reset command is issued",
filePath: "/mock/workspace/hooks/session-memory/HOOK.md",
baseDir: "/mock/workspace/hooks/session-memory",
handlerPath: "/mock/workspace/hooks/session-memory/handler.js",
hookKey: "session-memory",
emoji: "💾",
events: ["command:new"],
events: ["command:new", "command:reset"],
},
eligible,
),
@ -147,7 +147,7 @@ describe("onboard-hooks", () => {
{
value: "session-memory",
label: "💾 session-memory",
hint: "Save session context to memory when /new command is issued",
hint: "Save session context to memory when /new or /reset command is issued",
},
{
value: "command-logger",

View File

@ -13,7 +13,7 @@ export async function setupInternalHooks(
await prompter.note(
[
"Hooks let you automate actions when agent commands are issued.",
"Example: Save session context to memory when you issue /new.",
"Example: Save session context to memory when you issue /new or /reset.",
"",
"Learn more: https://docs.openclaw.ai/automation/hooks",
].join("\n"),

View File

@ -6,9 +6,9 @@ This directory contains hooks that ship with OpenClaw. These hooks are automatic
### 💾 session-memory
Automatically saves session context to memory when you issue `/new`.
Automatically saves session context to memory when you issue `/new` or `/reset`.
**Events**: `command:new`
**Events**: `command:new`, `command:reset`
**What it does**: Creates a dated memory file with LLM-generated slug based on conversation content.
**Output**: `<workspace>/memory/YYYY-MM-DD-slug.md` (defaults to `~/.openclaw/workspace`)

View File

@ -1,13 +1,13 @@
---
name: session-memory
description: "Save session context to memory when /new command is issued"
description: "Save session context to memory when /new or /reset command is issued"
homepage: https://docs.openclaw.ai/automation/hooks#session-memory
metadata:
{
"openclaw":
{
"emoji": "💾",
"events": ["command:new"],
"events": ["command:new", "command:reset"],
"requires": { "config": ["workspace.dir"] },
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with OpenClaw" }],
},
@ -16,11 +16,11 @@ metadata:
# Session Memory Hook
Automatically saves session context to your workspace memory when you issue the `/new` command.
Automatically saves session context to your workspace memory when you issue `/new` or `/reset`.
## What It Does
When you run `/new` to start a fresh session:
When you run `/new` or `/reset` to start a fresh session:
1. **Finds the previous session** - Uses the pre-reset session entry to locate the correct transcript
2. **Extracts conversation** - Reads the last N user/assistant messages from the session (default: 15, configurable)

View File

@ -44,8 +44,9 @@ async function runNewWithPreviousSessionEntry(params: {
tempDir: string;
previousSessionEntry: { sessionId: string; sessionFile?: string };
cfg?: OpenClawConfig;
action?: "new" | "reset";
}): Promise<{ files: string[]; memoryContent: string }> {
const event = createHookEvent("command", "new", "agent:main:main", {
const event = createHookEvent("command", params.action ?? "new", "agent:main:main", {
cfg:
params.cfg ??
({
@ -66,6 +67,7 @@ async function runNewWithPreviousSessionEntry(params: {
async function runNewWithPreviousSession(params: {
sessionContent: string;
cfg?: (tempDir: string) => OpenClawConfig;
action?: "new" | "reset";
}): Promise<{ tempDir: string; files: string[]; memoryContent: string }> {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const sessionsDir = path.join(tempDir, "sessions");
@ -86,6 +88,7 @@ async function runNewWithPreviousSession(params: {
const { files, memoryContent } = await runNewWithPreviousSessionEntry({
tempDir,
cfg,
action: params.action,
previousSessionEntry: {
sessionId: "test-123",
sessionFile,
@ -158,6 +161,21 @@ describe("session-memory hook", () => {
expect(memoryContent).toContain("assistant: 2+2 equals 4");
});
it("creates memory file with session content on /reset command", async () => {
const sessionContent = createMockSessionContent([
{ role: "user", content: "Please reset and keep notes" },
{ role: "assistant", content: "Captured before reset" },
]);
const { files, memoryContent } = await runNewWithPreviousSession({
sessionContent,
action: "reset",
});
expect(files.length).toBe(1);
expect(memoryContent).toContain("user: Please reset and keep notes");
expect(memoryContent).toContain("assistant: Captured before reset");
});
it("filters out non-message entries (tool calls, system)", async () => {
// Create session with mixed entry types
const sessionContent = createMockSessionContent([

View File

@ -1,7 +1,7 @@
/**
* Session memory hook handler
*
* Saves session context to memory when /new command is triggered
* Saves session context to memory when /new or /reset command is triggered
* Creates a new dated memory file with LLM-generated slug
*/
@ -167,16 +167,17 @@ async function findPreviousSessionFile(params: {
}
/**
* Save session context to memory when /new command is triggered
* Save session context to memory when /new or /reset command is triggered
*/
const saveSessionToMemory: HookHandler = async (event) => {
// Only trigger on 'new' command
if (event.type !== "command" || event.action !== "new") {
// Only trigger on reset/new commands
const isResetCommand = event.action === "new" || event.action === "reset";
if (event.type !== "command" || !isResetCommand) {
return;
}
try {
log.debug("Hook triggered for /new command");
log.debug("Hook triggered for reset/new command", { action: event.action });
const context = event.context || {};
const cfg = context.cfg as OpenClawConfig | undefined;

View File

@ -232,14 +232,14 @@ describe("resolveOpenClawMetadata", () => {
// This is the actual format used in the bundled hooks
const content = `---
name: session-memory
description: "Save session context to memory when /new command is issued"
description: "Save session context to memory when /new or /reset command is issued"
homepage: https://docs.openclaw.ai/automation/hooks#session-memory
metadata:
{
"openclaw":
{
"emoji": "💾",
"events": ["command:new"],
"events": ["command:new", "command:reset"],
"requires": { "config": ["workspace.dir"] },
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with OpenClaw" }],
},
@ -256,7 +256,7 @@ metadata:
const openclaw = resolveOpenClawMetadata(frontmatter);
expect(openclaw).toBeDefined();
expect(openclaw?.emoji).toBe("💾");
expect(openclaw?.events).toEqual(["command:new"]);
expect(openclaw?.events).toEqual(["command:new", "command:reset"]);
expect(openclaw?.requires?.config).toEqual(["workspace.dir"]);
expect(openclaw?.install?.[0].kind).toBe("bundled");
});