From 21b3d6d15a9ead5c034a827ddd0fe0f336776c3d Mon Sep 17 00:00:00 2001 From: PurplePincher Automation Date: Sat, 11 Jul 2026 18:34:37 -0800 Subject: [PATCH 1/8] chore: scaffold production-hardening round 4 notes --- PRODUCTION_HARDENING.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 PRODUCTION_HARDENING.md diff --git a/PRODUCTION_HARDENING.md b/PRODUCTION_HARDENING.md new file mode 100644 index 0000000..f0cb85a --- /dev/null +++ b/PRODUCTION_HARDENING.md @@ -0,0 +1,14 @@ +# Production Hardening — Round 4 (2026-07-11) + +This document tracks concrete production-readiness fixes for this round. +Each fix is verified independently (real test suite + ruff) before being pushed. + +## Scope + +Audit of actual source + tests (not just README) for: +- Bugs: wrong logic, off-by-one, untested error paths +- Fake-green tests (always pass regardless of code) +- README claims that don't match what the code actually does +- Missing but feasible test coverage for real branches + +See git log on branch `production-round4-2026-07-11` for the per-fix commits. From 01268bde68218c27c58389706fa145442576ed23 Mon Sep 17 00:00:00 2001 From: PurplePincher Automation Date: Sat, 11 Jul 2026 18:35:23 -0800 Subject: [PATCH 2/8] docs: fix false 'S3-compatible memory' claim; add honest component status table README/AGENT.md advertised 'S3-compatible memory' but there is zero S3 / object-storage code anywhere. Storage is a tiered in-memory store with an optional SurrealDB backend. Replaced the claim with an accurate description and a component-status table marking stubs honestly. --- AGENT.md | 2 +- README.md | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/AGENT.md b/AGENT.md index 2b6f7d0..40c4df6 100644 --- a/AGENT.md +++ b/AGENT.md @@ -4,7 +4,7 @@ ## Who I Am -I watch over exocortex. 🧠 Persistent cognitive substrate for multi-agent systems — S3-compatible memory, shadow rendering, tiered compute, ESP32 support +I watch over exocortex. 🧠 Persistent cognitive substrate for multi-agent systems — tiered in-memory store with optional SurrealDB backend, shadow rendering, tiered compute, ESP32-friendly TAP protocol I reside in this repository. This is my room. diff --git a/README.md b/README.md index 0e5ac5c..51af2ea 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CI](https://github.com/SuperInstance/exocortex/actions/workflows/ci.yml/badge.svg)](https://github.com/SuperInstance/exocortex/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -🧠 Persistent cognitive substrate for multi-agent systems — S3-compatible memory, shadow rendering, tiered compute, ESP32 support +🧠 Persistent cognitive substrate for multi-agent systems — tiered in-memory store with optional SurrealDB backend, shadow rendering, tiered compute, ESP32-friendly TAP protocol --- @@ -29,6 +29,25 @@ Part of the [SuperInstance](https://github.com/SuperInstance) fleet ecosystem - [🧮 constraint-tminus-bridge](https://github.com/SuperInstance/constraint-tminus-bridge) — Constraint networks for agent alignment - [🎻 symphony-orchestrator](https://github.com/SuperInstance/symphony-orchestrator) — Full stack orchestrator +## Component Status + +Feature maturity, stated honestly (a claim previously described storage as +"S3-compatible" — there is **no** S3 / object-storage code; storage is an +in-memory tiered store with an optional SurrealDB backend). + +| Component | Status | Notes | +|-----------|--------|-------| +| Tiered in-memory store (hot/warm/cold, half-life decay) | ✅ Implemented | In-process `OrderedDict`s; no persistence on restart | +| SurrealDB backend | 🟡 Optional | `SurrealDBMemoryLayer` exists; falls back to in-memory when the `surrealdb` package/DB is unavailable. Not exercised against a live DB in CI | +| Embedding | 🟡 Placeholder | `Operation.EMBED` returns **random** unit vectors — recall is by random-vector similarity, not semantic search | +| MicroNN train/predict | ✅ Implemented | Pure-Python single-hidden-layer net; training is simulated (random accuracy) | +| Dream cycle (k-means consolidation) | ✅ Implemented | Pure-Python k-means, no sklearn | +| Resonance engine | ✅ Implemented | Cross-agent cosine-similarity overlap detection | +| Cortical bus (pub/sub) | ✅ Implemented | Asyncio `PriorityQueue` + fan-out | +| FastAPI REST + TAP protocol | ✅ Implemented | TAP = plain-text endpoints sized for ESP32 | +| Textual TUI ("Plato's Cave") | ✅ Implemented | Requires a real terminal | +| A2A / MCP protocol servers | 🟡 Stub | Enum values only; no server implementations | + ## License MIT From 1525f553e4f7160ccb0e3ba91b8708931c6471fb Mon Sep 17 00:00:00 2001 From: PurplePincher Automation Date: Sat, 11 Jul 2026 18:36:47 -0800 Subject: [PATCH 3/8] fix(config): parse memory_retention_days as float, not string CortexConfig.load() used .rstrip("d") on the raw TOML value, which returns a str. The field is typed float, so it silently stored "30" instead of 30.0. Added _parse_duration_days() to coerce numeric and unit-suffixed values to float, with a regression test. --- src/config/__init__.py | 23 ++++++++++++++++++++++- tests/test_core.py | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/config/__init__.py b/src/config/__init__.py index c4b1271..7d11475 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -12,6 +12,27 @@ import tomli as tomllib # type: ignore +def _parse_duration_days(raw: Any) -> float: + """Parse a retention value into days as float. + + Accepts a number ("30", 30, 30.0) or a string with an optional trailing + unit suffix, e.g. "30d", "30.5d". Always returns a float. + """ + if isinstance(raw, (int, float)): + return float(raw) + text = str(raw).strip() + # Strip a single optional trailing duration unit (e.g. "30d" -> "30"). + # Use a conservative suffix check rather than str.rstrip, which would + # wrongly collapse values like "30dd" and is order-dependent. + suffixes = ("d", "days", "day") + lowered = text.lower() + for suffix in suffixes: + if lowered.endswith(suffix): + text = text[: -len(suffix)].strip() + break + return float(text) + + @dataclass class CortexConfig: """The single source of truth for exocortex configuration.""" @@ -65,7 +86,7 @@ def load(cls, path: Path | str = ".cortex.toml") -> CortexConfig: return cls( name=cortex.get("name", "default-cortex"), memory_backend=mem.get("backend", "memory"), - memory_retention_days=mem.get("retention", "30d").rstrip("d"), + memory_retention_days=_parse_duration_days(mem.get("retention", "30d")), embedding_dims=mem.get("embedding_dims", 384), hot_window_seconds=mem.get("hot_window_seconds", 60.0), warm_unreinforced_hours=mem.get("warm_unreinforced_hours", 24.0), diff --git a/tests/test_core.py b/tests/test_core.py index 09bbeb0..5352b67 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -239,3 +239,25 @@ def test_config_load_file(): import pathlib config = CortexConfig.load(pathlib.Path(__file__).parent.parent / ".cortex.toml") assert config.name == "demo-cortex" + + +def test_config_retention_days_is_float(): + """Regression: retention must be parsed to a float, not left as a string. + + The default config has no `retention` key, so the "30d" default was used. + Previously `.rstrip("d")` returned the string "30", which silently became + the value of a field typed `float`. + """ + import pathlib + from src.config import _parse_duration_days + + config = CortexConfig.load(pathlib.Path(__file__).parent.parent / ".cortex.toml") + assert isinstance(config.memory_retention_days, float) + assert config.memory_retention_days == 30.0 + + # Parser handles numbers and unit-suffixed strings + assert _parse_duration_days("30d") == 30.0 + assert _parse_duration_days("30") == 30.0 + assert _parse_duration_days(30) == 30.0 + assert _parse_duration_days(30.5) == 30.5 + assert _parse_duration_days("30.5d") == 30.5 From 30e4e5373bd3f18ef287b4f8604c5765966e60d5 Mon Sep 17 00:00:00 2001 From: PurplePincher Automation Date: Sat, 11 Jul 2026 18:38:15 -0800 Subject: [PATCH 4/8] fix(compute): reflex anomaly reports baseline mean, not contaminated value reflex_check() updated the running mean/var with the anomalous reading and then reported that post-update mean (~29.0) in the detail string and 'mean' field, instead of the baseline (~20.1) the z-score was actually computed against. Now the baseline is captured before the stats update. Added a regression test. --- src/compute/__init__.py | 7 +++++-- tests/test_core.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/compute/__init__.py b/src/compute/__init__.py index 2a4d849..a3e1ebf 100644 --- a/src/compute/__init__.py +++ b/src/compute/__init__.py @@ -185,6 +185,9 @@ async def reflex_check(self, source: str, value: float) -> dict[str, Any] | None z_score = abs(value - bl["mean"]) / std if z_score > 3.0: + # Capture the baseline this anomaly was measured against BEFORE + # we contaminate the running stats with the anomalous value. + baseline_mean = bl["mean"] # Still update stats even for anomalies new_mean = bl["mean"] + (value - bl["mean"]) / (n + 1) new_var = ((n * bl["var"]) + (value - bl["mean"]) * (value - new_mean)) / (n + 1) @@ -196,8 +199,8 @@ async def reflex_check(self, source: str, value: float) -> dict[str, Any] | None "source": source, "value": value, "sigma": z_score, - "mean": new_mean, - "detail": f"{source} = {value:.1f} ({z_score:.1f}σ from mean {bl['mean']:.1f})", + "mean": baseline_mean, + "detail": f"{source} = {value:.1f} ({z_score:.1f}σ from mean {baseline_mean:.1f})", } # Update running stats diff --git a/tests/test_core.py b/tests/test_core.py index 5352b67..5d6147b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -134,6 +134,25 @@ async def test_reflex_arc(): assert anomaly["sigma"] > 3.0 +@pytest.mark.asyncio +async def test_reflex_arc_reports_baseline_mean(): + """Regression: anomaly 'mean' must be the baseline before contamination. + + Previously the detail/mean reported the running mean *after* the anomalous + reading had been folded in (~29.0), not the baseline (~20.1) the anomaly + was actually measured against. + """ + engine = ComputeEngine() + for v in [20.0, 21.0, 19.0, 20.5, 20.0, 21.0, 19.5, 20.0]: + await engine.reflex_check("temp", v) + + baseline = sum([20.0, 21.0, 19.0, 20.5, 20.0, 21.0, 19.5, 20.0]) / 8 + anomaly = await engine.reflex_check("temp", 100.0) + assert anomaly is not None + assert abs(anomaly["mean"] - baseline) < 0.01 + assert "20.1" in anomaly["detail"] + + # --- Memory Layer --- @pytest.mark.asyncio From 06d7d6eb09422c99c22920ebfe4d380f6c4225ec Mon Sep 17 00:00:00 2001 From: PurplePincher Automation Date: Sat, 11 Jul 2026 18:39:57 -0800 Subject: [PATCH 5/8] fix(memory): get_random_memories no longer returns duplicate samples Every hot entry is also stored in warm, so concatenating tiers produced duplicate memory references in the dream-cycle sample (1 stored -> 2 returned). Dedupe by id across all tiers in both MemoryLayer and the SurrealDB fallback. Added a regression test. --- src/memory/__init__.py | 9 +++++++-- src/memory/surrealdb_backend.py | 9 ++++++--- tests/test_core.py | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/memory/__init__.py b/src/memory/__init__.py index cb6ba1f..750214d 100644 --- a/src/memory/__init__.py +++ b/src/memory/__init__.py @@ -176,8 +176,13 @@ async def tick(self) -> dict[str, int]: async def get_random_memories(self, n: int = 10) -> list[MemoryEntry]: """Sample random memories for dream cycle processing.""" import random - all_entries = list(self._warm.values()) + list(self._hot.values()) - return random.sample(all_entries, min(n, len(all_entries))) + # Dedupe by id: every hot entry is also present in warm, so naively + # concatenating the tiers yields duplicate samples. + by_id: dict[str, MemoryEntry] = {} + for entry in list(self._warm.values()) + list(self._hot.values()) + list(self._cold.values()): + by_id[entry.id] = entry + unique = list(by_id.values()) + return random.sample(unique, min(n, len(unique))) async def get_recent_memories(self, since: float, limit: int = 100) -> list[MemoryEntry]: """Get memories created after a timestamp.""" diff --git a/src/memory/surrealdb_backend.py b/src/memory/surrealdb_backend.py index 23ba5a4..d250e76 100644 --- a/src/memory/surrealdb_backend.py +++ b/src/memory/surrealdb_backend.py @@ -452,10 +452,13 @@ async def get_random_memories(self, n: int = 10) -> list[Any]: except Exception as e: logger.warning(f"SurrealDB random sample failed: {e}") - # Fallback: sample from in-memory + # Fallback: sample from in-memory (dedupe by id — hot ⊂ warm) import random - all_entries = list(self._warm.values()) + list(self._hot.values()) - return random.sample(all_entries, min(n, len(all_entries))) + by_id: dict[str, Any] = {} + for entry in list(self._warm.values()) + list(self._hot.values()) + list(self._cold.values()): + by_id[entry.id] = entry + unique = list(by_id.values()) + return random.sample(unique, min(n, len(unique))) async def get_recent_memories(self, since: float, limit: int = 100) -> list[Any]: """Get memories created after a timestamp.""" diff --git a/tests/test_core.py b/tests/test_core.py index 5d6147b..121efdd 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -190,6 +190,22 @@ async def test_memory_query_by_tags(): assert results[0].content == "garden data" +@pytest.mark.asyncio +async def test_get_random_memories_no_duplicates(): + """Regression: get_random_memories must not return the same memory twice. + + Every hot entry is also stored in warm, so concatenating tiers used to + yield duplicate samples (1 stored -> 2 returned). + """ + memory = MemoryLayer() + await memory.remember("only one", [0.1] * 384, "test") + + sampled = await memory.get_random_memories(10) + assert len(sampled) == 1 + ids = [e.id for e in sampled] + assert len(ids) == len(set(ids)) + + # --- Shadow Rendering --- def test_render_embed(): From 5f5ec8654492da644f8e27b6a7d3a8a34c9aed62 Mon Sep 17 00:00:00 2001 From: PurplePincher Automation Date: Sat, 11 Jul 2026 18:40:55 -0800 Subject: [PATCH 6/8] test(bus): make test_bus_backpressure actually assert backpressure The test published 10 events to a size-5 queue but never checked the return value of publish(), so it passed regardless of whether the queue shed overflow events (fake-green). Now it asserts the first 5 are accepted and the 5 overflow events return False. --- tests/test_core.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 121efdd..c92b410 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -78,15 +78,22 @@ async def subscriber(event): @pytest.mark.asyncio async def test_bus_backpressure(): - bus = CorticalBus(max_queue_size=5) - await bus.start() + """Regression: a full queue must shed events (publish returns False). - # Fill the queue + Previously this test published 10 events to a size-5 queue but never + checked the return value, so it passed regardless of whether backpressure + worked. Now we assert the first 5 are accepted and the overflow is shed. + """ + bus = CorticalBus(max_queue_size=5) + # Don't start the dispatch loop, so the queue fills deterministically + # instead of being drained concurrently. + results = [] for i in range(10): event = CortexEvent.new("test", "unit", importance=i / 10.0) - await bus.publish(event) + results.append(await bus.publish(event)) - await bus.stop() + assert results[:5] == [True] * 5, "first 5 events should fit" + assert results[5:] == [False] * 5, "overflow events should be shed" # --- Compute Engine --- From 1d71e3e1f06dfa70fc6f7555ecebaf03ec9a1553 Mon Sep 17 00:00:00 2001 From: PurplePincher Automation Date: Sat, 11 Jul 2026 18:42:42 -0800 Subject: [PATCH 7/8] fix(bus): should_render rate-limit logic contradicted its own docstring should_render() was dead code (never called) whose logic contradicted its docstring: the comment claimed it 'always render[s] the last one (summary)' but returned False for every event past the threshold, so no summary was ever shown. Corrected it to a clean per-trace rate limiter (first N pass, rest suppressed), documented honestly that it is an opt-in filter not wired into the dispatch loop, and added a regression test. --- src/bus/__init__.py | 24 +++++++++++++++--------- tests/test_core.py | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/bus/__init__.py b/src/bus/__init__.py index 17ae425..c8ff2cb 100644 --- a/src/bus/__init__.py +++ b/src/bus/__init__.py @@ -59,16 +59,22 @@ async def emit(self, event_type: str, source: str, **kwargs) -> bool: return await self.publish(event) def should_render(self, event: CortexEvent) -> bool: - """Rate limit: max N events per trace_id.""" + """Rate limit: render only the first N events per trace_id. + + Returns True for the first ``_max_per_trace`` events of a trace and + False for every subsequent event, so a chatty trace cannot flood the + shadow wall. + + NOTE: this is an opt-in filter — it is *not* invoked automatically by + the dispatch loop. Subscribers that want rate-limited shadows call it + explicitly. (An earlier version's comment claimed it would also render + a "summary" of the trace, but detecting the final event of a trace is + not possible here, so that was aspirational/incorrect.) + """ trace = event.trace_id - self._trace_counts[trace] += 1 - count = self._trace_counts[trace] - if count <= self._max_per_trace: - return True - # Always render the last one (summary) - if count == self._max_per_trace + 1: - return False # skip middle, will show summary - return False + count = self._trace_counts[trace] + 1 + self._trace_counts[trace] = count + return count <= self._max_per_trace async def start(self) -> None: """Start the bus dispatch loop.""" diff --git a/tests/test_core.py b/tests/test_core.py index c92b410..b479f64 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -96,6 +96,26 @@ async def test_bus_backpressure(): assert results[5:] == [False] * 5, "overflow events should be shed" +def test_bus_should_render_rate_limits_per_trace(): + """Regression: should_render rate-limits events per trace_id. + + An earlier version's logic contradicted its docstring (it claimed to + render a "summary" but returned False for everything past the threshold). + Now the first N events per trace return True and the rest return False. + """ + bus = CorticalBus() + trace = "trace-abc" + rendered = [] + for _ in range(8): + event = CortexEvent(event_type="test", source="unit", trace_id=trace) + rendered.append(bus.should_render(event)) + # max_per_trace == 5 -> first 5 rendered, rest suppressed + assert rendered == [True] * 5 + [False] * 3 + # A new trace starts fresh + other = CortexEvent(event_type="test", source="unit", trace_id="other") + assert bus.should_render(other) is True + + # --- Compute Engine --- @pytest.mark.asyncio From 5f20ae2b27c97453e85b4dffff4b746dd0f9885a Mon Sep 17 00:00:00 2001 From: PurplePincher Automation Date: Sat, 11 Jul 2026 18:48:14 -0800 Subject: [PATCH 8/8] ci+chore: clear ruff lint (40->0 errors) and gate ruff in CI CI previously ran only pytest, never ruff, so 40 lint errors went unseen (33 unused imports, 4 unused vars, 2 ambiguous 'l' names, 1 empty f-string). Removed genuinely-unused imports/vars across source and tests, renamed the ambiguous loop variables, and added a 'ruff check .' step to CI so lint failures fail the build going forward. --- .github/workflows/ci.yml | 2 ++ .venv/bin/python | 1 + .venv/bin/python3 | 1 + .venv/bin/python3.12 | 1 + .venv/lib64 | 1 + .venv/pyvenv.cfg | 5 +++++ src/compute/__init__.py | 4 ++-- src/compute/dream.py | 5 ++--- src/main.py | 4 +--- src/memory/__init__.py | 2 +- src/memory/surrealdb_backend.py | 3 +-- src/protocols/__init__.py | 10 +++------- src/shadows/__init__.py | 4 +--- src/tui/__init__.py | 4 +--- tests/test_core.py | 6 ++---- tests/test_phase2.py | 9 ++++----- 16 files changed, 29 insertions(+), 33 deletions(-) create mode 120000 .venv/bin/python create mode 120000 .venv/bin/python3 create mode 120000 .venv/bin/python3.12 create mode 120000 .venv/lib64 create mode 100644 .venv/pyvenv.cfg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04ea67c..22a398f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,5 +19,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install package and test dependencies run: pip install -e ".[dev]" + - name: Lint (ruff) + run: ruff check . - name: Run tests run: pytest -v diff --git a/.venv/bin/python b/.venv/bin/python new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/.venv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/.venv/bin/python3 b/.venv/bin/python3 new file mode 120000 index 0000000..ae65fda --- /dev/null +++ b/.venv/bin/python3 @@ -0,0 +1 @@ +/usr/bin/python3 \ No newline at end of file diff --git a/.venv/bin/python3.12 b/.venv/bin/python3.12 new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/.venv/bin/python3.12 @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/.venv/lib64 b/.venv/lib64 new file mode 120000 index 0000000..7951405 --- /dev/null +++ b/.venv/lib64 @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/.venv/pyvenv.cfg b/.venv/pyvenv.cfg new file mode 100644 index 0000000..d366aa0 --- /dev/null +++ b/.venv/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.12.3 +executable = /usr/bin/python3.12 +command = /usr/bin/python3 -m venv /tmp/claude-1000/-home-eileen/c25f18c4-8752-45a5-bacc-1c3e70a86ab7/scratchpad/round4/exocortex/.venv diff --git a/src/compute/__init__.py b/src/compute/__init__.py index a3e1ebf..c5ba250 100644 --- a/src/compute/__init__.py +++ b/src/compute/__init__.py @@ -54,8 +54,8 @@ def predict(self, x: list[float]) -> tuple[int, float]: """Predict class + confidence.""" logits = self.forward(x) # Softmax - max_l = max(logits) - exps = [math.exp(l - max_l) for l in logits] + max_logit = max(logits) + exps = [math.exp(logit - max_logit) for logit in logits] total = sum(exps) probs = [e / total for e in exps] best = max(range(len(probs)), key=lambda i: probs[i]) diff --git a/src/compute/dream.py b/src/compute/dream.py index bd25936..b0149d2 100644 --- a/src/compute/dream.py +++ b/src/compute/dream.py @@ -21,7 +21,7 @@ from dataclasses import dataclass, field from typing import Any -from ..core.types import CortexEvent, MemoryEntry +from ..core.types import CortexEvent logger = logging.getLogger(__name__) @@ -251,7 +251,7 @@ async def run(self) -> DreamReport: clusters: list[DreamCluster] = [] for ci in range(n_clusters): - member_indices = [i for i, l in enumerate(labels) if l == ci] + member_indices = [i for i, label in enumerate(labels) if label == ci] if not member_indices: continue members = [embedded[i] for i in member_indices] @@ -361,7 +361,6 @@ def _generate_narrative(self, report: DreamReport) -> str: parts = [] if report.clusters: - n_total = sum(len(c.memory_ids) for c in report.clusters) parts.append(f"Dreaming over {report.memories_sampled} memories") parts.append(f"into {len(report.clusters)} islands of thought") diff --git a/src/main.py b/src/main.py index c2fe85e..752e6cb 100644 --- a/src/main.py +++ b/src/main.py @@ -4,8 +4,6 @@ import asyncio import logging -import signal -import sys from .config import CortexConfig from .bus import CorticalBus @@ -68,7 +66,7 @@ async def stats_updater(): # Launch everything logger.info(f"📡 Server: http://{config.host}:{config.port}") - logger.info(f"🔮 TUI: Plato's Cave") + logger.info("🔮 TUI: Plato's Cave") await asyncio.gather( server_instance.serve(), diff --git a/src/memory/__init__.py b/src/memory/__init__.py index 750214d..71e8c67 100644 --- a/src/memory/__init__.py +++ b/src/memory/__init__.py @@ -11,7 +11,7 @@ from collections import OrderedDict from typing import Any -from ..core.types import MemoryEntry, Operation +from ..core.types import MemoryEntry logger = logging.getLogger(__name__) diff --git a/src/memory/surrealdb_backend.py b/src/memory/surrealdb_backend.py index d250e76..aea92bb 100644 --- a/src/memory/surrealdb_backend.py +++ b/src/memory/surrealdb_backend.py @@ -16,10 +16,9 @@ import math import time import uuid -from collections import OrderedDict from typing import Any -from . import MemoryLayer, LRU_MAX, HOT_WINDOW_SECONDS, WARM_UNREINFORCED_HOURS, COLD_CONFIDENCE_THRESHOLD +from . import MemoryLayer, HOT_WINDOW_SECONDS, WARM_UNREINFORCED_HOURS logger = logging.getLogger(__name__) diff --git a/src/protocols/__init__.py b/src/protocols/__init__.py index 741daff..8ad6b2c 100644 --- a/src/protocols/__init__.py +++ b/src/protocols/__init__.py @@ -2,20 +2,17 @@ from __future__ import annotations -import time -import uuid import logging from typing import Any -from fastapi import FastAPI, HTTPException, Query as QueryParam +from fastapi import FastAPI, Query as QueryParam from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel -from ..core.types import CortexRequest, CortexResponse, Operation, Protocol +from ..core.types import Operation from ..bus import CorticalBus from ..compute import ComputeEngine from ..memory import MemoryLayer -from ..shadows import render_shadow logger = logging.getLogger(__name__) @@ -74,7 +71,6 @@ def create_app(bus: CorticalBus, compute: ComputeEngine, memory: MemoryLayer) -> @app.post("/api/v1/embed") async def embed(req: EmbedRequest) -> dict[str, Any]: - start = time.time() result = await compute.execute(Operation.EMBED, { "content": req.content, "dims": req.dims, }) @@ -201,7 +197,7 @@ async def tap_remember(req: RememberRequest) -> str: """Plain text remember.""" result = await compute.execute(Operation.EMBED, {"content": req.content, "dims": 384}) embedding = result.get("embedding", []) - entry = await memory.remember(req.content, embedding, req.agent_id, req.tags) + await memory.remember(req.content, embedding, req.agent_id, req.tags) await bus.emit("remember", req.agent_id, payload={"preview": req.content[:40]}) return "remembered" diff --git a/src/shadows/__init__.py b/src/shadows/__init__.py index 9a9dee6..c1001f3 100644 --- a/src/shadows/__init__.py +++ b/src/shadows/__init__.py @@ -5,12 +5,10 @@ from __future__ import annotations -import math -import time from dataclasses import dataclass from enum import Enum -from ..core.types import CortexEvent, Operation, ShadowMode +from ..core.types import CortexEvent, Operation class ShadowColor(str, Enum): diff --git a/src/tui/__init__.py b/src/tui/__init__.py index 39c3d95..b3ce19d 100644 --- a/src/tui/__init__.py +++ b/src/tui/__init__.py @@ -2,17 +2,15 @@ from __future__ import annotations -import asyncio from datetime import datetime from typing import TYPE_CHECKING from textual.app import App, ComposeResult -from textual.containers import Container, Horizontal, Vertical from textual.widgets import Header, Footer, Static, RichLog from textual.reactive import reactive from rich.text import Text -from ..core.types import CortexEvent, ShadowMode +from ..core.types import CortexEvent from ..shadows import render_shadow, RenderedShadow, ShadowColor from ..bus import CorticalBus diff --git a/tests/test_core.py b/tests/test_core.py index b479f64..a530c75 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,19 +1,17 @@ """Tests for the exocortex core.""" import asyncio -import math import time import pytest from src.core.types import ( - CortexEvent, CortexRequest, Operation, ComputeTier, - Protocol, MemoryEntry, AgentInfo, Provenance, + CortexEvent, Operation, MemoryEntry, Provenance, ) from src.bus import CorticalBus from src.compute import ComputeEngine from src.memory import MemoryLayer -from src.shadows import render_shadow, ShadowColor, classify_color +from src.shadows import render_shadow, ShadowColor from src.config import CortexConfig diff --git a/tests/test_phase2.py b/tests/test_phase2.py index ca0bcf8..33ef669 100644 --- a/tests/test_phase2.py +++ b/tests/test_phase2.py @@ -1,15 +1,14 @@ """Tests for Phase 2: SurrealDB Backend, Dream Cycle, Resonance Engine.""" import asyncio -import math import time import pytest -from src.core.types import CortexEvent, MemoryEntry +from src.core.types import MemoryEntry from src.memory.surrealdb_backend import SurrealDBMemoryLayer, SurrealDBSchema, _cosine_similarity -from src.compute.dream import DreamCycle, DreamCluster, KMeans, DreamReport, _euclidean_distance -from src.core.resonance import ResonanceEngine, ResonanceHit, LearningEvent, ActiveQuery +from src.compute.dream import DreamCycle, KMeans, DreamReport, _euclidean_distance +from src.core.resonance import ResonanceEngine, LearningEvent # ============================================================================ @@ -76,7 +75,7 @@ async def test_surrealdb_backend_get_missing(): async def test_surrealdb_backend_tick_cooling(): """SurrealDB backend should cool memories from hot to cold.""" layer = SurrealDBMemoryLayer() - entry = await layer.remember("aging memory", [0.5] * 384, "agent-1") + await layer.remember("aging memory", [0.5] * 384, "agent-1") # Force age it beyond warm threshold for e in layer._warm.values():