fix(bigmind): apply 4 health-check fixes — BUG-1/2/3 + PERF-1

BUG-1: fix test_server_tools.py assert "ALWAYS" → "Always" (case mismatch)
BUG-2: export_memory() now includes hypotheses, upgrade_requests, token_saves,
        people tables; renamed bigmind_version → bigmind_schema_version (int)
BUG-3: auto_close.py replaced CURRENT_TIMESTAMP (SQLite) with Python
        datetime.now(timezone.utc).isoformat() for consistent UTC timestamps
PERF-1: context_builder.py caps get_facts() at _MAX_CONTEXT_FACTS=50 with
         overflow hint to prevent unbounded context growth

All 297 tests passing. Upgrade requests #6-9 resolved.
Health report: plans/BIGMIND_HEALTH_REPORT_2026-04-04.md
This commit is contained in:
pplate
2026-04-04 09:49:13 +02:00
parent 155d56e8e8
commit 42ffc85f0b
7 changed files with 265 additions and 11 deletions
+7 -4
View File
@@ -3,6 +3,7 @@ import logging
from datetime import datetime, timezone, timedelta
from bigmind.db import db
logger = logging.getLogger("BigMindAutoClose")
STALE_THRESHOLD_HOURS = 24
@@ -17,6 +18,7 @@ def auto_close_stale_sessions(user_id: str) -> int:
datetime.now(timezone.utc) - timedelta(hours=STALE_THRESHOLD_HOURS)
).isoformat()
now = datetime.now(timezone.utc).isoformat()
with db() as conn:
stale = conn.execute(
"""SELECT id, started_at FROM sessions
@@ -27,11 +29,11 @@ def auto_close_stale_sessions(user_id: str) -> int:
for session in stale:
conn.execute(
"""UPDATE sessions
SET ended_at=CURRENT_TIMESTAMP,
SET ended_at=?,
one_liner='[auto-closed — session exceeded 24h]',
outcome='Session automatically closed after exceeding 24h without a proper close call.'
WHERE id=?""",
(session["id"],),
(now, session["id"]),
)
logger.info(
"Auto-closed stale session %s (started %s)",
@@ -57,15 +59,16 @@ def close_orphaned_sessions(user_id: str, keep_session_id: str) -> list[str]:
(user_id, keep_session_id),
).fetchall()
now = datetime.now(timezone.utc).isoformat()
closed_ids = []
for session in orphans:
conn.execute(
"""UPDATE sessions
SET ended_at=CURRENT_TIMESTAMP,
SET ended_at=?,
one_liner='[orphaned — closed by memory_close_stale_sessions]',
outcome='Session was open but never properly closed (IDE crash or forgotten). Cleaned up manually.'
WHERE id=?""",
(session["id"],),
(now, session["id"]),
)
closed_ids.append(session["id"])
logger.info(
+13 -1
View File
@@ -21,6 +21,12 @@ def _format_date(iso_str: Optional[str]) -> str:
return iso_str[:10]
# Maximum facts loaded into context on session start.
# Keeps context lean as the facts table grows — older/excess facts remain
# searchable via memory_search_facts() but won't bloat every session startup.
_MAX_CONTEXT_FACTS = 50
def build_context(user_id: str, n_sessions: int = 10) -> str:
"""
Assemble the full bootstrapped context markdown for injection at session start.
@@ -55,13 +61,19 @@ def build_context(user_id: str, n_sessions: int = 10) -> str:
lines.append("")
# ── FACTS: Atomic personal facts ─────────────────────────────────────────
facts = memory_store.get_facts(user_id)
all_facts = memory_store.get_facts(user_id)
facts = all_facts[:_MAX_CONTEXT_FACTS]
overflow = len(all_facts) - len(facts)
if facts:
lines.append("### 🗂️ Stored facts")
for f in facts:
cat = f.get("category", "")
fact = f.get("fact", "")
lines.append(f"- **[{cat}]** {fact}")
if overflow > 0:
lines.append(
f"- *… {overflow} more facts available via `memory_search_facts()`*"
)
lines.append("")
# ── TIER 1: Recent Sessions ───────────────────────────────────────────────
+48 -2
View File
@@ -721,9 +721,14 @@ def health_check(user_id: str, stale_days: int = 30) -> dict:
# ── EXPORT ───────────────────────────────────────────────────────────────────────
def export_memory(user_id: str, output_path: str = None) -> dict:
"""Export all memory for a user to a portable JSON file."""
"""Export all memory for a user to a portable JSON file.
Exports ALL tables: facts, sessions (with Tier-2), conversation chunks,
people/contacts, hypotheses, token saves, and upgrade requests.
"""
import json
from pathlib import Path
from bigmind.db import SCHEMA_VERSION
if not output_path:
date_str = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
@@ -768,18 +773,57 @@ def export_memory(user_id: str, output_path: str = None) -> dict:
).fetchall()
]
# ── v3+ tables ───────────────────────────────────────────────────────
hypotheses = [
dict(r) for r in conn.execute(
"SELECT * FROM hypotheses WHERE user_id=? ORDER BY created_at",
(user_id,),
).fetchall()
]
upgrade_requests = [
dict(r) for r in conn.execute(
"SELECT * FROM upgrade_requests WHERE user_id=? ORDER BY created_at",
(user_id,),
).fetchall()
]
# ── v6+ tables ───────────────────────────────────────────────────────
token_saves = [
dict(r) for r in conn.execute(
"SELECT * FROM token_saves WHERE user_id=? ORDER BY created_at",
(user_id,),
).fetchall()
]
# ── v7+ tables ───────────────────────────────────────────────────────
people = [
dict(r) for r in conn.execute(
"SELECT * FROM people WHERE user_id=? ORDER BY created_at",
(user_id,),
).fetchall()
]
export_data = {
"export_date": datetime.now(timezone.utc).isoformat(),
"bigmind_version": "1.0",
"bigmind_schema_version": SCHEMA_VERSION,
"user": user_info,
"identity_profile": profile,
"facts": facts,
"sessions": sessions,
"conversation_chunks": chunks,
"hypotheses": hypotheses,
"upgrade_requests": upgrade_requests,
"token_saves": token_saves,
"people": people,
"stats": {
"facts_count": len(facts),
"sessions_count": len(sessions),
"chunks_count": len(chunks),
"hypotheses_count": len(hypotheses),
"upgrade_requests_count": len(upgrade_requests),
"token_saves_count": len(token_saves),
"people_count": len(people),
},
}
@@ -791,6 +835,8 @@ def export_memory(user_id: str, output_path: str = None) -> dict:
"facts_count": len(facts),
"sessions_count": len(sessions),
"chunks_count": len(chunks),
"hypotheses_count": len(hypotheses),
"people_count": len(people),
"file_size_kb": round(output.stat().st_size / 1024, 1),
}
+2
View File
@@ -771,6 +771,8 @@ def memory_export(output_path: str = None) -> str:
f"| **Facts** | {result['facts_count']} |\n"
f"| **Sessions** | {result['sessions_count']} |\n"
f"| **Chunks (Tier 3)** | {result['chunks_count']} |\n"
f"| **Hypotheses** | {result['hypotheses_count']} |\n"
f"| **People** | {result['people_count']} |\n"
f"| **File size** | {result['file_size_kb']} KB |"
)
+3 -2
View File
@@ -571,8 +571,9 @@ class TestExportMemory:
memory_store.export_memory(user["id"], out)
data = json.loads(Path(out).read_text())
for key in (
"export_date", "bigmind_version", "user", "identity_profile",
"facts", "sessions", "conversation_chunks", "stats",
"export_date", "bigmind_schema_version", "user", "identity_profile",
"facts", "sessions", "conversation_chunks", "hypotheses",
"upgrade_requests", "token_saves", "people", "stats",
):
assert key in data
+3 -2
View File
@@ -541,7 +541,7 @@ class TestMemoryGetInstructions:
def test_contains_mandatory_language(self, temp_db):
result = memory_get_instructions()
assert "ALWAYS" in result
assert "Always" in result
# ── memory_health_check ────────────────────────────────────────────────────────
@@ -674,7 +674,8 @@ class TestMemoryExport:
memory_export(output_path=out)
data = json.loads(Path(out).read_text())
assert "export_date" in data
assert data["bigmind_version"] == "1.0"
assert "bigmind_schema_version" in data
assert isinstance(data["bigmind_schema_version"], int)
# ── memory_deprecate_fact ──────────────────────────────────────────────────────