TL;DR — I built an open-source TypeScript framework where AI agents have persistent, shared memory backed by a knowledge graph. Agents can sync facts with provenance tracking, detect and resolve contradictions via LLM, and build trust over time through a sync protocol. The engine is pluggable across five LLM providers and uses Cognee's Rust-native graph/vector backend.
The Problem
Most AI agents today are stateless. They get a prompt, produce an answer, and forget everything the moment the conversation ends. Even agents that use RAG or chat history lack:
- Multi-agent shared memory — two agents working on the same domain have no way to share or reconcile their knowledge.
- Provenance — if Agent A learns something from Agent B, there's no chain-of-custody.
- Contradiction handling — conflicting facts quietly overwrite each other with no signal.
- Trust evolution — agents have no mechanism to decide which peers to believe.
Mycelium was built to solve these four problems.
Architecture
┌──────────────────┐
│ Dashboard │ Next.js 15 / React 19
│ d3-force viz │
└────────┬─────────┘
│ HTTP / data
┌───────────────┼────────────────┐
│ │ │
┌─────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Agent │ │ Agent │ │ Agent │
│ (planner) │ │ (assistant) │ │ (cli) │
│ dataset_a │ │ dataset_b │ │ dataset_c │
└─────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────┐
│ Sync Engine │
│ provenance · subscription · trust │
│ structural-diff · contradiction │
└──────────────────┬───────────────────────┘
│
┌──────────────────▼───────────────────────┐
│ Cognee Backend (Rust) │
│ Kuzu graph · brute-force vectors │
│ mock embeddings · 5 LLM providers │
└──────────────────────────────────────────┘
Every agent maps to one Cognee dataset. The sync engine brokers cross-dataset fact exchange with provenance tagging. The diff engine runs on every improve() call to detect structural changes, embedding drift, and logical contradictions.
Tech Stack
| Layer | Technology | Why |
|---|---|---|
| Language | TypeScript (ES2022) | Type safety for graph data structures |
| Runtime | Node.js via tsx
|
Fast TypeScript execution without build step |
| Monorepo | pnpm workspaces | Shared types, independent packages |
| Graph DB | Kuzu (embedded, via Cognee) | Native graph storage, no server needed |
| Vector DB | Brute-force (via Cognee) | Zero-config for prototyping |
| LLM Config | OpenAI / Groq / LM Studio / Ollama / custom | Pluggable via env var |
| Graph Memory | Cognee Rust addon (@cognee/cognee-ts) |
Graph extraction, triplet indexing |
| Dashboard | Next.js 15 / React 19 / d3-force / Recharts | Visualization layer |
| CLI | Node.js via tsx
|
Ad-hoc agent spawning and sync simulation |
Core Concepts
1. Agent
Each agent wraps a Cognee dataset and exposes a high-level API:
const planner = new Agent("travel_planner", "dataset_travel", client);
// Ingest facts (with optional provenance)
await planner.remember({ type: "text", text: "Alice loves flying." });
// Query the graph
const results = await planner.recall("What does Alice think about flying?");
// Improve memory: Cognee re-indexes + diff + contradiction detection
const improved = await planner.improve({ autoResolve: true });
2. CogneeClient
A configurable wrapper around the Cognee Rust SDK that handles the messy env-var setup (Cognee only accepts "openai" as LLM_PROVIDER internally, even when routing to Groq/Ollama):
const client = await CogneeClient.create({
llmProvider: "groq",
llmModel: "llama-3.1-8b-instant",
});
// client.llmConfig → { provider: "groq", model: "llama-3.1-8b-instant", endpoint: "...", apiKey: "..." }
3. Diff Engine (Three-Pass)
Every improve() runs three diff passes:
Pass 1 — Structural Diff (structuralDiff.ts):
const structural = diffSnapshots(before, after);
// → { nodes: { added, removed, modified }, edges: { added, removed, modified } }
Pure set-diff on node/edge IDs. A node is "modified" if its properties or type changed. No I/O, no LLM call.
Pass 2 — Embedding Drift (driftDetector.ts):
function cosineDistance(a: number[], b: number[]): number {
// 1 - (a·b / (|a|·|b|))
}
const drifts = detectDrift(before, after, { threshold: 0.15 });
Flags nodes whose embedding shifted beyond a threshold — useful for detecting semantic drift after re-indexing.
Pass 3 — Contradiction Detection (contradictionDetector.ts):
const contradictions = await detectContradictions(client, dataset, after, before);
// Sends each differing node pair to an LLM with a structured prompt
// → [{ nodeLabel, existingStatement, incomingStatement, isContradiction, confidence }]
The combined result:
export interface MemoryDiffResult {
before: GraphSnapshot;
after: GraphSnapshot;
structural: StructuralDiff;
drifts: DriftResult[];
contradictions: ContradictionResult[];
summary: {
nodesAdded, nodesRemoved, nodesModified,
edgesAdded, edgesRemoved, edgesModified,
driftsDetected, contradictionsDetected,
};
}
4. Contradiction Resolution
Once contradictions are detected, a pure decision engine resolves them:
const resolved = resolveContradictions(contradictions, {
strategy: "keep_newer", // or "keep_higher_trust" | "flag_all"
confidenceThreshold: 0.8,
});
// → [{ nodeLabel, resolution: "kept_incoming", confidence }]
The resolver is stateless — it returns a decision. The caller is responsible for executing it (e.g., forgetting the losing fact from the graph).
5. Sync Protocol
The sync engine is where agents share knowledge:
const engine = new SyncEngine({
trustStore,
autoMergeThreshold: 0.6, // trust ≥ 0.6 → auto-merge
});
const run = await engine.syncFromSource(
client, // Cognee backend
plannerDataset, // subscriber
plannerId, // subscriber agent ID
assistantDataset, // source dataset
assistantId, // source agent ID
assistantFacts, // string[]
);
// Accept or reject post-sync
await acceptSync(run.id, engine, trustStore, plannerId);
Trust model: Starting at 0.5, accepting a sync adds +0.05, rejecting deducts -0.2. This asymmetry nudges agents toward caution — rejection is weighted 4x more than acceptance.
6. Provenance
Every fact that crosses agent boundaries is tagged:
tagWithProvenance(text, { sourceAgentId, factId, timestamp })
// → '__provenance__:{"sourceAgentId":"assistant","factId":"src_123","timestamp":…}\n fact text here'
This prefix survives Cognee's text storage, so extractProvenance() can retrieve the chain of custody on recall.
LLM Provider Configuration
LLM_PROVIDER=groq # openai | groq | lm-studio | ollama | custom-openai-compatible
LLM_ENDPOINT=https://api.groq.com/openai/v1
LLM_API_KEY=gsk_...
LLM_MODEL=llama-3.1-8b-instant
Swap providers with one env var — no code changes:
# OpenAI
USE_LLM_PROVIDER=openai pnpm demo
# Local LM Studio (requires running LM Studio server)
USE_LLM_PROVIDER=lm-studio pnpm demo
# Local Ollama
USE_LLM_PROVIDER=ollama pnpm demo
The resolveLlmConfig() function merges explicit input > env vars > provider presets > hard defaults. Legacy aliases OPENAI_URL / OPENAI_TOKEN still work as fallbacks.
The Travel Assistant Demo
The end-to-end demo at examples/travel-assistant-demo/:
pnpm --filter travel-assistant-demo demo
- Creates two agents: Travel Planner and Personal Assistant
- Seeds the Planner with "Alice enjoys flying" and the Assistant with "Alice is afraid of flying" — a deliberate contradiction
- Runs contradiction detection via LLM call
- Syncs the Assistant's facts into the Planner's dataset with provenance tags
- Runs
improve()which captures before/after snapshots, runs the full diff pipeline, and optionally auto-resolves contradictions - Accepts the sync, bumping trust from 0.5 → 0.55
Directory Structure
packages/
core/ src/ — Agent, CogneeClient, diff, LLM config, types
cli/ src/ — inspectDiff, simulateSync, spawnAgent
dashboard/ src/ — Next.js 15 app with d3-force network graph
diff-engine/ — (planned: standalone npm package)
sync-protocol/ — (planned: standalone npm package)
trust/ — (planned: standalone npm package)
examples/
travel-assistant-demo/ — End-to-end demo script
How to Fork and Contribute
- Clone and install:
git clone https://github.com/harishkotra/mycelium
cd mycelium
pnpm install
- Set up your LLM provider:
cp packages/core/.env.example packages/core/.env
# Edit .env with your API key
- Run the demo:
pnpm --filter travel-assistant-demo demo
- Run tests:
npx tsx packages/core/test-llm.mjs # 36 LLM config tests
npx tsx packages/core/test-improve.ts # 32 improve/resolver tests
Feature Ideas for Contributors
| Feature | Difficulty | Description |
|---|---|---|
| Persist sync run history | Easy | Currently in-memory; write to JSON or SQLite |
| Agent.sync() method | Medium | Add agent.sync(peerAgent, engine) that exchanges diffs with peer |
| WebSocket live sync | Medium | Replace polling with real-time sync events between agents |
| Dashboard → real backend | Medium | Swap the mock data layer with CogneeClient API calls, one screen at a time |
| Diff engine as npm package | Easy | Extract @mycelium/diff-engine as a standalone package |
| Sync protocol as npm package | Easy | Extract @mycelium/sync-protocol as a standalone package |
| Cross-dataset contradiction scan | Medium | Scan all connected datasets for contradictions, not just before/after improve |
| Vector DB persistence | Medium | Brute-force vector DB loses index after restart; add proper persistence |
CLI: mycelium agent spawn |
Medium | Interactive CLI to create, seed, and inspect agents |
| Ollama embedding support | Hard | Cognee only supports OpenAI-compatible embeddings; add Ollama path |
| Trust decay | Medium | Trust scores decay over time if no recent sync activity |
| Fact expiry / TTL | Medium | Facts auto-expire after a configurable TTL, with notification |
| Git-like merge conflict UI | Hard | Show diff three-pane UI for sync review in dashboard |
Design Principles
-
No hidden side-effects — Pure functions (
diffSnapshots,resolveContradictions,cosineDistance) are explicitly marked and easily testable - Env-var-first config — One env var (LLM_PROVIDER) changes the entire LLM stack; no code changes needed
-
Provider abstraction — Contradiction detector calls raw
fetch()to OpenAI-compatible APIs rather than importing per-provider SDKs - Cognee as implementation detail — The Agent class wraps Cognee internally; consumers don't import Cognee types directly
Code & more: https://www.dailybuild.xyz/project/180-mycelium
Top comments (0)