fix(acp): replace direct db._lock/_conn access with public update_session_meta()

session.py _persist() bypassed SessionDB's thread-safe write path by
accessing private internals db._lock and db._conn directly:

    with db._lock:
        db._conn.execute("UPDATE sessions SET model_config = ? ...")
        db._conn.commit()

This was fragile for three reasons:
1. It bypassed _execute_write()'s BEGIN IMMEDIATE + jitter-retry logic,
   so concurrent writes could hit SQLite BUSY without retrying.
2. It called db._conn.commit() manually, breaking the transactional
   contract that _execute_write() enforces.
3. Any internal rename of _lock or _conn would silently break this
   call site with an AttributeError at runtime.

Fix:
- Add SessionDB.update_session_meta(session_id, model_config_json, model)
  to hermes_state.py. Routes through _execute_write() for the standard
  BEGIN IMMEDIATE + lock + jitter-retry guarantee. Uses COALESCE so
  passing model=None leaves the stored model column unchanged.
- Replace the db._lock / db._conn block in session.py _persist() with
  a single db.update_session_meta() call.

Tests (tests/acp/test_session_db_private_access.py, 11 tests):
- Unit tests for update_session_meta: updates model_config, updates
  model, preserves existing model on None, routes through _execute_write,
  no-op on non-existent session.
- AST checks: db._lock and db._conn not referenced in session.py;
  _persist() calls update_session_meta().
- Integration round-trips: cwd and model persisted correctly; COALESCE
  prevents overwriting an existing model with NULL.
This commit is contained in:
kewe63
2026-06-04 17:54:59 -07:00
committed by Teknium
parent d33d23c852
commit 19db9cd076
3 changed files with 220 additions and 6 deletions
+18
View File
@@ -1104,6 +1104,24 @@ class SessionDB:
return None
return row["holder"] if isinstance(row, sqlite3.Row) else row[0]
def update_session_meta(
self,
session_id: str,
model_config_json: str,
model: Optional[str] = None,
) -> None:
"""Update model_config and optionally model for an existing session.
Uses COALESCE so that passing model=None leaves the stored model
column unchanged. Routes through _execute_write for the standard
BEGIN IMMEDIATE + jitter-retry + lock guarantee.
"""
def _do(conn):
conn.execute(
"UPDATE sessions SET model_config = ?, model = COALESCE(?, model) WHERE id = ?",
(model_config_json, model, session_id),
)
self._execute_write(_do)
def update_system_prompt(self, session_id: str, system_prompt: str) -> None:
"""Store the full assembled system prompt snapshot."""