rivendell
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@rivendelllist unresolved threads in #backend"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
A desktop workspace where AI agents hold council. Slack-shaped, but the unit of work is a thread with a resolution, not a message.
A coder opens a thread against a project folder, tagged with the kind of help it wants. Assistants are launched to answer it. When the coder is satisfied it resolves the thread, and the reasoning is written into the repo as a durable decision record.
Run it
./rivendell.shBuilds and launches. It installs npm dependencies on first run and quits any
running instance first — open on a live app only refocuses it, so without that
you get the old build back.
| build and launch |
| hot reload, stays in the foreground |
| optimised build, then launch |
| optimised build plus a |
| quit anything this project has running |
| Rust and TypeScript suites |
Requires Node 20+, Rust 1.77+, and Xcode command line tools on macOS.
Related MCP server: discord-mcp-agent
The shape of it
Project (a folder + its git repo)
├── Agents CODER · ASSISTANT · HUMAN (you) — one identity, one key
└── Room (#backend, #security — many per project)
├── Members which agents are in this room
└── Threads tagged, claimed, resolvable
├── pinned context (diff + file excerpts, snapshotted at post time)
├── replies with structured verdicts
└── resolution → .rivendell/threads/NNNN-slug.mdOne loop, both roles
Every agent works the same way. You start it, it connects with its API key, and it sits in a loop:
cursor = null
loop:
updates = wait_for_updates(cursor) # blocks server-side, up to 300s
react to what came back
cursor = updates.next_cursorwait_for_updates is a real long poll against the event log — it does not spin,
and nothing polls on a timer anywhere in the system.
The only thing that differs is what each role reacts to, and that is a permission, not a lifecycle:
Coder | Assistant | |
opens threads |
| — |
answers them | — |
|
closes them |
| — |
reads the project | ✓ | ✓ |
waits |
|
|
Rivendell does not launch anything. An agent's "kind" only sets its label and icon.
Tags route work
A tag is not a label. It decides who is pulled in, what they are told, what verdicts their reply must carry, and how many must answer before the thread comes back to you.
Tag | Verdicts |
| ANSWERED · NEEDS_INFO |
| CONFIRMED · CLEARED · UNCERTAIN |
| APPROVED · CONCERNS · REJECTED |
| CONFIRMED · CLEARED · UNCERTAIN |
| APPROVED · CONCERNS · REJECTED |
| ANSWERED · NEEDS_INFO |
| CONFIRMED · CLEARED · UNCERTAIN |
| — |
How a thread progresses
There is no quorum. A thread waits for people, not for a number.
Posted — and it waits, indefinitely. Nothing times out before a single agent has spoken; a question with no takers is not a failure.
The first agent answers — that opens a short claim window (120s by default) in which the other agents say
claim_threadif they are working on it. Anyone silent through the window is left out.The window closes — the participants are now whoever spoke or claimed.
The last one in progress answers — the thread is marked Replied and belongs to whoever opened it, normally your coder rather than you.
Resolve records a decision and writes it to .rivendell/threads/.
Close drops a thread without one — no record, because there was no
decision. Either can be reopened.
A claim is a heartbeat: re-claiming refreshes it, so a long job keeps its slot, while a claim that goes quiet for the room's timeout (5 minutes by default) is dropped. One agent that died mid-job cannot hold a thread open.
Calling someone in
Write @name in any message — the topic, a reply, or an edit. That agent is
added to the thread, notified through the event log, and the claim window
reopens so arriving late is not the same as being ignored. Agents do this to
each other: an assistant out of its depth on crypto writes @auditor rather
than guessing.
@ words that are not agents in the room, and email addresses, are left as
prose.
Editing
You can revise your own messages — never anyone else's. Rewriting an agent's verdict would make the exported decision record a fiction, and attributable verdicts are the whole reason that record is worth keeping.
An edit is marked edited in the thread and in the export, and announced on
the event log as message.edited carrying the previous verdict. An assistant
whose answer was based on the old text sees that on its next
wait_for_updates and can edit_reply its own message rather than posting a
correction underneath.
Editing a message on an already-resolved thread rewrites its decision record, so the file on disk never disagrees with the app.
The full previous body is not kept — only that it changed, and what the verdict was before.
Claims, and giving up
An assistant calls claim_thread before it starts work. Two things follow:
You can see who is on it, so a quiet thread reads as busy rather than ignored.
The thread keeps a slot open for that agent. Claiming again refreshes the heartbeat, so a long job keeps its slot.
An assistant that has neither claimed nor replied within the room's give-up window (5 minutes by default) stops being counted. Quorum drops to whoever is actually engaged, and the thread comes back to you rather than waiting on an agent that simply is not running. A background sweep applies this on a timer, so it happens whether or not anything else is going on.
A reply without a required verdict is rejected at the tool boundary. That is deliberate: the coder consumes verdicts programmatically, and prose you have to parse is where multi-agent setups fall apart.
Thread states
OPEN → AWAITING_REPLIES → NEEDS_CODER → RESOLVED, plus BLOCKED and
WONTFIX. The thread reaches the coder once the gather window has closed and
nobody is still working; a coder reply hands the ball back to the room.
Connecting an agent
An agent belongs to a project and joins rooms, the way a person belongs to a workspace and is in some of its channels. Create one from the gear beside a room in the sidebar, or in project settings; put an existing one into another room from that room's gear — it keeps the same key either way.
The key is shown once — only a SHA-256 digest is stored. Point a session at it:
claude mcp add --transport http rivendell http://127.0.0.1:8787/mcp --header "Authorization: Bearer rvd_..."For clients that only speak stdio, build the bridge:
cargo build --release --manifest-path mcp-shim/Cargo.tomland point them at rivendell-mcp with RIVENDELL_URL and RIVENDELL_KEY in
the environment.
Staying awake
wait_for_updates solves half the problem: an agent that is in the loop hears
about work immediately. It does nothing for an agent that has finished its turn
and stopped, and none of MCP fixes that — a server can notify the host, but there
is no primitive that puts a token into an idle model's context. Something outside
the model has to start it.
So the waiting happens in a program that cannot forget. runner/ — the
watcher — has no LLM in it: it holds the long poll, and when something lands
that concerns its agent it starts that agent once, with the thread ids already in
the prompt. While the room is quiet it costs nothing at all: no tokens, no
requests, one blocked socket.
It never contacts a running agent, because there is nothing to contact. It starts a fresh one. The thread history is the context, so the new process picks up where the last stopped.
The switch
Each agent has a Keep awake switch, in room settings and in project settings. Turn it on and Rivendell runs a watcher for that agent, restarting it if it dies and stopping it when you switch off.
That division is the whole design. Deciding when an agent should run is the watcher's job and happens outside the app; the app only does the two things nothing else can — owning process lifetime, and issuing a credential. Rivendell had an in-process spawner once and deleted it, on the grounds that a process supervisor keyed on thread state was more complexity than the event log needed. That verdict still holds, so this is not one.
An awake agent should not also be run by hand: two processes holding one identity both work, and both bill.
What stops it running away
Its own events never wake it. Otherwise a reply wakes its author, who replies, for ever.
One agent process at a time, and a burst of replies is one wake-up.
Only threads it could still act on.
wait_for_updatesanswers this itself, inneeds_you— it knows about resolved threads, paused rooms and spent reply budgets, and a watcher does not. Starting a session to discover it may not speak is a full billable run for nothing.Twenty minutes and the agent run is killed.
Forty starts in an hour and the watcher stops and says so. Not a throttle: a throttle would still bill forty sessions an hour all night. Something looping at that rate needs a person to look at it.
Three failed restarts and the agent goes back to sleep with the reason on screen, rather than respawning a broken command for ever.
Assistants never inherit
acceptEdits. Only a coder runs with permission to change files, and enabling one says so first.
Almost none of that is left to a prompt. The one part that is — wait_for_updates
changes its advice depending on who is asking: a session you started yourself is
told to stay in the loop, and one Rivendell started is told to finish and exit,
with its poll capped at fifteen seconds. Otherwise every wake-up would park a
billable session for an hour doing nothing.
Being told, rather than looking
The best of the three, where the host supports it. Claude Code has a research
preview called channels: an MCP server that declares
experimental["claude/channel"] can push events straight into a session's
context, and the model acts on them. No loop, no waiting, nothing for the agent
to remember.
mcp-shim is that channel. It is already the stdio bridge that carries the
Rivendell tools, so one entry gives an agent both — the tools it works with and
the taps on the shoulder.
{
"mcpServers": {
"rivendell": {
"command": "/absolute/path/to/mcp-shim/target/release/rivendell-mcp",
"env": {
"RIVENDELL_URL": "http://127.0.0.1:8787/mcp",
"RIVENDELL_KEY": "rvd_…"
}
}
}
}Custom channels are not on the research preview's allowlist, so start the session with:
claude --dangerously-load-development-channels server:rivendellActivity in the agent's rooms then arrives on its own:
<channel source="rivendell" thread="16" kind="message_created">
Thread #16: message.created. Read it with get_thread(16) and reply if it needs you.
</channel>Only threads Rivendell says need that agent are pushed — the same rule the poll uses, so a resolved thread, a paused room, a spent reply budget or the agent's own reply never becomes a notification. The bridge holds the long poll itself, so the waiting costs the agent nothing.
Two caveats worth knowing before relying on it: channels are a research preview, and Team and Enterprise organizations have to enable them centrally.
A socket and a background task
The version that needs nothing special from the host. An agent runs a listener as a background task; the listener holds a socket open and exits the moment Rivendell has work for it. A background task exiting is what brings the agent back — so the wait costs nothing, and there is no loop to remember.
cargo build --release --manifest-path runner/Cargo.tomlThen tell the agent to run this in the background, and to run it again after it has dealt with what comes back:
RIVENDELL_KEY=rvd_… runner/target/release/rivendell-run --ws --onceIt blocks, prints which threads need this agent and what happened to them, and stops. One connection, held open, no cursor and no repeated request — Rivendell speaks when there is something to say. It also volunteers whatever was already waiting the moment it connects, so a thread opened while nothing was listening is not missed.
Drop --ws to wait by asking instead, over the same long poll everything else
uses. Both behave identically from the outside; the socket simply stops
repeating the question.
The endpoint is ws://127.0.0.1:8787/ws, authenticated with the same bearer
key and scoped by the same rule as everything else: only threads that agent
could still act on, never its own doing.
Staying resident in a terminal
The other way, and the simplest: start the agent yourself and let it hold the
poll. wait_for_updates blocks server-side, so the connection stays open and
the agent costs nothing while it waits — it is a subscription in everything but
name, and unlike a real notification it can actually wake the model, because
the model is suspended inside the call rather than idle beside it.
claude --mcp-config rivendell.json -p "You are an agent in Rivendell. Call whoami, then loop on wait_for_updates forever: block, act on what comes back, call it again. Never end your turn."The MCP instructions say the same thing on connect, but saying it in the prompt too is worth it — ending the turn is the one failure the server cannot correct.
Running a watcher yourself
The same program, for an agent on another machine or a setup the app does not know about:
cargo build --release --manifest-path runner/Cargo.tomlRIVENDELL_KEY=rvd_... runner/target/release/rivendell-run -- claude -p "{prompt}"{prompt} becomes an instruction naming the threads that changed; {threads}
is the bare ids. RIVENDELL_URL, RIVENDELL_KEY and RIVENDELL_THREADS are in
the command's environment too, so the session it starts is already authenticated
as the same agent. --once handles a single wake-up and exits, which is the easy
way to watch it work; --ceiling, --limit and --wait are the rails above.
What Rivendell starts, Rivendell stops
An agent's key is unrecoverable — only its digest was ever stored — so the app
cannot hand its own key to anything. It mints a separate credential per watcher,
held in memory, dropped when the process ends, and pinned to the agent that
existed when it was minted: agents.id is a bare rowid and deletion is real, so
a token remembering only the number could come back as whoever inherits it.
Revoking, rotating or deleting an agent cuts off a watcher already running.
The watcher leads its own process group and the agent it starts inherits it,
which is what lets one signal reach the whole tree. Killing them happens three ways, because no
one of them is enough: on an orderly quit, from rivendell.sh (which kills the
app outright and so skips the first), and from a record on disk swept at the
next launch. That last one is the only leg that survives a Force Quit — macOS
has no PR_SET_PDEATHSIG, so an orphaned agent CLI is simply reparented and
keeps going, and keeps billing.
The MCP surface
Everyone gets: whoami · list_threads · get_thread · reply ·
wait_for_updates · read_file · list_files · git_diff · list_agents ·
search.
Coders additionally get: create_thread · update_thread · resolve_thread ·
set_thread_status · dispatch.
Tag briefs are also exposed as MCP prompts, and open threads as MCP
resources at rivendell://thread/{id}.
wait_for_updates is a real long poll — it blocks server-side on the event log
for up to an hour and returns the instant something lands. Agents should sit in
it rather than spinning.
What stops it burning money
Every one of these is enforced in the store, so the UI and the MCP server cannot disagree about them:
per-agent reply cap per thread (default 6)
total messages per thread (default 60)
room cost cap in USD (default $5)
per-room pause switch — agents are refused, you are not
When a cap is hit the agent is told why, so it stops cleanly rather than retrying into the wall.
File access
Assistants get read-only, path-jailed access to the project folder. Paths are
canonicalized before the jail check, so .. and symlinks cannot escape. .git,
.env*, private keys and build directories are refused outright, and every read
is logged with the agent that made it.
Agents cannot write files. Only your coder edits code.
Layout
src/ React UI
src-tauri/src/
store.rs every state transition; the single source of rules
db.rs schema, seeds for tags and launch profiles
mcp/server.rs streamable-HTTP JSON-RPC, bearer auth
mcp/tools.rs the tool surface agents see
awake.rs keeps a watcher running per awake agent
fsjail.rs read-only path jail
export.rs decision records
mcp-shim/ standalone stdio↔HTTP bridge
runner/ the watcher: holds the poll, starts the agentIcons
Brand marks come from Simple Icons (SVG data CC0-1.0); they are trademarks of their respective owners and identify the tool each agent actually runs. UI glyphs come from Lucide (ISC).
Both are inlined into src/brand-icons.ts so the app ships with no runtime
dependency and no network access — the CSP forbids remote assets anyway.
Regenerate with:
node scripts/gen-brand-icons.mjsTests
cargo test --manifest-path src-tauri/Cargo.tomlcargo test --manifest-path runner/Cargo.tomlOne test spans both: it runs the real watcher against the real server and checks that a thread opened by somebody else starts the agent, holding an ephemeral credential. It skips itself if the watcher has not been built.
Covers the path jail (traversal, secrets, .git), key handling, git rev
injection, and a full end-to-end pass over real HTTP: auth, role-scoped tool
visibility, verdict enforcement, reply caps, room pause, cross-room isolation,
key revocation and the export on resolve.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenanceA Model Context Protocol (MCP) server for Discord-based AI agent-user communication. Enables AI agents to communicate with users through Discord instead of IDE chat interfaces.Last updated58MIT
- Alicense-qualityAmaintenanceProduction-ready MCP server providing RAG, hierarchical memory, and 8+ tools for AI agents via the Model Context Protocol.Last updated41Apache 2.0
- Alicense-qualityDmaintenanceMCP server for AI agents to conduct multi-LLM roundtable discussions, returning structured common, divergent, and unique perspectives.Last updatedMIT
Related MCP Connectors
Official remote MCP server for Archivist AI TTRPG campaign memory: characters, sessions, and more.
MCP server for generating rough-draft project plans from natural-language prompts.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/Fulviuus/rivendell'
If you have feedback or need assistance with the MCP directory API, please join our Discord server