20.8 KB · updated 2026-05-19 · md

architecture.fr.md

docs/i18n/architecture.fr.md

Architecture

<!-- translations:start -->

English · 한국어 · 中文 · 日本語 · Русский · Español · Français · Deutsch

<!-- translations:end --> Tesserae transforme un répertoire de matériaux sources en graphe de connaissances contrôlé et typé, puis projette ce graphe à travers une couche wiki markdown durable vers un site web statique adapté à l’IA. La refonte d’avril 2026 a réorganisé le système autour d’un modèle Karpathy à trois couches : les preuves brutes restent brutes, un graphe typé gouverne l’ontologie, et une couche wiki markdown se place entre le graphe et toute sortie rendue. Le site statique est désormais un moteur de rendu de cette couche wiki, plutôt qu’un dump direct du graphe, avec l’ontologie contrôlée dans MD0 comme schéma.

Le modèle Karpathy à trois couches

Le cadrage d’Andrej Karpathy pour les bases de connaissances adaptées aux LLM distingue trois couches, chacune avec sa propre garantie de durabilité :

CouchePréoccupationEmplacement dans le dépôtPropriétaire
L1 — Sources brutesLes octets littéraux que l’utilisateur a rédigés ou collectés. Append-only.data/, docs/, arbres de projet référencés dans .tesserae/config.jsonl’utilisateur
L2 — WikiPages markdown typées (sources, concepts, entities, papers, repos, topics, syntheses, questions) avec YAML frontmatter. Idempotent : régénéré à chaque compilation, mais réécrit uniquement lorsque les hashes de contenu changent..tesserae/wiki/WikiPageStore, WikiLayerProjector, SynthesisProjector
L3 — RenduLe site HTML statique, les exports AI-sibling, l’index de recherche, les sitemaps, JSON-LD. Effacé et réécrit à chaque compilation, mais stable octet pour octet entre les relances..tesserae/site/StaticSiteBuilder (tesserae/site/)

Le schéma traverse les trois couches comme un axe séparé : ResearchGraph dans graph.json est l’ontologie contrôlée vers laquelle pointent les pages L2, et ResearchNodeType / la whitelist des arêtes dans MD3 est la source de vérité pour les types qui existent.

La refonte a ajouté explicitement L2. Avant avril 2026, le site statique était projeté directement depuis graph.json; la couche wiki n’existait qu’à l’intérieur de l’export Obsidian vault. La séparer nous a donné :

  • Une surface unique modifiable par un humain (ouvrez .tesserae/wiki/ dans Obsidian ou n’importe quel éditeur markdown).
  • Des reconstructions idempotentes : relancer project compile produit zéro diff de fichier sauf si le contenu source a changé.
  • Un journal d’évolution : les pages de synthèse s’accumulent au fil du temps et permettent au projet de se raconter lui-même.

Pipeline

data/, docs/, src/                                    (L1 raw)
        │
        ▼  project compile  (tesserae/project.py)
┌───────────────────────────┐
│ ResearchGraphExtractor    │   deterministic + selective Claude
│ + canonicalization        │
└───────────┬───────────────┘
            │
            ▼
┌───────────────────────────┐
│ ResearchGraph (graph.json)│   schema: research_graph.py
└───────────┬───────────────┘
            │
            ├──▶ WikiLayerProjector   (one page per L1/L2 node)
            ├──▶ SynthesisProjector   (pulse, daily, weekly, topic, …)
            │
            ▼
┌───────────────────────────┐
│ .tesserae/wiki/  (L2 md)  │   sources/, concepts/, entities/,
│                            │   papers/, repos/, topics/,
│                            │   syntheses/, questions/
└───────────┬───────────────┘
            │
            ▼  StaticSiteBuilder.write_site
┌───────────────────────────┐
│ .tesserae/site/  (L3 html)│   index.html, <kind>/index.html,
│                            │   <kind>/<slug>.html,
│                            │   per-page .txt + .json siblings,
│                            │   llms.txt, llms-full.txt,
│                            │   graph.json, graph.jsonld,
│                            │   search-index.json,
│                            │   sitemap.xml, rss.xml,
│                            │   robots.txt, ai-readme.md,
│                            │   manifest.json
└───────────────────────────┘

Chaque étape est incrémentale. L’extracteur de graphe utilise les hashes de contenu de manifest.json pour ignorer les fichiers sources inchangés. WikiPageStore.write_page renvoie False (et saute l’écriture) lorsque le hash du corps correspond à ce qui est déjà sur disque. StaticSiteBuilder efface et réécrit .tesserae/site/, mais sa sortie est déterministe — voir « Récit d’idempotence » ci-dessous.

