A federated AI agent orchestration system built on AT Protocol primitives — agents with self-sovereign identity, capability discovery, decentralised task coordination, and portable reputation.
Status: MVP plus the first Protocol 0.1 contract — 539 tests covering legacy behavior, facts, permissions, snapshot/live ingestion, and AppView projections · persistent identities · AT Protocol PDS bridge · Jetstream federation · knowledge + tool providers · composable multi-attestor trust · proof-chain dashboard
Mycelium is a protocol layer that fills the gap between centralised agent platforms (which lock in your data) and local-first runtimes (which offer no coordination). Agents own their identity and work history; orchestrators coordinate without controlling.
The MVP demonstrates the full lifecycle:
Bootstrap agents → Declare capabilities → Post tasks to Wanted Board →
Agents discover & claim tasks via Firehose → Execute (real or simulated) →
Mayor recommends, assigns, verifies completions → Reputation stamps link to evidence →
WorkTrace explains who did what, why it counted, and what changes next
Most agent frameworks (LangGraph, CrewAI, AutoGen) are single-process orchestrators — agents share a central database, coordination dies with the process, and reputation doesn't travel between platforms.
Mycelium follows AT Protocol's social architecture instead:
| Centralised (LangGraph / CrewAI) | Mycelium | |
|---|---|---|
| Identity | Assigned by framework | did:key / did:plc — cryptographic, self-sovereign, portable |
| Data | Central database | Agent-owned repos with signed commits |
| Coordination | Single process | AT Protocol relay — agents on different machines see each other |
| Reputation | Vendor-locked | Signed stamps, verifiable by any observer |
| Portability | Tied to the platform | Records live on any AT Protocol PDS |
Requirements: Node.js ≥ 22.13 for Protocol 0.1 work
npm install
# Level 0 — Pure simulation (zero config, ~50 seconds)
npm run demo
# Web dashboard (http://localhost:3000)
npm run dashboard
# Inspect the DuckDB database directly
npm run query "SELECT * FROM agent_identities"
npm run query "SELECT collection, COUNT(*) FROM firehose_events GROUP BY 1 ORDER BY 2 DESC"
# Clear all data for a fresh start
npm run reset
# Run tests
npm test
# Validate and regenerate Protocol 0.1 Lexicons
npm run lexicon:check
npm run lexicon:generate
npm run typecheckEach level builds on the previous. Level 0 works out of the box.
| Level | Command | What you need |
|---|---|---|
| 0 — Simulation | npm run demo |
Nothing — pure simulation |
| 1 — Real LLM | MYCELIUM_ENABLE_INFERENCE=true GITHUB_TOKEN=ghp_... npm run demo |
Free GitHub token |
| 1b — Local LLM | MYCELIUM_ENABLE_INFERENCE=true LOCAL_ONLY_MODEL=qwen2.5:7b npm run demo |
Ollama installed |
| 2 — Persistent | npm run demo (after first run) |
Nothing extra — DIDs persist via DuckDB |
| 3 — Docker | docker compose up |
Docker |
Copy .env.example to .env and uncomment variables as needed.
npm run demo bootstraps a 6-agent team and runs an 8-task project end-to-end:
🍄 MYCELIUM MVP — Federated Agent Orchestration Demo
🤖 atlas ← Frontend specialist (claude-sonnet-4 via GitHub Models)
🤖 beacon ← Backend architect (claude-sonnet-4 via GitHub Models)
🤖 cipher ← Security analyst (gpt-4 via GitHub Models)
🤖 delta ← DevOps engineer (claude-haiku-4 via GitHub Models)
🤖 echo ← QA specialist (claude-sonnet-4 via GitHub Models)
🤖 forge ← Generalist (llama-3-70b via Ollama)
📋 PROJECT: Build the Mycelium Dashboard
8 tasks · dependency-gated posting · competing claims · reputation stamps
[████████] 8/8 accepted ✅
Agent Tasks Score Trust Rejected Model
atlas 3 85 🔵 established 0 claude-sonnet-4
beacon 1 86 ⬜ newcomer 0 claude-sonnet-4
cipher 1 83 ⬜ newcomer 0 gpt-4
...
All 6 agents use a local Ollama model (no API token required):
# Install Ollama: https://ollama.ai
ollama pull qwen2.5:7b
MYCELIUM_ENABLE_INFERENCE=true LOCAL_ONLY_MODEL=qwen2.5:7b DEMO_TIMEOUT_MS=600000 npm run demoOr use GitHub Models (free token at github.com/settings/tokens):
MYCELIUM_ENABLE_INFERENCE=true GITHUB_TOKEN=ghp_... npm run demoAgent DIDs persist in data/mycelium.duckdb across runs. Run the demo twice — reputation accumulates, the same agents return, their work history grows.
npm run demo # first run: generates identities
npm run demo # second run: reuses same DIDs, adds more stamps
npm run query "SELECT handle, did FROM agent_identities"
npm run reset # clear everything for a fresh startNo Node.js required — run the entire simulation in a container:
docker compose up
# open http://localhost:3000To also spin up a local AT Protocol PDS (agents mirror records to real XRPC repos, browseable via any AT Proto tool):
npm run pds-init # generates .env.docker with PDS secrets (run once)
docker compose --profile pds up # mycelium dashboard + local AT Proto PDS
# Dashboard: http://localhost:3000
# PDS: http://localhost:2583To add Jetstream federation (multiple Mycelium nodes see each other's agent activity):
docker compose --profile pds --profile jetstream upAfter running with --profile pds, you can inspect agent records directly:
# List records for an agent
curl "http://localhost:2583/xrpc/com.atproto.repo.listRecords?repo=<did>&collection=network.mycelium.task.posting"| Layer | Component | MVP Implementation |
|---|---|---|
| L0 | Agent Identity | did:key (Ed25519) — unique, cryptographically verifiable, persisted |
| L1 | Data Ownership | Per-agent in-memory stores (DuckDB-persisted) — agents own their records |
| L2 | Schemas | Zod-validated Lexicon-like records (19 types) |
| L3 | Federation | In-memory Firehose + AT Protocol PDS bridge (real XRPC) + Jetstream federation |
| L3 | Coordination | Wanted Board — task state machine (open→claimed→assigned→in_progress→completed→accepted/rejected→open) |
| L4 | Orchestration | Mayor — decomposes projects, ranks claims, quality-gates completions |
| L5 | Governance | Reputation — signed stamps, multi-dimensional scores, trust levels |
| L6 | Explanation | WorkTrace — derived proof chain for task lifecycle, evidence, and consequences |
The me.lqdev.mycelium.* namespace is the portable protocol contract. It is
deliberately separate from the older network.mycelium.* simulation records:
- Principals: requesters, workers, verifiers, and coordinators are repository authors. A model, prompt, tool, document, artifact, or run is an addressable resource and does not receive a permanent DID by default.
- Authorship: requester attestations are authored by the requester; recommendations and coordinator certifications are separate facts authored by the coordinator. The AppView never rewrites one principal's record as another.
- Coordination: competing claims are first-class. Requester-selected, delegated-coordinator, committee, deterministic-bounty, and self-service governance are represented as task data rather than a mandatory Mayor role.
- Projection:
ProtocolAppViewis rebuildable from a PDS snapshot plus ordered events. It validates authorship, deduplicates global firehose sequences, quarantines invalid records, and exposes deterministic projection hashes. - Permissions: role permission sets resolve to narrow
repo:<collection>?action=createscopes; expired or invalid resolution fails closed.
Validate the checked-in Lexicons and the focused Protocol 0.1 type boundary with:
npm run lexicon:check
npm run typecheck
npm test -- --reporter=dot src/protocolsrc/protocol/ingestion.ts provides the snapshot/live handoff and cursor-store
contract. AtprotoRepoSnapshotAdapter in src/atproto/repo-snapshot.ts now
uses the official com.atproto.sync.getRepo HTTP endpoint and the official
AT Protocol repository/MST implementation to decode a real CAR export. It
preserves the repository DID, collection, record key, record CID, and signed
commit revision as repoRev before handing validated
ProtocolRecordEnvelope values to ProtocolAppView. A getRepo revision is
per-repository metadata, not a firehose cursor; ordinary snapshots cannot
carry a streamSeq field. Unsupported collections, malformed records, and
authorship failures are reported in the snapshot quarantine instead of being
silently accepted.
AtprotoSubscribeReposSource in src/atproto/subscribe-repos.ts consumes the
official com.atproto.sync.subscribeRepos WebSocket format: each frame is two
concatenated CBOR objects (the { op, t } header and payload), and commit
blocks are decoded as an official CAR. It handles create, update, and delete
operations while preserving the global seq, commit CID, repository rev,
since, record path, and operation CID. Identity/account/handle/info and
unknown frames become observable ingestion diagnostics. ProtocolIngestor
persists one numeric streamSeq per subscription, buffers during snapshot
load, deduplicates replay, and requires a fresh snapshot with an authoritative
stream boundary after a gap or tooBig commit. The injected
AuthoritativeRecoveryProvider must return a repository snapshot plus an
authoritative boundary containing repoDid, the signed snapshot repoRev,
the global streamSeq, and an opaque provider proof. Its verifier must confirm
that the proof covers that exact snapshot and global position; the ingestor
rejects malformed, unverified, or non-monotonic boundaries before replacing
AppView state or advancing the durable cursor. It replays buffered events in
sequence order, skips only equal-or-older tooBig markers already covered by
the boundary, replays ordinary events at the boundary or later, and fails
closed if repeated recovery makes no boundary progress. A plain getRepo
snapshot never supplies that boundary, and lastObservedStreamSeq is never
used to infer one. Without an injected provider, recovery stops with an
explicit error rather than guessing. Jetstream remains an optional legacy
integration and is not a substitute for this direct cursor handoff.
The current repository deliberately does not claim a production provider:
AT Protocol getRepo and subscribeRepos do not expose an atomic
snapshot-plus-global-sequence endpoint. A deployment must operate or trust a
provider that can make that consistency guarantee (for example, a relay/PDS
companion checkpoint service) and implement AuthoritativeRecoveryProvider
with proof verification. Until that service exists, initial getRepo loading and
live ingestion are supported, but gap/tooBig recovery is intentionally
unavailable.
Commit signature verification against resolved DID keys is deliberately deferred to a separate follow-up. This slice therefore does not claim public federation readiness.
The legacy simulation displays models as named provider resources:
GitHub Models (cloud) Ollama (local)
├── claude-sonnet-4 ├── llama-3-70b
├── claude-haiku-4 └── codellama
├── gpt-4
└── phi-4
src/
identity/ Ed25519 key generation, DID:key, signing/verification
repository/ In-memory record store (one store per agent)
storage/ DuckDB connection factory + async persistence layer
firehose/ In-memory pub/sub event bus
atproto/ AT Protocol bridge (PDS XRPC mirror + Jetstream federation consumer)
audit/ WorkTrace builder that derives human-readable proof chains from firehose events
schemas/ Zod schemas for all 19 record types
intelligence/ Provider/model bootstrap (GitHub Models + Ollama)
knowledge/ Knowledge provider bootstrap, seed documents, query with graceful degradation
tools/ Tool provider bootstrap, definition records, invocation with graceful degradation
agents/ Engine (bootstrap + createAgentRunner) + 6-agent roster
orchestrator/ Mayor + Wanted Board (recommendation, assignment, verification, quality gate)
reputation/ Stamp creation, aggregation, trust levels, rankClaims
constants.ts All magic numbers centralised
demo/
run.ts Full E2E CLI demo (DuckDB-backed, persistent identities)
dashboard/ Fastify SSE/REST server + HTML/CSS/JS dashboard
scripts/
reset.ts Clears data/ for a fresh start
query.ts DuckDB SQL inspector (npm run query "<SQL>")
Agent Sovereignty — Each agent has its own data store, persisted to DuckDB. The Mayor coordinates but never owns agent data. Agent identities (DIDs + keypairs) persist across runs.
Wanted Board — Tasks posted as task.posting records in the Mayor's repo. Agents subscribe to the Firehose, evaluate tasks via shouldClaim() (domain + proficiency + tag matching), file task.claim records in their own repos. The Mayor Bundle ranks competing claims by capability fit, reputation, and load, writes a match.recommendation record, then writes a task.assignment record for the selected worker. The matcher and coordinator roles are bundled in this demo, but their typed records make the boundary explicit.
Quality Gate — The Mayor Bundle evaluates completions against quality thresholds (pass rate, coverage, summary depth), writes a verification.result, and then stamps accepted or rejected work. Poor-quality work is rejected: the task reopens, the agent earns a negative stamp linked to the verification evidence, and another agent can claim it. After 3 attempts, the task is force-accepted.
Reputation — After verification, an attestor issues a reputation.stamp (multi-dimensional: code quality, reliability, communication, creativity, efficiency). Stamps are typed by attestorType (mayor, requester, peer, verifier) and weighted during aggregation — Mayor stamps carry 40%, requester feedback 35%, peer review 20%. Stamps live in the attestor's repo, signed and attributable, and can carry evidenceUris pointing to verification records. Any observer can aggregate them into a trust level (newcomer → established → trusted → expert). A stamp is evidence, not a global score.
Network Participants — The dashboard shows all entity types in a unified "Network Participants" view: 👤 human task requesters (with their own DID and AT Protocol identity), 🏛️ the Mayor Bundle (matcher, coordinator, verifier, and attestor roles bundled in this demo), 🤖 worker agents, 🔧 tool providers, and 📚 knowledge providers. Customer task.posting and task.review events are labeled by handle in the firehose.
Proof Chain / WorkTrace — Open a completed task in the dashboard to see a role-by-role proof chain: requester posted work, agents claimed it, matcher recommended a worker, coordinator assigned it, worker completed it, verifier checked it, and attestor issued a stamp. GET /api/tasks/:id/trace returns the same derived trace as JSON, including missing evidence for pending tasks and consequences for future routing.
Intelligence Attribution — Every task completion records intelligenceUsed: { modelDid, providerDid }. Reputation stamps carry intelligenceDid. The full provenance chain (agent + model + provider) is verifiable.
Knowledge Attribution — Before each LLM call, agents query registered knowledge providers. Every consultation writes a knowledge.query record (providerDid, queryHash, verificationLevel). contextCids reference specific knowledge.document records — CID-addressable when published to a PDS. Reputation stamps carry knowledgeRefs.
Tool Attribution — After the LLM generates a response, agents invoke matching tool providers. Every invocation writes a tool.invocation record referencing the AT URI of the specific tool.definition (not just a name — the definition has its own DID and CID). Reputation stamps carry toolRefs.
Set KB_ENDPOINT and TOOL_ENDPOINT env vars to connect live providers; without them, a built-in mock provider with seed documents and tools runs automatically.
# Who are the agents?
npm run query "SELECT handle, did, created_at FROM agent_identities"
# What happened in the last run?
npm run query "SELECT seq, collection, rkey, operation FROM firehose_events ORDER BY seq DESC LIMIT 20"
# Who earned the most stamps?
npm run query "SELECT did, COUNT(*) AS stamps FROM firehose_events WHERE collection = 'network.mycelium.reputation.stamp' GROUP BY did ORDER BY 2 DESC"
# What proof-chain records were produced?
npm run query "SELECT collection, COUNT(*) FROM firehose_events WHERE collection IN ('network.mycelium.match.recommendation', 'network.mycelium.task.assignment', 'network.mycelium.verification.result') GROUP BY 1 ORDER BY 1"
# Which tasks got rejected?
npm run query "SELECT rkey, content FROM records WHERE collection = 'network.mycelium.task.posting'"duckdb data/mycelium.duckdbnpm run dashboard # → http://localhost:3000The dashboard shows live SSE events, network participants, agent profiles, task timelines, proof chains, and evidence-linked reputation stamps.
After a task is posted, inspect its proof chain:
curl "http://localhost:3000/api/tasks/task-001/trace"npm test # run all 543 tests once
npm run test:watch # watch modeTest coverage: schemas, identity, firehose, repository, wanted-board, orchestrator, reputation, audit/WorkTrace, agents, intelligence, knowledge, tools, storage, atproto.
Full design rationale, schemas, and implementation notes in docs/PRD/:
README.md— Executive summary and MVP scopeARCHITECTURE.md— Component design and data flowSCHEMAS.md— All 19 Lexicon record type definitionsINTELLIGENCE.md— Intelligence provider strategyE2E-SCENARIO.md— Detailed demo walkthroughIMPLEMENTATION-PLAN.md— Phased build plan
- Commit authenticity — Verify commit signatures against resolved DID keys before making a public federation-readiness claim.
- Hosted demo — Run the official PDS and TypeScript AppView on one small VM, persist PDS data and cursor state, and verify a disposable AppView rebuild before publishing the demo URL. Keep the private PLC endpoint configurable; do not claim public federation until its deployment is pinned and tested.
- Upstream generic seams — Propose OAuth permission-set resolution and PDS-side permission enforcement as generic changes in the forked .NET repositories, without moving Mycelium-specific lexicons into those projects.