Skip to content

🪲 BUG-#19: Persist only new messages instead of rewriting the whole session - #54

Merged
FernandoCelmer merged 5 commits into
masterfrom
feature/19
Aug 16, 2026
Merged

🪲 BUG-#19: Persist only new messages instead of rewriting the whole session#54
FernandoCelmer merged 5 commits into
masterfrom
feature/19

Conversation

@FernandoCelmer

@FernandoCelmer FernandoCelmer commented Aug 15, 2026

Copy link
Copy Markdown
Member

Description

  • pycodeloop/core/session.py — Adds a dirty: bool field (default False) to Session. replace_messages(), trim() (when it actually truncates), and _repair_dangling_tool_calls() (when it inserts synthetic results) now set dirty = True to signal that history was mutated rather than appended.
  • pycodeloop/store/sqlite_sessions.pypost() now appends only the messages added since the last save (tracked by record.message_count), falling back to a full delete-and-reinsert when the session is new, a different object was posted for the same key, session.dirty is set, or the message count shrank. Adds _session_identity dict to detect object identity changes across calls. Clears session.dirty and records the identity after a successful commit.
  • tests/core/test_session.py — New TestSessionDirtyFlag suite covering all dirty-marking paths and their no-op counterparts.
  • tests/store/test_sqlite_sessions.py — New integration tests spy on Query.delete to prove the incremental path skips the rewrite, and that dirty/different-object/new-session cases fall back correctly.

Motivation and Context

SqliteSessions.post() deleted and reinserted every MessageRecord on every call, even though Agent.on_message fires after each message including every tool result. For a 20-tool-call turn on a 200-message session that's 20 × (200 DELETE + 200 INSERT) = 8,000 row writes. Closes #19.

Types of changes

  • Bug fix (change that fixes an issue)
  • New feature (change which adds functionality)
  • Documentation

Checklist

  • I have performed a self-review of my own code
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the CHANGELOG
  • I have updated the documentation accordingly

Storage backends need to tell an append-only save from one where
existing history was replaced or reordered. replace_messages(),
trim() (when it actually truncates), and _repair_dangling_tool_calls()
(when it actually inserts a repair message) now set session.dirty,
giving SqliteSessions a signal to fall back to a full rewrite instead
of assuming everything before the last known count is unchanged.
…ession

post() deleted and reinserted every MessageRecord on every call, even
though Agent's on_message fires after each message including every
tool result — a 20-tool-call turn on a 200-message session did
20 × (200 DELETE + 200 INSERT) writes. Now inserts only the messages
appended since the last save (tracked via record.message_count),
falling back to the previous full delete-and-reinsert when the
session is new, a different Session object was posted for the same
key (checked via identity — a count watermark alone can't tell an
overwrite from an append), session.dirty is set, or the message
count shrank.

@FernandoCelmer FernandoCelmer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Code Review

Code issues found: 1

# Severity Comment
1 [Suggestion] session.dirty read without _lock in post()

Comment thread pycodeloop/store/sqlite_sessions.py
@FernandoCelmer FernandoCelmer added the bug Something isn't working label Aug 15, 2026
post() read session.dirty and session.messages unlocked while
_repair_dangling_tool_calls() sets dirty under session._lock, letting
a concurrent repair's synthetic messages be dropped silently. Snapshot
both fields together under the lock before touching the DB.
@FernandoCelmer
FernandoCelmer merged commit 33d564d into master Aug 16, 2026
8 checks passed
@FernandoCelmer
FernandoCelmer deleted the feature/19 branch August 16, 2026 02:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SqliteSessions.post() deletes and rewrites all messages on every incremental save — O(n) writes per tool call

1 participant