You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The current session system (pkg/session/manager.go) stores everything in an in-memory map and serializes full JSON files to disk on every Save(). This works for simple cases, but breaks down in a few real scenarios:
Full serialization is expensive — every Save() marshals the entire session to JSON and does a temp-file-rename dance. On embedded devices with limited I/O bandwidth, this adds latency that users can feel.
No session lifecycle management — there's no way to expire old sessions, no size limits, no cleanup. On a long-running gateway, sessions/ just grows forever.
I ran into the race condition issue (#704) myself while testing on a Pi, and when I dug into the session code I realized the problems go deeper than just adding a mutex.
💡 Proposed Solution
Replace the JSON file backend with SQLite, using a pure-Go driver (modernc.org/sqlite or crawshaw.io/sqlite) so we keep zero-CGo cross-compilation.
The idea is to keep SessionManager's public API mostly the same so nothing upstream breaks, but swap the storage layer underneath.
AddMessage becomes an INSERT instead of full-file rewrite
TruncateHistory + Save can run in a transaction — no more race condition
Session expiry is just DELETE FROM sessions WHERE updated_at < ?
Later, a memory table can hold long-term facts for multi-agent shared context
🛠 Potential Implementation
I'd break this into a few PRs:
PR 1: New pkg/memory/ package with a Store interface + SQLite implementation + tests. Doesn't touch anything existing yet.
PR 2: Wire it into AgentLoop — replace SessionManager.Save() calls with the new store. Keep the JSON path as fallback (auto-detect: if sessions/*.json exists, migrate on first load).
PR 3: Add lifecycle management — TTL-based session expiry, configurable max message count per session, /session stats command or similar.
I want to keep the interface clean enough that someone could later swap in a different backend (Postgres for a server deployment, etc.) without touching agent code.
🚦 Impact & Roadmap Alignment
This is a Core Feature
This is a Nice-to-Have / Enhancement
This aligns with the current Roadmap
This was specifically mentioned in the 26M2W3 community meeting notes under the Agent board: "记忆系统:引入 SQLite". It also directly addresses #704 (race condition) and provides the foundation for #294 (multi-agent shared context).
bbolt / badger (embedded KV stores): More overhead than SQLite for structured data. We need relational queries (messages by session, ordered by time) and SQLite is purpose-built for this.
Keep JSON but write incrementally (append-only log): Considered this. It handles the write performance, but makes truncation/expiry complicated and doesn't help with the shared memory use case.
💬 Additional Context
I've already had two PRs merged (#173, #186) and I'm familiar with the session/agent code paths. Happy to take this on if the direction looks right.
Would appreciate feedback from the agent board maintainers on:
Is modernc.org/sqlite acceptable, or is there a preference for another driver? (It adds ~3-4MB to the binary — need to check if that fits the 20M target from the meeting notes)
Should the migration from JSON be automatic, or a separate picoclaw migrate subcommand?
Any concerns about SQLite on the target embedded platforms?
🎯 The Goal / Use Case
The current session system (
pkg/session/manager.go) stores everything in an in-memory map and serializes full JSON files to disk on everySave(). This works for simple cases, but breaks down in a few real scenarios:maybeSummarize()launches a background goroutine that callsTruncateHistory()+Save()while new messages are still being appended. This can corrupt persisted history and cause 400 errors on the next LLM call (see Race condition in session history causes "tool_call_ids did not have response messages" (HTTP 400) #704).Save()marshals the entire session to JSON and does a temp-file-rename dance. On embedded devices with limited I/O bandwidth, this adds latency that users can feel.sessions/just grows forever.I ran into the race condition issue (#704) myself while testing on a Pi, and when I dug into the session code I realized the problems go deeper than just adding a mutex.
💡 Proposed Solution
Replace the JSON file backend with SQLite, using a pure-Go driver (
modernc.org/sqliteorcrawshaw.io/sqlite) so we keep zero-CGo cross-compilation.The idea is to keep
SessionManager's public API mostly the same so nothing upstream breaks, but swap the storage layer underneath.Rough schema I'm thinking:
Key benefits:
AddMessagebecomes an INSERT instead of full-file rewriteTruncateHistory+Savecan run in a transaction — no more race conditionDELETE FROM sessions WHERE updated_at < ?memorytable can hold long-term facts for multi-agent shared context🛠 Potential Implementation
I'd break this into a few PRs:
PR 1: New
pkg/memory/package with aStoreinterface + SQLite implementation + tests. Doesn't touch anything existing yet.PR 2: Wire it into
AgentLoop— replaceSessionManager.Save()calls with the new store. Keep the JSON path as fallback (auto-detect: ifsessions/*.jsonexists, migrate on first load).PR 3: Add lifecycle management — TTL-based session expiry, configurable max message count per session,
/session statscommand or similar.I want to keep the interface clean enough that someone could later swap in a different backend (Postgres for a server deployment, etc.) without touching agent code.
🚦 Impact & Roadmap Alignment
This was specifically mentioned in the 26M2W3 community meeting notes under the Agent board: "记忆系统:引入 SQLite". It also directly addresses #704 (race condition) and provides the foundation for #294 (multi-agent shared context).
🔄 Alternatives Considered
💬 Additional Context
I've already had two PRs merged (#173, #186) and I'm familiar with the session/agent code paths. Happy to take this on if the direction looks right.
Would appreciate feedback from the agent board maintainers on:
modernc.org/sqliteacceptable, or is there a preference for another driver? (It adds ~3-4MB to the binary — need to check if that fits the 20M target from the meeting notes)picoclaw migratesubcommand?