Skip to content

Architecture

  1. Domain-Driven Design — Core domain is pure business logic. No framework dependencies, no I/O, no platform specifics. Plugins adapt the outside world to the domain.
  2. Clean Architecture — Dependencies point inward. Core knows nothing about Telegram, Discord, PostgreSQL, or Anthropic. Plugins know about core, never the reverse.
  3. Stability over features — LTS releases. A working version stays working.
  4. Own every line — Apache 2.0 licensed. No CLA, no dual-licensing. Fork-friendly. Patent grant included.
  5. Boring technology — TypeScript, Node.js, Nx. No experiments in the foundation.
  6. Example-driven extensibility — Core plugins are the reference implementation. Adding a new channel, provider, or tool should be obvious from reading an existing one.
  7. Container-first deployment — The container IS the product. Security via isolation, not sandboxing.
  8. Source-based updates — Pull source → rebuild from source tree (plugins included) → restart. Forks and custom plugins are first-class citizens.
┌────────────────────────────────────────────────────────┐
│ Plugins (Adapters) │
│ │
│ Channels Providers Memory Tools │
│ Telegram Anthropic Postgres Shell │
│ Discord Google File I/O │
│ Agent xAI Search (glob/grep) │
│ Voice Ollama Web Search/Fetch │
│ vllm Interaction │
│ llama-server MCP Client │
│ │
│ All plugins implement core interfaces. │
│ All plugins are replaceable. │
│ None are imported by core. │
├────────────────────────────────────────────────────────┤
│ Application Layer │
│ │
│ Boot — composition root, reads YAML config, │
│ invokes manifest.register(ctx) per │
│ discovered plugin │
│ Runtime — thin compositor, registration, │
│ routing, lifecycle │
│ TurnHandler — single message turn processing │
│ CLI — rivetos start/stop/status/doctor/ │
│ init/config/mesh/keys/memory/db/ │
│ plugin/skill/service/logs/etc. │
│ │
│ This layer composes domain + plugins. │
│ It is the only layer that knows concrete types. │
├────────────────────────────────────────────────────────┤
│ Domain Layer │
│ │
│ Agent Loop — message → LLM → tools → response │
│ Router — inbound message → agent → provider │
│ Workspace — load/inject workspace files │
│ Queue — message ordering, command intercept │
│ Hooks — composable pipeline (before/after) │
│ Delegation — intra-instance agent-to-agent │
│ Mesh Deleg. — cross-instance delegation via HTTP │
│ Subagent — child session management │
│ Skills — skill discovery and matching │
│ Heartbeat — periodic scheduling │
│ Safety — shell danger, workspace fence, audit │
│ Circuit Break— provider failure tracking, open/close │
│ Reconnect — exponential backoff for channels │
│ Auto-Actions — post-tool automation (format, lint) │
│ Sessions — session lifecycle and history │
│ Mesh — multi-agent mesh registry + discovery │
│ │
│ Pure logic. No I/O. Depends only on interfaces │
│ defined in @rivetos/types. │
├────────────────────────────────────────────────────────┤
│ Types Layer │
│ │
│ Provider, Channel, Tool, Memory, Workspace │
│ Message, ToolCall, InboundMessage, OutboundMessage │
│ AgentConfig, RuntimeConfig, StreamEvent, HookConfig │
│ DeploymentConfig, MeshNode, MeshRegistry │
│ RivetError, ChannelError, MemoryError, ToolError │
│ SubagentSession, Skill, SkillManager │
│ │
│ Interfaces + error classes. Zero dependencies. │
│ Leaf package. Every other package depends on this. │
│ Nothing else. │
└────────────────────────────────────────────────────────┘

Dependency Rule: Every arrow points inward. Plugins depend on types. Domain depends on types. Application depends on domain + types. Nothing depends on plugins.

