diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4dd35485..7d8ceb5c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,9 +2,9 @@ name: AIngle CI
on:
push:
- branches: [ main, develop ]
+ branches: [ main, dev ]
pull_request:
- branches: [ main, develop ]
+ branches: [ main, dev ]
env:
CARGO_TERM_COLOR: always
diff --git a/.gitignore b/.gitignore
index bb7ee1d0..3d9e8360 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,9 @@ Thumbs.db
CLAUDE.md
.claude_settings
.claudeignore
+.mcp.json
+docs/superpowers/
+.superpowers/
# GitHub Copilot
.copilot/
@@ -113,6 +116,8 @@ llm-instructions.md
*.profraw
*.profdata
aingle_iot.db
+data/
+*.sled
# Logs
*.log
@@ -127,4 +132,9 @@ aingle_iot.db
.env.local
.secrets
contexto/
-contexto/*
\ No newline at end of file
+contexto/*
+
+# Local neural-embedder test models (never commit — ~470MB fp32 e5-small, fetched on demand)
+crates/ineru/test-models/
+# Local ONNX Runtime dylib for running the gated neural tests (set ORT_DYLIB_PATH to it)
+.tmp-ort/
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index d422051e..b2743605 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -70,7 +70,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
+ "getrandom 0.3.4",
"once_cell",
+ "serde",
"version_check",
"zerocopy",
]
@@ -97,7 +99,7 @@ dependencies = [
[[package]]
name = "aingle_ai"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"blake2",
"candle-core 0.9.2",
@@ -119,7 +121,7 @@ dependencies = [
[[package]]
name = "aingle_contracts"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"blake3",
"dashmap 6.1.0",
@@ -138,9 +140,10 @@ dependencies = [
[[package]]
name = "aingle_cortex"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"aingle_graph",
+ "aingle_ingest",
"aingle_logic",
"aingle_raft",
"aingle_wal",
@@ -149,6 +152,7 @@ dependencies = [
"async-graphql",
"async-graphql-axum",
"axum",
+ "base64 0.22.1",
"blake3",
"chrono",
"dashmap 6.1.0",
@@ -157,6 +161,7 @@ dependencies = [
"futures",
"hex",
"if-addrs 0.13.4",
+ "ignore",
"ineru",
"jsonwebtoken",
"log",
@@ -169,6 +174,7 @@ dependencies = [
"regex",
"reqwest",
"rmcp",
+ "rsa",
"rustls",
"rustls-pemfile",
"schemars",
@@ -193,7 +199,7 @@ dependencies = [
[[package]]
name = "aingle_graph"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"bincode",
"blake3",
@@ -215,9 +221,21 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "aingle_ingest"
+version = "0.7.0"
+dependencies = [
+ "aingle_graph",
+ "blake3",
+ "once_cell",
+ "regex",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "aingle_logic"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"aingle_graph",
"chrono",
@@ -233,7 +251,7 @@ dependencies = [
[[package]]
name = "aingle_minimal"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"async-io",
"async-tungstenite",
@@ -275,7 +293,7 @@ dependencies = [
[[package]]
name = "aingle_raft"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"aingle_graph",
"aingle_wal",
@@ -296,7 +314,7 @@ dependencies = [
[[package]]
name = "aingle_viz"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"aingle_graph",
"aingle_minimal",
@@ -318,7 +336,7 @@ dependencies = [
[[package]]
name = "aingle_wal"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"bincode",
"blake3",
@@ -330,7 +348,7 @@ dependencies = [
[[package]]
name = "aingle_zk"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"blake3",
"bulletproofs",
@@ -670,7 +688,7 @@ dependencies = [
"async-graphql-value",
"async-trait",
"asynk-strim",
- "base64",
+ "base64 0.22.1",
"blocking",
"bytes",
"chrono",
@@ -914,7 +932,7 @@ checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
dependencies = [
"axum-core",
"axum-macros",
- "base64",
+ "base64 0.22.1",
"bytes",
"form_urlencoded",
"futures-util",
@@ -994,6 +1012,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -1497,6 +1521,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
[[package]]
name = "cbc"
version = "0.1.2"
@@ -1658,7 +1691,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
- "libloading",
+ "libloading 0.8.9",
]
[[package]]
@@ -1761,6 +1794,21 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "compact_str"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "serde",
+ "static_assertions",
+]
+
[[package]]
name = "compression-codecs"
version = "0.4.37"
@@ -2325,6 +2373,15 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "dary_heap"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "dashmap"
version = "5.5.3"
@@ -2457,6 +2514,37 @@ dependencies = [
"powerfmt",
]
+[[package]]
+name = "derive_builder"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
+dependencies = [
+ "darling 0.20.11",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
+dependencies = [
+ "derive_builder_core",
+ "syn 2.0.117",
+]
+
[[package]]
name = "derive_more"
version = "2.1.1"
@@ -2908,6 +2996,12 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "esaxx-rs"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6"
+
[[package]]
name = "esp-idf-hal"
version = "0.44.1"
@@ -3041,6 +3135,21 @@ dependencies = [
"siphasher",
]
+[[package]]
+name = "fastembed"
+version = "5.17.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "545e4fb17fc48768ff36c2a3854aa5b0b809d0ed595ab5530fa8ac94f31bd0ea"
+dependencies = [
+ "anyhow",
+ "ndarray",
+ "ort",
+ "safetensors 0.8.0",
+ "serde",
+ "serde_json",
+ "tokenizers",
+]
+
[[package]]
name = "fastrand"
version = "2.3.0"
@@ -3946,7 +4055,7 @@ version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"futures-channel",
"futures-util",
@@ -4153,12 +4262,13 @@ dependencies = [
[[package]]
name = "ineru"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"bincode",
"blake3",
"chrono",
"criterion",
+ "fastembed",
"log",
"rusqlite",
"serde",
@@ -4393,7 +4503,7 @@ version = "10.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1"
dependencies = [
- "base64",
+ "base64 0.22.1",
"ed25519-dalek",
"getrandom 0.2.17",
"hmac",
@@ -4412,7 +4522,7 @@ dependencies = [
[[package]]
name = "kaneru"
-version = "0.6.3"
+version = "0.7.0"
dependencies = [
"chrono",
"criterion",
@@ -4530,6 +4640,16 @@ dependencies = [
"windows-link 0.2.1",
]
+[[package]]
+name = "libloading"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
+dependencies = [
+ "cfg-if",
+ "windows-link 0.2.1",
+]
+
[[package]]
name = "libm"
version = "0.2.16"
@@ -4675,6 +4795,22 @@ dependencies = [
"zerocopy-derive",
]
+[[package]]
+name = "macro_rules_attribute"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520"
+dependencies = [
+ "macro_rules_attribute-proc_macro",
+ "paste",
+]
+
+[[package]]
+name = "macro_rules_attribute-proc_macro"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30"
+
[[package]]
name = "maplit"
version = "1.0.2"
@@ -4830,6 +4966,28 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "monostate"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67"
+dependencies = [
+ "monostate-impl",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "monostate-impl"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "more-asserts"
version = "0.3.1"
@@ -5172,6 +5330,28 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+[[package]]
+name = "onig"
+version = "6.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2"
+dependencies = [
+ "bitflags 2.11.0",
+ "libc",
+ "once_cell",
+ "onig_sys",
+]
+
+[[package]]
+name = "onig_sys"
+version = "69.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
[[package]]
name = "oorandom"
version = "11.1.5"
@@ -5295,6 +5475,25 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+[[package]]
+name = "ort"
+version = "2.0.0-rc.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7de3af33d24a745ffb8fab904b13478438d1cd52868e6f17735ef6e1f8bf133"
+dependencies = [
+ "libloading 0.9.0",
+ "ndarray",
+ "ort-sys",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "ort-sys"
+version = "2.0.0-rc.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7b497d21a8b6fbb4b5a544f8fadb77e801a09ae0add9e411d31c6f89e3c1e90"
+
[[package]]
name = "oxilangtag"
version = "0.1.5"
@@ -5462,7 +5661,7 @@ version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
- "base64",
+ "base64 0.22.1",
"serde_core",
]
@@ -6014,6 +6213,17 @@ dependencies = [
"rayon-core",
]
+[[package]]
+name = "rayon-cond"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f"
+dependencies = [
+ "either",
+ "itertools 0.14.0",
+ "rayon",
+]
+
[[package]]
name = "rayon-core"
version = "1.13.0"
@@ -6195,7 +6405,7 @@ version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
@@ -6340,7 +6550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e"
dependencies = [
"async-trait",
- "base64",
+ "base64 0.22.1",
"bytes",
"chrono",
"futures",
@@ -6650,6 +6860,19 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "safetensors"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79b079b829cb27a1c3c374341345ed2e8b2c0c839034522cee576c140bd7f846"
+dependencies = [
+ "hashbrown 0.16.1",
+ "libc",
+ "serde",
+ "serde_json",
+ "tempfile",
+]
+
[[package]]
name = "same-file"
version = "1.0.6"
@@ -7152,6 +7375,18 @@ dependencies = [
"der",
]
+[[package]]
+name = "spm_precompiled"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326"
+dependencies = [
+ "base64 0.13.1",
+ "nom",
+ "serde",
+ "unicode-segmentation",
+]
+
[[package]]
name = "sse-stream"
version = "0.2.3"
@@ -7260,7 +7495,7 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e0fd33c04d4617df42c9c84c698511c59f59869629fb7a193067eec41bce347"
dependencies = [
- "base64",
+ "base64 0.22.1",
"crc",
"lazy_static",
"md-5",
@@ -7548,6 +7783,39 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+[[package]]
+name = "tokenizers"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b238e22d44a15349529690fb07bd645cf58149a1b1e44d6cb5bd1641ff1a6223"
+dependencies = [
+ "ahash 0.8.12",
+ "aho-corasick",
+ "compact_str",
+ "dary_heap",
+ "derive_builder",
+ "esaxx-rs",
+ "getrandom 0.3.4",
+ "itertools 0.14.0",
+ "log",
+ "macro_rules_attribute",
+ "monostate",
+ "onig",
+ "paste",
+ "rand 0.9.2",
+ "rayon",
+ "rayon-cond",
+ "regex",
+ "regex-syntax",
+ "serde",
+ "serde_json",
+ "spm_precompiled",
+ "thiserror 2.0.18",
+ "unicode-normalization-alignments",
+ "unicode-segmentation",
+ "unicode_categories",
+]
+
[[package]]
name = "tokio"
version = "1.50.0"
@@ -7872,7 +8140,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6a8b8ac3543b2a8eb0b28c7ac3d5f2db6221e057f3b3ae47cf7637b1333a5c3"
dependencies = [
"async-trait",
- "base64",
+ "base64 0.22.1",
"futures",
"log",
"md-5",
@@ -7931,6 +8199,15 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+[[package]]
+name = "unicode-normalization-alignments"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de"
+dependencies = [
+ "smallvec",
+]
+
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
@@ -7943,6 +8220,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+
[[package]]
name = "universal-hash"
version = "0.5.1"
diff --git a/Cargo.toml b/Cargo.toml
index 672ec8e7..3c691e85 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ members = [
"crates/aingle_graph", # Native Semantic GraphDB
"crates/aingle_zk", # Zero-Knowledge Proofs (Privacy)
"crates/ineru", # Ineru Memory System
+ "crates/aingle_ingest", # Structural ingestion (markdown/code → triples + chunks)
"crates/aingle_ai", # AI Integration Layer
"crates/aingle_logic", # Proof-of-Logic Validation Engine
"crates/kaneru", # Kaneru — Unified Multi-Agent Execution System
diff --git a/README.md b/README.md
index 271eb2cc..d01074ef 100644
--- a/README.md
+++ b/README.md
@@ -157,6 +157,61 @@ Interactive D3.js dashboard. Watch your DAG evolve in real-time. Filter, search,
---
+## Grounded Retrieval & Provenance
+
+AIngle includes a retrieval layer that turns a corpus of documents into
+**cited, provenance-backed context** for question answering, with an explicit
+signal for how well an answer is supported by the underlying sources. The intent
+is *grounded* generation: a downstream language model is given only material that
+can be traced back to verifiable records, rather than being trusted to recall
+facts on its own.
+
+The pipeline has three stages:
+
+1. **Ingestion (`aingle_ingest`).** A pure, deterministic extractor maps a
+ document `(path, content)` to an `Extraction` of **provenanced triples** and
+ **text chunks**. Structure (headings, sections, links) is preserved as
+ semantic triples in the graph, while chunks become the unit of retrieval.
+ Because ingestion is deterministic, the same input always yields the same
+ triples and chunks — a prerequisite for reproducible provenance.
+
+2. **Provenance anchoring.** Each ingested unit is tied to the append-only DAG
+ action that recorded it via a `provenance_anchor` — the signed hash of that
+ action (see the `dag-sign` feature). This makes a retrieved chunk not just
+ *findable* but *attestable*: you can point at the exact, tamper-evident record
+ it came from.
+
+3. **Grounded retrieval (`service::ground`).** A question is embedded and matched
+ against the ingested chunks; the service returns a `GroundedContext`
+ containing the cited chunks (with their `provenance_anchor`) and a
+ **groundedness** classification:
+
+ | groundedness | meaning |
+ |--------------|---------|
+ | `grounded` | strong similarity **and** enough corroborating chunks — the answer is well supported by the sources. |
+ | `weak` | some relevant material, but below the corroboration threshold — answer with caution. |
+ | `ungrounded` | no sufficiently similar source — the sources do not support an answer. |
+
+ The groundedness signal is derived from the best match plus a minimum number
+ of corroborating chunks, so a caller (or an LLM prompted with the context) can
+ explicitly say when the sources *don't* support an answer instead of
+ hallucinating one.
+
+These capabilities are exposed to Model-Context-Protocol clients through three
+tools (see the [MCP server](#mcp-server) section):
+
+| Tool | Purpose |
+|------|---------|
+| `aingle_ingest` | Ingest a document `(path, content)` → provenanced triples + chunks. |
+| `aingle_ground` | Answer a question with cited, provenance-backed context + a groundedness signal. |
+| `aingle_sources` | List the sources that have been ingested. |
+
+Because retrieval results carry provenance anchors into the signed DAG history,
+grounded answers are **auditable**: every cited passage resolves to a verifiable
+action in the ledger.
+
+---
+
## Clustering
AIngle supports multi-node clustering via Raft consensus for high availability and horizontal scalability. Writes are replicated to all nodes; reads can be served from any node with optional quorum consistency.
diff --git a/assets/aingle-legacy.svg b/assets/aingle-legacy.svg
new file mode 100644
index 00000000..e55a326d
--- /dev/null
+++ b/assets/aingle-legacy.svg
@@ -0,0 +1,80 @@
+
diff --git a/assets/aingle.svg b/assets/aingle.svg
index e55a326d..b1651e21 100644
--- a/assets/aingle.svg
+++ b/assets/aingle.svg
@@ -1,80 +1,11 @@
-