"A brain that starts empty, learns from experience, and remembers what it learned — forever."
Manas (Sanskrit: मनस् — mind, intellect, the seat of thought) is a self-growing local AI brain written entirely from scratch in Rust. It starts with zero knowledge, learns one fact at a time, stores that knowledge directly inside neural network weights, and never forgets what it learned — even as it keeps learning new things.
No cloud. No GPU. No external ML framework. Runs on your laptop CPU. Everything lives in one
.manasfile.
- Why v2 Exists
- The Core Goal
- Core Principles
- What Makes Manas Different
- System Overview
- The Engine — Associative Memory
- Anti-Forgetting System
- The Growth System
- Crate Structure
- Crate Details
- The .manas Binary Format
- Data Flow — Full Pipeline
- Neuron Lifecycle
- The Importance Scoring System
- The Freshness System
- Error Handling Strategy
- Benchmarks and Integration Gates
- What Manas Is Not
Manas v1 was built with the right vision but the wrong engine.
The v1 ask command answered questions by searching a text file (brain.manas.sources)
using token overlap. The neural network — all its neurons, backprop, importance scoring,
and protection levels — played no role in answering questions. Knowledge did not live in
the weights. It lived in a text file on disk.
This was proven by a simple test:
# teach Manas a fact
./manas teach "A cat is a small domesticated animal."
./manas ask "What is a cat?"
# → correct answer ✅
# delete the text sidecar
rm brain.manas.sources brain.manas.sourceindex
# ask again — neural weights only
./manas ask "What is a cat?"
# → "Not enough local memory to answer this yet." ✅ confirmed broken
# → falls back to DuckDuckGo web search ✅ confirmed brokenThe neural network knew nothing. It was decoration.
Manas v2 fixes this at the foundation. Knowledge lives in weights. The neural network IS the memory. No text sidecar. No keyword search. The network answers directly from what it learned.
Build a neural network that works like a human brain learning from experience — starting empty, growing as it learns, never forgetting what it knew, and running entirely on a local CPU.
In concrete terms:
Step 1: Brain starts empty — zero neurons, zero knowledge
Step 2: Teach it "A cat is a small animal with fur"
Step 3: The neural weights now encode the concept "cat → animal, fur, small"
Step 4: Teach it 100 more completely unrelated facts
Step 5: Ask "What is a cat?"
Step 6: The neural weights answer correctly — no text file involved
Step 7: The brain grew neurons during steps 2-4 as needed
Step 8: Old knowledge was never destroyed by new learning
This is the demo that matters. Everything in this architecture exists to make that demo work reliably.
The neural network weights ARE the memory. ask queries the network directly.
No text sidecars. No keyword search. No fallback to the internet for taught facts.
Once a fact is learned and its neurons are protected, no future learning can destroy it. Anti-forgetting is built into the learning objective itself, not bolted on afterward.
The network starts with zero neurons and grows a new neuron only when it cannot represent something well enough. Growth is measured, not unbounded.
One .manas file. No account. No API key. No internet required after facts are taught.
The user owns their brain completely.
No Candle. No HuggingFace. No burn. No tch. No external ML framework. Every neuron, every gradient, every matrix multiplication is Rust written by hand. This gives full control over the growth system, the anti-forgetting system, and the storage format — none of which exist in any external framework.
Manas is not a ChatGPT replacement. It is not a general-purpose LLM. It is a local associative memory system that learns and remembers facts. Every milestone is honest about what works and what does not.
Every version is testable before the next begins. No feature is added until the foundation beneath it is proven to work.
| Problem | How Everyone Else Solves It | How Manas Solves It |
|---|---|---|
| Storing knowledge locally | Save text to a file, search it | Knowledge lives in neural weights |
| Learning new facts | Retrain the whole model | Online learning — one fact at a time |
| Forgetting old facts | Accepted as normal (catastrophic forgetting) | Protection system prevents it |
| Growing with new knowledge | Fixed parameter count forever | Network grows new neurons as needed |
| Running locally | Requires GPU or cloud | Runs on any laptop CPU |
| Knowledge cutoff | Hard cutoff date, stale forever | Teach it new facts anytime |
| Water-cooled datacenters | Required for training | Not required — ever |
Input
─────
[Raw Text] [Local Files] [Folders]
│ │ │
└──────────────┼─────────────┘
│
▼
┌─────────────────┐
│ manas-ingest │ normalize, tokenize, chunk
└────────┬────────┘
│
▼
┌─────────────────┐
│ manas-learn │ associative learning engine
│ │ anti-forgetting system
│ │ growth signal
└────────┬────────┘
│
┌────────┴────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ manas-core │ │ importance + │
│ neurons │ │ protection │
│ layers │ │ system │
│ network │ │ (inside core) │
│ growth │ └──────────────────┘
└──────┬───────┘
│
▼
┌────────────────┐
│ manas-store │ .manas binary file
│ read / write │ append-only growth
│ integrity │ CRC32 checksum
└────────────────┘
Query
──────
manas ask "What is a cat?"
│
▼
manas-learn: encode question → query vector
│
▼
manas-core: forward pass → retrieve answer activation
│
▼
manas-learn: decode activation → human-readable answer
This is the heart of Manas v2. Everything else exists to support this.
v1 used MSE loss on next-token prediction as its learning objective. The network learned: given token A, predict token B. This is a language model objective — good for generating text, useless for storing and retrieving facts.
v2 uses associative memory learning as its core objective.
The network learns to associate an input pattern with an output pattern:
Input pattern: [tokens of "cat"]
Output pattern: [tokens of "animal", "fur", "small", "domesticated"]
Input pattern: [tokens of "paris"]
Output pattern: [tokens of "city", "france", "eiffel"]
When you later ask "What is a cat?", the network encodes the question into an input vector, runs a forward pass, and the output activations reconstruct the associated concepts.
Encoding:
"cat is a small animal"
→ tokenize → [cat, is, a, small, animal]
→ embed each token → Vec<f32> per token
→ average over tokens → single Vec<f32> (the input vector)
Learning:
input_vec = encode("cat")
target_vec = pack_answer_words("small domesticated animal with fur and whiskers")
loss = mse(network.forward(input_vec), target_vec)
gradients = backprop(loss)
update weights (respecting protection levels)
Querying:
question_vec = encode("What is a cat")
output_vec = read best hidden output column
answer = decode(output_vec) → recover packed known answer words
When the network learns cat → animal fur whiskers small domesticated,
the weights between neurons that activated for "cat" and neurons that activate for
"animal fur whiskers" are strengthened. That connection IS the knowledge.
No text file. No index. The weights are the memory.
Catastrophic forgetting is the #1 enemy of continual learning. When a standard network learns fact B, it overwrites the weights that stored fact A.
Manas v2 solves this with three layers of protection:
Every neuron has one of three protection states:
pub enum ProtectionLevel {
Open, // newly created — full weight updates allowed
Guarded, // recently used — small weight updates only
Frozen, // important/core — zero weight updates, ever
}Rules:
- A neuron starts as
Openwhen created - Stage 11 promotion uses weighted importance scores:
Open → Guardedat 0.50, thenGuarded → Frozenat 0.85 Frozenneurons are never updated by backprop — zero gradient appliedGuardedneurons receive updates clamped to[-GUARD_DELTA, +GUARD_DELTA]- Promotion is monotonic: protection can be strengthened but never weakened
This is enforced inside apply_gradients() in manas-core — not in the
trainer, not in the CLI. The protection is structural, not optional.
Every neuron has an importance_score: f32 computed from:
importance = 0.40 × activation_frequency
+ 0.30 × recency_score
+ 0.20 × weight_magnitude
+ 0.10 × age_grace
Neurons with high importance are promoted to Guarded or Frozen automatically
after each learning step. Low-importance neurons stay Open and are candidates
for reuse or compression.
Stage 11 formalizes this weighted score and recomputes it after learning.
When a new fact cannot be represented well by existing neurons (loss stays above
GROWTH_THRESHOLD after MAX_UPDATE_ATTEMPTS), Manas grows a new neuron
rather than forcing existing neurons to compromise their stored knowledge.
This means old knowledge is never diluted. New knowledge gets fresh capacity.
After teaching 1000 facts:
fact #1 (learned first) → still answerable ✅
fact #500 (learned middle) → still answerable ✅
fact #1000 (learned last) → still answerable ✅
This is the guarantee Manas v2 is designed to deliver and test against.
The network starts empty and grows exactly when needed.
teach("paris is the capital of france")
→ encode → input_vec
→ forward pass → output_vec
→ compute loss against target_vec
→ loss > GROWTH_THRESHOLD?
→ try updating existing Open neurons (up to MAX_ATTEMPTS)
→ loss still > GROWTH_THRESHOLD?
→ grow new neuron in most appropriate layer
→ initialize with small random weights
→ set ProtectionLevel::Open
→ importance_score = 0.0
Stage 17 adds real new-depth layer growth. Hidden layers are all layers before the final output layer. The output layer reads a flattened vector of every hidden activation, so adding a new hidden layer appends new output columns without rewriting older bound answer columns.
A new layer is added when:
- Existing hidden memory is saturated (
GuardedorFrozen) or the deepest hidden layer has reachedMAX_NEURONS_PER_LAYER - Loss is still above threshold after update attempts
- Network depth is below
MAX_LAYERS
pub const MAX_NEURONS_PER_LAYER: usize = 512;
pub const MAX_LAYERS: usize = 16;
pub const GROWTH_THRESHOLD: f32 = 0.35;
pub const MAX_UPDATE_ATTEMPTS: u32 = 3;The brain cannot grow infinitely. When it hits the bounds, it compresses low-importance neurons before growing further.
Every teach operation reports exactly what grew:
Teaching complete
neurons grown : 2
layers grown : 0
neurons protected : 14
neurons frozen : 7
total neurons : 23
manas/
├── Cargo.toml ← workspace root, edition 2024
├── ARCHITECTURE.md ← this file
├── ROADMAP.md
├── README.md
│
├── manas-core/ ← THE ENGINE
│ ├── Cargo.toml ← deps: rand only
│ └── src/
│ ├── lib.rs
│ ├── activation.rs ← ReLU, Sigmoid, Tanh, Linear
│ ├── neuron.rs ← Neuron, ProtectionLevel, Source
│ ├── layer.rs ← Layer struct
│ ├── network.rs ← Network, forward, grow, apply_gradients
│ └── error.rs ← ManasError enum
│
├── manas-store/ ← PERSISTENCE
│ ├── Cargo.toml ← no external deps
│ └── src/
│ ├── lib.rs
│ ├── format.rs ← binary layout constants, magic bytes
│ ├── writer.rs ← write full brain to .manas
│ ├── reader.rs ← read full brain from .manas
│ ├── patcher.rs ← append single neuron without full rewrite
│ └── integrity.rs ← CRC32 checksum, header validation
│
├── manas-learn/ ← LEARNING ENGINE
│ ├── Cargo.toml ← deps: manas-core, manas-store
│ └── src/
│ ├── lib.rs
│ ├── tokenizer.rs ← character n-gram tokenizer
│ ├── embedder.rs ← token → Vec<f32>, positional encoding
│ ├── encoder.rs ← text → single input vector
│ ├── decoder.rs ← output vector → human-readable text
│ ├── backprop.rs ← MSE loss, gradient computation
│ ├── trainer.rs ← learn(), query(), grow decision logic
│ ├── importance.rs ← importance scoring, promotion logic
│ ├── diagnostics.rs ← inspect, neuron list, trace data models
│ └── compression.rs ← forget plans and safe compaction reports
│
├── manas-ingest/ ← INPUT PIPELINE
│ ├── Cargo.toml ← deps: manas-core
│ └── src/
│ ├── lib.rs
│ ├── chunker.rs ← split text into overlapping chunks
│ ├── normalizer.rs ← clean text before learning
│ ├── file_reader.rs ← read a single file
│ ├── folder_walker.rs ← walk folder recursively
│ └── format/
│ ├── mod.rs
│ ├── plaintext.rs
│ ├── markdown.rs
│ ├── rust_source.rs
│ ├── json.rs
│ ├── toml.rs
│ └── csv.rs
│
├── manas-agent/ ← EXPLICIT INTERNET REFRESH
│ ├── Cargo.toml ← deps: manas-core, manas-learn, ureq, serde_json
│ └── src/
│ └── lib.rs ← DuckDuckGo client, fixtures, refresh planner
│
├── manas-language/ ← FLUENT GENERATION
│ ├── Cargo.toml ← deps: manas-core, manas-learn
│ └── src/
│ └── lib.rs ← sentence generation over neural concepts
│
├── manas-cli/ ← USER INTERFACE
│ ├── Cargo.toml ← deps: all runtime crates
│ └── src/
│ └── main.rs ← std-only arg parsing, command routing, formatting
│
└── manas-benches/ ← TOOLING ONLY
└── benches/
└── bench.rs ← B1-B9 benchmark harness, BENCHMARKS.md generator
Runtime crates: 7
Tooling crates: 1 (manas-benches, not part of the runtime path)
manas-language is generation-only — it turns neural-weight query concepts
into fluent sentences. It does not store knowledge, answer from sidecars, or
replace associative memory.
manas-agent is refresh-only — it powers explicit manas refresh.
It is not used by default manas ask, which remains neural-weight retrieval only.
No manas-memory crate — importance scoring and protection now live
directly inside manas-core and manas-learn where they belong.
In v1, they were separate but didn't actually influence the learning path.
In v2, they are structural — built into apply_gradients() and trainer.rs.
The neural network runtime. No ML frameworks. No external math libraries. Every operation is plain Rust.
Dependencies: rand only (for weight initialization)
// activation.rs
pub enum Activation {
ReLU,
Sigmoid,
Tanh,
Linear,
}
impl Activation {
pub fn apply(&self, x: f32) -> f32 { ... }
pub fn derivative(&self, x: f32) -> f32 { ... }
}// neuron.rs
pub enum ProtectionLevel {
Open, // full updates allowed
Guarded, // clamped updates only: [-GUARD_DELTA, +GUARD_DELTA]
Frozen, // zero updates, always
}
pub enum Source {
RawText,
LocalFile { path: String },
Internet { url: String },
Unknown,
}
pub struct Neuron {
pub id: u64,
pub weights: Vec<f32>,
pub bias: f32,
pub activation: Activation,
pub importance_score: f32,
pub protection_level: ProtectionLevel,
pub born_at: u64, // unix timestamp
pub last_activated: u64, // unix timestamp
pub activation_count: u64,
pub source: Source,
pub freshness_category: u8, // 0=timeless 1=slow 2=fast 3=realtime
pub memory_input: String, // refresh subject; not used by ask
pub memory_target: String, // refresh target; not used by ask
pub refreshed_at: u64, // unix timestamp of explicit refresh
}// layer.rs
pub struct Layer {
pub id: u32,
pub neurons: Vec<Neuron>,
pub activation: Activation,
}
impl Layer {
pub fn forward(&self, input: &[f32]) -> Vec<f32> { ... }
}// network.rs
pub struct Network {
pub layers: Vec<Layer>,
pub total_neurons: u64,
pub created_at: u64,
pub version: u8,
pub next_id: u64, // monotonic neuron ID counter
}
impl Network {
pub fn new(input_dim: usize, hidden_dim: usize, output_dim: usize) -> Self { ... }
pub fn new_empty(embed_dim: usize) -> Self { ... }
pub fn forward(&self, input: &[f32]) -> Vec<f32> { ... }
pub fn forward_with_cache(&self, input: &[f32]) -> ForwardCache { ... }
pub fn grow_neuron(&mut self, layer_id: u32, input_size: usize) -> Result<u64, ManasError> { ... }
pub fn grow_layer(&mut self, input_size: usize, neuron_count: usize) -> Result<u32, ManasError> { ... }
pub fn bind_hidden_neuron_to_fact(&mut self, neuron_id: u64, input: &[f32], target: &[f32])
-> Result<usize, ManasError> { ... }
pub fn readout_from_best_hidden(&self, input: &[f32]) -> Option<HiddenReadout> { ... }
pub fn neuron_count(&self) -> u64 { ... }
pub fn layer_count(&self) -> usize { ... }
pub fn open_neuron_count(&self) -> u64 { ... }
pub fn frozen_neuron_count(&self) -> u64 { ... }
pub fn apply_gradients(&mut self, gradients: &[(u64, NeuronGradients)], lr: f32) { ... }
pub fn recompute_next_id(&mut self) { ... }
}This is where anti-forgetting is enforced structurally:
pub fn apply_gradients(&mut self, gradients: &[(u64, NeuronGradients)], lr: f32) {
for (neuron_id, grad) in gradients {
let neuron = self.find_neuron_mut(*neuron_id);
match neuron.protection_level {
ProtectionLevel::Frozen => continue, // zero update, always
ProtectionLevel::Guarded => apply_clamped(neuron, grad, lr, GUARD_DELTA),
ProtectionLevel::Open => apply_full(neuron, grad, lr),
}
}
}Protection is not a suggestion. It is enforced here and cannot be bypassed
by any layer above manas-core.
Custom binary persistence. Everything about the brain lives in one .manas file.
Dependencies: none (pure Rust std only)
[MAGIC: 4 bytes] [VERSION: 1 byte] [HEADER: variable] [NEURONS: variable] [CRC32: 4 bytes]
See Section 11 for the full binary format specification.
pub struct ManasBrain {
pub path: PathBuf,
}
impl ManasBrain {
pub fn new(path: impl Into<PathBuf>) -> Self { ... }
pub fn save(&self, network: &Network) -> Result<(), ManasError> { ... }
pub fn save_state(&self, state: &BrainState) -> Result<(), ManasError> { ... }
pub fn load(&self) -> Result<Network, ManasError> { ... }
pub fn load_state(&self) -> Result<BrainState, ManasError> { ... }
pub fn metadata(&self) -> Result<BrainMetadata, ManasError> { ... }
pub fn exists(&self) -> bool { ... }
pub fn size_bytes(&self) -> u64 { ... }
}Stage 14 exposes BrainMetadata from the existing header fields:
format version, created time, modified time, total neurons, layer count,
input dimension, and vocab size. This does not change the version 2 binary
format.
Future storage patching can append newly grown neurons without rewriting the
whole brain. The current implementation saves validated full BrainState
snapshots with CRC32 integrity.
The learning engine. This is where the associative memory objective lives.
Dependencies: manas-core, manas-store, rand
v1 used a whitespace word splitter. v2 uses character n-grams.
"cat" → ["c", "ca", "cat", "#cat"]
"cats" → ["c", "ca", "cat", "cats", "#cats"]
"category" → ["c", "ca", "cat", "cate", "categ", ...]
Why:
- "cat" and "cats" share substructure → the model learns they are related
- Order information is preserved inside n-grams
- No external tokenizer library needed
- Works for any language, including Sanskrit
pub struct Tokenizer {
pub vocab: HashMap<String, u32>,
pub id_to_token: HashMap<u32, String>,
pub vocab_size: u32,
pub max_ngram: usize, // default: 4
}
impl Tokenizer {
pub fn encode(&mut self, text: &str) -> Vec<u32> { ... }
pub fn decode(&self, ids: &[u32]) -> String { ... }
pub fn vocab_size(&self) -> u32 { ... }
}v1 averaged all token embeddings (order-blind). v2 uses positional encoding so order is preserved.
embed("cat sat") ≠ embed("sat cat")
pub struct Embedder {
pub embed_table: HashMap<u32, Vec<f32>>,
pub embed_dim: usize,
pub positional_scale: f32,
}
impl Embedder {
pub fn new(embed_dim: usize) -> Self { ... }
pub fn with_seed(embed_dim: usize, seed: u64) -> Self { ... }
pub fn get_or_create(&mut self, id: u32) -> &Vec<f32> { ... }
pub fn embed_with_position(&self, id: u32, position: usize) -> Vec<f32> { ... }
pub fn encode_sequence(&mut self, ids: &[u32]) -> Vec<f32> { ... }
pub fn encode_existing_sequence(&self, ids: &[u32]) -> Vec<f32> { ... }
// positional encoding: sinusoidal pair rotation + bounded modulation
}pub fn encode(text: &str, tokenizer: &mut Tokenizer, embedder: &mut Embedder) -> Vec<f32> {
let ids = tokenizer.encode(text);
embedder.encode_sequence(&ids)
// returns single Vec<f32> of length embed_dim
// this is the input vector to the network
}pub fn decode(output_vec: &[f32], tokenizer: &Tokenizer, embedder: &Embedder) -> String {
// find the token whose embedding is closest (cosine similarity)
// to each dimension cluster in output_vec
// reconstruct the most likely answer tokens
// join into human-readable string
}pub struct Trainer {
pub tokenizer: Tokenizer,
pub embedder: Embedder,
pub learning_rate: f32,
pub growth_threshold: f32,
pub max_update_attempts: u32,
pub freshness_category: u8,
pub source: Source,
}
impl Trainer {
// Teach one fact. Returns what happened.
pub fn learn(&mut self, network: &mut Network, input: &str, target: &str)
-> Result<LearnReport, ManasError> { ... }
// Teach one fact and preserve source metadata on the best matching neuron.
pub fn learn_with_source(
&mut self,
network: &mut Network,
input: &str,
target: &str,
source: Source,
) -> Result<LearnReport, ManasError> { ... }
// Teach one fact with explicit freshness metadata.
pub fn learn_with_source_and_freshness(
&mut self,
network: &mut Network,
input: &str,
target: &str,
source: Source,
freshness: FreshnessCategory,
) -> Result<LearnReport, ManasError> { ... }
// Ask the network a question. Returns best answer from weights.
pub fn query(&mut self, network: &Network, question: &str)
-> Result<QueryResult, ManasError> { ... }
// Promote neurons based on current importance scores
pub fn update_protection_levels(&self, network: &mut Network) { ... }
}
pub struct LearnReport {
pub loss_before: f32,
pub loss_after: f32,
pub neurons_grown: u32,
pub layers_grown: u32,
pub neurons_promoted: u32, // Open → Guarded or Guarded → Frozen
pub neurons_frozen: u32,
pub total_neurons: u64,
pub update_applied: bool,
}
pub struct QueryResult {
pub answer: String,
pub confidence: f32, // 0.0 → 1.0
pub answered_from: AnswerSource,
pub freshness_warning: Option<FreshnessWarning>,
}
pub struct HiddenReadout {
pub hidden_index: usize,
pub activation: f32,
pub output: Vec<f32>,
}
pub struct FreshnessWarning {
pub category: FreshnessCategory,
pub age_days: u64,
}
pub enum AnswerSource {
NeuralWeights, // answered from network weights directly
NotEnough, // network does not have enough activation
}// importance.rs
pub fn compute_importance(neuron: &Neuron, now: u64) -> f32 {
let freq = (neuron.activation_count as f32 / 10_000.0).clamp(0.0, 1.0);
let recency = recency_score(neuron.last_activated, now);
let magnitude = weight_magnitude(neuron);
let age_grace = age_grace_score(neuron.born_at, now);
0.40 * freq + 0.30 * recency + 0.20 * magnitude + 0.10 * age_grace
}
pub fn promote_if_needed(neuron: &mut Neuron, now: u64) {
let score = compute_importance(neuron, now);
neuron.importance_score = score;
match neuron.protection_level {
ProtectionLevel::Open if score >= 0.50 => neuron.guard_all(),
ProtectionLevel::Guarded if score >= 0.85 => neuron.freeze_all(),
_ => {}
}
}Unified input pipeline. Converts anything into clean text chunks for learning. Stage 10 implements this with std-only parsing and deterministic folder order.
Dependencies: manas-core (for Source type)
pub const CHUNK_SIZE: usize = 512; // characters
pub const CHUNK_OVERLAP: usize = 64; // characters of overlap between chunks
pub enum IngestSource {
RawText(String),
File(PathBuf),
Folder(PathBuf),
}
pub struct TextChunk {
pub text: String,
pub source: Source,
pub chunk_id: u64,
}
pub fn ingest(source: IngestSource) -> Result<Vec<TextChunk>, ManasError> { ... }
pub fn chunk_text(text: &str) -> Vec<String> { ... }
pub fn normalize(text: &str) -> String { ... }| Format | How Parsed |
|---|---|
.txt |
Read as-is, chunk by paragraph |
.md |
Strip markdown syntax, chunk by section |
.rs |
Extract doc comments + function signatures |
.toml |
Extract key-value pairs as sentences |
.json |
Flatten nested keys to readable sentences |
.csv |
Each row becomes a sentence |
Explicit internet refresh. This crate is the only runtime crate with HTTP and JSON dependencies.
manas-agent scans stale refreshable hidden memories, builds deduped
DuckDuckGo queries, parses Instant Answer JSON, and re-teaches updated facts
through manas-learn. It does not answer questions directly.
Fluent language generation over associative-memory answers. This crate keeps
generation separate from retrieval: manas-learn answers from weights first,
then manas-language realizes the decoded concepts into a sentence.
Dependencies: manas-core, manas-learn
pub struct GenerationConfig {
pub max_words: usize,
}
pub struct GenerationResult {
pub text: String,
pub confidence: f32,
pub answered_from: AnswerSource,
pub freshness_warning: Option<FreshnessWarning>,
pub concepts: Vec<String>,
}
pub struct LanguageGenerator { ... }No .manas format change is required. Generation is deterministic and local.
User-facing commands. Thin layer over the learning engine. All business logic lives in the crates above. The CLI only routes and formats.
Dependencies: all crates above. Stage 9 uses std-only argument parsing; external CLI parsing can be added later if needed.
manas teach <text|file|folder> [--recursive]
Teach raw text, a supported file, or a folder
manas ask [--fluent] "<QUESTION>"
Ask a question from neural weights; `--fluent`
formats the neural concepts as a sentence
manas generate "<PROMPT>" [--max-words N]
Generate a fluent sentence from learned neural concepts
manas inspect Show brain, network, learning, freshness, source, layer stats
manas neurons List/filter neurons with importance, protection, source
manas trace "<QUESTION>" Trace query variants, activations, and output values
manas forget [--dry-run] Compress stale low-importance mergeable neurons
manas refresh [--fast] [--dry-run] [--limit N]
Explicitly refresh stale internet-sensitive memories
manas reset Delete the brain and start fresh
forget is conservative: it never removes Frozen neurons, never rewrites
frozen output edges, and only removes low-importance Open hidden neurons when
their output column can be merged into a highly similar retained neighbor.
Teaching complete
Input
mode : text
chunks processed : 1
facts learned : 1
Network
neurons grown : 2
layers grown : 0
neurons promoted : 3
neurons frozen : 1
total neurons : 18
total layers : 2
Learning
loss (before) : 0.4821
loss (after) : 0.1203
update applied : yes
Protection
open neurons : 11
guarded neurons : 5
frozen neurons : 2
Answer
A cat is a small domesticated animal with fur and whiskers.
Confidence
0.87
Answered from
neural weights
Generated
A cat is a small domesticated animal with fur and whiskers.
Confidence
0.87
Generated from
neural weights
Everything about the brain lives in one file. No sidecars. No .sources. No .sourceindex.
In v1, the answering came from sidecar files. In v2, the network answers directly.
Offset Size Field
────── ────── ─────────────────────────────
0 4 Magic bytes: 0x4D 0x41 0x4E 0x53 ("MANS")
4 1 Format version: 3
5 8 Created at (unix timestamp, u64 LE)
13 8 Last modified (unix timestamp, u64 LE)
21 8 Total neurons (u64 LE)
29 4 Total layers (u32 LE)
33 4 Embed dim (u32 LE)
37 4 Vocab size (u32 LE)
41 N Vocab section (variable length)
41+N M Layer + neuron section (variable length)
41+N+M 4 CRC32 checksum of entire file content before this field
[vocab_entry_count: u32 LE]
for each entry:
[token_len: u16 LE]
[token_bytes: token_len bytes UTF-8]
[token_id: u32 LE]
[embed_vec: embed_dim × 4 bytes (f32 LE)]
[layer_count: u32 LE]
for each layer:
[layer_id: u32 LE]
[activation: u8] 0=ReLU 1=Sigmoid 2=Tanh 3=Linear
[neuron_count: u32 LE]
for each neuron:
[neuron_id: u64 LE]
[weight_count: u32 LE]
[weights: weight_count × 4 bytes (f32 LE)]
[bias: f32 LE]
[activation: u8]
[importance_score: f32 LE]
[protection_level: u8] 0=Open 1=Guarded 2=Frozen
[born_at: u64 LE]
[last_activated: u64 LE]
[activation_count: u64 LE]
[source_type: u8] 0=RawText 1=LocalFile 2=Unknown 3=Internet
[source_len: u16 LE]
[source_bytes: source_len bytes UTF-8]
[freshness_category: u8]
[memory_input_len: u32 LE]
[memory_input_bytes: memory_input_len bytes UTF-8]
[memory_target_len: u32 LE]
[memory_target_bytes: memory_target_len bytes UTF-8]
[refreshed_at: u64 LE]
v1 had: .manas + .manas.sources + .manas.sourceindex + .manas.seq
.manas.transformer+.manas.langmeta
v2 has: .manas — one file, everything inside.
The answering logic uses the network weights directly. There is no text to store in a sidecar. The vocab section stores embeddings. The neuron section stores the learned associations. That is everything needed.
manas teach "A cat is a small domesticated animal with fur and whiskers."
1. manas-cli: parse command, detect raw text input
2. manas-ingest: normalize → "a cat is a small domesticated animal with fur and whiskers"
3. manas-ingest: chunk (single chunk, text is short)
4. manas-learn: tokenize chunk → token IDs
5. manas-learn: embed with positional encoding → input_vec (Vec<f32>)
6. manas-learn: pack meaningful answer word IDs into target_vec
7. manas-core: grow or reuse an Open keyed hidden neuron
8. manas-core: bind the hidden neuron to input_vec and write target_vec into its output column
9. manas-learn: update importance, source, freshness, and protection metadata
10. manas-store: persist network weights, vocab, and neuron metadata in .manas
11. manas-cli: print LearnReport
manas ask "What is a cat?"
1. manas-cli: parse command
2. manas-learn: build query variants such as "cat" from "What is a cat?"
3. manas-learn: encode each query variant → question_vec (Vec<f32>)
4. manas-core: select best activated hidden neuron and read only its output column
5. manas-learn: decode packed answer word IDs and score by hidden activation
6. confidence > MIN_CONFIDENCE?
→ yes: decode(output_vec) → "small domesticated animal with fur and whiskers"
answered_from = AnswerSource::NeuralWeights
→ no: "Not enough knowledge yet."
answered_from = AnswerSource::NotEnough
7. manas-cli: print QueryResult
No text file. No sidecar. No internet. The network answers from its own weights.
┌─────────────┐
│ Created │
│ (Open) │ ← importance = 0.0
└──────┬──────┘
│ used in forward passes
│ activation_count grows
│ importance_score rises above 0.50
▼
┌─────────────┐
│ Guarded │ ← clamped updates only
└──────┬──────┘
│ continues being activated
│ importance_score rises above 0.85
▼
┌─────────────┐
│ Frozen │ ← zero updates, ever
└──────┬──────┘
│
┌──────────┴───────────┐
│ │
▼ ▼
stays frozen forever (future) compression
— knowledge preserved if neuron cluster is
redundant
Importance determines whether a neuron gets promoted to a higher protection level.
importance = 0.40 × freq + 0.30 × recency + 0.20 × magnitude + 0.10 × age_grace
| Component | Formula | Meaning |
|---|---|---|
freq |
activation_count / 10_000 clamped to [0, 1] |
How often this neuron fires |
recency |
exp(-0.1 × days_since_last_activation) |
How recently it was used |
magnitude |
L2_norm(weights) / 10.0 clamped to [0, 1] |
How strong its connections are |
age_grace |
exp(-age_days / 7.0) |
Smooth grace period for new neurons |
Promotion thresholds:
Open → Guarded: importance >= 0.50Guarded → Frozen: importance >= 0.85
Scores are recomputed after every teach call.
Some knowledge goes stale. Facts about current events, software versions, and prices change over time. The freshness system tracks this.
Every neuron has a freshness_category: u8:
| Category | Value | Examples | Re-learn trigger |
|---|---|---|---|
| Timeless | 0 | Mathematical proofs, definitions, laws of physics | Never |
| Slow | 1 | Historical facts, biographies | 1 year |
| Fast | 2 | Software versions, news, prices | 1 month |
| Realtime | 3 | Stock prices, live scores | 1 day |
The freshness category is detected automatically from the text content during teach:
pub fn detect_freshness(text: &str) -> FreshnessCategory {
// keywords like "theorem", "law", "always" → 0 (Timeless)
// keywords like "today", "breaking", "live" → 3 (Realtime)
// keywords like "released", "version" → 2 (Fast)
// default → 1 (Slow)
}When ask retrieves an answer from a neuron with a stale freshness category,
it notes the staleness in the output:
Answer
Rust 1.70 was released in June 2023.
Confidence
0.81
Answered from
neural weights
Note
This knowledge may be outdated (Fast freshness, learned 47 days ago).
The internet agent uses freshness to decide when explicit refresh should re-fetch stale memories.
All errors go through ManasError. No .unwrap() in library code.
The CLI is the only place that handles errors with user-visible messages.
// manas-core/src/error.rs
#[derive(Debug)]
pub enum ManasError {
NeuronNotFound(u64),
LayerNotFound(u32),
GrowthFailed(String),
FileReadError { path: PathBuf, source: std::io::Error },
FileWriteError { path: PathBuf, source: std::io::Error },
CorruptBrain { reason: String },
ChecksumMismatch { expected: u32, found: u32 },
EncodingError(String),
EmptyInput,
}
impl std::fmt::Display for ManasError { ... }
impl std::error::Error for ManasError { ... }Stage 16 keeps performance and regression claims measurable.
manas-benches is a dedicated non-runtime crate with a custom no-dependency
benchmark harness:
| ID | What it measures |
|---|---|
| B1 | Single teach call |
| B2 | Single ask call |
| B3 | .manas save |
| B4 | .manas load |
| B5 | Tokenizer throughput on 1000 words |
| B6 | Full anti-forgetting proof |
| B7 | Estimated 1000-neuron memory footprint |
| B8 | Brain file growth per new fact |
| B9 | Single fluent generation |
The committed benchmark report is generated with:
cargo bench -p manas-benches -- --write-markdown BENCHMARKS.mdCI runs the quick benchmark smoke:
cargo bench -p manas-benches -- --quickThe Stage 16 integration gate lives in manas-cli/tests/stage16_integration.rs
because the workspace root is virtual and cargo test --workspace only runs
tests attached to workspace packages. It covers the real demo, anti-forgetting,
persistence, growth, protection, compression, freshness, and ingestion.
Manas v2 is honest about its scope:
| Manas IS | Manas IS NOT |
|---|---|
| A local associative memory system | A ChatGPT replacement |
| A self-growing neural network | A general-purpose LLM |
| A continual learning research project | A production AI system |
| A from-scratch Rust implementation | A wrapper around Candle or HuggingFace |
| Knowledge stored in neural weights | A text search engine |
| Runs on your laptop CPU | Requires a GPU or cloud |
| Answers from what it was taught | Answers from the internet by default |
The goal is to prove that a neural network can store and retrieve knowledge from weights alone, grow as it learns, and never forget — running on anyone's laptop. That is enough. That is the project.