Carte des modules

Wiki + synthèse (L2)

ModuleResponsabilité
MD0Dataclass WikiPage, WikiPageStore pour les I/O du système de fichiers. Parseur YAML-subset frontmatter uniquement stdlib. Idempotence par hash du corps.
MD0WikiLayerProjector : mappe chaque nœud ResearchGraph de type wiki-layer vers une page markdown dans le bon dossier kind/.
MD0SynthesisProjector : modèles déterministes pour pulse, daily_digest, weekly, topic, comparison, field_overview. Ajoute les nœuds Synthesis et les arêtes synthesizes / summarizes dans le graphe.

Graphe + ontologie

ModuleResponsabilité
MD0Enum ResearchNodeType (incl. SYNTHESIS), whitelist des types d’arêtes (incl. synthesizes, summarizes), validation.
MD0Canonicalisation des alias + file de revue des quasi-doublons.
MD0Extracteur Python AST déterministe pour la tranche développement.
MD0Extracteur sélectif Claude CLI/OAuth.

Rendu du site (L3)

ModuleResponsabilité
MD0StaticSiteBuilder.write_site : efface + reconstruit le site, parcourt toutes les routes, émet les exports + AI siblings + manifest.
MD0Un moteur de rendu par route (home, indexes, detail pages, timeline, graph, about). SiteContext transporte des index précalculés pour que les renderers restent purs.
MD0Primitives HTML : breadcrumbs, card, badge, node_table, edge_list, sparkline_svg, heatmap_svg, toc, page_shell, ai_siblings_footer.
MD0Design tokens — variables CSS, thèmes clair + sombre, layout, typographie; tous les composants sont stylés ici.
MD0Bundle JS client : search palette, theme toggle, sigma + 3D-force graph view.
MD0Renderer markdown uniquement stdlib (links, autolinks, code, emphasis, headings). Aucune dépendance externe.
MD0Scoring de pertinence à quatre signaux (direct link, source overlap, Adamic-Adar, type affinity), utilisé par chaque section Related.
MD0Constructeur de search-index.json. Wiki-layer kinds uniquement.
MD0Renderers d’index/détail de sessions pour l’historique harness importé : sections project-memory summary, rail des tours de conversation, rendu de markdown transcript et blocs tool-use repliés.
MD0llms.txt, llms-full.txt, graph.jsonld, sitemap.xml, rss.xml, robots.txt, ai-readme.md, siblings .txt/.json par page.

Orchestration du pipeline

ModuleResponsabilité
MD0ProjectWiki.compile : pilote extraction → graph → wiki layer → site. Possède ProjectPaths (config, graph, manifest, wiki, site, etc.).
MD0Toutes les sous-commandes tesserae project …, dont compile, build-site, serve, watch, deploy.
MD0project deploy : pousse .tesserae/site/ vers une branche gh-pages via worktree, active éventuellement Pages via gh.

Adaptateurs externes (inchangés dans ce cycle)

ModuleResponsabilité
MD0Projection Obsidian vault (coloration du graphe, Dataview dashboard, raw assets).
MD0Exports harness Claude Code / Codex / Gemini / Kiro / Cursor / OpenCode.
MD0Découverte des sessions entrantes Claude Code/Codex, normalisation, stockage sous .tesserae/harness_sessions/, et résumés markdown expurgés.
MD0Temporal-fact JSONL + synchronisation live Graphiti optionnelle.
MD0Bundle JSONL de nœuds/arêtes Cognee et chemin direct add/cognify.
MD0Serveur MCP stdio exposant schema, graph_summary, search_nodes, node_context, search_facts, timeline.

Layout du workspace projet

.tesserae/
  config.json                 project name, source kind, source list
  graph.json                  validated ResearchGraph (incl. Synthesis nodes)
  manifest.json               per-source content hashes (input dedup)
  sqlite.db                   SQLite graph store
  temporal_facts.jsonl        Graphiti-style temporal projection
  graphiti_episodes.jsonl     dependency-free Graphiti episode export
  report.md                   graph quality / summary
  competitive_report.md       comparison vs. MegaMem / Graphiti / others
  markdown_projection/        flat human-readable markdown
  obsidian_vault/             Obsidian projection w/ .obsidian/, raw/assets/
  agent_harness/              Claude Code / Codex / etc. harness files
  harness_sessions/           imported local Claude Code/Codex sessions
  cognee_bundle/              Cognee nodes/edges/manifest JSONL
  wiki/                       L2 markdown wiki — see below
  site/                       L3 static site — see below

