Skip to content

Commit 0191379

Browse files
Fix all 10 issues from cofounder audit
Critical: - Dashboard error now tells user: pip install octopoda[server] - Snapshot add_node() no longer passes unsupported kwargs (_background, metadata) - Loop detection Signals 2+3 (key overwrites, velocity) now work WITHOUT AI deps - recall_similar() returns helpful message when embeddings not installed - consolidate() early returns now include dry_run key Polish: - Banner version updated from v1.0.0 to v3.0.3 - Fact extraction logs downgraded from WARNING to DEBUG (no more console flood) - "No API key" warning fires once per session, not per agent - test_full_audit.py UTF-8 encoding declaration added (Windows fix) - Signal 1 tells user to install octopoda[ai] for semantic similarity 43 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 19296e1 commit 0191379

4 files changed

Lines changed: 68 additions & 53 deletions

File tree

synrix_runtime/api/runtime.py

Lines changed: 54 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
_repeat_tracker: dict = {} # agent_id -> [{"embedding": ndarray, "time": float, "key": str}, ...]
3232
_repeat_tracker_lock = threading.Lock()
3333

34+
# Lightweight write tracker (no embeddings needed) — always active for loop detection
35+
_write_tracker: dict = {} # agent_id -> [{"time": float, "key": str}, ...]
36+
_write_tracker_lock = threading.Lock()
37+
3438

3539
@dataclass
3640
class MemoryResult:
@@ -73,6 +77,7 @@ class SearchResult:
7377
items: list
7478
count: int
7579
latency_us: float
80+
note: Optional[str] = None
7681

7782
def __iter__(self):
7883
return iter(self.items)
@@ -265,6 +270,17 @@ def remember(self, key: str, value: Any, tags: list = None) -> MemoryResult:
265270
except Exception:
266271
pass
267272

