Self-hosting
Run the Nairi agent daemon on your own infrastructure.
Self-hosting runs the agent daemon (nairid) on your hardware. Nairi's backend still orchestrates events (Slack mentions, Discord messages, API calls, scheduled jobs) and routes them to your machine over a WebSocket. The actual model calls, file edits, branch pushes, and PR creation all happen on your side.
When to self-host:
- Data residency / compliance. Source code, repo contents, and tool output never leave your infrastructure.
- Private network access. The agent needs to reach an internal database, VPN-only API, or any service that isn't on the public internet.
- Free, unlimited capacity. Self-hosted agents don't count against your plan's concurrency cap.
For the trade-offs against managed runtimes, see Where and how your agent runs.
Run nairid in an isolated environment (Docker, VM, dedicated user). When nairid runs with --claude-bypass-permissions, anyone with access to your Slack workspace or Discord server can execute arbitrary commands on the host with your user's privileges. See Permission modes below.
1. Install dependencies
Install nairid
Via Homebrew (recommended)
brew install presmihaylov/taps/nairidTo upgrade later:
brew upgrade presmihaylov/taps/nairidFrom source
Requires Go 1.24+.
git clone https://github.com/nairiai/nairid.git
cd nairid
make buildThe compiled binary will be at bin/nairid. Either run it directly or copy it onto your $PATH.
Install the rest
- Git.
- GitHub CLI (
gh) —nairidshells out toghto push branches and open PRs. - One AI coding CLI:
- Claude Code — recommended. The default agent.
- Codex — OpenAI's CLI.
- OpenCode — multi-provider option.
You also need a Nairi account. Sign up here if you don't have one.
nairid runs on macOS, Linux, and Windows, with native binaries for both Intel and ARM.
2. Authenticate the tools
Git identity
The agent commits under whatever identity you configure here. The commits will end up in PRs the agent opens, so pick something recognisable.
git config --global user.name "Nairi Agent"
git config --global user.email "agent@example.com"GitHub CLI
nairid uses gh for everything GitHub: push, fetch, create pull request. Authenticate with a fine-grained personal access token scoped to the repos the agent should touch (Contents, Pull Requests, and Workflows: read/write).
gh auth login
gh auth status # verifyPersonal account vs bot account
Two patterns work:
- Your personal GitHub account. PRs the agent opens are attributed to you. Simplest for solo use.
- A dedicated bot account. Cleaner for teams: every agent commit and PR is clearly machine-authored, and the bot's permissions can be scoped independently. Create the bot account, generate a fine-grained token there, and run
gh auth login --with-tokeninside thenairidhost as that account.
SSH keys (optional)
If your repos are cloned over SSH instead of HTTPS, make sure the agent's user has the right key loaded:
ssh-add ~/.ssh/id_rsanairid itself doesn't manage SSH keys — it inherits whatever git finds on the host.
Log into the AI coding CLI
Log into whichever CLI you'll use as the agent. nairid does not handle these credentials itself. It spawns the CLI as a subprocess and the CLI uses its own stored credentials.
claude # Claude Code
codex login # Codex
opencode auth # OpenCodeEach CLI persists credentials in its own location (~/.claude/.credentials.json for Claude Code, etc.).
3. Clone your repository
nairid works in a directory you control. If the agent should operate on a code repo, clone it:
git clone https://github.com/your-org/your-repo.git
cd your-repoUse a dedicated clone. nairid creates branches, makes commits, and opens PRs autonomously. Don't point it at the same working tree you develop in. Clone the repo into a separate directory (e.g. ~/nairid-workdirs/your-repo) so the agent's branches and worktrees don't collide with your own.
No-repo mode
Skip the clone if you want a chat-only agent that doesn't touch any code. Create an empty directory and cd into it. In this mode, nairid requires NAIRI_AGENT_ID to be set (see Start nairid below), since it has no repo URL to derive an instance ID from.
Multi-repository setup
The nairid process can only be bound to one repository at startup, but no-repo mode is a clean workaround if the agent should operate across several. Start nairid in no-repo mode from a working directory of your choice, then inside it create a repos/ folder and clone every repository you want the agent to work on:
mkdir -p ~/nairid-workdir/repos
cd ~/nairid-workdir/repos
git clone https://github.com/your-org/service-a.git
git clone https://github.com/your-org/service-b.git
git clone https://github.com/your-org/web.git
cd ~/nairid-workdir
NAIRI_API_KEY=… NAIRI_AGENT_ID=multi-repo-bot nairid --agent claude --claude-bypass-permissionsThe agent runs from ~/nairid-workdir and can cd into any of the cloned repos when a task requires it. You'll need to tell the agent which one to pick — usually with a rule that names each repo and what it's for, or by mentioning the repo name in the prompt.
nairid won't create branches or open PRs automatically in this mode (it doesn't know which repo is "the" repo). The agent handles git operations itself, the same way it would in any working directory it can shell into.
The Agent API key authenticates a nairid instance to the Nairi backend. It's a separate credential from the Public API key you'd use to call api.nairi.ai/api/public/v1 from your own code.
- In the dashboard, create the agent and toggle Self-hosted on.
- Open the Self-host page.
- Click Generate Agent API Key. The dialog shows the key once — copy it now, because you won't be able to see it again.
Rotating the key
On the Self-host page, click Regenerate. A warning dialog tells you exactly what will happen:
This will invalidate the old key and any running
nairidinstances using the old key will stop working until you update them with the new key.
If you have multiple self-hosted agents under the same org, all of them share this one key. Update every instance after a regeneration.
5. Start nairid
In the repo's directory (or your no-repo working directory):
export NAIRI_API_KEY="your-api-key-here"
# Optional but recommended.
# Required when running multiple nairid instances on the same machine
# under the same $HOME (e.g. several repos sharing one user account)
# so each instance gets its own worktrees, state, and logs.
# Required in no-repo mode.
# In a git repo, nairid derives a stable ID from the remote URL if unset.
export NAIRI_AGENT_ID="my-agent-id"
# Default: Claude Code, asks for approval on every file edit.
nairid
# Or pick a different agent / pass a model / bypass permissions.
nairid --agent claude --claude-bypass-permissionsThe agent's ID is shown on its detail page in the dashboard. Use the same value here.
Put $HOME on a persistent volume. nairid keeps its config directory at ~/.config/eksecd/ and its worktrees at ~/.eksec_worktrees/. Inside those it persists:
- the state file (
~/.config/eksecd/instances/<namespace>/state.json) — tracks in-flight jobs and seen messages so jobs survive a process restart - downloaded artifacts (rules, MCP configs, skills)
- logs under
~/.config/eksecd/instances/<namespace>/logs/ - git worktrees under
~/.eksec_worktrees/<namespace>/
If you're running nairid in a Docker container, a Kubernetes pod, or any environment with an ephemeral filesystem, mount a persistent volume at $HOME (or at the two paths above). Without that, every restart wipes the state file and nairid can't recover jobs that were in flight — they're abandoned silently, no retry, no notification.
All command-line options
nairid [OPTIONS]
--agent=[claude|codex|opencode] Which AI assistant to spawn (default: claude)
--claude-bypass-permissions Skip the per-tool approval prompt (sandbox only)
--model=MODEL Model override (agent-specific, see below)
-v, --version Print version and exit
-h, --help Print help and exitPer-agent commands
Claude Code (default)
# Default: acceptEdits mode (asks for approval on every file change)
nairid --agent claude
# Pick a model: sonnet / haiku / opus, or a full pinned version
nairid --agent claude --model sonnet
nairid --agent claude --model claude-sonnet-4-5-20250929
# Bypass permissions (sandbox only)
nairid --agent claude --claude-bypass-permissionsCodex
# Default: acceptEdits mode
nairid --agent codex
# Bypass approvals (sandbox only)
nairid --agent codex --claude-bypass-permissions
# Default model: gpt-5.5. Any model string accepted.
nairid --agent codex --model gpt-5.5OpenCode
OpenCode only supports bypass mode. The --claude-bypass-permissions flag is required.
# Default model: opencode/claude-sonnet-4-6
nairid --agent opencode --claude-bypass-permissions
# Custom provider/model
nairid --agent opencode --claude-bypass-permissions --model anthropic/claude-3-5-sonnetPermission modes
nairid has two modes that decide how the underlying CLI handles tool calls:
Secure mode (acceptEdits, default)
In an interactive terminal, acceptEdits is the mode where the CLI auto-approves file edits but prompts the user for shell commands and other non-edit tool calls. Under nairid, there is no interactive terminal — no human is sitting there to click "yes" on a prompt. If a tool call would prompt, the call effectively can't happen.
That means nairid itself doesn't enforce approvals. What actually controls which tools the agent can run is whatever permission config you've set up in the underlying CLI's own config files.
Claude Code
Permissions live in ~/.claude/settings.json (user-global) or .claude/settings.json checked into the repo (project-scoped). The permissions block has allow, deny, and defaultMode fields:
{
"permissions": {
"defaultMode": "acceptEdits",
"allow": [
"Bash(npm install)",
"Bash(npm test)",
"Bash(git status)",
"Bash(git diff:*)",
"Read(./src/**)",
"Edit(./src/**)"
],
"deny": [
"Bash(rm -rf *)",
"Read(./.env)",
"Read(./.env.*)"
]
}
}Tool patterns in allow are pre-approved; patterns in deny are blocked outright. Anything outside the allow list falls back to whatever defaultMode decides — which for a non-interactive daemon means it won't run.
See the Claude Code settings docs for the full list of supported tools and pattern syntax.
Codex
Codex has its own permission model configured under ~/.codex/config.toml. Refer to the Codex docs for the current format and supported tools.
OpenCode
Not supported in secure mode. OpenCode always runs in bypass mode (see below).
Bypass mode (bypassPermissions)
The agent runs every tool call without prompting. File edits, shell commands, network calls — all go through immediately.
When you'd want bypass mode:
- The host is sandboxed (Docker container, dedicated VM, throwaway user account).
- No human is online to respond to approval prompts (overnight runs, scheduled jobs).
- You want the agent to truly be autonomous.
With bypass mode on, anyone who can mention the agent in Slack or Discord can run arbitrary commands on the host as the user running nairid. Only enable bypass mode in an isolated environment (Docker, VM, dedicated user) where the host being compromised is acceptable.
Enable bypass with --claude-bypass-permissions. The flag is named after Claude's mode but applies to Codex too.
6. Configure agent behaviour (optional)
nairid inherits configuration from the CLI you ran it under. Anything Claude Code (or Codex / OpenCode) picks up from the filesystem applies to nairid too.
| Setting | Claude Code | OpenCode | Codex |
|---|---|---|---|
| Rules | individual .md files under ~/.claude/rules/ | concatenated into ~/.config/opencode/AGENTS.md | concatenated into ~/.codex/AGENTS.md |
| MCP servers | ~/.claude/mcp.json | ~/.config/opencode/mcp.json | ~/.codex/mcp.json |
| Skills | ~/.claude/skills/ | ~/.config/opencode/skills/ | ~/.codex/skills/ |
See the Claude Code settings docs or OpenCode config docs for the canonical reference.
Using dashboard-managed artifacts
Even on a self-hosted runtime, you can still attach rules, skills, and MCP configs from the Settings → Artifacts page. nairid fetches them from the backend at startup and writes them into the agent's config directory. Changes in the dashboard apply on the next restart.
What nairid does at startup
Useful context when something doesn't behave the way you expect.
- Acquires filesystem locks under
~/.eksec_worktrees/<namespace>/.lockso two instances can't trample each other's state. - Validates the git environment if you're in a repo. Runs
gh repo viewto confirmghis authenticated and the remote is reachable. Fails loudly if not. - Fetches artifacts from the Nairi backend (rules, MCP configs, skills) and writes them into the right places on disk.
- Loads persisted job state from
~/.config/eksecd/instances/<namespace>/state.json. Recovers in-flight jobs that survived a restart. - Pre-creates worktrees in a small pool so jobs can start fast without paying a
git worktree addcost on every message. - Connects to the backend over WebSocket. From here on,
nairidis listening for messages.
If the backend is unreachable, nairid retries with exponential backoff (2s up to 10s) indefinitely.
Logs
nairid writes rotating logs to both stdout and disk:
- Path:
~/.config/eksecd/instances/<namespace>/logs/ - Rotation: 10 MB per file.
- Retention: 7 days for session logs.
When running multiple instances on the same machine, each has its own log directory under its namespace.
Limitations vs managed runtimes
- Secret vaults aren't supported. There's no secret proxy on a self-hosted runtime, so the
CCASECRET_*injection mechanism doesn't apply. Use plain env vars,direnv, or your own secret manager for credentials. Be aware that anything in an env var is visible to the agent's process. - MCP credentials live on the agent's filesystem. On managed runtimes the mcp-proxy sidecar holds MCP configs and the agent never sees them. On self-hosted,
nairidwrites MCP configs from the dashboard straight to local files (~/.claude/mcp.jsonetc.) that the CLI reads. Any credentials inlined in those configs are readable by anyone (or anything) with shell access to the host. - MCP Marketplace (Composio) isn't supported. The OAuth-based marketplace requires the managed mcp-proxy. Custom MCP configs that you paste into the dashboard still work.
- No automatic updates. You're responsible for keeping
nairid, the CLI agent, and the OS toolchain current.brew upgrade presmihaylov/taps/nairidis the usual move. - GitHub token rotation is your problem. Use a long-lived fine-grained token, or wire up your own rotation (
nairidre-readsGH_TOKENevery minute, so updating it in-place works).
Running multiple instances on one machine
The default state directory layout assumes one nairid per user account. Two instances sharing $HOME without explicit IDs will fight over locks and worktrees and one of them will refuse to start.
Set NAIRI_AGENT_ID to a unique value per instance:
# Terminal 1
NAIRI_API_KEY=… NAIRI_AGENT_ID=prod-bot nairid --agent claude --claude-bypass-permissions
# Terminal 2
NAIRI_API_KEY=… NAIRI_AGENT_ID=staging-bot nairid --agent claude --claude-bypass-permissionsEach instance gets its own:
- Worktrees:
~/.eksec_worktrees/<NAIRI_AGENT_ID>/ - State:
~/.config/eksecd/instances/<NAIRI_AGENT_ID>/state.json - Logs:
~/.config/eksecd/instances/<NAIRI_AGENT_ID>/logs/ - Lock:
~/.eksec_worktrees/<NAIRI_AGENT_ID>/.lock
Troubleshooting
nairid exits immediately with "namespace already locked".
Another nairid is running under the same NAIRI_AGENT_ID in this $HOME. Either let it finish, or run the second instance with a different NAIRI_AGENT_ID.
nairid exits with "git remote unreachable" or "gh not authenticated".
Run gh auth status to verify auth. Run git ls-remote to verify the remote is reachable from the host. If using SSH, run ssh-add -l to verify a key is loaded.
Jobs from Slack / Discord aren't reaching the agent.
On the agent's detail page in the dashboard, check the last heartbeat timestamp. If it's stale, nairid isn't connected. Common causes:
NAIRI_API_KEYis wrong, or was rotated and the local copy is stale.- A firewall is blocking outbound WebSocket connections to
api.nairi.ai. - The agent in the dashboard is not marked self-hosted.
Claude Code asks for permission on every tool call.
You're in secure mode (the default). Pass --claude-bypass-permissions if you want it to run autonomously. Only do this in an isolated environment.
OpenCode fails with "permission mode required".
OpenCode only supports bypass mode. Add --claude-bypass-permissions to the command.
GitHub PRs fail to open.
Check gh auth status. The token needs Contents, Pull Requests, and Workflows at read/write on the target repo. Fine-grained tokens default to read-only.
The agent's branches keep colliding with my own.
You're pointing nairid at your dev clone. Stop. Clone the repo into a dedicated directory and point nairid there instead. See Step 3.
The host is filling up with worktree directories.
Worktrees live under ~/.eksec_worktrees/<namespace>/. Recent versions of nairid (v0.0.107+) periodically clean up orphans, but very old versions can leak. Upgrade with brew upgrade presmihaylov/taps/nairid or rebuild from source.
Related
- Where and how your agent runs
- How to deploy an agent
- Vaults — managed-only, but worth understanding the alternative
nairidrepositorynairidCHANGELOG
Can't find what you're looking for? Email support@nairi.ai.