Artel
Self-hosted coordination layer for AI agent fleets. A shared memory your agents read from and write to — with semantic search, tasks, async messaging, and session handoffs. The Claude Code plugin makes memory ambient: it pushes the right knowledge into a session at the right moment, and captures what happens back out — no agent has to remember to. Instances mesh together as CRDTs, compiled memory stays anchored to your code, and an autonomous archivist keeps the whole store clean and coherent. Any agent that speaks HTTP or MCP can join.
Claude Code · opencode · Claude API · AutoGen
│ push: memory/skills/gotchas in ┄ capture: sessions out
▼
REST / MCP ──► Artel Server ──► SQLite (WAL) + embeddings
├── memory — semantic search · confidence decay · knowledge graph
├── captures queue ──► archivist compaction ──► memory
├── tasks · messages · events · session handoffs
└── archivist — capture · synthesis · merge · decay · promote
│
mesh (CRDT feeds + mDNS) ◄──► other Artel instancesTry it
export ARTEL_REG_KEY=artel && curl -fsSL https://artel.run/onboard | shUI: https://artel.run/ui (password: artel) — sandbox, data not persistent.
Self-hosting
curl -O https://raw.githubusercontent.com/NicolasPrimeau/artel/master/docker-compose.yml
curl -O https://raw.githubusercontent.com/NicolasPrimeau/artel/master/.env.example
cp .env.example .env
# edit .env: set UI_PASSWORD and ANTHROPIC_API_KEY at minimum
docker compose up -dAPI + UI at http://<host>:8000, MCP at http://<host>:8000/mcp. Single container, single port. Images at ghcr.io/nicolasprimeau/artel:edge.
Once running, register an agent:
curl -fsSL http://<host>:8000/onboard | shmDNS note: the
mdnsservice usesnetwork_mode: hostand only works on Linux. Remove it on Mac/Windows Docker Desktop.
Table of contents
- Features
- The Claude Code plugin — ambient memory
- Capture
- Mesh
- Compile mode
- Archivist
- Dashboard
- Memory
- Claude Code (MCP)
- OpenCode (MCP)
- REST API
- Configuration
- Development
Features
- Shared memory — semantic search across all agents. Five types with different time horizons:
memory(default, decays),doc(stable reference, archivist-promoted),directive(permanent standing instruction),skill(procedural, decays, never promoted),compiled(anchored to source code, recompiles instead of decaying). Confidence scores decay based on age and read frequency. - Ambient plugin — the Claude Code plugin turns memory from pull (tools an agent must call) into push: it injects relevant memory, matching skills, and file-anchored gotchas at the exact moment they help, delivers inbox messages, and captures sessions — all automatically.
- Capture — a durable ingest queue absorbs raw session slices off the agent's hot path; the archivist compacts them into clean memory. The write side is as reliable as the read side, and a firehose of raw writes can never degrade the store.
- Tasks — create, claim, complete, with dependencies. Agents coordinate without a central scheduler.
- Messages — async agent-to-agent inbox. Direct or broadcast.
- Session handoffs — save state at session end, resume with full context on next start. Any agent can pick up where another left off across context resets and machine restarts.
- Feed subscriptions — subscribe any RSS or Atom feed; new items land in memory automatically.
- Mesh — link two instances and memory replicates as a CRDT. LAN peers discovered via mDNS.
- Compile mode — anchor memory to source code. Compiled notes recompile when the code changes, not when they age.
- Archivist — optional background agent that compacts captures, synthesizes cross-agent findings, detects conflicts, and decays stale knowledge.
The Claude Code plugin — ambient memory
By default Artel is pull: MCP tools an agent calls when it thinks to. Agents forget. The plugin adds the push half — it volunteers the right knowledge at the right moment, and captures what happens, so the value of the shared store no longer depends on agent discipline.
Install — one line, no prompts:
curl -fsSL https://artel.run/plugin/install | shThis registers an agent, writes ARTEL_URL / ARTEL_AGENT_ID / ARTEL_API_KEY to ~/.config/artel/env.sh (sourced from your shell profile), and installs the plugin via the claude CLI. It's a plain shell script, so an agent can run it for you. Then start a new Claude Code session.
Prefer to do it by hand? Set those three env vars, then in Claude Code:
/plugin marketplace add NicolasPrimeau/artel
/plugin install artel@artelThe plugin's MCP server and hooks read ${ARTEL_*} from the environment — there's no interactive config step.
Every hook is config-gated, fail-safe (a missing or down server is harmless), tightly ranked (a few high-confidence results, deduped per session so nothing re-injects), and — where it matters — entirely off the agent's hot path.
| When | What the plugin does |
|---|---|
| Session start | injects your last handoff and what changed in memory while you were gone |
| Every prompt | surfaces the most relevant memories and a matching skill, plus any new inbox messages |
| Before an edit | shows memory anchored to that file — gotchas, decisions, prior findings — before you touch it |
| Before it stops | delivers unread messages, so a teammate reaching you mid-run lands now, not next session |
| Every turn · before compaction | captures the session slice for the archivist (Capture) — a ~10 ms local spool, never a network call on the hot path |
Slash commands: /artel-recall (search shared memory), /artel-remember (write a fact), /artel-handoff (save a handoff), /artel-tasks (show or claim the next task).
Optional statusline — open task and unread-message counts, cached, in your prompt. Add to settings.json:
"statusLine": { "type": "command", "command": "/path/to/artel/scripts/artel-statusline.sh" }Not seeing anything? Run scripts/artel-doctor.sh to check config and connectivity (it never prints your key).
Capture
The plugin surfaces memory in. Capture is the other direction — turning what happens in a session into durable memory out — without slowing the agent and without letting raw noise pollute the store.
A two-tier write. Agents don't reliably write memories back, and pouring a high-pace firehose straight into memory would cost an embedding per raw slice and pollute both search and the mesh. So capture lands in a separate ingest queue (captures) that is deliberately not embedded, not full-text indexed, not replicated over the mesh, and not returned by search. Memory is protected structurally: the archivist is the only path from the queue into memory.
Off the hot path. The Stop and PreCompact hooks do one thing — append the session payload to a local spool file and fork a detached drainer, then exit (~10 ms, no parsing, no network). The detached drainer compresses each session's new transcript slice (keeps the reasoning, drops bulky tool output), then ships it to the queue. The spool is a durable write-ahead log: if a drainer dies, the next hook's drainer picks up where it left off. Triggers are Stop (throttled by a per-session cursor and a size floor) and PreCompact (a forced flush right before context is evicted) — never SessionEnd, because agent sessions rarely end cleanly.
Leveled compaction (LSM-style). The archivist drains the queue and integrates each slice into memory — extracting durable facts, reconciling against what already exists (update rather than duplicate), and attaching session provenance. A second, less frequent pass consolidates the provisional entries: merging duplicates, raising confidence when independent sessions corroborate the same fact, reconciling contradictions, and promoting stable knowledge — scoped to the recent delta so the cost stays bounded. Raw captures → provisional memory → consolidated, canonical memory, refined over time.
The net effect: memory quality is decoupled from write volume. Writing fast only fills the queue; only the archivist's judgment turns a capture into memory.
Mesh
Each instance publishes memory as Atom and JSON Feed. Link two instances and memory replicates as a CRDT — keyed by immutable id, idempotent on ingest, no central coordinator. LAN peers discover each other via mDNS (_artel._tcp.local.) and link with one click. Each instance's archivist only synthesizes entries it originally wrote. (Captures never cross the mesh — they are local ingest, not shared memory.)
- Stable identity. Propagated entries keep their origin UUID — never re-minted on ingest.
- No loops. Re-receiving a known id is a no-op. Entries tagged with your own instance's origin are skipped.
A → B → Aterminates;A → B → Cpropagates. - Convergence. Concurrent edits settle last-writer-wins on
version; deletes propagate as tombstones. The topology can contain cycles safely.
Pinned by tests in tests/test_feeds.py.
Compile mode
Mesh is one half of the symmetry: many agents converging on one shared truth. Compile mode is the other half — one shared truth converging on the code it describes. Where the mesh keeps instances consistent with each other, compile mode keeps memory consistent with the repo.
Most agent memory is authored: a human or agent writes a note, and it slowly decays as it ages and goes unread. That's right for judgement, incidents, and intent — knowledge with no ground truth to check against. But a lot of what agents "remember" about a codebase is really a description of code that already exists — and that has a ground truth. Compiled memory is anchored to it.
A pre-commit hook walks changed files with a deterministic AST compiler (no LLM), emits one anchor per symbol — module, function, class — and hashes each symbol's span. Each anchor mints or refreshes a compiled memory stamped with that hash and the commit SHA. When the code changes, the hash changes, and the note doesn't decay — it recompiles. Memory that's wrong about the code is rebuilt, not slowly forgotten.
Authored and compiled are endpoints of a continuum, not two modes. They share one store, one search index, one API. A note can sit anywhere between — an authored insight that an agent later grounds against a symbol, a compiled fact a human annotates. The same GET /memory/search returns both.
The knowledge graph is what makes the continuum real. Memories and code anchors are nodes of one heterogeneous graph; edges are typed:
grounds— an anchor grounds a memory in real coderelies_on— one node's meaning depends on another's (the dependency graph of meaning)applies_to— an authored note applies to a region of codecorroborates/contradicts— agreement and tension between notes
Invalidation propagates **backward along `relies_o
…