273+
# Always track writes for loop detection (no AI deps required)
274+
tracker_key = f"{self.tenant_id}:{self.agent_id}"
275+
now = time.time()
276+
with _write_tracker_lock:
277+
wt_entries = _write_tracker.get(tracker_key, [])
278+
wt_entries = [e for e in wt_entries if e["time"] >= (now - 300)]
279+
wt_entries.append({"time": now, "key": key})
280+
if len(wt_entries) > 50:
281+
wt_entries = wt_entries[-50:]
282+
_write_tracker[tracker_key] = wt_entries
283+
268284
# Write to DB with embedding (searchable immediately)
269285
start = time.perf_counter_ns()
270286
node_id = self.backend.write(
@@ -290,13 +306,13 @@ def _enrich_background(backend, agent_id, nid, nkey, text, llm_config=None):
290306
from synrix.fact_extractor import FactExtractor
291307
from synrix.embeddings import EmbeddingModel
292308
safe_config = {k: ("***" if "key" in k.lower() or "secret" in k.lower() else v) for k, v in (llm_config or {}).items()}
293-
logger.info("Fact extraction starting for node %s (config=%s)", nid, safe_config)
309+
logger.debug("Fact extraction starting for node %s (config=%s)", nid, safe_config)
294310
fact_extractor = FactExtractor.get(config=llm_config)
295311
emb_model = EmbeddingModel.get()
296312
if fact_extractor is None:
297-
logger.warning("FactExtractor.get() returned None — no LLM available")
313+
logger.debug("FactExtractor.get() returned None — no LLM configured")
298314
if emb_model is None:
299-
logger.warning("EmbeddingModel.get() returned None")
315+
logger.debug("EmbeddingModel.get() returned None")
300316
if fact_extractor and emb_model:
301317
fact_result = fact_extractor.extract_facts(text)
302318
logger.info("Fact extraction result: %d facts, used_llm=%s, provider=%s, time=%.0fms",
@@ -497,6 +513,18 @@ def recall_similar(self, query: str, limit: int = 10) -> SearchResult:
497513
Requires sentence-transformers to be installed.
498514
Returns empty results if embeddings are not available.
499515
"""
516+
# Check if embeddings are available before searching
517+
try:
518+
from synrix.embeddings import EmbeddingModel
519+
if not EmbeddingModel.get():
520+
logger.warning("Semantic search requires embeddings: pip install octopoda[ai]")
521+
return SearchResult(items=[], count=0, latency_us=0,
522+
note="Semantic search requires embeddings. Install with: pip install octopoda[ai]")
523+
except ImportError:
524+
logger.warning("Semantic search requires embeddings: pip install octopoda[ai]")
525+
return SearchResult(items=[], count=0, latency_us=0,
526+
note="Semantic search requires embeddings. Install with: pip install octopoda[ai]")
527+
500528
start = time.perf_counter_ns()
501529
# Search scoped to this agent's data only via SQL prefix filter
502530
agent_prefix = f"agents:{self.agent_id}:"
@@ -978,11 +1006,19 @@ def get_loop_status(self) -> dict:
9781006
"action": "Monitor — may resolve naturally or escalate.",
9791007
})
9801008
except ImportError:
981-
pass
1009+
signals.append({
1010+
"type": "write_similarity",
1011+
"severity": "info",
1012+
"detail": "Semantic similarity detection requires: pip install octopoda[ai]",
1013+
"suggestion": "Install AI extras for full loop detection (embedding-based write similarity).",
1014+
"action": "pip install octopoda[ai]",
1015+
})
9821016

983-
# --- Signal 2: Key overwrite frequency ---
984-
with _repeat_tracker_lock:
985-
recent_keys = [e.get("key", "") for e in recent_entries]
1017+
# --- Signal 2: Key overwrite frequency (works WITHOUT AI deps) ---
1018+
with _write_tracker_lock:
1019+
wt_entries = _write_tracker.get(tracker_key, [])
1020+
wt_recent = [e for e in wt_entries if e["time"] >= (now - 300)]
1021+
recent_keys = [e.get("key", "") for e in wt_recent]
9861022
key_counts = {}
9871023
for k in recent_keys:
9881024
key_counts[k] = key_counts.get(k, 0) + 1
@@ -1000,10 +1036,9 @@ def get_loop_status(self) -> dict:
10001036
"action": f"Check the agent's logic around key '{worst_key}'. Consider using remember_with_ttl() for temporary values.",
10011037
})
10021038

1003-
# --- Signal 3: Write velocity (burst detection) ---
1004-
with _repeat_tracker_lock:
1005-
last_60s = [e for e in entries if e["time"] >= (now - 60)]
1006-
last_300s = [e for e in entries if e["time"] >= (now - 300)]
1039+
# --- Signal 3: Write velocity (burst detection, works WITHOUT AI deps) ---
1040+
last_60s = [e for e in wt_recent if e["time"] >= (now - 60)]
1041+
last_300s = wt_recent
10071042
writes_per_minute = len(last_60s)
10081043
writes_per_5min = len(last_300s)
10091044
if writes_per_minute >= 10:
@@ -1288,23 +1323,10 @@ def snapshot(self, label: str = None) -> SnapshotResult:
12881323
"created_at": time.time(),
12891324
}
12901325
size_bytes = len(json.dumps(snapshot_payload).encode())
1291-
try:
1292-
raw_client = self.backend.client
1293-
raw_client.add_node(
1294-
name=f"agents:{self.agent_id}:snapshots:{label}",
1295-
data=json.dumps(snapshot_payload),
1296-
collection=self.backend.collection,
1297-
node_type="snapshot",
1298-
metadata={"type": "snapshot", "agent_id": self.agent_id},
1299-
_background=True,
1300-
)
1301-
except Exception as e:
1302-
logger.warning("Snapshot write with _background failed (%s), retrying normal", e)
1303-
self.backend.write(
1304-
f"agents:{self.agent_id}:snapshots:{label}",
1305-
snapshot_payload,
1306-
metadata={"type": "snapshot", "agent_id": self.agent_id}
1307-
)
1326+
self.backend.write(
1327+
f"agents:{self.agent_id}:snapshots:{label}",
1328+
snapshot_payload,
1329+
)
13081330
latency_us = (time.perf_counter_ns() - start) / 1000
13091331

13101332
if self._metrics:
@@ -1362,23 +1384,10 @@ def restore(self, label: str = None) -> RestoreResult:
13621384
except Exception:
13631385
pass
13641386

1365-
# Restore each key — use raw client with _background=True to avoid
1366-
# blocking on _write_lock during heavy enrichment periods
1387+
# Restore each key
13671388
restored = 0
1368-
raw_client = self.backend.client
13691389
for key, value in keys_data.items():
1370-
try:
1371-
raw_client.add_node(
1372-
name=key,
1373-
data=json.dumps(value) if isinstance(value, dict) else json.dumps({"value": value}),
1374-
collection=self.backend.collection,
1375-
node_type="memory",
1376-
metadata={"type": "restored", "agent_id": self.agent_id},
1377-
_background=True,
1378-
)
1379-
except Exception:
1380-
# Fallback to normal write if _background fails
1381-
self.backend.write(key, value, metadata={"type": "restored", "agent_id": self.agent_id})
1390+
self.backend.write(key, value)
13821391
restored += 1
13831392

13841393
recovery_us = (time.perf_counter_ns() - start) / 1000
@@ -1758,9 +1767,9 @@ def consolidate(self, similarity_threshold: float = 0.90, dry_run: bool = True)
17581767
from synrix.embeddings import EmbeddingModel
17591768
emb_model = EmbeddingModel.get()
17601769
if not emb_model:
1761-
return {"error": "Embedding model not available", "consolidated": 0}
1770+
return {"error": "Embedding model not available", "consolidated": 0, "dry_run": dry_run}
17621771
except ImportError:
1763-
return {"error": "numpy or embeddings not installed", "consolidated": 0}
1772+
return {"error": "numpy or embeddings not installed — pip install octopoda[ai]", "consolidated": 0, "dry_run": dry_run}
17641773

17651774
prefix = f"agents:{self.agent_id}:"
17661775
all_items = self.backend.query_prefix(prefix, limit=10000)

synrix_runtime/auth_flow.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -322,12 +322,14 @@ def ensure_authenticated(allow_local: bool = False) -> str:
322322
print()
323323
return ""
324324
else:
325-
# Non-interactive: log a warning but don't block
326-
logger.warning(
327-
"No Octopoda API key found. Set OCTOPODA_API_KEY environment variable "
328-
"or run interactively to sign up. Running in limited local mode. "
329-
"Sign up free at https://octopodas.com"
330-
)
325+
# Non-interactive: log once, not per agent
326+
if not getattr(ensure_authenticated, "_warned", False):
327+
ensure_authenticated._warned = True
328+
logger.warning(
329+
"No Octopoda API key found. Set OCTOPODA_API_KEY environment variable "
330+
"or run interactively to sign up. Running in local mode. "
331+
"Sign up free at https://octopodas.com"
332+
)
331333
return ""
332334

333335

synrix_runtime/start.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
STARTUP_BANNER = """
3232
+=======================================================+
33-
| OCTOPODA AGENT RUNTIME v1.0.0 |
33+
| OCTOPODA AGENT RUNTIME v3.0.3 |
3434
| Persistent Memory Kernel for AI Agents |
3535
+=======================================================+
3636
| Backend: + {backend:<28s}|
@@ -118,7 +118,10 @@ def start_dashboard():
118118
app.run(host="0.0.0.0", port=config.dashboard_port, debug=False, threaded=True, use_reloader=False)
119119
except Exception as e:
120120
_dash_error.append(str(e))
121-
print(f"\n [DASHBOARD ERROR] Failed to start: {e}")
121+
if "flask" in str(e).lower():
122+
print(f"\n [DASHBOARD] Flask not installed. Run: pip install octopoda[server]")
123+
else:
124+
print(f"\n [DASHBOARD ERROR] Failed to start: {e}")
122125

123126
dash_thread = threading.Thread(target=start_dashboard, daemon=True)
124127
dash_thread.start()

tests/test_full_audit.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- coding: utf-8 -*-
12
"""
23
Octopoda Full System Audit
34
===========================

0 commit comments

Comments
 (0)