A neuromorphic AI engine written in Julia. GrugBot models cognition through competing populations of pattern nodes — not if-else waterfalls, not transformers, not lookup tables. Many rocks compete to be loudest. Loudest rock gets to talk. Sometimes a quiet rock gets lucky (coinflip). That is how Grug think.
Download the prebuilt binary from grug-binary/:
chmod +x grugbot420
./grugbot420Requires Julia 1.9+ on your PATH. First run detects a missing Julia install, opens the download page, and waits. Every run after that goes straight to the Brain > prompt.
| Command | What it does |
|---|---|
/mission <text> |
Send input to the engine. This is the main command. Also accepts image binary (Base64 or hex). |
/wrong |
Tell GrugBot its last response was bad. Penalizes every node that voted via coinflip strength decay. Nodes that reach 0 become graves. |
/explicit <cmd> [<node_id>] <text> |
Force a specific command+node combination, bypassing the vote system. |
/grow <json> |
Plant one or more new nodes from a JSON packet (see format below). |
/addRule <rule text> [prob=0.0-1.0] |
Add a stochastic orchestration rule. Fires with given probability on every response. Supports template tags. |
/pin <text> |
Pin text permanently to the memory cave wall. Pinned messages survive the 10,000-message rolling window. |
| Command | What it does |
|---|---|
/nodes |
Show all nodes: ID, pattern, strength, neighbor count, grave status. |
/status |
Full system health snapshot: node count, Hopfield cache, memory estimate, lobe summary, BrainStem stats, ChatterMode stats. |
/arousal <0.0-1.0> |
Manually set the EyeSystem arousal level. Higher arousal = tighter visual attention cutout. |
| Command | What it does |
|---|---|
/addVerb <verb> <class> |
Add a verb to a relation class (e.g. /addVerb triggers causal). Takes effect immediately on next /mission. |
/addRelationClass <name> |
Create a new verb class bucket (e.g. /addRelationClass epistemic). |
/addSynonym <canonical> <alias> |
Register a synonym normalization (e.g. /addSynonym causes triggers). Alias is rewritten to canonical before triple extraction. |
/listVerbs |
Dump all registered verb classes, their verbs, and synonym mappings. |
| Command | What it does |
|---|---|
/newLobe <id> <subject> |
Create a new subject partition (e.g. /newLobe language "natural language processing"). Cap: 20,000 nodes per lobe, 64 lobes max. |
/connectLobes <id_a> <id_b> |
Link two lobes bidirectionally. BrainStem uses connections for lateral signal propagation (60% decay per hop). |
/lobeGrow <lobe_id> <json> |
Grow a node directly into a specific lobe. JSON must have pattern and action_packet fields. |
/lobes |
Show all lobes: node counts, connection graph, fire counts. |
/tableStatus <lobe_id> |
Show hash table chunk sizes for a lobe (nodes, json, drop, hopfield, meta chunks). |
/tableMatch <lobe_id> <chunk> <pattern> |
Pattern-activate entries in a lobe's hash table. Use node_id for prefix match, any other token for token match. |
| Command | What it does |
|---|---|
/thesaurus <word1> | <word2> |
Dimensional similarity comparison: overall %, semantic %, contextual %, associative %, confidence %. |
/thesaurus <w1> | <w2> :: <ctx1> :: <ctx2> |
Same comparison with context lists (comma-separated) to modulate scoring. |
| Command | What it does |
|---|---|
/negativeThesaurus add <word> [--reason <text>] |
Register a word as inhibited. Filtered from input before scan. |
/negativeThesaurus remove <word> |
Remove a word from the inhibition list. |
/negativeThesaurus list |
Show all inhibited words with reasons and timestamps. |
/negativeThesaurus check <word> |
Quick check if a word is currently inhibited. |
/negativeThesaurus flush |
Clear all inhibitions at once. |
| Command | What it does |
|---|---|
/nodeAttach <target> <id1> <pattern1> [<id2> <pattern2> ...] |
Attach up to 4 nodes to a target node. When the target fires during scan_and_expand, each attached node does a strength-biased coinflip to decide if it should fire too. Patterns support quoted multi-word strings (e.g. "deep learning"). |
/nodeDetach <target> <attach_id> |
Remove a specific attachment from a target node. |
/attachments |
Show the full attachment map — every target and its attached nodes with patterns and slot usage. |
| Command | What it does |
|---|---|
/saveSpecimen <filepath> |
Freeze the entire cave state to a gzip-compressed JSON file. Every node, lobe, rule, message, verb, thesaurus entry, inhibition, attachment, arousal level, trajectory state, temporal coherence, and morph cooldowns — everything. |
/loadSpecimen <filepath> |
Restore the entire cave state from a previously saved specimen file. Destructive — current state is wiped and replaced (full brain transplant). |
/help
Prints the full command reference inside the CLI.
Nodes are the atomic unit of GrugBot. Each node has a pattern (the text it matches against), an action packet (what it does when it fires), optional JSON data, and an optional drop table (co-activation neighbors).
JSON packet format:
{
"nodes": [
{
"pattern": "machine learning neural network",
"action_packet": "reason[dont hallucinate]^4 | analyze^2 | explain^1",
"data": {
"system_prompt": "Technical ML domain active.",
"required_relations": ["uses"],
"relation_weights": {"uses": 2.0}
},
"drop_table": []
}
]
}Action packet format: action[neg1, neg2]^weight | action2[neg3]^weight | action3^weight
- Actions:
reason,analyze,ponder,calculate,greet,welcome,smile,laugh,flee,hide,fight,explain,clarify,describe,define,elaborate,comfort,support,validate,acknowledge,reassure,alert,warn,caution,notify,flag - Negatives in
[...]are constraints injected into the AIML payload ^weightsets the relative voting weight for the superposition orchestrator
Example:
/grow {"nodes":[{"pattern":"sad unhappy depressed","action_packet":"comfort[dont dismiss]^3 | validate^2 | support^1","data":{"system_prompt":"Emotional support mode active."}}]}
GrugBot supports full long-term persistence via specimen files. A specimen file is a gzip-compressed JSON snapshot of the entire cave state — every node, lobe, rule, message, verb, thesaurus entry, inhibition, and more. Save your cave at any time, share it with others, or restore it later.
/saveSpecimen mycave.specimen.gz
This freezes the entire cave state into mycave.specimen.gz. The file contains compressed JSON covering all 17 state categories (v2.1 format).
/loadSpecimen mycave.specimen.gz
This is a destructive operation — current cave state is completely wiped and replaced with the specimen file contents. Think of it as a full brain transplant.
The file is validated before any state is wiped. If validation fails, zero changes are made.
| # | State Category | Description |
|---|---|---|
| 1 | nodes | Full Node structs — id, pattern, signal, action_packet, strength, neighbors, graves, drop_table, response_times, hopfield_key, relational_patterns, throttle, json_data |
| 2 | hopfield_cache | Familiar input fast-path cache with hit counts (UInt64 hash → node IDs) |
| 3 | rules | AIML_DROP_TABLE stochastic orchestration rules (text + fire probability) |
| 4 | message_history | Up to 10,000 ChatMessage entries with pin flags preserved |
| 5 | lobes | LOBE_REGISTRY — subject, node_ids, connected_lobe_ids, fire/inhibit counts |
| 6 | node_to_lobe_idx | NODE_TO_LOBE_IDX reverse index (node → lobe mapping) |
| 7 | lobe_tables | LOBE_TABLE_REGISTRY with all chunks (nodes, json, drop, hopfield, meta) and NodeRef objects |
| 8 | verb_registry | SemanticVerbs — all verb classes, verbs, and synonym normalizations |
| 9 | thesaurus_seeds | Thesaurus SYNONYM_SEED_MAP (hardcoded defaults + runtime additions) |
| 10 | inhibitions | InputQueue NegativeThesaurus entries (word, reason, timestamp) |
| 11 | arousal | EyeSystem arousal state (level, decay_rate, baseline) |
| 12 | id_counters | NODE ID_COUNTER and MSG_ID_COUNTER atomic values |
| 13 | brainstem | BrainStem dispatch count and propagation history |
| 14 | attachments | ATTACHMENT_MAP — target→attached node mappings with patterns and pre-baked signal vectors |
| 15 | trajectory | ActionTonePredictor ring buffer + config — behavioral inertia through action-tone space (Lorenz damping) |
| 16 | temporal_coherence | ImageSDF TEMPORAL_COHERENCE_LEDGER — SDF timing patterns and coherence scores |
| 17 | morph_cooldowns | ChatterMode MORPH_COOLDOWN_MAP — 24h morph cooldown timestamps per node |
id_counters → verb_registry → thesaurus_seeds → lobes → lobe_tables → nodes → node_to_lobe_idx → hopfield_cache → rules → inhibitions → message_history → arousal → brainstem → attachments → trajectory → temporal_coherence → morph_cooldowns
This ensures upstream entities exist before downstream references (e.g., lobes exist before nodes reference them).
- Extension convention:
.specimen.gz(not enforced, any path works) - Compression: gzip (system
gzip/gunzipvia pipeline — no extra Julia packages) - Content: JSON with pretty-print indentation (human-readable when decompressed)
- Metadata:
_metasection records version, timestamp, and format identifier
The relational fire system lets you wire nodes into explicit firing chains. When a target node fires during scan_and_expand, its attached nodes each do a strength-biased coinflip to decide whether they should fire too. Think of it as user-defined relay circuitry overlaid on top of the stochastic scan.
/nodeAttach node_0 node_1 "machine learning" node_2 "gradient descent"
This attaches node_1 and node_2 to node_0. The patterns ("machine learning", "gradient descent") are connector patterns — middleman reasons that explain WHY these nodes are related to the target.
Confidence is computed once at attach time (JIT), not every fire cycle. When you issue /nodeAttach, the engine immediately:
- Scans the connector pattern against the attached node's own pattern (Jaccard token overlap)
- Adds a strength bonus:
(attachment_strength / STRENGTH_CAP) * 0.5 - Stores the result as
base_confidencein theAttachedNodestruct
When node_0 fires:
- Each attachment does a strength-biased coinflip:
scan_prob = 0.20 + (strength / STRENGTH_CAP) * 0.70 - Winners use the pre-baked
base_confidencewith stochastic jitter:confidence = max(0.1, base_confidence + randn() * 0.05)— floor of 0.1 so attachments always have some voice; small jitter keeps vote pool diverse - The connector pattern surfaces downstream as a
RelationalTriple(target_id, "relay_attached", connector_pattern)so the generative pipeline knows WHY the relay fired - The active cap (biological attention bottleneck,
rand(600:1800)) is respected — if the relay pass hits the cap, remaining attachments are skipped
/imgnodeAttach node_0 img_node_1 "data:image/png;base64,iVBOR..." 64 64
Does everything /nodeAttach does but for image nodes. Instead of text connector patterns, uses image binary converted to nonlinear SDF at attach time via real GPU kernel dispatch:
- Image binary is detected and decoded from the input (Base64 data URI, hex dump, or raw bytes)
- Converted to
SDFParamsviaJITGPU(binary; width, height)— realKernelAbstractions.jlkernel dispatch. Backend selected at runtime:CUDABackend()(NVIDIA),ROCBackend()(AMD),MetalBackend()(Apple Silicon), orCPU()multithreaded fallback on CI/no-GPU. Two-pass kernel: parallel pixel decode →synchronize→ paralleltanh(3×grad_mag)SDF activation - Flattened to a signal vector via
sdf_to_signal()for PatternScanner compatibility base_confidenceis baked from SDF cosine similarity between the connector signal and the attached image node's own signal, plus strength bonus- The attached node must be an image node (
is_image_node=true); text nodes are rejected with an explicit error
Width and height can be omitted (defaults to 8×8) but should be specified for accurate SDF conversion.
- Max 4 attachments per target node (hard cap)
- Target and attachment nodes must exist on the map and must not be graves
- A node cannot attach to itself
- Duplicate attachments are rejected
/nodeAttach: patterns support quoted multi-word strings/imgnodeAttach: attach node must be an image node; image data must not be empty; dimensions must be > 0- Every error is explicit — no silent failures
/nodeDetach node_0 node_1
/imgnodeDetach node_0 img_node_1
Removes the attached node from the target's attachment list. /imgnodeDetach reuses the same detach_node! function — both text and image attachments live in the same ATTACHMENT_MAP. If that was the last attachment, the target's entry is cleaned up entirely.
/attachments
Prints every target and its attached nodes with base_confidence, connector patterns, signal vector lengths, and slot usage (N/4).
The attachment relay runs as Pass 3 in scan_and_expand(), after the primary scan (Pass 1) and lobe cascade (Pass 2). It iterates every node in the expanded set, checks for attachments, and fires winners into the vote pool. Deduplication ensures no node appears twice. The relay has its own independent active_cap sample.
Attachments are fully serialized in /saveSpecimen (section 14) and restored in /loadSpecimen (section 4.14). Each attachment entry stores target_id, node_id, pattern, signal, and the JIT-baked base_confidence. On load, if base_confidence is missing (backward compatibility), it is re-computed: text attachments re-run _token_overlap_similarity, image attachments use _sdf_signal_similarity, with strength bonus added. If the signal vector is also missing, it is re-baked from the pattern via words_to_signal.
Rules are injected into every response payload. They support template tags and fire stochastically.
Template tags: {MISSION}, {PRIMARY_ACTION}, {SURE_ACTIONS}, {UNSURE_ACTIONS}, {ALL_ACTIONS}, {CONFIDENCE}, {NODE_ID}, {MEMORY}, {LOBE_CONTEXT}
Examples:
/addRule Always ground responses in {MISSION} before expanding.
/addRule If confidence {CONFIDENCE} is below 0.5, hedge your answer. [prob=0.7]
/addRule Current lobe state: {LOBE_CONTEXT} — use cross-domain reasoning. [prob=0.5]
Rules with no [prob=X] suffix default to prob=1.0 (always fire).
When the cave has been quiet for ~120 seconds (±30s jitter), GrugBot runs an idle action automatically — a 50/50 coinflip between:
- Chatter (1000+ nodes only): 50–500 node clones gossip and exchange patterns. Only weak nodes morph — receivers must be weaker than senders. Each node can only morph once per 24 hours (cooldown enforced). New specimens with < 1000 nodes skip chatter entirely.
- Phagy (1000+ nodes only): One maintenance automaton runs (orphan pruning, strength decay, grave recycling, cache validation, drop table compaction, rule pruning, or memory forensics).
Both chatter and phagy share the same slow idle timer and the same 1000+ node population gate. New specimens with < 1000 nodes skip all idle actions entirely. If fewer than 50 eligible nodes exist in a chatter round, the group size floors at whatever is available. You don't need to trigger this manually. It runs between CLI prompts.
When multiple nodes compete for the same response slot, ties happen. The orchestrator now handles them explicitly instead of silently picking the first sorted result.
-
Sure Basket: All votes within 0.05 of the max confidence are bucketed as "sure" candidates.
-
Exact Tie Detection: Within the sure basket, votes at the exact same confidence (within floating-point epsilon) are identified as tied.
-
Random Winner: When ties exist, the tied group is shuffled and one is picked at random. No more deterministic first-in-sort-order bias.
-
SURE vs UNSURE: If the primary winner stands alone at the top, the vote is classified as
SURE. If ties existed, it'sUNSURE. This classification is available to AIML rules via{VOTE_CERTAINTY}. -
Tied Alternatives: Non-selected tied winners are listed at the bottom of the response with their node ID, action, confidence, and relational triples — so you can see what the other tied rocks looked like.
-
Runner-Up Possibilities: Unsure votes (below the sure threshold but kept via coinflip) are listed as "Other Possibilities" with their relations.
Two new tags are available in /addRule templates:
| Tag | Expands To |
|---|---|
{VOTE_CERTAINTY} |
SURE or UNSURE — whether a tie existed |
{TIED_ALTERNATIVES} |
Comma-separated list of tied non-winners with their actions and confidence |
Example rule: /addRule When {VOTE_CERTAINTY} is UNSURE, also consider: {TIED_ALTERNATIVES} [prob=0.8]
Once a specimen reaches maturity (≥ 1000 nodes), an automata-based immune system activates to protect the node population from funky inputs. This is not adversarial security — it's biological: tolerance-based, stochastic, and imperfect by design.
-
AST Scan: Every structure-storing command gets a high-resolution structural scan before touching anything. The scan produces an AST signature — a structural fingerprint of the input.
-
Hopfield Immune Memory: Non-funky signatures are stored in an attractor memory. Repeated safe inputs strengthen their basin, making future recognition instant.
-
Funky Detection: If a signature doesn't match known patterns in the immune Hopfield memory, it's flagged as funky.
-
Population Coinflip: Funky inputs trigger an automata population (1/3 of node count). Each agent coinflips independently (50/50) before intervening — this prevents explosion.
-
Quarantine → Patch → Delete: Materialized agents quarantine the input, attempt structural patching within a stochastic timer, and delete on failure.
-
No Silent Failures: Every decision — funky detection, coinflip skip, patch success, patch failure, deletion — is logged in an append-only immune ledger. Nothing happens in the dark.
| Constant | Value | Purpose |
|---|---|---|
MATURITY_THRESHOLD |
1000 | Immune system sleeps below this node count |
AUTOMATA_POPULATION_RATIO |
1/3 | Automata count = nodes ÷ 3 |
COINFLIP_PROBABILITY |
0.5 | Per-agent materialization probability |
PATCH_TIMEOUT_SECONDS |
2.0 | Max time for patch attempt (± 0.5s jitter) |
HOPFIELD_FAMILIARITY_THRESHOLD |
3 | Sightings needed before a signature is "strongly known" |
The immune system gates all structure-storing commands automatically. Gated commands (critical gates marked with ⚡):
| Command | Gate | Notes |
|---|---|---|
/grow |
⚡ Critical | Modifies node population |
/lobeGrow |
⚡ Critical | Grows nodes into lobes |
/loadSpecimen |
⚡ Critical | Replaces entire brain state |
/addRule |
Standard | Stores AIML orchestration rules |
/pin |
Standard | Stores pinned memory |
/addVerb |
Standard | Modifies verb registry |
/addRelationClass |
Standard | Creates verb class buckets |
/addSynonym |
Standard | Modifies synonym map |
/newLobe |
Standard | Creates lobe structure |
/connectLobes |
Standard | Links lobes bidirectionally |
/negativeThesaurus add |
Standard | Adds inhibition entries |
/nodeAttach |
Standard | Modifies node attachments |
/imgnodeAttach |
Standard | Modifies image node attachments |
Exempt commands (read-only or destructive-remove-only): /mission, /wrong, /explicit, /nodes, /status, /lobes, /listVerbs, /thesaurus, /help, /arousal, /saveSpecimen, /attachments, /tableStatus, /tableMatch, /nodeDetach, /imgnodeDetach, /negativeThesaurus remove/flush/list/check.
When the immune system rejects an input, you'll see:
[IMMUNE] ⛔ /grow REJECTED by immune system: Funky input failed patching and was deleted
All gates use the shared immune_gate() helper — no copy-pasted immune logic. Immune state (Hopfield memory + ledger) is saved/restored with /saveSpecimen and /loadSpecimen.
Full specification: docs/immune_system.html
| File | Role |
|---|---|
src/Main.jl |
Entry point. CLI loop, memory cave, mission processor, idle manager, specimen persistence. |
src/engine.jl |
Core node engine: node creation, scanning, voting, Hopfield cache, drop-table expansion, relational fire (JIT confidence baking, SDF image attachments). |
src/stochastichelper.jl |
@coinflip macro and bias() helper for weighted probabilistic branching. |
src/patternscanner.jl |
Signal-level pattern matching: cheap_scan, medium_scan, high_res_scan. Scan tier selected per-node based on input + pattern complexity. Tier-1 nodes use _bidirectional_cheap_scan (forward + reverse, smoothed confidence). |
src/Lobe.jl |
Subject-specific node partitions with O(1) reverse index. |
src/LobeTable.jl |
Per-lobe chunked hash table storage (nodes, json, drop, hopfield, meta chunks). |
src/BrainStem.jl |
Winner-take-all dispatcher with cross-lobe signal propagation and fire-count decay. |
src/Thesaurus.jl |
Dimensional similarity engine with seed synonym dictionary, gate filter, and runtime seed injection. |
src/InputQueue.jl |
FIFO input queue and NegativeThesaurus inhibition filter. |
src/ChatterMode.jl |
Idle gossip system (v7.1): 50–500 ephemeral clones, 1000+ node gate, weak-only morph, 24h cooldown, 120s±30s shared timer. |
src/PhagyMode.jl |
Seven idle-time maintenance automata for self-healing map management (includes memory forensics). |
src/EyeSystem.jl |
Visual attention: edge blurring, arousal-gated center cutout, attention modulation. |
src/ImageSDF.jl |
JITGPU(binary) — real KernelAbstractions.jl GPU kernel dispatch for image→SDF conversion. CPU reference path (image_to_sdf_params) kept for backward compat. |
src/SemanticVerbs.jl |
Live mutable verb registry: causal, spatial, temporal classes + runtime synonyms. |
src/ActionTonePredictor.jl |
Pre-vote input classifier: predicts action type and tone, nudges arousal and confidence weights. |
src/ImmuneSystem.jl |
Specimen immune system: automata-based anomaly handling for growth/ledger commands. AST scanning, Hopfield immune memory, quarantine-patch-delete pipeline. |
grugbot_whitepaper.html |
Full technical documentation and architecture reference. |
Open grugbot_whitepaper.html in a browser for the full technical whitepaper covering architecture, formal mathematics, all subsystems, and design rationale.
See docs/immune_system.html for the immune system specification (grug analogy, academic details, math/lambda, flowchart).
The first ~100 nodes you plant are the specimen's DNA. Before hitting 1,000 nodes, automatic neighbor latching is suppressed — you control topology manually via drop tables. Recommendations:
- Seed orthogonal archetypes first — distinct semantic poles, not 50 near-identical nodes
- Use
required_relationsas semantic gates from day one so nodes don't fire on noise - Name action packets deliberately — distinct action families give the superposition orchestrator something meaningful to work with
- Wire drop tables manually for known co-activation pairs
- The engine enforces structure at scale (1,000+ nodes). You enforce meaning at the start.