Skip to content

Commit 8425d76

Browse files
authored
fix(react_agent): persist tokens via workspace repo, not control-plane DB (#713) (#785)
* fix(react_agent): persist tokens via workspace repo, not control-plane DB (#713) _persist_token_usage called Database(...).initialize(), which runs SchemaManager.create_schema() and seeds users/api_keys/sessions + an admin@localhost row into every per-workspace state.db. It also masked #712 (initialize 'succeeded' without ever creating token_usage). Persist through a plain TokenRepository(sync_conn=...) over the workspace DB instead — reusing the existing INSERT + MetricsTracker cost calc, with no control-plane schema creation. token_usage itself is created by workspace._init_database (#712). Closes #713 * test/style: assert accounts+audit_logs absent, hoist sqlite3 import (#713 review)
1 parent 35f4744 commit 8425d76

2 files changed

Lines changed: 70 additions & 9 deletions

File tree

codeframe/core/react_agent.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import json
1212
import logging
1313
import os
14+
import sqlite3
1415
import threading
1516
from pathlib import Path
1617
from datetime import datetime, timezone
@@ -343,21 +344,27 @@ def _estimate_total_cost(self) -> float:
343344
def _persist_token_usage(self, task_id: str) -> None:
344345
"""Persist accumulated token records to the workspace database.
345346
346-
Uses MetricsTracker.record_token_usage_sync for synchronous writes.
347+
Writes through a plain per-workspace TokenRepository (issue #713): the
348+
control-plane Database.initialize() runs SchemaManager, which seeds
349+
users/api_keys/sessions + an admin row into the domain DB. The
350+
token_usage table itself is created by workspace._init_database (#712).
347351
Failures are logged but never propagated to the caller.
348-
The database connection is always closed via try/finally.
352+
The connection is always closed via try/finally.
349353
"""
350354
if not self._token_records:
351355
return
352356

353-
db = None
357+
conn = None
354358
try:
355359
from codeframe.lib.metrics_tracker import MetricsTracker
356-
from codeframe.platform_store.database import Database
360+
from codeframe.platform_store.repositories.token_repository import (
361+
TokenRepository,
362+
)
357363

358-
db = Database(str(self.workspace.db_path))
359-
db.initialize()
360-
tracker = MetricsTracker(db=db)
364+
conn = sqlite3.connect(str(self.workspace.db_path))
365+
# TokenRepository exposes save_token_usage, the only method
366+
# MetricsTracker needs; no control-plane schema is created.
367+
tracker = MetricsTracker(db=TokenRepository(sync_conn=conn))
361368

362369
# v1 tasks have integer PKs; v2 workspaces use UUID strings.
363370
# Pass the raw value — SQLite preserves the type, and downstream
@@ -386,9 +393,9 @@ def _persist_token_usage(self, task_id: str) -> None:
386393
"Token usage persistence failed for task %s", task_id, exc_info=True,
387394
)
388395
finally:
389-
if db is not None:
396+
if conn is not None:
390397
try:
391-
db.close()
398+
conn.close()
392399
except Exception:
393400
pass
394401

tests/core/test_react_agent_tokens.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,57 @@ def test_react_agent_token_persistence_failure_doesnt_crash(
294294
# Token records should still be available in-memory
295295
records = agent.get_token_usage()
296296
assert len(records) == 1
297+
298+
299+
class TestPersistDoesNotPolluteWorkspaceDb:
300+
"""Issue #713 / P0.2: _persist_token_usage must write through the
301+
per-workspace schema, NOT the control-plane Database (which seeds
302+
users/api_keys/sessions + an admin row into the workspace DB)."""
303+
304+
def _real_workspace(self, tmp_path):
305+
from codeframe.core.workspace import create_or_load_workspace
306+
307+
repo = tmp_path / "repo"
308+
repo.mkdir()
309+
return create_or_load_workspace(repo)
310+
311+
def test_persists_token_usage_without_control_plane_tables(self, tmp_path):
312+
import sqlite3
313+
314+
from codeframe.core.react_agent import ReactAgent
315+
316+
ws = self._real_workspace(tmp_path)
317+
agent = ReactAgent(workspace=ws, llm_provider=MockProvider())
318+
agent._token_records = [
319+
{
320+
"input_tokens": 100,
321+
"output_tokens": 50,
322+
"model": "claude-sonnet-4-5",
323+
"call_type": "task_execution",
324+
"iteration": 1,
325+
}
326+
]
327+
328+
agent._persist_token_usage("a1b2c3d4-uuid-task")
329+
330+
conn = sqlite3.connect(str(ws.db_path))
331+
try:
332+
row_count = conn.execute("SELECT COUNT(*) FROM token_usage").fetchone()[0]
333+
tables = {
334+
r[0]
335+
for r in conn.execute(
336+
"SELECT name FROM sqlite_master WHERE type='table'"
337+
)
338+
}
339+
finally:
340+
conn.close()
341+
342+
# The record was written...
343+
assert row_count == 1
344+
# ...and none of the control-plane/auth tables (nor the admin row they
345+
# carry) were seeded into the per-workspace domain DB.
346+
assert "users" not in tables
347+
assert "accounts" not in tables
348+
assert "api_keys" not in tables
349+
assert "sessions" not in tables
350+
assert "audit_logs" not in tables

0 commit comments

Comments
 (0)