Skip to content

Contributing

Last updated: April 2026

Thanks for your interest in contributing! This guide covers development setup, the Nx workflow, code style, and PR guidelines.

  • Node.js ≥ 22 (24 is used in CI and containers) — download
  • npm (comes with Node)
  • Git
  1. Fork the repo on GitHub
  2. Clone your fork:
    Terminal window
    git clone https://github.com/<your-username>/rivetOS.git
    cd rivetOS
  3. Install dependencies (automatically builds all packages):
    Terminal window
    npm install
  4. Verify everything works:
    Terminal window
    npm run ci # runs lint + build + test across every workspace project
  5. Create a branch from main:
    Terminal window
    git checkout -b feat/my-feature

RivetOS uses Nx to orchestrate the monorepo. Nx understands the dependency graph across the workspace (packages, plugins, services, and apps) and provides caching, parallel execution, and affected-only runs.

Terminal window
# ── Full local pipeline ──────────────────────────────────
npm run ci # lint + build + test all packages
# (CI also runs typecheck + boundary/secret scans)
# ── Individual targets across all packages ───────────────
npm run lint # ESLint all packages
npm run build # Rebuild all packages (also runs automatically on npm install)
npm test # Vitest all packages
npm run typecheck # tsc --noEmit all packages
# ── Single package ───────────────────────────────────────
npx nx run core:test # Test @rivetos/core only
npx nx run channel-agent:lint # Lint the agent mesh channel plugin
npx nx run provider-anthropic:build # Build the Anthropic provider
npx nx run tool-shell:test # Test the shell tool plugin
# ── Only what you changed ────────────────────────────────
npx nx affected -t test # Test packages affected by your changes
npx 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 plugin
npx nx g @rivetos/nx:pr # Interactive PR wizard with quality gates

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/agent channel-agent npx nx run channel-agent:lint
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/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
packages/nx-plugin @rivetos/nx npx nx run @rivetos/nx:test

Tip: Run npx nx show projects to list all project names, or npx nx show project <name> to see available targets for a specific project.

Terminal window
npx nx graph # Opens an interactive graph in your browser

This shows how packages depend on each other. Useful for understanding what a change in @rivetos/types will affect downstream.

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.

Terminal window
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.

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.

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-agent): improve mesh peer discovery
fix(provider-anthropic): handle 529 overloaded responses
docs: update Nx commands in CONTRIBUTING.md
test(core): add agent loop abort tests
chore: update Nx to 22.7
refactor(core): extract TurnHandler from runtime
  • TypeScript — strict mode, no any unless 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:

Terminal window
npm run lint # All packages
npx nx run core:lint # Just core
npx nx affected -t lint # Only changed packages

RivetOS follows a strict layered architecture:

Types → Domain → Runtime → Boot

The 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. Its only workspace dependency is @rivetos/den-protocol.
  • 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, lifecycle
    • turn-handler.ts — single message turn processing
    • media.ts — attachment resolution and multimodal content
    • streaming.ts — stream events → channel delivery
    • sessions.ts — session lifecycle and history
    • commands.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.

The recommended way to add a plugin is with the @rivetos/nx generator:

Terminal window
npx nx g @rivetos/nx:plugin

This 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 root tsconfig.base.json)
  • src/index.ts (interface stub implementing Channel, Provider, or Tool)
  • src/index.test.ts (skeleton test file)
  • eslint.config.mjs (inherits shared config)

You can also pass options directly:

Terminal window
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:

  1. Implement the interface in src/index.ts — the stub has TODO comments for each method.
  2. Register the plugin in packages/boot/ via a registrar.
  3. Write tests — the skeleton test file is ready to fill in.
  4. Verify:
    Terminal window
    npx nx run <project-name>:lint
    npx nx run <project-name>:build
    npx nx run <project-name>:test
    npx nx graph # confirm it appears with correct dependencies
Manual setup (without generator)
  1. Create the package directory:
    Terminal window
    mkdir -p plugins/<category>/<name>
  2. Add a package.json with the @rivetos/ scope, depending on @rivetos/types only:
    {
    "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": "0.4.0-beta.6"
    }
    }
  3. Add a tsconfig.json extending the root:
    {
    "extends": "../../../tsconfig.base.json",
    "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
    },
    "include": ["src"]
    }
  4. Implement the relevant interface from @rivetos/types (e.g., Channel, Provider, Tool).
  5. Register the plugin in packages/boot/ via a registrar.
  6. Verify:
    Terminal window
    npx nx run <project-name>:lint
    npx nx run <project-name>:build
    npx nx run <project-name>:test
    npx nx graph # confirm it appears with correct dependencies

Tests use Vitest and run via Nx. Each package has its own test files (*.test.ts or *.spec.ts) colocated with source.

Terminal window
# All tests
npm test
# Single package
npx nx run core:test
# Watch mode (for development)
npx nx run core:test --watch
# Only affected packages
npx nx affected -t test
# With coverage
npx nx run core:test -- --coverage

The recommended way to create a PR is with the @rivetos/nx PR generator:

Terminal window
npx nx g @rivetos/nx:pr

This will:

  1. Ask for change type (feat/fix/refactor/chore/docs/plugin/test/perf)
  2. Create a branch with conventional naming (e.g., feat/add-slack-channel)
  3. Detect affected packages from your changes
  4. Run nx affected -t lint build test as a quality gate (must pass)
  5. Generate a PR description with affected packages, summary, and checklist
  6. Create the PR via gh pr create with appropriate labels

You can also pass options directly:

Terminal window
npx nx g @rivetos/nx:pr --type=feat --description="Add Slack channel" --issue=34
npx nx g @rivetos/nx:pr --dryRun # preview without creating anything

If you’re not using the PR generator, verify before submitting:

  • Branch is based on main
  • Commit messages follow conventional commits
  • npm run ci passes (lint + build + test)
  • No new lint warnings (npm run lint is clean)
  • New features include tests
  • No credentials, API keys, or secrets in code
  • Plugin depends on @rivetos/types only (if adding/modifying a plugin)
  • Documentation updated if applicable

CI runs lint, typecheck, test, and build (via nx affected) on every PR, plus boundary probes and a secrets scan. If your changes break any of them, the PR will be blocked.

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/plugins

RivetOS ships as container images built from source.

Terminal window
npx rivetos build
# or
npx nx build container-rivetos

The Postgres datahub uses upstream pgvector/pgvector:pg16 directly — schema is applied by the migrate role at stack startup, so there is no custom datahub image to build.

Terminal window
# From the repo root:
docker compose -f infra/docker/rivetos/docker-compose.yml up -d

Multi-agent fleets are deployed as separate hosts/CTs joined into a mesh — see docs/mesh.md — rather than as N agent services in one Compose file.

Containers are stateless. All data lives on the host:

  • ~/.rivetos/config.yaml — configuration (bind mount, read-only)
  • ~/.rivetos/.env — secrets (bind mount, read-only)
  • rivetos-pgdata — PostgreSQL (named volume)

See infra/containers/DATA-PERSISTENCE.md for details.

Skills are markdown documents, not code. Anyone can contribute skills.

Terminal window
# Scaffold a new skill
npx rivetos skill init my-skill --description="Does something useful"
# Validate it
npx rivetos skill validate my-skill

See docs/SKILLS.md for the full guide.

Use the bug report or feature request templates.

By contributing, you agree that your contributions will be licensed under the Apache License 2.0.