DEV Community

Cover image for Mycelium: A Multi-Agent Memory Mesh with Provenance, Trust, and Contradiction Resolution
Harish Kotra (he/him)
Harish Kotra (he/him)

Posted on

Mycelium: A Multi-Agent Memory Mesh with Provenance, Trust, and Contradiction Resolution

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       │
         └──────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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: "..." }
Enter fullscreen mode Exit fullscreen mode

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 } }
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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 }]
Enter fullscreen mode Exit fullscreen mode

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,
  };
}
Enter fullscreen mode Exit fullscreen mode

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 }]
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  1. Creates two agents: Travel Planner and Personal Assistant
  2. Seeds the Planner with "Alice enjoys flying" and the Assistant with "Alice is afraid of flying" — a deliberate contradiction
  3. Runs contradiction detection via LLM call
  4. Syncs the Assistant's facts into the Planner's dataset with provenance tags
  5. Runs improve() which captures before/after snapshots, runs the full diff pipeline, and optionally auto-resolves contradictions
  6. 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
Enter fullscreen mode Exit fullscreen mode

How to Fork and Contribute

  1. Clone and install:
   git clone https://github.com/harishkotra/mycelium
   cd mycelium
   pnpm install
Enter fullscreen mode Exit fullscreen mode
  1. Set up your LLM provider:
   cp packages/core/.env.example packages/core/.env
   # Edit .env with your API key
Enter fullscreen mode Exit fullscreen mode
  1. Run the demo:
   pnpm --filter travel-assistant-demo demo
Enter fullscreen mode Exit fullscreen mode
  1. Run tests:
   npx tsx packages/core/test-llm.mjs           # 36 LLM config tests
   npx tsx packages/core/test-improve.ts         # 32 improve/resolver tests
Enter fullscreen mode Exit fullscreen mode

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)