feat(sessions): directory-per-session store#42186
feat(sessions): directory-per-session store#42186Diaspar4u wants to merge 1 commit intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR replaces the monolithic
Three style-level observations (no correctness bugs):
Confidence Score: 4/5
Last reviewed commit: 61f5ebc |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 61f5ebc536
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Closing for rework. Will resubmit with fixes. |
Summary
sessions.jsonfile creates write contention on multi-agent gateways. Every session update (any agent, any channel) serializes the entire store to a single file, protected by a process-wide lock. On gateways with many agents and active sessions, this causes lock contention, increased write latency, and unnecessarily large I/O (rewriting hundreds of KB for a single session change).sessions.d/directory (sibling to the legacysessions.json). Each file is named{sanitized-session-key}.jsonand contains a singleSessionEntry. Writes are atomic (tmp + rename) and diff-based -- only changed/removed entries touch disk. A one-time auto-migration convertssessions.jsontosessions.d/on gateway startup, backing up the original file. All existing store operations (loadSessionStore,saveSessionStore,updateSessionStore,updateSessionStoreEntry,updateLastRoute,readSessionUpdatedAt) transparently use the directory layout whensessions.d/exists.SessionEntrytype, the session key normalization logic, the write lock semantics, or any consumer of the session store API. The legacy JSON path is fully preserved for installations that have not yet migrated.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
sessions.jsonis automatically migrated to asessions.d/directory. The original file is renamed tosessions.json.bak.<timestamp>.readSessionUpdatedAtfor directory stores reads only the single target file instead of loading the entire store -- faster for large session sets.Security Impact (required)
Repro + Verification
Environment
Steps
sessions.jsonfile(s)sessions.d/directory exists with individual JSON filesExpected
Actual (before this PR)
sessions.jsonfile, full rewrite on every updateEvidence
Attach at least one:
New test suite:
src/config/sessions/store.directory.test.ts(723 lines, 30+ tests) covering:readSessionUpdatedAtfast path (reads single file, not entire store)updateSessionStoreEntryandupdateLastRoutewith directory storeHuman Verification (required)
sessions.json, ongoing session updates post-migration,readSessionUpdatedAtreads single file.updatedAt), migration with pre-existingsessions.d/(merge mode, skips existing entries), sessions with colons/slashes in keys (sanitized for filesystem safety), Windows path separators.Review Conversations
Compatibility / Migration
sessions.jsonis backed up tosessions.json.bak.<timestamp>.Failure Recovery (if this breaks)
sessions.json.bak.*back tosessions.jsonand delete thesessions.d/directory. The code falls back to legacy JSON mode when nosessions.d/directory exists.sessions.jsonfrom the.bakfiledeleteSessionEntryFromDirissue).Risks and Mitigations
sessions.d.migrating/) with atomic rename. If the gateway crashes during migration, the staging dir is cleaned up on next attempt. The legacysessions.jsonis not removed until all entries are written.loadSessionStoreArchitecture
Locking model: All write paths (
updateSessionStore,updateSessionStoreEntry,updateLastRoute) hold the same per-storePath lock (viawithSessionStoreLock). This serializes write syscalls for individual ~1KB session files, which is the correct granularity -- the lock covers the read-mutate-write cycle for a single session entry. TheupdateSessionStorepath takes apreviousSnapshotbefore mutation and usescomputeStoreDiffto write only changed/removed entries.Migration: Runs once per agent on gateway startup (
server.impl.ts). Scans all agent session directories on disk (vialistAgentSessionDirs) so sub-agents and ephemeral agents are covered. Also handles customsession.storeconfig paths. Migration is idempotent -- safe to run multiple times.Key sanitization: Session keys can contain colons (e.g.,
telegram:12345:67890), slashes, and backslashes. These are percent-encoded for filesystem safety (sanitizeSessionKey/desanitizeSessionKey). The encoding is reversible and handles the%character itself to prevent double-encoding.Cache integration: Directory mode uses TTL-only caching (no mtime/size checks, since individual file mtimes don't reliably propagate to directory mtime). Cache is invalidated explicitly on writes via
invalidateSessionStoreCache.AI Disclosure
AI-assisted (Claude). Fully tested and reviewed.
Resubmission of #39991 (closed for clean commit history).