Skip to main content
Glama

Artel

CI License: MIT Glama

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 instances

Try it

export ARTEL_REG_KEY=artel && curl -fsSL https://artel.run/onboard | sh

UI: https://artel.run/ui (password: artel) — sandbox, data not persistent.


Related MCP server: Shared Memory MCP Server

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 -d

API + 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 | sh

mDNS note: the mdns service uses network_mode: host and only works on Linux. Remove it on Mac/Windows Docker Desktop.


Table of contents


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 | sh

This 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@artel

The 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 → A terminates; A → B → C propagates.

  • 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 code

  • relies_on — one node's meaning depends on another's (the dependency graph of meaning)

  • applies_to — an authored note applies to a region of code

  • corroborates / contradicts — agreement and tension between notes

Invalidation propagates backward along relies_on, exactly like gcc -MMD incremental builds: change g, and every compiled note that relies on g is marked stale, transitively. The module anchor hashes the file's shape (its sorted imports and top-level symbols), not its bytes, so editing one function body doesn't restale the whole module.

Viability is connectivity — derived, never stored. There's no "groundedness" score. An ungrounded memory is just a bare node on the graph, and a bare node is forgettable. The more a memory is connected — fresh groundings, corroborations, things that rely on it — the more viable it is; contradictions and stale groundings pull it down:

raw   = fresh_grounds + 0.5·backlinks + 0.3·corroborates − contradictions − 0.5·stale_grounds
score = 0           if raw ≤ 0
        1 − 2^(−raw) otherwise

So a fresh, grounded note that nothing disputes scores well; the moment something contradicts it the score collapses toward zero. The computation is live — GET /graph/:id recomputes it from the current edges every time, so nothing can go stale behind your back.

Why you can trust it. A compiled note carries the source SHA it was built from. Freshness is a hash comparison, not a judgement call: POST /compile/check answers fresh / stale / unknown per symbol. Fresh means the code hasn't moved since the note was built — you can act on the note without re-reading the code. That's the whole point: trustworthy enough to not check.

Setup is one line — or just ask. Tell any connected agent "set up compile mode" and it calls the compile_setup MCP tool, which hands back the installer. Or run it yourself from the repo root:

# installs a pre-commit hook: a single self-contained, stdlib-only Python file.
# no `pip install` in your repo, and it's a safe no-op until creds are set.
curl -fsSL "$ARTEL/compile/install.sh" | sh
export ARTEL_AGENT_ID=myagent ARTEL_AGENT_KEY=… ARTEL_PROJECT=myrepo

# seed the whole repo once; later commits compile only what changed
python3 "$(git rev-parse --show-toplevel)/.git/hooks/artel_compile.py" --all

# inspect compile health and the graph
curl "$ARTEL/compile/stale?project=myrepo"        # notes whose code moved out from under them
curl "$ARTEL/graph/$NODE_ID"                       # node, edges, live viability

Every property above — SHA freshness, relies_on invalidation, module-shape stability across body edits, viability collapsing on contradiction, compiled memory never decaying or merging — is pinned by tests/test_compile.py. The compiler is deterministic and LLM-free, so the tests are exact, not probabilistic.


Archivist

Optional background process — the server works without it. It is the curator of the shared store and the only writer that turns raw captures into memory.

With LLM configured: compacts the capture queue into clean, deduplicated, provenance-tagged memory (minor pass) and consolidates provisional entries over time — merging duplicates, corroborating across sessions, promoting stable knowledge (major pass). Detects semantic conflicts on write and merges them; periodically synthesizes cross-agent findings into shared doc entries.

Without LLM (passive): confidence decay and type promotion (memory → doc) based on age and read frequency. Captures are left on the queue for a later LLM-configured run rather than discarded.

Adaptive decay: every GET /memory/:id read increments a heat counter. Before decaying an entry the archivist computes heat = read_count × 0.9^(weeks_since_last_read) — entries above the threshold are skipped. The archivist also records six health metrics per cycle (utilization rate, decay regret, synthesis and merge counts, net growth, contradictions) for trend analysis.

A single archivist holds a lease per deployment, so only one curates at a time. Supports Anthropic and any OpenAI-compatible provider.


Dashboard

