Skip to content

Commit fab221b

Browse files
committed
Add session state: save/load working state between agent instances
- New 'session' memory type for capturing in-progress work, blockers, decisions - save_session() supersedes previous session automatically - load_session() returns the most recent state for the project - MCP tools: save_session, load_session - CLI: agentmem save-session, agentmem load-session - Prevents cold-start problem when context compresses or conversations end
1 parent d101827 commit fab221b

File tree

4 files changed

+104
-1
lines changed

4 files changed

+104
-1
lines changed

agentmem/cli.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,31 @@ def stats(ctx):
187187
mem.close()
188188

189189

190+
@main.command("save-session")
191+
@click.argument("summary")
192+
@click.pass_context
193+
def save_session(ctx, summary):
194+
"""Save session state for the next agent instance."""
195+
mem = _get_mem(ctx)
196+
record = mem.save_session(summary)
197+
click.echo(f"Session saved: {record.id}")
198+
mem.close()
199+
200+
201+
@main.command("load-session")
202+
@click.pass_context
203+
def load_session(ctx):
204+
"""Load the most recent session state."""
205+
mem = _get_mem(ctx)
206+
record = mem.load_session()
207+
if record:
208+
click.echo(f"Last session ({record.created_at}):\n")
209+
click.echo(record.content)
210+
else:
211+
click.echo("No previous session found.")
212+
mem.close()
213+
214+
190215
@main.command()
191216
@click.pass_context
192217
def serve(ctx):

agentmem/core.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,51 @@ def stats(self) -> dict:
203203
"db_size_kb": round(db_size / 1024, 1),
204204
}
205205

206+
def save_session(self, summary: str, tags: list[str] | None = None) -> MemoryRecord:
207+
"""Save current session state. Supersedes any previous session for this project.
208+
209+
Call this before a conversation ends or when context is about to compress.
210+
The summary should capture: what's in progress, what's blocked, what's done,
211+
and any decisions made this session.
212+
"""
213+
# Find and supersede the previous session
214+
prev = self._conn.execute(
215+
"SELECT id FROM memories WHERE type = 'session' AND project = ? "
216+
"ORDER BY created_at DESC LIMIT 1",
217+
(self.project,),
218+
).fetchone()
219+
220+
supersedes = prev["id"] if prev else ""
221+
222+
return self.add(
223+
type="session",
224+
title=f"Session state — {self.project or 'default'}",
225+
content=summary,
226+
tags=tags or ["session", "state"],
227+
source="session",
228+
supersedes=supersedes,
229+
)
230+
231+
def load_session(self) -> MemoryRecord | None:
232+
"""Load the most recent session state for this project.
233+
234+
Call this at the start of a conversation to pick up where the last instance left off.
235+
"""
236+
row = self._conn.execute(
237+
"SELECT * FROM memories WHERE type = 'session' AND project = ? "
238+
"ORDER BY created_at DESC LIMIT 1",
239+
(self.project,),
240+
).fetchone()
241+
if not row:
242+
return None
243+
244+
self._conn.execute(
245+
"UPDATE memories SET accessed_at = ?, access_count = access_count + 1 WHERE id = ?",
246+
(_now(), row["id"]),
247+
)
248+
self._conn.commit()
249+
return _row_to_record(row)
250+
206251
def close(self):
207252
self._conn.close()
208253

agentmem/mcp_server.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,26 @@ async def list_tools():
107107
},
108108
},
109109
),
110+
Tool(
111+
name="save_session",
112+
description="Save current session state before conversation ends or context compresses. Capture: what's in progress, what's blocked, what's done, decisions made. The next agent instance loads this automatically.",
113+
inputSchema={
114+
"type": "object",
115+
"properties": {
116+
"summary": {"type": "string", "description": "Full session state: in-progress work, blocked items, completed items, key decisions"},
117+
"tags": {"type": "array", "items": {"type": "string"}, "default": ["session", "state"]},
118+
},
119+
"required": ["summary"],
120+
},
121+
),
122+
Tool(
123+
name="load_session",
124+
description="Load the most recent session state. Call this at the start of a conversation to pick up where the last instance left off.",
125+
inputSchema={
126+
"type": "object",
127+
"properties": {},
128+
},
129+
),
110130
]
111131

112132
@server.call_tool()
@@ -166,6 +186,19 @@ async def call_tool(name: str, arguments: dict):
166186
lines = [f"[{r.type}] {r.title} (id: {r.id})" for r in records]
167187
return [TextContent(type="text", text="\n".join(lines))]
168188

189+
elif name == "save_session":
190+
record = mem.save_session(
191+
summary=arguments["summary"],
192+
tags=arguments.get("tags", ["session", "state"]),
193+
)
194+
return [TextContent(type="text", text=f"Session saved: {record.id}\n{record.content[:200]}...")]
195+
196+
elif name == "load_session":
197+
record = mem.load_session()
198+
if record:
199+
return [TextContent(type="text", text=f"Last session ({record.created_at}):\n\n{record.content}")]
200+
return [TextContent(type="text", text="No previous session found.")]
201+
169202
return [TextContent(type="text", text=f"Unknown tool: {name}")]
170203

171204
import asyncio

agentmem/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from dataclasses import dataclass, field
66

7-
MEMORY_TYPES = ("setting", "bug", "decision", "procedure", "context", "feedback")
7+
MEMORY_TYPES = ("setting", "bug", "decision", "procedure", "context", "feedback", "session")
88

99

1010
@dataclass

0 commit comments

Comments
 (0)