Projets · visualisation

Il affiche, il ne retient jamais : Hypermnesia MCP Viz, suivi dans son code

Cortex écrit dans PostgreSQL ce qu’une session a appris. Claude Code laisse sur disque une trace distincte, un fichier JSONL par session. Ni l’une ni l’autre n’est lisible sans requête. Cet outil transforme les deux — plus votre git local et un graphe de code — en six angles de lecture dans un onglet de navigateur, servis par un processus lié au bouclage qui s’éteint après dix minutes d’inactivité. Il affiche ; il ne retient pas. Cette phrase est exactement vraie des tables de mémoire de Cortex et fausse de la base dans son ensemble, et la deuxième pièce dit où passe la ligne au lieu d’attendre que vous la trouviez.

Processusun serveur MCP stdio, un serveur HTTP détaché lié à 127.0.0.1:3458, et un processus de construction du graphe
Litles tables PostgreSQL de Cortex, ~/.claude/projects/*.jsonl et votre git local
Écritcinq tables qui lui appartiennent dans cette même base, dont une ligne par appel d'outil dans session_activity
Vuessix onglets ; Trace est celui par défaut et n'a besoin d'aucune base
Arbre mesuré166 fichiers Python, 35 211 lignes ; 96 fichiers JavaScript, 22 274 lignes hors vendor/
Contrôle d'accèsla liaison au bouclage, un contrôle Host et un contrôle Origin. Ni jeton, ni cookie, ni clé d'API
Pièce 01 · architecture

Un serveur MCP, un serveur web détaché, trois sources qui ne lui appartiennent pas

L’hôte MCP démarre un lanceur, qui installe les dépendances propres à l’outil dans un répertoire privé, puis exécute le serveur dans le même processus.3 Ce serveur parle stdio et publie exactement deux outils.2 Ouvrir une visualisation ne rend rien dans ce processus : il engendre un processus système séparé et détaché qui écoute sur 127.0.0.1 et sert la page navigateur.4 Un troisième processus construit le graphe, parce qu’une construction gourmande en calcul affame le GIL quand elle tourne dans un fil d’exécution.6 Tout ce qui suit décrit la distribution hypermnesia-mcp-viz en version 3.2.0.1

Hôte MCP Claude Code · Codex CLI Cursor · Windsurf · VS Code BOOTSTRAP scripts/launcher.py pip install --target deps/ SERVEUR MCP · STDIO cortex_viz/__main__.py open_visualization() get_methodology_graph() Popen(start_new_session=True) PROCESSUS OS DÉTACHÉ http_standalone.py écoute 127.0.0.1:3458 ThreadingMixIn · HTTP/1.1 arrêt auto après 600 s build_process.py spawn · Queue(maxsize=4096) Page navigateur ui/ — 96 fichiers .js 22 274 lignes aucun bundler, aucun cadre POSTGRESQL CORTEX · LECTURE memories · entities memory_entities · wiki.* psycopg 3 · pools 2/8 et 1/2 SELECT uniquement SES PROPRES TABLES · ÉCRITES workflow_graph_layout(_lod) workflow_graph_snapshot* session_activity CREATE · INSERT · DELETE ~/.claude/projects/*.jsonl + git local lecture seule — sans base POST /api/activity Hooks de session délai 0,5 s · sortie 0 toujours lance runpy.run_module stdio SSE · NDJSON · Arrow IPC lit écrit lit
écrit, ou démarrelit seulement
Rien ici n’est un service en ligne. L’adresse d’écoute est 127.0.0.1 à chaque point de liaison, et le navigateur est ouvert via une liste blanche qui rejette silencieusement toute URL ne correspondant pas à ^https?://127\.0\.0\.1:\d{1,5}.4 Le port 3458 est une préférence, pas une garantie : le serveur se rabat sur un port attribué par le système et consigne le port réel dans ~/.cache/cortex/viz-server.json.5 L’arbre à cette révision mesure 166 fichiers Python, 35 211 lignes et 96 fichiers JavaScript, 22 274 lignes hors vendor/ — recompté ici parce que les propres documents du projet en donnent trois chiffres qui se contredisent.8
Pièce 02 · la frontière

« Lecture seule », dit exactement

Ce que dit l’emballage, c’est que l’outil n’écrit jamais une mémoire. C’est vrai, et c’est plus étroit qu’il n’y paraît. Rien n’écrit dans les tables de mémoire de Cortex : toute la surface de méthodes du lecteur est en SELECT, le préflight de schéma est une unique requête catalogue, la sonde de santé est un SELECT 1.16 Mais la connexion n’est pas ouverte en lecture seule — ni SET TRANSACTION READ ONLY, ni rôle restreint, et autocommit=True, qui valide chaque instruction immédiatement, DDL comprise.9 L’outil crée et écrit cinq tables qui lui appartiennent, dans la même base111213. Le projet énonce lui-même cette correction plutôt que de laisser subsister l’ancienne affirmation globale.10

UNE BASE · UN POOL DE CONNEXIONS · NI TRANSACTION EN LECTURE SEULE, NI RÔLE RESTREINT ConnectionPool(kwargs={"row_factory": dict_row, "autocommit": True}) TABLES DE CORTEX · LECTURE SEULE memories entities memory_entities consolidation_log prospective_memories procedural_skills schemas wiki.pages wiki.links wiki.citations wiki.page_sources wiki.memos wiki.claim_events LES SEULS LECTEURS MemoryReader.query() — SELECT uniquement schema_preflight — une requête catalogue db_probe — SELECT 1 wiki_page_actions_pg — deux SELECT SES PROPRES TABLES · CRÉÉES ET ÉCRITES workflow_graph_layout DELETE sans WHERE, puis INSERT par executemany workflow_graph_layout_lod CREATE TABLE IF NOT EXISTS + CREATE INDEX + DELETE workflow_graph_snapshot · _snapshot_scoped CREATE TABLE — aussi depuis le chemin de LECTURE session_activity CREATE TABLE + INSERT … RETURNING id une ligne par appel d’outil, à chaque session La garantie est structurelle, pas un privilège de base : no import mcp_server.* anywhere in cortex_viz/ PostToolUse (.*) · UserPromptSubmit POST /api/activity, exempté du contrôle same-origin
Pourquoi cela compte avant d’installer. Les cinq tables vivent dans la base de Cortex elle-même, et session_activity reçoit une ligne pour chaque appel d’outil de chaque session — c’est ce flux qui rend la vue vivante.14 Deux gardes séparent une page web de ce serveur : chaque requête doit porter un Host de bouclage (sinon 421), et chaque POST une Origin ou un Referer de bouclage (sinon 403), avec une exemption documentée pour le hook, qui est une commande et non un navigateur.15 Il n’existe nulle part de jeton, de cookie ni de clé d’API : le contrôle d’accès, c’est la liaison au bouclage.32 Le hook est lui-même tenu à un contrat dur : ne jamais bloquer, ne jamais lever, toujours sortir en zéro.7
Pièce 03 · pipeline

De la ligne au pixel

Une ligne de mémoire n’a pas de position. Tout ce qui sépare le SELECT du point affiché à l’écran est calculé ici, et chaque étape porte des constantes choisies contre une mesure plutôt que par goût. Les arêtes viennent de trois canaux additifs : les entités partagées pondérées par la fréquence documentaire inverse, les plus proches voisins dans l’espace d’embeddings, et la co-consultation temporelle.17 La couleur vient d’une détection de communautés Leiden, à une résolution prise au genou d’un balayage mesuré.18 La position vient du DrL d’igraph, et seulement au-delà de deux cents nœuds.19

1 · LIRE ET PLACER memory_read.py SELECT * FROM memories effective_heat(m, NOW()) entities · memory_entities memory_associations.py trois canaux additifs TF-IDF top_k 8 · kNN 0.6 fenêtre temporelle 2,0 h community_detection.py Leiden · objectif CPM gamma 0,0005 · graine 42 absent → couleur par type layout_engine.py igraph DrL si N ≥ 200 sinon FR, niter=200 → [−1, 1] · SHA-256[:16] 2 · PERSISTER layout_pg_store.py workflow_graph_layout pages keyset de 50 000 lod_aggregator.py 2^L × 2^L · max_level 7 sum(count) == len(rows) snapshot_pg_store.py ndjson.v1, gzippé 1 000 nœuds · 5 000 arêtes/trame graph_wire.py [id, kind, x, y] aucune arête sur le fil 3 · SERVIR ET RENDRE /api/graph/full/stream blocs de lecture 1 Mio ligne minimale 512 Kio /api/quadtree Arrow IPC, float32 gzip niveau 6 /api/tile/{z}/{x}/{y}.png Datashader 512 × 512 eq_hist · spread(px=1) graph_stream_loader.js FRAME_BUDGET_MS 25 complet, ou marqué truncated Pourquoi tout est découpé en trames : le document unique franchissait ~1,17 Go décompressé à 278 557 nœuds et 5 526 064 arêtes. mesuré 2026-07-02 · snapshot_pg_store.py:57-59
L’honnêteté est dans la comptabilité. Le chargeur navigateur ne déclare un chargement complet que si les comptes reçus égalent les totaux annoncés ; sinon le résultat porte truncated: true plutôt qu’une image discrètement amputée.23 Le budget par image est de 25 ms — la moitié du seuil de tâche longue de 50 ms, cité dans le fichier comme le modèle RAIL. Au-delà de 25 000 nœuds, le rendu retire les arêtes calls, un seuil pris sur des points de gel mesurés du fil principal plutôt que sur un chiffre rond.24 Deux limites méritent d’être connues avant de viser une grande base : la cuisson DrL a été observée sur des heures pour un graphe de 278k nœuds, et le paramètre seed du layout est déclaré mais jamais lu, donc le layout n’est pas reproductible.19 Deux comptabilités de plus se tiennent derrière la figure : la pyramide de niveau de détail affirme que les comptes de chaque niveau somment aux lignes qu'on lui a données,20 et le niveau AST est retenu jusqu'à ce que vous le demandiez, par lots de deux cents symboles et sous un plafond de trois minutes par projet.25
Pièce 04 · les vues

Six angles de lecture, et ce qu’il faut à chacun

L’outil MCP n’accepte que deux valeurs de vue, galaxy et brain ; les six angles sont des onglets dans la page, pas des arguments de l’outil.2 L’un d’eux fonctionne sans aucune base : Trace est la vue d’atterrissage par défaut et ne lit que vos journaux de session et votre git, ce qui rend l’outil utile avant même que Cortex soit installé.27 Les cinq autres ont besoin de la base, et le disent nativement au lieu d’échouer au premier clic.29

ui/unified-viz.html — SERVIE SUR / Graph data-view=graph Trace data-view=trace VUE PAR DÉFAUT Knowledge data-view=knowledge Wiki data-view=wiki Board data-view=timeline LIBELLÉ ≠ ID Graph · /api/graph/full/stream, puis phases conditionnées par /api/graph/progress Trace · /api/trace/{domains, sessions, chain, file, impact} — servi en direct, jamais figé Knowledge et Board · /api/memories, paginé, facettes côté serveur Wiki · /api/wiki/{list, projects, page, memos, graph} INDICATEUR DE COUVERTURE : GRAPH ET TRACE — PAS LES QUATRE AUTRES Brain — servie sur /brain page distincte : le bouton navigue three.js r137, CDN avec SRI brain.glb 13 161 040 octets, CC-BY-4.0 GET /api/capabilities sans PostgreSQL → trace seule : cinq onglets désactivés nativement, les routes base répondent 503 Annoncées dans la description de l’outil, absentes de la page : Pipeline (retirée le 2026-07-05 avec sa feuille de style et son script), Atlas, Emotion. Atteignable par URL mais dans aucune barre de vues : /atom. Présente dans l’arbre, routée par rien : methodology-viz.html. open_visualization.py:40, :80 · unified-viz.html:14, :454
À quoi ressemble un mode dégradé quand il est conçu. Sans base accessible, les onglets sont nativement désactivés et portent la raison dans leur attribut title ; les routes adossées à la base répondent 503 avec un corps db_unavailable explicite nommant la dépendance manquante, plutôt qu’un 404.2829 L’indicateur de couverture — la fonction qui promet qu’une vue dira quand elle ne peut pas tout montrer — n’est enregistré que pour deux vues sur six, un écart que le projet consigne contre lui-même.30

Ce qui est mesuré, et ce qui est seulement affirmé

Le projet sépare ces deux registres dans ses propres fichiers, et ce dossier conserve la séparation. La colonne du milieu est reproductible et datée. Celle de droite porte des intentions de conception sans protocole attaché, citées comme telles plutôt que reprises comme des performances.

SujetMesuré, avec une dateAffirmé, sans protocole trouvé
Taille du corpus278 557 nœuds / 5 526 064 arêtes, 2026-07-02 — la mesure qui a imposé le format en trames21« the galaxy builds end-to-end at 75k+ nodes »
Résolution de communautésbalayage sur 9 889 mémoires / 27 346 arêtes, 2026-07-07 ; gamma 0,0005 pris au genou18
Seuils navigateurpoints de gel mesurés sur le saut N ≈ 17k → 27k24« first paint of the skeleton lands in ~1 s »
Tuiles64 658 lignes lues en 35 ms, extrapolées à 250 000 lignes dans un budget de 200 ms22« <5 ms even for 10M-row tables »
Tests et audits988 réussis / 10 ignorés, 81 % de couverture d’instructions ; OpenSSF Scorecard 7,4, Best Practices Silver, tous deux le 2026-08-0331
Maturitéclassifier Development Status :: 3 - Alpha, mainteneur unique33

Trois limites sont énoncées par le projet plutôt que découvertes contre lui : il n’écrira jamais dans les tables de mémoire de Cortex ; il ne sera pas déployé à distance, parce que la liaison au bouclage et l’absence d’authentification sont la conception ;32 et Windows n’est pas déclaré supporté, seulement partiellement traité.33 Une affirmation de la documentation ne survit pas à la lecture de l’arbre : la phrase de fonctionnement hors ligne de PRIVACY.md est contredite par KaTeX, d3, mermaid et CodeMirror chargés depuis des CDN sur la page unifiée, plusieurs sans empreinte d’intégrité.26

1cortex_viz/identity.py:8 :8-10 — nom de distribution hypermnesia-mcp-viz, identifiant de registre, version 3.2.0
2cortex_viz/__main__.py:22 :22-31 (FastMCP), :119 (mcp.run(transport="stdio")) — deux outils enregistrés ; l’argument MCP view n’accepte que galaxy et brain
3scripts/launcher.py:99 :99-117 (installation atomique --target), :156, :174-175 (DATABASE_URL par défaut à postgresql://127.0.0.1:5432/cortex)
4cortex_viz/server/http_launcher.py:25 :25 (PORTS = {"unified": 3458}), :336 (start_new_session=True), :355-365 (liste blanche d’ouverture du navigateur)
5cortex_viz/server/http_standalone.py:48 :48-51 (serveur multi-fils, fils démons), :137 (HTTP/1.1, exigé par le SSE), :197-199 (liaison 127.0.0.1, puis port attribué par le système) · server/viz_instance.py écrit le registre de port
6cortex_viz/server/build_process.py:85 :85-90 — contexte spawn, Queue(maxsize=4096), processus cortex-graph-build-proc ; l’enfant ouvre son propre lecteur et rend le graphe par un pickle temporaire
7cortex_viz/hooks/activity_capture.py:10 :10-12 (ne jamais bloquer, ne jamais lever, toujours sortir en 0), :24 (_TIMEOUT_S = 0.5)
8README.md:266 :266 — l’invariant d’extraction. Volumes recomptés sur l’arbre en 2482563 : docs/ARCHITECTURE.md annonce 98 fichiers / 26k lignes, SECURITY.md 91 fichiers JS / 25k lignes
9cortex_viz/infrastructure/memory_read.py:188 :188-195 — pool ouvert avec autocommit: True ; le docstring de query() affirme encore qu’un DML « serait annulé », ce que cette ligne contredit
10docs/ASSURANCE_CASE.md:27 :27-37 — « A precision the older docs got wrong… It is not a read-only database client » ; SECURITY.md et PRIVACY.md ont été corrigés dans le même changement
11cortex_viz/infrastructure/layout_pg_store.py:69 :69-78 — purge, puis insertion en masse, validée explicitement
12cortex_viz/infrastructure/lod_pg_store.py:20 :20-33 — DDL auto-assurée à la première utilisation
13cortex_viz/infrastructure/snapshot_pg_store.py:88 :88, :113 (les deux tables de snapshot), :169 — _ensure_table, atteint aussi à la lecture du dernier snapshot
14cortex_viz/infrastructure/activity_store.py:18 :18-39 (table et trois index), :73 (insertion), :100 (le chemin de lecture assure la table lui aussi)
15cortex_viz/server/http_security.py:22 :22 (ensemble de bouclage), :35 (Host), :61 (l’Origin exacte reflétée, jamais *), :82 (écriture same-origin) · http_standalone.py:139-144 (421), :164-172 (403 et l’exemption du hook)
16cortex_viz/infrastructure/schema_preflight.py:12 :12-13 (« ONE read-only catalog query — no DDL, no writes »), :74, :159
17cortex_viz/infrastructure/memory_associations.py:97 :97 (top_k = 8), :103 (plafond de mot-vide 0,10), :110 (min_sim = 0.6 ; corpus p25 0,628, médiane 0,679, p75 0,744, 2026-07-07), :117 (fenêtre de 2,0 h)
18cortex_viz/core/community_detection.py:49 :49-57 — balayage sur 9 889 mémoires / 27 346 arêtes co-entité, 2026-07-07 ; :63 (gamma 0,0005), :70 (graine 42). La propagation d’étiquettes remplacée effondrait 87–93 % des mémoires en une seule communauté
19cortex_viz/core/layout_engine.py:85 :85-88 (DrL au-delà de 200 nœuds, Fruchterman-Reingold en deçà), :26-44 (empreinte de topologie, SHA-256 tronqué à 16 hex) ; l’argument seed à :52 n’est jamais lu · server/graphbuildrun.py:348 (des heures à 278k nœuds, 2026-07-02)
20cortex_viz/core/lod_aggregator.py:57 :57 (max_level = 7), :71-73 (l’invariant de conservation et sa complexité)
21cortex_viz/infrastructure/snapshot_pg_store.py:57 :57-59 (la mesure de 1,17 Go), :151-152 (tailles de trame)
22cortex_viz/core/tile_renderer.py:62 :62 (tuiles de 512 px), :99-102 (Datashader count_cat, eq_hist, spread) · handlers/tile_handler.py — plafond de rendu brut à 250 000 lignes, et sa base de 64 658 lignes en 35 ms
23ui/unified/js/graph_stream_loader.js:33 :33-35 (hautes et basses eaux, budget de 25 ms), :137-145 (porte de complétude, drapeau truncated)
24ui/unified/js/workflow_graph_lod.js:24 :24-26 — lourd au-delà de 8 000, aimantation au-delà de 15 000, extrême au-delà de 25 000 · workflow_graph.js:30 (toujours canvas), :216-220 (décroissance alpha 0,018/0,022, décroissance de vitesse 0,78)
25cortex_viz/server/graph_build_l6.py:28 :28 (180 s par projet), :102 (lots de 200 symboles) — le niveau AST est différé jusqu’à ce que vous le demandiez
26ui/unified-viz.html:21 :21-23 (KaTeX 0.16.11, sans integrity) · ui/unified/js/workflow_graph.js:22 (d3 7.8.5) · ui/unified/js/wiki.js (mermaid 10.9.0 et CodeMirror 6 depuis esm.sh) — contre le « work fully offline » de PRIVACY.md
27ui/unified/js/state.js:20 :20 (activeView: 'trace') · README.md:87 — Trace n’exige ni Cortex, ni PostgreSQL, ni installation
28cortex_viz/server/http_standalone_nodb.py:37 :37 (l’ensemble des routes adossées à la base), :66-90 (503 choisi plutôt que 404 ou 410, avec la raison écrite), :106-115 (la carte des capacités)
29ui/unified/js/capabilities.js:24 :24-27 — disabled natif, title « Requires the Cortex memory engine (PostgreSQL) — not connected »
30ui/unified/js/coverage_indicator.js:22 :22-36 — seuls graph et trace sont enregistrés ; docs/ROADMAP.md consigne l’écart
31docs/ASSURANCE_CASE.md:1 § 7 — totaux de tests, couverture d’instructions, zéro alerte CodeQL ouverte, zéro mutant survivant sur le primitif de containment de chemin · README.md:288-289 (badges OpenSSF)
32docs/ROADMAP.md:76 :76-78 — « The server binds 127.0.0.1 and has no authentication by design. Exposing it would require a different threat model and a different product. »
33docs/ROADMAP.md:44 :44-46 (Windows « partly addressed », nécessitant une passe de vérification), :5-7 (mainteneur unique) · classifiers de pyproject.toml — Alpha