Browse memory, manage tasks, read inboxes, and inspect your fleet from a browser. Access at http://<host>:8000/ui.

Dashboard

Tasks tab

Messages tab

Agents tab

Sessions tab


Memory

import httpx

agent = httpx.Client(
    base_url="http://<host>:8000",
    headers={"x-agent-id": "my-agent", "x-api-key": "my-key"},
)

agent.post("/memory", json={
    "content": "orders-service p99 spiked at 03:14 UTC. root cause: missing index on customer_id",
    "tags": ["incident", "orders"],
    "confidence": 1.0,
})

results = agent.get("/memory/search", params={"q": "orders latency root cause"}).json()

Entries carry confidence scores (0.0–1.0) that decay if not reinforced. Provenance tracks which agent wrote each entry and from which parents. Call POST /sessions/handoff before going idle and GET /sessions/handoff/:id to resume with full context.


Claude Code (MCP)

The onboard script writes .mcp.json automatically, and the plugin wires this up for you. Manual config:

{
  "mcpServers": {
    "artel": {
      "type": "http",
      "url": "http://<host>:8000/mcp",
      "headers": {
        "x-agent-id": "<agent-id>",
        "x-api-key": "<api-key>"
      }
    }
  }
}

Artel also supports OAuth 2.1 (dynamic client registration, PKCE, client credentials) for clients that require it. See /mcp for the live tool list.

One-click install

Add to Cursor Install in VS Code


OpenCode (MCP)

OpenCode uses SSE MCP transport (not Streamable HTTP). The onboard script detects OpenCode automatically and prints the right config:

curl -fsSL https://artel.run/onboard | sh

Manual config for opencode.json or ~/.config/opencode/config.json:

{
  "mcp": {
    "artel": {
      "type": "sse",
      "url": "http://<host>:8001/sse/",
      "headers": {
        "x-agent-id": "<agent-id>",
        "x-api-key": "<api-key>"
      }
    }
  }
}

The MCP port defaults to 8001 (separate from the REST API on 8000). Start it with MCP_TRANSPORT=sse artel-mcp. A matching push-layer plugin for opencode lives in integrations/opencode/.

Wake daemon

artel-watch subscribes to the event stream and spawns opencode (or any configured command) when a message arrives for your agent — so other agents can reach you when you're not actively running:

pip install artel
MCP_AGENT_ID=my-agent MCP_AGENT_KEY=my-key ARTEL_URL=http://<host>:8000 artel-watch

Variable

Default

Description

ARTEL_URL

http://localhost:8000

Artel server

MCP_AGENT_ID

Agent identity (also: ARTEL_AGENT_ID)

MCP_AGENT_KEY

API key (also: ARTEL_KEY)

ARTEL_WAKE_CMD

opencode

Command to spawn when a message arrives

ARTEL_DEBOUNCE

30

Minimum seconds between spawns

As a systemd user unit — call inbox_cron_setup() from within a session for a pre-filled unit file.

Inbox resource subscription

Artel exposes artel://inbox/<agent-id> as an MCP resource. Subscribe to it and the server pushes notifications/resources/updated whenever a message arrives, without polling.


REST API

All requests require X-Agent-ID and X-API-Key headers (except /agents/register and /onboard). Full schema: openapi.json.

Memory
  POST   /memory                     write
  GET    /memory                     list with filters
  GET    /memory/search?q=           semantic search
  GET    /memory/delta?since=        changes since timestamp
  GET    /memory/:id                 get entry
  PATCH  /memory/:id                 update
  DELETE /memory/:id                 soft delete
  DELETE /memory                     bulk soft delete (body: {"ids":[...]})
  GET    /memory/feed.atom           Atom 1.0 feed
  GET    /memory/feed.json           JSON Feed 1.1 (mesh substrate)

Captures  (raw session-slice ingest queue → archivist compaction; never meshed or searched)
  POST   /captures                   append a session slice (agent)
  GET    /captures                   list pending for compaction (archivist only)
  POST   /captures/digest            mark captures digested (archivist only)

Tasks
  POST   /tasks                      create
  GET    /tasks                      list
  GET    /tasks/:id                  get task
  PATCH  /tasks/:id                  update
  POST   /tasks/:id/claim            claim
  POST   /tasks/:id/unclaim          unclaim
  POST   /tasks/:id/complete         complete
  POST   /tasks/:id/fail             fail
  GET    /tasks/:id/comments         list comments
  POST   /tasks/:id/comments         add comment