Agent — a named identity with a provider and workspace config
Turn — one user message → one assistant response (may include N tool calls)
Session — a sequence of turns for a given user on a given channel
Workspace — a directory of markdown files that define agent personality and context
Core files: CORE.md, USER.md, WORKSPACE.md, MEMORY.md
Extended: + CAPABILITIES.md
Transcript — the permanent, append-only record of all messages across all agents
Mesh — a fleet of RivetOS instances that can discover and delegate to each other
Message — { role, content, toolCalls?, toolCallId? }
ToolCall — { id, name, arguments }
ContentPart — TextPart | ImagePart (multimodal support)
InboundMessage — platform-normalized incoming message
OutboundMessage — platform-normalized outgoing message
EditResult — { primary, overflow[] } for multi-message edits
AgentLoop — owns a single turn's execution. Created per turn, not shared.
Holds: abort controller, steer queue, iteration count.
Pure: takes interfaces, returns result. No I/O of its own.
Router — owns agent→provider mapping. Stateless lookup.
Workspace — owns file loading and system prompt construction.
Cacheable. Invalidated on file change or explicit clear.
FileMeshRegistry — owns mesh node registration, heartbeat, pruning.
File-based (mesh.json), syncs across peers.
┌──────────────────────────────────────────────────────────────┐
│ RivetOS Runtime │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────────────────────┐ │
│ │ Channels │───>│ Router │───>│ Turn Handler │ │
│ │ (plugin) │ │ (domain) │ │ (application) │ │
│ │ │ │ │ │ │ │
│ │ Telegram │ │ message │ │ hooks → media → loop │ │
│ │ Discord │ │ → agent │ │ → stream → respond │ │
│ │ Agent │ │ → prov │ │ → memory append │ │
│ │ Voice │ │ │ │ │ │
│ └──────────┘ └──────────┘ └───────────┬────────────┘ │
│ ▲ │ │
│ │ ┌───────────────────────┘ │
│ │ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ Response │<───│Workspace │ │ Memory │ │
│ │ sent to │ │ (domain) │ │ (plugin) │ │
│ │ channel │ │ │ │ │ │
│ │ │ │ CORE.md │ │ append transcript │ │
│ │ │ │ USER.md │ │ search context │ │
│ │ │ │ MEMORY.md│ │ hybrid FTS+vector │ │
│ └──────────┘ └──────────┘ └──────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Boot Layer │ │
│ │ │ │
│ │ Config loader → Registrars → Lifecycle │ │
│ │ │ │
│ │ registrars/plugins.ts — manifest-driven: every │ │
│ │ discovered provider, │ │
│ │ channel, tool, memory, │ │
│ │ and transport plugin │ │
│ │ registrars/hooks.ts — wire safety/auto-actions │ │
│ │ registrars/agents.ts — delegation/subagent/skills │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Observability Layer │ │
│ │ │ │
│ │ Metrics — turns/min, latency, tokens, errors │ │
│ │ Health — GET /health endpoint, full runtime status │ │
│ │ Logger — structured JSON or pretty-print modes │ │
│ │ Audit — append-only log with rotation/retention │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Mesh Layer │ │
│ │ │ │
│ │ Registry — mesh.json, heartbeat, pruning │ │
│ │ Discovery — seed nodes or mDNS │ │
│ │ Delegation — route to remote agents via HTTP │ │
│ │ Agent Chan — /api/mesh/* endpoints for peer comms │ │
│ └───────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
1. Channel (plugin) receives inbound message
2. Runtime (application) passes to Router (domain)
3. Router determines: which agent? which provider?
4. Turn Handler creates a turn:
a. Execute turn:before hooks
b. Build system prompt from Workspace (domain)
— Loads CORE.md, USER.md, WORKSPACE.md, MEMORY.md
— Optionally CAPABILITIES.md for extended context
c. Resolve attachments via Media module
d. Create AgentLoop (domain) for this turn:
i. Send messages + tools to Provider (plugin) via chatStream()
ii. Stream text chunks to channel as they arrive
iii. If tool calls → execute via Tool (plugin) → append results → go to (i)
iv. Check AbortSignal between iterations (/stop)
v. Check steer queue between iterations (/steer)
vi. Max iteration limit prevents runaway (safety cap)
e. Execute turn:after hooks
5. Turn Handler sends response back via Channel (plugin)
6. Turn Handler appends user message + response to Memory (plugin)

Notice: the domain layer (steps 3, 4d) never touches I/O. It works with interfaces. The application layer (Turn Handler) is the compositor that wires real plugins to domain logic.

rivetOS/
README.md ← project overview
CHANGELOG.md ← release history
CONTRIBUTING.md ← contributor guide
.env.example ← secret template
docs/
ARCHITECTURE.md ← this file (design-oriented, for contributors)
CODEBASE-REFERENCE.md ← engineering reference (file-by-file)
MEMORY-DESIGN.md ← memory system design
infra/ ← Container Dockerfiles + Compose + provisioning
containers/
rivetos/ ← Unified runtime image — built once, dispatched via `--role`
DATA-PERSISTENCE.md ← data persistence model documentation
docker/ ← Compose stacks
rivetos/ ← canonical stack (datahub + migrate + workers + agent)
mcp-stack/ ← standalone MCP server stack
scripts/ ← provision-ct.sh, setup-mesh-hosts.sh, …
templates/ ← workspace + config skeletons used by `init`
.github/
workflows/
pipeline.yml ← single orchestrator: secrets-scan → ci → (publish-npm, containers) → notify-ops
packages/
types/ ← interfaces only, zero deps
src/
index.ts ← all exports
message.ts ← Message, ToolCall, ContentPart, TextPart, ImagePart
provider.ts ← Provider, LLMResponse, LLMChunk, ChatOptions, ProviderError
channel.ts ← Channel, InboundMessage, OutboundMessage, EditResult
tool.ts ← Tool, ToolDefinition, ToolContext, ToolResult
plugin.ts ← Plugin, PluginConfig
memory.ts ← Memory, MemoryEntry, MemorySearchResult
workspace.ts ← Workspace, WorkspaceFile
config.ts ← AgentConfig, RuntimeConfig, HookConfig
deployment.ts ← DeploymentConfig, DockerConfig, ProxmoxConfig, KubernetesConfig
events.ts ← StreamEvent, SessionState, DelegationRequest, TokenUsage
hooks.ts ← HookEventName, HookContext variants, HookPipeline
errors.ts ← RivetError, ChannelError, MemoryError, ConfigError, ToolError, etc.
mesh.ts ← MeshNode, MeshRegistry, MeshConfig, MeshDelegationRoute
skill.ts ← Skill, SkillManager
subagent.ts ← SubagentSession, SubagentSpawnRequest, SubagentManager
utils.ts ← splitMessage, getTextContent, hasImages, etc.
core/ ← domain + application layer
src/
domain/
loop.ts ← AgentLoop — the turn executor
router.ts ← Router — message → agent → provider
workspace.ts ← WorkspaceLoader — file loading + prompt building
queue.ts ← MessageQueue — ordering, dedup, command intercept
hooks.ts ← HookPipelineImpl — composable async pipeline
delegation.ts ← DelegationEngine — intra-instance delegation
mesh-delegation.ts ← MeshDelegationEngine — cross-instance via HTTP
mesh.ts ← FileMeshRegistry — file-based mesh registry
subagent.ts ← SubagentManagerImpl — child sessions
skills/ ← Skill system (7 files)
index.ts ← barrel exports
manager.ts ← SkillManagerImpl — discovery, matching, embedding
list-tool.ts ← skill_list tool
manage-tool.ts ← skill_manage tool
manage-actions.ts ← create/edit/patch/delete/retire actions
manage-helpers.ts ← changelog, dedup, validation helpers
frontmatter.ts ← YAML frontmatter parsing
security.ts ← skill content validation
heartbeat.ts ← HeartbeatRunner — periodic scheduling
safety-hooks.ts ← Shell danger, workspace fence, audit, custom rules
circuit-breaker.ts ← Provider failure tracking, open/half-open/closed
reconnect.ts ← ReconnectionManager — exponential backoff
auto-actions.ts ← Post-tool automation (format, lint, test, git check)
session-hooks.ts ← Session lifecycle hooks (summary, auto-commit)
constants.ts ← SILENT_RESPONSES and shared constants
runtime/
runtime.ts ← Runtime — thin compositor, registration, lifecycle
turn-handler.ts ← TurnHandler — single turn processing
media.ts ← Attachment resolution, download, multimodal
streaming.ts ← StreamManager — stream events → channel
sessions.ts ← SessionManager — session lifecycle and history
commands.ts ← CommandHandler — slash commands (/stop, /steer, /new)
health.ts ← HealthServer — GET /health endpoint
metrics.ts ← Runtime metrics collector
index.ts ← Module barrel exports
security/
secrets.ts ← redactSecrets, ensureEnvPermissions, 1Password resolution
audit-rotation.ts ← Audit log rotation, compression, retention
logger.ts ← Structured logger (JSON + pretty modes)
runtime.ts ← Backward-compat re-export → runtime/runtime.ts
index.ts ← Package exports (everything above)
boot/ ← composition root
src/
index.ts ← boot() — load config, call registrars, start
config.ts ← Config loader (YAML → typed config, env var resolution)
discovery.ts ← Walks plugin dirs, reads `package.json#rivetos`
lifecycle.ts ← PID file, signals, shutdown
validate.ts ← validateConfig() entry point
validate/
sections.ts ← Per-section validators
cross-refs.ts ← Cross-reference validation (agent→provider, bindings)
deployment.ts ← Deployment config validation
types.ts ← Validation result types
registrars/
plugins.ts ← Generic manifest-driven loader (providers,
← channels, tools, memory, transports)
hooks.ts ← Wire safety, auto-actions, sessions, learning
agents.ts ← Delegation, subagent, skills registration
cli/ ← command-line interface
src/
index.ts ← Entry point, command router
commands/
init.ts ← Legacy init (directory + default config)
init/ ← Interactive wizard (6 phases)
index.ts, wizard.ts, types.ts, detect.ts,
deployment.ts, agents.ts, channels.ts,
review.ts, generate.ts
start.ts, stop.ts, status.ts, doctor.ts, test.ts
update.ts ← rivetos update (source-based container rebuild)
build.ts ← rivetos build (container image build)
config.ts ← rivetos config show/validate/edit/path
agent.ts ← rivetos agent add/remove/list
logs.ts ← rivetos logs (tail agent output)
mesh.ts ← rivetos mesh list/ping/status/join
keys.ts ← rivetos keys rotate/list (mesh SSH keys)
memory.ts ← rivetos memory backfill-tool-synth/queue-status
db.ts ← rivetos db (low-level pg helpers)
model.ts ← rivetos model (switch models)
provider.ts ← rivetos <provider> status/setup/...
plugins.ts ← rivetos plugins (list/info)
plugin-init.ts ← rivetos plugin init (wraps Nx generator)
service.ts ← rivetos service init/start/stop/status/logs (systemd)
skills.ts ← rivetos skills list
skill-init.ts ← rivetos skill init (scaffolds SKILL.md)
skill-validate.ts ← rivetos skill validate
version.ts
nx-plugin/ ← custom Nx generators/executors (if needed)
plugins/
channels/
telegram/ ← grammY, Telegram Bot API
src/
index.ts ← TelegramChannel implementation
format.ts ← Markdown → Telegram HTML formatting
discord/ ← discord.js v14
src/
index.ts ← DiscordChannel implementation
agent/ ← HTTP agent-to-agent channel + mesh endpoints
src/
index.ts ← AgentChannel with /api/mesh/* routes
voice-discord/ ← xAI Realtime API + Discord voice
src/
index.ts ← entry point
plugin.ts ← VoicePlugin orchestrator
voice-session.ts ← per-channel voice session management
xai-client.ts ← xAI Realtime WebSocket client
audio-player.ts ← Opus audio encoding/decoding
transcript.ts ← Speech-to-text transcript handling
providers/
anthropic/ ← Claude (Messages API, adaptive thinking, prompt caching)
google/ ← Gemini (Generative Language API, thought signatures)
xai/ ← Grok (live search, conversation caching)
ollama/ ← Ollama (native API)
vllm/ ← vLLM server (full vLLM surface: sampling extensions, mm/chat_template kwargs, video, reasoning_content)
llama-server/ ← llama.cpp llama-server (lean: top_k/min_p + extra_body)
claude-cli/ ← Drives `claude` CLI via stream-json; embedded MCP bridge
memory/
postgres/ ← Full transcript + hybrid search + summary DAG
schema/ ← Co-located SQL DDL + migrations (PR-G)
workers/ ← Event-driven embedding + compaction workers
embedding/ ← Postgres LISTEN → embedding model (GPU)
compaction/ ← Postgres LISTEN → summarization model (CPU)
src/
adapter.ts ← PostgresMemory — implements Memory interface
search.ts ← Hybrid FTS + vector search with scoring
scoring.ts ← Search result relevance scoring
expand.ts ← Summary expansion (drill into source messages)
compactor/ ← Summary DAG (v5 pipeline)
compactor.ts ← CompactionEngine — hierarchical summarization
types.ts ← v5 prompts (leaf/branch/root), types, formatters
tool-synth.ts ← synthesizeToolCallContent — natural-language tool-call rows
tools/ ← Memory-as-tools
search-tool.ts ← memory_search tool
browse-tool.ts ← memory_browse tool
stats-tool.ts ← memory_stats tool
helpers.ts ← shared tool helpers
index.ts ← barrel exports + manifest
tools/
shell/ ← Shell command execution with safety
file/ ← file_read, file_write, file_edit (3 tool files)
search/ ← search_glob, search_grep (2 tool files)
web-search/ ← internet_search, web_fetch
interaction/ ← ask_user, todo (2 tool files)
mcp-client/ ← MCP server connection + tool discovery
transports/
mcp-server/ ← @rivetos/mcp-server — exposes RivetOS tools
← (memory_*, web_*, skill_*, runtime) over MCP
← StreamableHTTP. Has its own `rivetos-mcp-server` bin.
skills/ ← Optional per-instance skills

Every plugin directory includes a README.md that serves as documentation AND a guide for writing your own. The reference plugins ARE the documentation.

interface Provider {
id: string;
name: string;
chatStream(messages: Message[], options?: ChatOptions): AsyncIterable<LLMChunk>;
chat?(messages: Message[], options?: ChatOptions): Promise<LLMResponse>;
isAvailable(): Promise<boolean>;
getModel(): string;
setModel(model: string): void;
}

The primary method is chatStream() — streaming is the default path. chat() is optional for providers that support a non-streaming mode. getModel()/setModel() allow runtime model switching.

Reference implementation: plugins/providers/anthropic/

interface Channel {
id: string;
platform: string;
start(): Promise<void>;
stop(): Promise<void>;
send(message: OutboundMessage): Promise<string | null>;
edit?(channelId: string, messageId: string, text: string, overflowIds?: string[]): Promise<EditResult | null>;
react?(messageId: string, emoji: string, channelId: string): Promise<void>;
startTyping?(channelId: string): void;
stopTyping?(channelId: string): void;
onMessage(handler: MessageHandler): void;
onCommand(handler: CommandHandler): void;
}

Key details:

  • send() takes a full OutboundMessage object (not separate text/channelId params)
  • edit() supports overflow — when edited text exceeds platform limits, the channel handles splitting internally. Returns EditResult with primary + overflow message IDs.
  • Message splitting, typing indicators, and platform limits are the channel’s responsibility
  • No maxMessageLength on the interface — channels handle this internally

Reference implementation: plugins/channels/telegram/

interface Tool extends ToolDefinition {
execute(
args: Record<string, unknown>,
signal?: AbortSignal,
context?: ToolContext,
): Promise<ToolResult>;
}

Tools extend ToolDefinition (name, description, parameters JSON schema). The signal parameter enables abort propagation from /stop commands. context provides the tool with runtime info (workspace path, agent name, etc.).

Reference implementation: plugins/tools/shell/

Memory — persistent storage and retrieval

Section titled “Memory — persistent storage and retrieval”
interface Memory {
append(entry: MemoryEntry): Promise<string>;
search(query: string, options?: { agent?: string; limit?: number; scope?: 'messages' | 'summaries' | 'both' }): Promise<MemorySearchResult[]>;
getContextForTurn(query: string, agent: string, options?: { maxTokens?: number }): Promise<string>;
getSessionHistory(sessionId: string, options?: { limit?: number }): Promise<Message[]>;
saveSessionSettings?(sessionId: string, settings: Record<string, unknown>): Promise<void>;
loadSessionSettings?(sessionId: string): Promise<Record<string, unknown> | null>;
}

getContextForTurn() builds a context window from recent messages + relevant search results, within a token budget. getSessionHistory() restores conversation history from persistent storage on reconnect. Session settings persistence is optional.

Reference implementation: plugins/memory/postgres/

Transport — exposes RivetOS over an inbound protocol

Section titled “Transport — exposes RivetOS over an inbound protocol”

Transports have no core interface — each transport opens its own listening surface (HTTP, stdio, gRPC, …). The plugin’s manifest.register(ctx) is responsible for binding the surface and registering a shutdown via ctx.registerShutdown(). To enumerate the finalized tool set after every other plugin has registered, transports use ctx.onRegistrationComplete().

Reference implementation: plugins/transports/mcp-server/ (StreamableHTTP MCP server exposing memory_*, web_*, skill_* tools).

Every plugin’s index.ts exports a manifest: PluginManifest:

export const manifest: PluginManifest = {
type: 'provider', // 'provider' | 'channel' | 'tool' | 'memory' | 'transport'
name: 'anthropic', // must match `package.json#rivetos.name`
async register(ctx) {
// ctx.config — full validated runtime config (RivetConfig)
// ctx.pluginConfig — slice for this plugin (config.providers.<name>, etc.)
// ctx.env — process.env snapshot
// ctx.workspaceDir — resolved workspace path
// ctx.logger — scoped logger
//
// Register what you provide:
// ctx.registerProvider(p) | registerChannel(c) | registerTool(t) | registerMemory(m)
// ctx.registerHook(h) | ctx.registerShutdown(fn)
// ctx.lateBindTool(name) → closure that resolves the tool at execution time
// ctx.onRegistrationComplete(snapshot => ...) — fires once after all plugins register
},
}

Boot’s registrars/plugins.ts walks every discovered package.json#rivetos descriptor, dynamic-imports the package, validates that manifest.type/name matches the descriptor, and calls manifest.register(ctx). There are no per-kind registrars anymore — one manifest-driven loader handles every plugin type.

channels:
discord:
channel_bindings:
"channel_id_1": opus
"channel_id_2": grok

Message arrives in channel → agent determined by binding → provider determined by agent config.

Agent channel plugin (@rivetos/channel-agent) exposes an HTTP endpoint. Agents send messages to peers via the delegate_task or subagent_spawn tools. Incoming agent messages are processed through the full pipeline — memory, hooks, tools, everything.

MeshDelegationEngine extends delegation across instances. When a delegate_task targets an agent not available locally, the mesh registry is consulted to find the remote node hosting that agent. Delegation happens transparently via HTTP to the remote agent channel endpoint.

  • Each turn creates an AbortController
  • /stop calls abort() on it
  • AbortSignal passed to:
    • Provider chatStream() (cancels HTTP request via fetch signal)
    • Tool execute() (cancels shell commands, etc.)
    • Checked between tool iterations in AgentLoop
  • Response: immediate. No queue delays.
  • Pushes a message onto the AgentLoop’s steer queue
  • Agent sees it as a system message on the next tool iteration
  • Use case: “hey, also check X while you’re at it”
  • Aborts active turn (if any)
  • Clears in-memory conversation history
  • Transcript in postgres is unaffected (it’s permanent)

AbortController is synchronous signal propagation. When you say stop, the fetch call is cancelled mid-flight. No queue modes, no message coalescing, no waiting for the current turn to finish.

Composable async pipeline with priority ordering (0-99):

Lifecycle events: provider:before, provider:after, provider:error, tool:before, tool:after, turn:before, turn:after, turn:reflect, skill:before, skill:after, session:start, session:end, compact:before, compact:after, delegation:before, delegation:after

Built-in hooks (wired via boot registrars):

  • Safety hooks — Shell danger blocker (P10), workspace fence (P15), custom rules (P20), audit logger (P90)
  • Auto-actions — Post-tool format/lint/test/git-check (opt-in)
  • Session hooks — Daily context loading, session summaries, auto-commit, pre/post-compact

YAML with ${ENV_VAR} resolution. API keys always via environment variables.

runtime:
workspace: ~/.rivetos/workspace
default_agent: opus
max_tool_iterations: 75
agents:
opus:
provider: anthropic
default_thinking: medium
grok:
provider: xai
providers:
anthropic:
model: claude-sonnet-4-6
xai:
model: grok-4-1-fast-reasoning
channels:
telegram:
owner_id: "your-telegram-user-id"
discord:
channel_bindings:
"channel_id": opus
memory:
postgres:
connection_string: ${RIVETOS_PG_URL}
# Optional: containerized deployment
deployment:
target: docker # or proxmox, kubernetes, manual
datahub:
postgres: true
shared_storage: true
image:
build_from_source: true
docker:
network: rivetos

RivetOS ships as container images built from source. The container IS the security boundary — agents can only touch what’s inside their container.

Data persistence: Containers are stateless. All persistent data lives on the host via bind mounts and named volumes:

  • ./workspace/ → agent workspace files (CORE.md, memory/, skills/)
  • rivetos-pgdata → PostgreSQL data
  • rivetos-shared → shared storage (/rivet-shared/)
  • .env → API keys and secrets
  • rivet.config.yaml → deployment configuration

Update model: Pull source → rebuild containers from source tree → restart. Plugins live in the source tree and survive updates automatically.

TargetImplementationUse Case
DockerDocker Compose (infra/docker/)Desktop, single-server, getting started
ProxmoxLXC + infra/scripts/provision-ct.shHomelab, multi-node
Manualsystemd + npm installBare-metal, custom setups

Provisioning is intentionally script-and-Compose-driven — there is no single-command IaC orchestrator. The container Dockerfiles live under infra/containers/; the Compose files under infra/docker/; the shell scripts under infra/scripts/.

Multiple RivetOS instances can form a mesh for cross-instance collaboration:

  • Registry: File-based mesh.json with heartbeat and pruning
  • Discovery: Seed nodes or mDNS-based auto-discovery
  • Delegation: Transparent routing — delegate_task checks local agents first, then mesh peers
  • Join flow: rivetos init --join <host> discovers existing datahub and registers with the mesh
  • Fleet updates: rivetos update --mesh rolls updates across all mesh nodes with health checks
  • main branch: current development
  • lts/X.Y branches: frozen releases
    • Security patches and bug fixes only
    • No new features, no breaking changes
    • Maintained for 12 months minimum
  • Semantic versioning: MAJOR.MINOR.PATCH