feat(session): integrate JSONL persistence into agent loop#1170
feat(session): integrate JSONL persistence into agent loop#1170yinwm merged 8 commits intosipeed:mainfrom
Conversation
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 sipeed#1169
8 tests covering the full SessionStore contract through the JSONL backend: message roundtrip, tool calls, summary, truncation with compaction, history replacement, empty sessions, session isolation, and the complete summarization flow (SetSummary → TruncateHistory → Save). Includes compile-time interface satisfaction checks for both SessionManager and JSONLBackend. Part of sipeed#1169
Replace the concrete *SessionManager field with the SessionStore interface and initialize the JSONL backend by default. Legacy .json session files are auto-migrated on first startup. Falls back to SessionManager if the JSONL store cannot be initialized. The agent loop code (loop.go) requires zero changes — all method calls work identically through the interface. Closes sipeed#1169
Code ReviewOverview
✅ Strengths
|
| Operation | Before (SessionManager) | After (JSONLBackend) |
|---|---|---|
| AddMessage | In-memory, waits for Save() | JSONL append + fsync (immediate durability) |
| Save() | Full JSON rewrite | Compact only if truncated (often no-op) |
| TruncateHistory | O(n) array copy + rewrite | O(1) metadata update |
| Crash recovery | Lose messages since last Save() | No data loss |
Summary
| Category | Assessment |
|---|---|
| Architecture | ✅ Good |
| Code quality | |
| Test coverage | ✅ Good |
| Documentation | ✅ Clear PR description |
| Risk | ✅ Low |
Recommendation: Fix Issue 1 before merging. Issue 2 can be addressed in a follow-up PR if preferred.
Overall, this is a well-designed integration. Nice work! 🎉
Save() was swallowing the error returned by Compact and always returning nil. Callers checking Save's return value would never see a compaction failure. Return the error directly so the agent loop can log or handle it as needed.
Add Close() error to SessionStore so callers can release resources through the interface. JSONLBackend already had Close; this adds a no-op implementation to SessionManager for compatibility.
|
@yinwm 感谢 review!两个问题都修了: Issue 1 — Issue 2 — 两个 fix 分开提交的:
|
PR Review: JSONL Session Store IntegrationThanks for this well-structured PR! The interface abstraction is clean and the migration strategy is solid. Here are my findings: ✅ Strengths
|
| Area | Status |
|---|---|
| Design | ✅ Excellent |
| Code Quality | ✅ Good |
| Tests | ✅ Good |
| Backward Compatibility | ✅ Excellent |
| Resource Management |
Recommendation: Please address the Close() resource leak before merging. The rest looks good!
🤖 Generated with Claude Code
- Add Close() to AgentInstance, AgentRegistry, and AgentLoop so JSONL file handles are released during gateway shutdown and CLI exit. - Fall back to SessionManager when migration fails, preventing a split state where some sessions live in JSONL and others remain in JSON. - Add defer agentLoop.Close() in the CLI agent command path. - Document SessionStore interface methods (fire-and-forget contract).
|
@yinwm 感谢二次 review!两个问题都修了: Issue 1: Close() 资源泄漏 — 给 AgentInstance、AgentRegistry、AgentLoop 都加了 Close(),形成完整的关闭链路:
Issue 2: 迁移失败处理 — migration 失败现在会 close 掉 JSONL store 并回退到 SessionManager,避免出现 JSON/JSONL 混合的脑裂状态。之前只是 log 然后继续用 JSONL store,但 migration 失败说明 store 写入本身有问题,继续用没意义。 Minor: 接口文档 — SessionStore 每个方法都加了注释,特别说明了 write 方法的 fire-and-forget 契约。 commit: de9d470 |
yinwm
left a comment
There was a problem hiding this comment.
Re-Review: All Issues Addressed ✅
Thanks for the quick fixes! All the concerns from my previous review have been properly addressed:
✅ Resource Management Fixed
The close chain is now complete:
CLI/Gateway → AgentLoop.Close() → AgentRegistry.Close() → AgentInstance.Close() → Sessions.Close()
AgentInstance.Close()- releases session store resourcesAgentLoop.Close()- delegates to registryAgentRegistry.Close()- iterates all agents and closes them- Both CLI and gateway entry points now call
Close()on shutdown
✅ Migration Failure Handling Fixed
if merr != nil {
store.Close() // Clean up failed store
return session.NewSessionManager(dir) // Fallback
}This prevents the split state issue I was concerned about.
✅ Interface Documentation Added
SessionStore now has clear method documentation explaining the fire-and-forget contract.
LGTM! This is ready to merge. 🚀
* feat(session): add SessionStore interface and JSONL backend adapter 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 sipeed#1169 * test(session): add JSONLBackend integration tests 8 tests covering the full SessionStore contract through the JSONL backend: message roundtrip, tool calls, summary, truncation with compaction, history replacement, empty sessions, session isolation, and the complete summarization flow (SetSummary → TruncateHistory → Save). Includes compile-time interface satisfaction checks for both SessionManager and JSONLBackend. Part of sipeed#1169 * feat(agent): wire JSONL session store into agent loop Replace the concrete *SessionManager field with the SessionStore interface and initialize the JSONL backend by default. Legacy .json session files are auto-migrated on first startup. Falls back to SessionManager if the JSONL store cannot be initialized. The agent loop code (loop.go) requires zero changes — all method calls work identically through the interface. Closes sipeed#1169 * fix(session): propagate compact error from Save Save() was swallowing the error returned by Compact and always returning nil. Callers checking Save's return value would never see a compaction failure. Return the error directly so the agent loop can log or handle it as needed. * feat(session): add Close to SessionStore interface Add Close() error to SessionStore so callers can release resources through the interface. JSONLBackend already had Close; this adds a no-op implementation to SessionManager for compatibility. * fix(session): close session stores on shutdown and harden migration - Add Close() to AgentInstance, AgentRegistry, and AgentLoop so JSONL file handles are released during gateway shutdown and CLI exit. - Fall back to SessionManager when migration fails, preventing a split state where some sessions live in JSONL and others remain in JSON. - Add defer agentLoop.Close() in the CLI agent command path. - Document SessionStore interface methods (fire-and-forget contract).
* feat(session): add SessionStore interface and JSONL backend adapter 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 sipeed#1169 * test(session): add JSONLBackend integration tests 8 tests covering the full SessionStore contract through the JSONL backend: message roundtrip, tool calls, summary, truncation with compaction, history replacement, empty sessions, session isolation, and the complete summarization flow (SetSummary → TruncateHistory → Save). Includes compile-time interface satisfaction checks for both SessionManager and JSONLBackend. Part of sipeed#1169 * feat(agent): wire JSONL session store into agent loop Replace the concrete *SessionManager field with the SessionStore interface and initialize the JSONL backend by default. Legacy .json session files are auto-migrated on first startup. Falls back to SessionManager if the JSONL store cannot be initialized. The agent loop code (loop.go) requires zero changes — all method calls work identically through the interface. Closes sipeed#1169 * fix(session): propagate compact error from Save Save() was swallowing the error returned by Compact and always returning nil. Callers checking Save's return value would never see a compaction failure. Return the error directly so the agent loop can log or handle it as needed. * feat(session): add Close to SessionStore interface Add Close() error to SessionStore so callers can release resources through the interface. JSONLBackend already had Close; this adds a no-op implementation to SessionManager for compatibility. * fix(session): close session stores on shutdown and harden migration - Add Close() to AgentInstance, AgentRegistry, and AgentLoop so JSONL file handles are released during gateway shutdown and CLI exit. - Fall back to SessionManager when migration fails, preventing a split state where some sessions live in JSONL and others remain in JSON. - Add defer agentLoop.Close() in the CLI agent command path. - Document SessionStore interface methods (fire-and-forget contract).
* feat(session): add SessionStore interface and JSONL backend adapter 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 sipeed#1169 * test(session): add JSONLBackend integration tests 8 tests covering the full SessionStore contract through the JSONL backend: message roundtrip, tool calls, summary, truncation with compaction, history replacement, empty sessions, session isolation, and the complete summarization flow (SetSummary → TruncateHistory → Save). Includes compile-time interface satisfaction checks for both SessionManager and JSONLBackend. Part of sipeed#1169 * feat(agent): wire JSONL session store into agent loop Replace the concrete *SessionManager field with the SessionStore interface and initialize the JSONL backend by default. Legacy .json session files are auto-migrated on first startup. Falls back to SessionManager if the JSONL store cannot be initialized. The agent loop code (loop.go) requires zero changes — all method calls work identically through the interface. Closes sipeed#1169 * fix(session): propagate compact error from Save Save() was swallowing the error returned by Compact and always returning nil. Callers checking Save's return value would never see a compaction failure. Return the error directly so the agent loop can log or handle it as needed. * feat(session): add Close to SessionStore interface Add Close() error to SessionStore so callers can release resources through the interface. JSONLBackend already had Close; this adds a no-op implementation to SessionManager for compatibility. * fix(session): close session stores on shutdown and harden migration - Add Close() to AgentInstance, AgentRegistry, and AgentLoop so JSONL file handles are released during gateway shutdown and CLI exit. - Fall back to SessionManager when migration fails, preventing a split state where some sessions live in JSONL and others remain in JSON. - Add defer agentLoop.Close() in the CLI agent command path. - Document SessionStore interface methods (fire-and-forget contract).
Includes JSONL session persistence (sipeed#1170), spawn_status tool, Azure provider, credential encryption, and various fixes. SubTurn features preserved and integrated with new spawn_status functionality.
Summary
Wires the
pkg/memoryJSONL store (merged in #732) into the agent loop, replacing the full-JSON serialization backend. This is the integration step that makes the JSONL persistence actually active.Key design decision: extract a
SessionStoreinterface fromSessionManager, so the agent loop doesn't know or care which backend is in use.Changes
pkg/session/session_store.go—SessionStoreinterface with the 8 methods the agent loop calls. BothSessionManagerandJSONLBackendsatisfy it.pkg/session/jsonl_backend.go— Adapter that wrapsmemory.StoreintoSessionStore. Write errors are logged (matchingSessionManager's fire-and-forget contract).Save()triggers compaction to reclaim space after truncation.pkg/agent/instance.go— Field type changed from `*SessionManager` to `SessionStore`. JSONL backend initialized by default with auto-migration from legacy `.json` files. Falls back to `SessionManager` if initialization fails.What changes at runtime
What does NOT change
loop.go— zero modifications. Every method call works identically through the interface.loop_test.go— zero modifications. Tests createSessionManagerwhich still satisfies the interface..jsonfiles are auto-migrated to.jsonlon first startup, originals renamed to.json.migrated.Test plan
8 new tests in
pkg/session/jsonl_backend_test.go:Plus compile-time interface checks for both backends.
Type of change
Closes #1169 — follows up on #732.