Contributing
Last updated: April 2026
Thanks for your interest in contributing! This guide covers development setup, the Nx workflow, code style, and PR guidelines.
Prerequisites
- Node.js ≥ 22 — download
- npm (comes with Node)
- Git
Getting Started
- Fork the repo on GitHub
- Clone your fork:
Terminal window git clone https://github.com/<your-username>/rivetOS.gitcd rivetOS - Install dependencies:
Terminal window npm install - Verify everything works:
Terminal window npm run ci # runs lint + build + test across all 22 packages - Create a branch from
main:Terminal window git checkout -b feat/my-feature
Working with Nx
RivetOS uses Nx to orchestrate the monorepo. Nx understands the dependency graph between the 22 packages and provides caching, parallel execution, and affected-only runs.
Everyday commands
# ── Full pipeline (what CI runs) ─────────────────────────npm run ci # lint + build + test all packages
# ── Individual targets across all packages ───────────────npm run lint # ESLint all packagesnpm run build # TypeScript compile all packagesnpm test # Vitest all packagesnpm run typecheck # tsc --noEmit all packages
# ── Single package ───────────────────────────────────────npx nx run core:test # Test @rivetos/core onlynpx nx run channel-telegram:lint # Lint the Telegram channel pluginnpx nx run provider-anthropic:build # Build the Anthropic providernpx nx run tool-shell:test # Test the shell tool plugin
# ── Only what you changed ────────────────────────────────npx nx affected -t test # Test packages affected by your changesnpx nx affected -t lint build test # Full pipeline, affected only
# ── @rivetos/nx generators ───────────────────────────────npx nx g @rivetos/nx:plugin # Scaffold a new channel/provider/tool pluginnpx nx g @rivetos/nx:pr # Interactive PR wizard with quality gatesPackage names for nx run
Core packages use their directory name. Plugins use the directory name without the category prefix:
| Package | Nx project name | nx run example |
|---|---|---|
packages/types | types | npx nx run types:build |
packages/core | core | npx nx run core:test |
packages/boot | boot | npx nx run boot:build |
packages/cli | cli | npx nx run cli:build |
plugins/channels/telegram | channel-telegram | npx nx run channel-telegram:lint |
plugins/channels/discord | channel-discord | npx nx run channel-discord:test |
plugins/providers/anthropic | provider-anthropic | npx nx run provider-anthropic:build |
plugins/providers/google | provider-google | npx nx run provider-google:lint |
plugins/providers/xai | provider-xai | npx nx run provider-xai:test |
plugins/providers/ollama | provider-ollama | npx nx run provider-ollama:build |
plugins/providers/openai-compat | provider-openai-compat | npx nx run provider-openai-compat:lint |
plugins/memory/postgres | memory-postgres | npx nx run memory-postgres:build |
plugins/tools/shell | tool-shell | npx nx run tool-shell:test |
plugins/tools/file | tool-file | npx nx run tool-file:test |
plugins/tools/search | tool-search | npx nx run tool-search:lint |
plugins/tools/web-search | tool-web-search | npx nx run tool-web-search:build |
plugins/tools/interaction | tool-interaction | npx nx run tool-interaction:test |
plugins/tools/mcp-client | tool-mcp-client | npx nx run tool-mcp-client:build |
plugins/tools/coding-pipeline | tool-coding-pipeline | npx nx run tool-coding-pipeline:lint |
packages/nx-plugin | @rivetos/nx | npx nx run @rivetos/nx:test |
Tip: Run
npx nx show projectsto list all project names, ornpx nx show project <name>to see available targets for a specific project.
Exploring the dependency graph
npx nx graph # Opens an interactive graph in your browserThis shows how packages depend on each other. Useful for understanding what a change in @rivetos/types will affect downstream.
Caching
Nx caches lint, build, test, and typecheck results based on file inputs. If you run the same target twice without changing files, the second run replays from cache instantly.
npx nx reset # Clear the local cache (if builds seem stale)CI also persists .nx/cache between runs via GitHub Actions cache, so only changed packages rebuild on PRs.
Targets reference
These are defined in nx.json and available on every package:
| Target | Command | Depends on | Cached |
|---|---|---|---|
lint | eslint src/ | — | ✅ |
build | tsc -p tsconfig.json | ^build (deps build first) | ✅ |
test | vitest run | — | ✅ |
typecheck | tsc --noEmit -p tsconfig.json | ^typecheck | ✅ |
The ^ prefix means “run this target on dependencies first.” So nx run boot:build will first build types and core (its dependencies), then build boot.
Conventional Commits
All commits must follow Conventional Commits:
| Prefix | Use for |
|---|---|
feat: | New features |
fix: | Bug fixes |
docs: | Documentation changes |
test: | Adding or updating tests |
chore: | Tooling, CI, dependency updates |
refactor: | Code changes that don’t fix bugs or add features |
Scope is optional but encouraged for plugin work:
feat(channel-telegram): add inline keyboard supportfix(provider-anthropic): handle 529 overloaded responsesdocs: update Nx commands in CONTRIBUTING.mdtest(core): add agent loop abort testschore: update Nx to 22.7refactor(core): extract TurnHandler from runtimeCode Style
- TypeScript — strict mode, no
anyunless unavoidable - ESLint — flat config in
eslint.config.mjs, shared across all packages - No default exports — use named exports everywhere
- Interfaces over types for plugin contracts (defined in
@rivetos/types) - No barrel re-exports in plugins — keep dependency graphs clean
Run the linter before committing:
npm run lint # All packagesnpx nx run core:lint # Just corenpx nx affected -t lint # Only changed packagesArchitecture Rules
RivetOS follows a strict layered architecture:
Types → Domain → Runtime → BootThe most important rule: Plugins depend on @rivetos/types only. Never on @rivetos/core, never on other plugins, never on boot.
packages/types— Interfaces and contracts. Zero dependencies.packages/core/src/domain— Pure domain logic. Depends on types only.packages/core/src/runtime/— Application layer. Thin compositor with focused modules:runtime.ts— registration, routing, lifecycleturn-handler.ts— single message turn processingmedia.ts— attachment resolution and multimodal contentstreaming.ts— stream events → channel deliverysessions.ts— session lifecycle and historycommands.ts— slash command processing
packages/boot/— Composition root. The only layer that knows concrete plugin types. Uses registrars to wire everything.packages/cli/— Command-line interface. Imports from@rivetos/boot.plugins/*— Implement interfaces from@rivetos/types. No cross-plugin imports.
Platform-specific concerns stay in plugins. Message splitting, typing indicators, API format differences — these belong in the channel or provider plugin, not in the runtime.
Adding a New Plugin
The recommended way to add a plugin is with the @rivetos/nx generator:
npx nx g @rivetos/nx:pluginThis will interactively prompt for plugin type, name, and description, then scaffold the entire package with:
package.json(scoped@rivetos/<type>-<name>, depends on@rivetos/types)tsconfig.json(extends roottsconfig.base.json)src/index.ts(interface stub implementingChannel,Provider, orTool)src/index.test.ts(skeleton test file)eslint.config.mjs(inherits shared config)
You can also pass options directly:
npx nx g @rivetos/nx:plugin --type=channel --name=slack --description="Slack workspace integration"npx nx g @rivetos/nx:plugin --type=provider --name=mistral --description="Mistral AI models"npx nx g @rivetos/nx:plugin --type=tool --name=database --description="SQL query tool"After scaffolding:
- Implement the interface in
src/index.ts— the stub has TODO comments for each method. - Register the plugin in
packages/boot/via a registrar. - Write tests — the skeleton test file is ready to fill in.
- Verify:
Terminal window npx nx run <project-name>:lintnpx nx run <project-name>:buildnpx nx run <project-name>:testnpx nx graph # confirm it appears with correct dependencies
Manual setup (without generator)
- Create the package directory:
Terminal window mkdir -p plugins/<category>/<name> - Add a
package.jsonwith the@rivetos/scope, depending on@rivetos/typesonly:{"name": "@rivetos/<category>-<name>","version": "0.0.5","private": true,"scripts": {"lint": "eslint src/","build": "tsc -p tsconfig.json","test": "vitest run"},"dependencies": {"@rivetos/types": "workspace:*"}} - Add a
tsconfig.jsonextending the root:{"extends": "../../../tsconfig.base.json","compilerOptions": {"outDir": "dist","rootDir": "src"},"include": ["src"]} - Implement the relevant interface from
@rivetos/types(e.g.,Channel,Provider,Tool). - Register the plugin in
packages/boot/via a registrar. - Verify:
Terminal window npx nx run <project-name>:lintnpx nx run <project-name>:buildnpx nx run <project-name>:testnpx nx graph # confirm it appears with correct dependencies
Testing
Tests use Vitest and run via Nx. Each package has its own test files (*.test.ts or *.spec.ts) colocated with source.
# All testsnpm test
# Single packagenpx nx run core:test
# Watch mode (for development)npx nx run core:test --watch
# Only affected packagesnpx nx affected -t test
# With coveragenpx nx run core:test -- --coverageCreating a Pull Request
The recommended way to create a PR is with the @rivetos/nx PR generator:
npx nx g @rivetos/nx:prThis will:
- Ask for change type (feat/fix/refactor/chore/docs/plugin/test/perf)
- Create a branch with conventional naming (e.g.,
feat/add-slack-channel) - Detect affected packages from your changes
- Run
nx affected -t lint build testas a quality gate (must pass) - Generate a PR description with affected packages, summary, and checklist
- Create the PR via
gh pr createwith appropriate labels
You can also pass options directly:
npx nx g @rivetos/nx:pr --type=feat --description="Add Slack channel" --issue=34npx nx g @rivetos/nx:pr --dryRun # preview without creating anythingManual PR checklist
If you’re not using the PR generator, verify before submitting:
- Branch is based on
main - Commit messages follow conventional commits
-
npm run cipasses (lint + build + test) - No new lint warnings (
npm run lintis clean) - New features include tests
- No credentials, API keys, or secrets in code
- Plugin depends on
@rivetos/typesonly (if adding/modifying a plugin) - Documentation updated if applicable
CI runs nx affected -t lint build test on every PR. If your changes break any package’s lint, build, or tests, the PR will be blocked.
Plugin Discovery
RivetOS uses convention-based plugin discovery. Every plugin declares itself in package.json:
{ "name": "@rivetos/provider-mistral", "rivetos": { "type": "provider", "name": "mistral" }}Boot scans plugins/*/package.json for the rivetos field. Config determines which plugins load. You don’t need to edit any registrar files — just add the manifest and reference the plugin in config.
For user plugins outside the monorepo, add their directory to config:
runtime: plugin_dirs: - /path/to/my/pluginsWorking with Containers
RivetOS ships as container images built from source.
Build containers locally
npx rivetos build# ornpx nx build agent-containernpx nx build datahub-containerRun the stack
docker compose up -d # Single agentdocker compose --profile multi up -d # Multi-agentData persistence
Containers are stateless. All data lives on volumes:
./workspace/— agent files (bind mount)./config.yaml— configuration (bind mount)./.env— secrets (bind mount)rivetos-pgdata— PostgreSQL (named volume)rivetos-shared— shared storage (named volume)
See containers/DATA-PERSISTENCE.md for details.
Writing Skills
Skills are markdown documents, not code. Anyone can contribute skills.
# Scaffold a new skillnpx rivetos skill init my-skill --description="Does something useful"
# Validate itnpx rivetos skill validate my-skillSee docs/SKILLS.md for the full guide.
Reporting Issues
Use the bug report or feature request templates.
License
By contributing, you agree that your contributions will be licensed under the Apache License 2.0.