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:
@@ -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(
|
||||
|
||||
@@ -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 ───────────────────────────────────────────────
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
@@ -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 |"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ──────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user