From 1680e98414637745b9adc949312dba4b503d1911 Mon Sep 17 00:00:00 2001 From: xiaoen <2768753269@qq.com> Date: Fri, 6 Mar 2026 12:45:56 +0800 Subject: [PATCH] feat(session): add SessionStore interface and JSONL backend adapter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract a SessionStore interface from the methods the agent loop uses (AddMessage, GetHistory, SetSummary, TruncateHistory, Save, etc.). Both SessionManager and the new JSONLBackend satisfy this interface, allowing the persistence layer to be swapped transparently. JSONLBackend wraps memory.Store and maps its error-returning API to the fire-and-forget contract that the agent loop expects — write errors are logged, reads return empty defaults on failure. Save() triggers compaction to reclaim space after logical truncation. Part of #1169 --- pkg/session/jsonl_backend.go | 84 ++++++++++++++++++++++++++++++++++++ pkg/session/session_store.go | 18 ++++++++ 2 files changed, 102 insertions(+) create mode 100644 pkg/session/jsonl_backend.go create mode 100644 pkg/session/session_store.go diff --git a/pkg/session/jsonl_backend.go b/pkg/session/jsonl_backend.go new file mode 100644 index 000000000..04bbd8dc9 --- /dev/null +++ b/pkg/session/jsonl_backend.go @@ -0,0 +1,84 @@ +package session + +import ( + "context" + "log" + + "github.com/sipeed/picoclaw/pkg/memory" + "github.com/sipeed/picoclaw/pkg/providers" +) + +// JSONLBackend adapts a memory.Store into the SessionStore interface. +// Write errors are logged rather than returned, matching the fire-and-forget +// contract of SessionManager that the agent loop relies on. +type JSONLBackend struct { + store memory.Store +} + +// NewJSONLBackend wraps a memory.Store for use as a SessionStore. +func NewJSONLBackend(store memory.Store) *JSONLBackend { + return &JSONLBackend{store: store} +} + +func (b *JSONLBackend) AddMessage(sessionKey, role, content string) { + if err := b.store.AddMessage(context.Background(), sessionKey, role, content); err != nil { + log.Printf("session: add message: %v", err) + } +} + +func (b *JSONLBackend) AddFullMessage(sessionKey string, msg providers.Message) { + if err := b.store.AddFullMessage(context.Background(), sessionKey, msg); err != nil { + log.Printf("session: add full message: %v", err) + } +} + +func (b *JSONLBackend) GetHistory(key string) []providers.Message { + msgs, err := b.store.GetHistory(context.Background(), key) + if err != nil { + log.Printf("session: get history: %v", err) + return []providers.Message{} + } + return msgs +} + +func (b *JSONLBackend) GetSummary(key string) string { + summary, err := b.store.GetSummary(context.Background(), key) + if err != nil { + log.Printf("session: get summary: %v", err) + return "" + } + return summary +} + +func (b *JSONLBackend) SetSummary(key, summary string) { + if err := b.store.SetSummary(context.Background(), key, summary); err != nil { + log.Printf("session: set summary: %v", err) + } +} + +func (b *JSONLBackend) SetHistory(key string, history []providers.Message) { + if err := b.store.SetHistory(context.Background(), key, history); err != nil { + log.Printf("session: set history: %v", err) + } +} + +func (b *JSONLBackend) TruncateHistory(key string, keepLast int) { + if err := b.store.TruncateHistory(context.Background(), key, keepLast); err != nil { + log.Printf("session: truncate history: %v", err) + } +} + +// Save persists session state. Since the JSONL store fsyncs every write +// immediately, the data is already durable. Save runs compaction to reclaim +// space from logically truncated messages (no-op when there are none). +func (b *JSONLBackend) Save(key string) error { + if err := b.store.Compact(context.Background(), key); err != nil { + log.Printf("session: compact %s: %v", key, err) + } + return nil +} + +// Close releases resources held by the underlying store. +func (b *JSONLBackend) Close() error { + return b.store.Close() +} diff --git a/pkg/session/session_store.go b/pkg/session/session_store.go new file mode 100644 index 000000000..0396b49e2 --- /dev/null +++ b/pkg/session/session_store.go @@ -0,0 +1,18 @@ +package session + +import "github.com/sipeed/picoclaw/pkg/providers" + +// SessionStore defines the persistence operations used by the agent loop. +// Both SessionManager (legacy JSON backend) and JSONLBackend satisfy this +// interface, allowing the storage layer to be swapped without touching the +// agent loop code. +type SessionStore interface { + AddMessage(sessionKey, role, content string) + AddFullMessage(sessionKey string, msg providers.Message) + GetHistory(key string) []providers.Message + GetSummary(key string) string + SetSummary(key, summary string) + SetHistory(key string, history []providers.Message) + TruncateHistory(key string, keepLast int) + Save(key string) error +}