.tesserae/wiki/ (L2)

wiki/
  sources/<slug>.md           raw documents from data/ + docs/, with frontmatter
  concepts/<slug>.md          Concept / TechnicalTerm / Algorithm / etc.
  entities/<slug>.md          Model / Dataset / Benchmark / Metric / Org / Person
  papers/<slug>.md            Paper hub
  repos/<slug>.md             Repository / Project / CodeProject
  topics/<slug>.md            ResearchField / ResearchTopic / ApproachFamily / Trend
  syntheses/<slug>.md         pulse, daily_digest, weekly, topic, comparison, field_overview
  questions/<slug>.md         OpenQuestion

Chaque fichier est modifiable à la main ; la prochaine compilation respecte les modifications utilisateur tant que le hash du corps diffère de ce que le projector écrirait. (Modifier seulement le corps gagne ; modifier le frontmatter perd à la compilation suivante parce que le frontmatter est régénéré.) Les utilisateurs d’Obsidian peuvent ouvrir .tesserae/wiki/ directement ; l’adaptateur obsidian_vault/ existant est une projection séparée, pas un substitut.

.tesserae/site/ (L3)

site/
  index.html                  home + project pulse
  about.html                  schema, build info
  assets/{style.css,app.js}   single CSS bundle + single JS bundle
  sources/index.html
  sources/<slug>.html
  sources/<slug>.txt          AI sibling — plain text
  sources/<slug>.json         AI sibling — structured record
  concepts/…  entities/…  papers/…  repos/…  topics/…  syntheses/…  questions/…
  sessions/index.html          imported harness-session index
  sessions/<project>/<id>.html session detail: summary, metadata, turn rail, markdown turns, collapsed tools
  timeline/index.html
  graph/index.html            interactive 2D + 3D force layout
  graph.json                  full graph payload (incl. code nodes, for tooling)
  graph.jsonld                schema.org Dataset, wiki-layer nodes only
  search-index.json           palette + page search; wiki-layer kinds only
  llms.txt                    llmstxt.org — short index
  llms-full.txt               llmstxt.org — every page body, capped 5MB
  sitemap.xml                 every emitted route
  rss.xml                     last 30 syntheses
  robots.txt                  permissive (crawl + index)
  ai-readme.md                machine-readable site map
  manifest.json               sha256 + size for every emitted file

Ce qui est volontairement exclu

La refonte a tracé une ligne explicite : les nœuds code-class et code-function restent dans graph.json (donc les consommateurs MCP, Cognee et Graphiti les voient toujours), mais ils n’obtiennent jamais de pages HTML, n’apparaissent jamais dans search-index.json et n’apparaissent jamais dans la navigation. C’est le contrat côté utilisateur : le wiki est une base de connaissances orientée documents, pas un navigateur de fonctions.

Concrètement, StaticSiteBuilder ignore tout nœud dont le type n’est pas dans la map des types wiki L2 (tesserae/wiki_projector.py::_KIND_FOR_TYPE) :

  • Exclus de L2 + L3 : CodeClass, CodeFunction, CodeModule, Dependency, EvidenceSpan, SourceFile, toutes les variantes Claim (Claim, ContributionClaim, PerformanceClaim, ComparisonClaim, LimitationClaim, CausalClaim).
  • Surfaces où ils apparaissent encore : comme bullets, badges, neighbor counts ou evidence excerpts inline dans les pages wiki liées, et dans graph.json pour le downstream tooling.

Si vous avez besoin de navigation au niveau code, pointez un outil LSP / call-graph directement vers l’arbre source — c’est un problème différent de « wiki de ce que ce projet sait ».

Récit d’idempotence

La refonte vise une sortie identique octet pour octet sur deux exécutions consécutives de project compile avec des entrées inchangées. Les éléments :

  1. L’extraction des sources utilise les hashes de contenu de manifest.json ; les fichiers inchangés sont ignorés, donc le graphe reste stable.
  2. Les écritures de la couche wiki sont idempotentes au niveau du corps. WikiPageStore.write_page lit le fichier existant, retire le frontmatter, calcule le sha256 du corps, et court-circuite si le nouveau corps a le même hash — même si le nouveau frontmatter a un timestamp generated_at différent. C’est l’astuce clé qui garde les git diffs serrés lors des reconstructions.
  3. La sortie de synthèse porte content_hash: sha256-… dans son frontmatter. Le hash du corps est calculé sans generated_at, donc des compilations répétées sur le même graphe produisent le même hash, et les nœuds Synthesis portent le même content_hash dans les métadonnées du graphe.
  4. Le rendu du site efface site/ au début de write_site, puis écrit de façon déterministe : les routes sont triées, les dictionnaires dumpés avec sort_keys=True, manifest.json parcouru via sorted(rglob("*")). Deux exécutions produisent des fichiers identiques octet pour octet, y compris le manifest.

