_build_hebbian_clusters() only used hebbian edges, but 97% of hebbian
edges are self-loops from reinforce_record(). Only 7 are cross-record.
temporal_next edges (140) provide the cross-record connectivity needed
to form clusters >= CLUSTER_MIN_SIZE. Without them, consolidation never
creates summaries, no consolidated_from edges exist, and the graph
remains fragmented with 514 isolated nodes and max CC=58.
Qdrant's scroll() defaults to returning only payloads, not vectors.
Without with_vectors=True, all 1963 records had empty embeddings,
causing community detection to fail (0 communities) and topology
to show insufficient_data. Now 1959/1963 records have 1024-dim
embeddings and 1844 communities are detected.
- capture.py: call link_temporal_next() after store.insert() in capture_turn()
- retrieve.py: call link_temporal_next() in contradict() for contradict inserts
- qdrant_store.py: events_query() was json.loads() on encrypted data_json,
losing record_id — pass raw string so query_events() can decrypt it
The flattened text output now uses markdown headers so the model can
distinguish L0 identity from L1 summary from L2 episodes from handles.
This replaces undifferentiated newline-separated text with structured
sections: ## L0 Identity, ## L1 Recent Summary, ## Rich Club,
## L2 Episode, ## Handles.
- _TableShim.add(): route by table name to dedicated upsert methods for all 5 tables
- _TableShim.to_pandas(): full DataFrame construction for edges/events/budget/ratelimit
- _parse_where: full parser with >, >=, <, <= (→Range), = (→MatchValue), AND splitting
- records_as_dataframe: include provenance_json, created_at, schema_version columns
- iter_records: wire columns parameter to Qdrant with_payload
- iter_record_columns: stop forcing 'id' into every yielded row
- events_query: scroll all events + Python-side since filter (handles missing ts_epoch)
- events_add: store ts_epoch alongside ts for numeric range filtering
- Module docstring: document _TableShim interface
Cleans up duplicate edge points left by the boost_edges() bug (fixed
in previous commit). Each unique (src, dst, edge_type) keeps the
highest-weight point; duplicates are deleted. Default --dry-run.
Duplicate edges root cause — critical logic bug in QdrantStore:
boost_edges() and add_contradicts_edge() always created new points with
random UUIDs instead of reusing existing point IDs. Each boost call
added a duplicate point rather than updating in-place, causing 190
duplicate edge rows for 73 unique edges.
Sigma fix — compute_sigma() checked total node count (311 >= 200 floor)
but the largest connected component had only 2 nodes. Now checks the
largest CC size, which is the correct semantic for Humphries-Gurney
sigma validity.
The Qdrant _TableShim returned updated_at as ISO strings (not datetime
objects), causing str.replace(tzinfo=...) to fail with
"str.replace() takes no keyword arguments". Parse strings via
datetime.fromisoformat() before timezone handling.
Also add missing delete(where) and update(where=, values=) methods to
_TableShim so the LanceDB-compatible shim works with Qdrant's payload
collection, plus a _parse_where() helper to convert where clauses to
Qdrant Filters.
- Fix build_runtime_graph to use backend-agnostic records_as_dataframe()
and edges_as_dataframe() instead of LanceDB-specific open_table()
- Fix CLI topology command: JSON-RPC envelope + result extraction
- Fix community.py KeyError when graph has nodes but no edges
- Update doctor check (i) to report Qdrant collection counts when
Qdrant is active, LanceDB versions when LanceDB is active
- Fix HIBERNATION startup exit: dispatch REQUEST_ARRIVED on boot
- Fix systemd unit: StartLimit* keys in [Unit] section
- Broaden capture.py exception handler for deferred capture failures
- Add records_as_dataframe() and edges_as_dataframe() to MemoryStore
- doctor: skip LanceDB check when qdrant_storage/ dir detected
- topology: use daemon socket instead of local store (avoids lancedb crash)
- qdrant_store: add records_as_dataframe() + wire into _TableShim
so build_runtime_graph() works with Qdrant (was returning empty)