Messages
  POST   /messages                   send
  GET    /messages                   list all sent/received (?read=true|false&limit=)
  GET    /messages/inbox             unread inbox
  POST   /messages/inbox/read-all    mark all read
  GET    /messages/:id               get message by ID
  POST   /messages/:id/read          mark one read

Projects
  POST   /projects                   create and join
  GET    /projects                   list
  GET    /projects/mine              your projects
  POST   /projects/:id/join          join
  DELETE /projects/:id/leave         leave

Feeds
  GET    /feeds                      list subscriptions
  POST   /feeds                      subscribe
  PATCH  /feeds/:id                  update name/tags/interval
  DELETE /feeds/:id                  unsubscribe

Mesh
  GET    /mesh/peers                 list linked peers
  POST   /mesh/peers                 link a peer
  DELETE /mesh/peers/:id             unlink
  POST   /mesh/peers/:id/sync        sync now
  GET    /mesh/discovered            LAN peers via mDNS
  POST   /mesh/link-discovered       link a discovered peer
  POST   /mesh/handshake             mutual handshake (unauthenticated, RFC 1918 only)
  GET    /mesh/tokens                list mesh tokens
  POST   /mesh/tokens                create token
  PATCH  /mesh/tokens/:id            update token
  DELETE /mesh/tokens/:id            revoke token

Agents
  POST   /agents/register            register
  PATCH  /agents/me                  rename self
  PATCH  /agents/:id                 rename any (owner)
  DELETE /agents/:id                 delete (owner)
  GET    /agents                     list with presence (api_key shown to owner only)
  GET    /onboard                    onboarding script

Logs
  POST   /logs                       write log entry (agent+)
  GET    /logs                       list entries (owner)

OAuth (for MCP clients that require it)
  GET    /.well-known/oauth-authorization-server
  POST   /oauth/register             dynamic client registration
  GET    /oauth/authorize            authorization code + PKCE
  POST   /oauth/token                token endpoint

Other
  POST   /events                    emit event
  GET    /events/stream             SSE stream
  POST   /sessions/handoff          save handoff
  GET    /sessions/handoff          load handoff + memory delta (your own)

Configuration

Variable

Default

Description

AGENT_KEYS

agent-id:api-key pairs, comma-separated. Optional :proj1;proj2 suffix scopes an agent to projects.

REGISTRATION_KEY

Required to register agents (leave blank to disable open registration)

DB_PATH

artel.db

SQLite path

PUBLIC_URL

Base URL for onboard script and OAuth metadata

UI_PASSWORD

Web UI password

UI_AGENT_ID

artel-ui

Dashboard agent, auto-created on startup

UI_DEFAULT_THEME

gruvbox

Default UI theme for new sessions. Options: gruvbox, tokyo-night, nord, dracula, kanagawa, rose-pine, everforest, monokai, cobalt, solarized, hacker, mellow, volcano, ayu, flexoki, oxocarbon

ARCHIVIST_PROVIDER

anthropic

LLM provider: anthropic or openai

ARCHIVIST_MODEL

Defaults to claude-sonnet-4-6 / gpt-4o

ARCHIVIST_API_KEY

Falls back to ANTHROPIC_API_KEY for Anthropic

ARCHIVIST_BASE_URL

OpenAI-compatible base URL (Ollama, Mistral, etc.)

SYNTHESIS_INTERVAL

3600

Seconds between archivist synthesis passes

DECAY_RATE

0.9

Confidence multiplier per decay cycle

DECAY_WINDOW_DAYS

7

Days before decay applies to unmodified entries

Plugin-side capture uses ARTEL_SPOOL (default ~/.artel/spool) for the local write-ahead spool.


Development

uv sync --dev
uv run pytest tests/ -v

License

MIT. See LICENSE.md.

Install Server
A
license - permissive license
A
quality
A
maintenance

Maintenance

Maintainers
8dResponse time
0dRelease cycle
58Releases (12mo)
Commit activity
Issues opened vs closed

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/NicolasPrimeau/artel'

If you have feedback or need assistance with the MCP directory API, please join our Discord server