Ceci est vérifié par tests/test_site_pages.py et le smoke end-to-end dans tests/test_project_e2e_redesign.py (compiler deux fois, diff des sites, attendre zéro delta de fichier).

Notes de mise à l’échelle

  • Limite de nœuds de la vue graphe. MD0 borne le payload embarqué dans la page pour le layout de force interactif. Au-delà d’environ 1500 nœuds, la simulation côté navigateur devient lente sur du matériel moyen ; la page retire donc d’abord les nœuds wiki-layer de plus faible degré lorsque le nombre dépasse la limite. Le graph.json exporté n’est pas affecté — il contient toujours le graphe complet. Les code nodes sont filtrés avant l’application de la limite.
  • Limite de llms-full.txt. Une limite de sécurité de 5 MB s’applique dans MD1 ; si la limite est atteinte, le fichier se termine par le marqueur [TRUNCATED — see graph.jsonld for the full set]. graph.jsonld n’est pas limité parce que les consommateurs JSON-LD attendent l’ensemble complet.
  • Index de recherche. Wiki-layer kinds uniquement. Les code-graph nodes n’entrent jamais dans search-index.json ; l’objectif de refonte est < 500 KB pour le corpus dogfood et nous sommes largement en dessous aujourd’hui.
  • Budget d’octets par page (règle empirique). Chaque detail page < 60 KB gz HTML, shared CSS < 30 KB, shared JS < 25 KB, sigma vendor seulement sur la graph page (~60 KB). La graph view utilise 3D-force-graph + Three.js chargés une seule fois ; toutes les autres pages restent vanilla.
  • Temps de compilation sur dogfood. ~300 fichiers markdown s’extraient en moins de 5 s sur une machine de dev récente ; le rendu du site ajoute encore ~2 s. L’idempotence de la couche wiki signifie que les compilations suivantes ne touchent que les chemins modifiés.

Surface d’interaction frontend

  • Search palettecmd+k / ctrl+k / /. Fuzzy match sur search-index.json, limité aux wiki kinds. Les pages récentes sont persistées dans localStorage.
  • Theme toggle — bouton en haut à droite ; data-theme="dark" est stocké dans localStorage et appliqué avant le paint pour éviter le flash.
  • Sticky right TOC — desktop uniquement ; se replie en drawer <details> sur mobile. Généré depuis les <h2> / <h3> dans le corps de page.
  • Activity heatmap — SVG de 26 semaines avec labels month + weekday. Les cellules pointent vers la source page digest.md du jour quand elle existe. (Per-day timeline detail pages — /timeline/<YYYY-MM-DD>.html — est un follow-up explicite ; la notice inline dans render_timeline le signale. ⚠ in-progress.)
  • Graph view/graph/. 3D force layout (3d-force-graph + Three.js) avec hover tooltips, edge labels, zoom ancré au curseur et 2D fallback view. Les couleurs de nœuds viennent de ResearchNodeType.
  • Mobile shell — drawer rail, bottom nav, fluid type, touch-safe hit targets (≥ 44 px).

Stratégie de test

  • Unittests/test_wiki_store.py, tests/test_synthesis.py, tests/test_site_components.py, tests/test_site_pages.py, tests/test_site_exports.py, tests/test_relevance.py.
  • Idempotencetests/test_project_e2e_redesign.py compile deux fois et affirme zéro diff dans wiki/ et site/.
  • Intégrité des lienstests/test_frontend.py parse chaque HTML émis pour les hrefs et affirme que chaque lien interne se résout vers un fichier généré. Aucun nodes/codeclass-*.html n’est produit.
  • AI siblings — pour chaque path/foo.html, la suite de tests affirme que path/foo.txt et path/foo.json existent ; le JSON se parse et contient {title, kind, body, links}.
  • Pas de Playwright — pytest vanilla sous PYTEST_DISABLE_PLUGIN_AUTOLOAD=1.

Documents liés