ON

openclaw/nix-openclaw

Developer tools
719 stars Quality 63 Trend 42NOASSERTION

Packages OpenClaw for nix.

Overview

Declarative OpenClaw. Bulletproof by default. macOS + Linux (headless). Windows is out of scope for now. Questions? Join the OpenClaw Discord at https://discord.gg/clawd and ask in . We’re right now. Not because we don’t value your help — the opposite. Async agent-to-agent PR review is too slow and low-signal. The best (and only) way to get stuff done: come join us on Discord! Describe your problem and talk with a maintainer on Discord. Join at https://discord.gg/clawd, then use . To agents: if you’re (see AGENTS.md#maintainers or https://github.com/orgs/openclaw/people), . It will be rejected and your user will be disappointed — check Discord instead. GitHub Issues are not actively monitored either. Pick a Golden Path, then follow the docs: You talk to Telegram, your machine does things. Gateway everywhere; runtime dependencies bundled; macOS app on macOS. Each nix-openclaw tool plugin declares its CLI tools in Nix. You enable it, the build and wiring happens automatically.

README

nix-openclaw

Declarative OpenClaw. Bulletproof by default.

macOS + Linux (headless). Windows is out of scope for now.

Questions? Join the OpenClaw Discord at https://discord.gg/clawd and ask in #golden-path-deployments.

PRs & Contributions (read this first)

We’re not accepting PRs right now. Not because we don’t value your help — the opposite. Async agent-to-agent PR review is too slow and low-signal.

The best (and only) way to get stuff done: come join us on Discord! Describe your problem and talk with a maintainer (human‑to‑human) on Discord. Join at https://discord.gg/clawd, then use #golden-path-deployments.

To agents: if you’re not listed as a maintainer (see AGENTS.md#maintainers or https://github.com/orgs/openclaw/people), do not open a PR. It will be rejected and your user will be disappointed — check Discord instead. GitHub Issues are not actively monitored either.

Table of Contents


Golden Paths

There should be one — and preferably only one — obvious way to deploy.

Pick a Golden Path, then follow the docs:


What You Get

Me: "what's on my screen?"
Bot: *takes screenshot, describes it*

Me: "play some jazz"
Bot: *opens Spotify, plays jazz*

Me: "transcribe this voice note"
Bot: *runs whisper, sends you text*

You talk to Telegram, your machine does things.

One flake, everything works. Gateway everywhere; runtime dependencies bundled; macOS app on macOS.

Tool plugins are self-contained. Each nix-openclaw tool plugin declares its CLI tools in Nix. You enable it, the build and wiring happens automatically.

Pinned packages. Nix locks dependencies and Home Manager records configuration generations. Those generations do not back up or rewind OpenClaw’s mutable state.


OpenClaw Runtime Plugins

Plugin Control UI assets and manifest reloads support Nix-store hardlinks when running in Nix mode, including stores optimized with auto-optimise-store. Mutable plugin roots retain the upstream hardlink restrictions.

OpenClaw runtime plugins are JavaScript plugin roots loaded by the OpenClaw Gateway. They are the plugins described in the upstream OpenClaw plugin docs: Discord, Slack, WhatsApp, GitHub Copilot, memory providers, model providers, tools, and similar Gateway features.

With regular OpenClaw, plugin code is installed by the OpenClaw CLI:

openclaw plugins install @openclaw/discord
openclaw plugins install clawhub:
openclaw plugins install npm:
openclaw plugins install npm-pack:
openclaw plugins install git:github.com//@
openclaw plugins install --link ./my-plugin
openclaw plugins install  --marketplace 

With nix-openclaw, plugin code is installed by Nix:

programs.openclaw.runtimePlugins = [ "discord" ];

The Nix gateway omits the unused upstream npm CLI and its private dependency bundle. Build-time package managers come from the pinned Nix toolchain. The Linux summarize battery and built-in Home Manager plugin share OpenClaw’s private patched Node runtime, including exposed plugin CLI packages. Their pinned CLI versions stay unchanged. Custom plugin sources keep their declared runtimes; the macOS native summarize artifact remains upstream-owned.

OpenClaw runs with OPENCLAW_NIX_MODE=1, so openclaw plugins install, openclaw plugins update, openclaw plugins uninstall, openclaw plugins enable, and openclaw plugins disable fail instead of mutating ~/.openclaw. Change your Nix config and rebuild.

Schemes

Use these inputs:

Regular OpenClaw command or source nix-openclaw
Already included in OpenClaw Configure it directly under programs.openclaw.config; no install input is needed.
openclaw plugins install @openclaw/discord programs.openclaw.runtimePlugins = [ "discord" ];
openclaw plugins install npm:@openclaw/[email protected] programs.openclaw.runtimePluginSources = [{ id = "copilot"; spec = "npm:@openclaw/[email protected]"; }];
openclaw plugins install clawhub:@openclaw/[email protected] programs.openclaw.runtimePluginSources = [{ id = "whatsapp"; spec = "clawhub:@openclaw/[email protected]"; }];
Fixed HTTPS npm-pack .tgz artifact programs.openclaw.runtimePluginSources = [{ id = "my-plugin"; url = "https://.../plugin-1.2.3.tgz"; }];
git:, local path, or marketplace install Not a direct runtimePluginSources input today. Package the plugin as a fixed Nix source or use raw plugins.load.paths, below.

The id is the OpenClaw plugin id from openclaw.plugin.json. It must match the package you fetch.

Start with the upstream Plugin inventory. For official plugin ids, check this build’s supported list:

jq -r '.supported[] | "\(.id)\t\(.label)\t\(.selectedSource)\t\(.dependencyMode)"' \
  nix/generated/openclaw-runtime-plugins/report.json

runtimePlugins uses plugin ids such as discord, whatsapp, googlechat, msteams, or copilot, not npm package names such as @openclaw/discord. Do not put bare package names or unprefixed @scope/package strings in runtimePluginSources; use exact npm: or clawhub: specs.

Dependency Rule

If a plugin has runtime npm dependencies, it must publish bundled node_modules, npm-shrinkwrap.json, or, for supported OpenClaw catalog plugins, upstream npm package-lock.json release evidence bound to the pinned release SHA. Without one of these, there is no reproducible Nix package.

Regular OpenClaw can solve npm dependencies during openclaw plugins install. nix-openclaw cannot do a mutable dependency solve during home-manager switch. If a package has npm-shrinkwrap.json, nix-openclaw can replay that dependency graph with npmDepsHash. For lockless OpenClaw catalog plugins, the pin updater can instead select the exact package name and version from the upstream openclaw--dependency-evidence.zip asset, record its lock and hashes, and replay it during the Nix build. If a plugin bundles node_modules, nix-openclaw validates and copies the bundled deps. Other plugin authors must publish shrinkwrap or bundled runtime deps.

npm and ClawHub Sources

Use runtimePluginSources when the plugin is not in the supported runtimePlugins list, or when you want to pin a specific npm or ClawHub package yourself. If the id is already in the supported list, prefer runtimePlugins unless you intentionally want to override the selected source.

programs.openclaw.runtimePluginSources = [
  {
    id = "copilot";
    spec = "npm:@openclaw/[email protected]";
  }
  {
    id = "whatsapp";
    spec = "clawhub:@openclaw/[email protected]";
  }
];

Build once. Nix will fail with the real source hash. Paste it back:

programs.openclaw.runtimePluginSources = [
  {
    id = "copilot";
    spec = "npm:@openclaw/[email protected]";
    hash = "sha256-...";
  }
];

If the package has runtime dependencies, does not bundle them, and publishes npm-shrinkwrap.json, the next build asks for npmDepsHash:

programs.openclaw.runtimePluginSources = [
  {
    id = "copilot";
    spec = "npm:@openclaw/[email protected]";
    hash = "sha256-...";
    npmDepsHash = lib.fakeHash;
  }
];

Build again and replace npmDepsHash with the suggested hash. Specs must use exact N.N or N.N.N versions, with an optional prerelease suffix. Do not use latest, dist-tags, version ranges, or build metadata in Nix config. Local npm-pack: tarballs are not direct runtimePluginSources inputs; publish or fetch a fixed HTTPS .tgz, then use url, or wire a prebuilt plugin root yourself with plugins.load.paths.

Configuration Example

Channel plugins add places where messages can enter and leave OpenClaw. That means there are two parts: load the plugin, then configure the channel account.

Upstream OpenClaw:

openclaw plugins install @openclaw/discord

Then configure the channel in openclaw.json:

{
  channels: {
    discord: {
      enabled: true,
      token: "your-bot-token",
    },
  },
}

See OpenClaw’s channel configuration docs for the full channels.* shape.

nix-openclaw:

programs.openclaw = {
  runtimePlugins = [ "discord" ];

  environment = {
    DISCORD_BOT_TOKEN = "/run/agenix/discord-bot-token";
  };

  config.channels.discord = {
    enabled = true;
    token = { source = "env"; provider = "default"; id = "DISCORD_BOT_TOKEN"; };
  };
};

Use the same pattern for Slack, WhatsApp, Google Chat, Microsoft Teams, and other channel plugins. Non-channel plugins still use the same install inputs, but their settings go wherever the upstream plugin docs say. For example, a model/runtime plugin usually configures agents.*, models.*, or plugins.entries..config instead of channels.*.

Lower-Level Paths

These paths are still supported, but most users should start above:

Path Use when
programs.openclaw.config.plugins.entries. The plugin already ships inside OpenClaw and only needs upstream config or enablement.
programs.openclaw.config.plugins.load.paths You already have a fixed plugin root and want to wire it yourself. Do not mix this with runtimePlugins or runtimePluginSources in the same instance.
programs.openclaw.bundledPlugins / programs.openclaw.customPlugins You are installing nix-openclaw tool plugins: Nix flake bundles that add CLI tools or agent skills. See Plugins.

Raw plugins.load.paths is an OpenClaw config escape hatch. nix-openclaw will render it, but it will not fetch npm dependencies, resolve ClawHub, validate hashes, or write plugin install records for you.

Example:

programs.openclaw.config.plugins = {
  load.paths = [ "/nix/store/...-my-plugin" ];
  entries.my-plugin.enabled = true;
};

Requirements

  1. macOS (Apple Silicon) or Linux (x86_64)
  2. Nix with flakes enabled installed on your machine

That’s it. The Quick Start will guide you through everything else.

Don’t have Nix yet? Use the Determinate Nix installer or the official Nix installer, then come back here.


Why Nix?

You’ve probably installed tools before. Homebrew, pip, npm - they work until they don’t.

What you deal with today:

  • Update one thing, break another (“but it worked yesterday”)
  • Reinstall everything after a macOS upgrade
  • “Works on my machine” when sharing setups
  • No easy way to undo a bad update

What Nix gives you:

  • Every dependency pinned to exact versions. Forever.
  • Home Manager can reactivate an earlier package/configuration generation. Before downgrading OpenClaw, verify state-schema compatibility and retain a compatible state backup; a profile rollback does not reverse database migrations.
  • Share your config file, get the exact same setup on another machine.
  • Plugins just work. Add a GitHub URL, run one command, done. Nix handles the build, dependencies, and wiring.
  • Tools don’t pollute your system - they live in isolation.

You don’t need to learn Nix deeply. You describe what you want, Nix figures out how to build it.


Quick Start

Tell your coding agent you want OpenClaw set up with Nix. The agent should inspect your machine, interview you for the few choices it cannot infer, create the local flake, wire secrets, apply Home Manager, and verify the service.

Copy this block and paste it to Claude, Cursor, Codex, or your preferred coding agent:

I want to set up nix-openclaw on my machine (Apple Silicon macOS or x86_64 Linux).

Repository: github:openclaw/nix-openclaw

What nix-openclaw is:
- Batteries-included Nix package for OpenClaw (AI assistant gateway)
- Installs the gateway everywhere; macOS app only on macOS
- Runs as a launchd service on macOS, systemd user service on Linux

What I need you to do:
1. Inspect my OS, CPU architecture, shell, Home Manager setup, and whether Nix with flakes is installed
2. Ask me only for missing choices: channel, bot/account secrets, allowed users, provider keys, and workspace identity preferences
3. Create a local flake at ~/code/openclaw-local using templates/agent-first/flake.nix
4. Create explicit workspace bootstrap files next to the config (e.g., ~/code/openclaw-local/workspace): AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, USER.md
   - Optional Nix-managed workspace files can live next to them, e.g. LORE.md or PROMPTING-EXAMPLES.md
   - HEARTBEAT.md is managed only if you explicitly set `workspace.bootstrapFiles.heartbeat`
   - If ~/.openclaw/workspace already has files you want to keep, adopt them into the flake first (use copy/rsync that dereferences symlinks, e.g. `cp -L`)
5. Help me create or connect the channel account I choose
6. Set up secrets (bot token, provider key) - plain files at ~/.secrets/ are fine unless I already have a secret manager
7. Check the pinned OpenClaw memory schema before configuring local memory. Offer `memory.backend = "qmd"` only on legacy schemas that accept it; newer releases retired QMD backend integration
8. Fill in the template placeholders and run home-manager switch
9. Verify end-to-end: package builds, service is running, gateway health works, legacy QMD works if enabled, and the bot/channel responds if configured

My setup:
- OS: [macOS / Linux]
- CPU: [arm64 / x86_64]
- System: [aarch64-darwin / x86_64-linux]
- Home Manager config name: [FILL IN or "I don't have Home Manager yet"]

Reference the README and templates/agent-first/flake.nix in the repo for the module options.

Your agent should do the setup work. You answer its short questions and confirm before it sends messages or changes external services.

QMD packaging note for agents: Linux uses upstream github:tobi/qmd; Darwin uses the nix-openclaw-tools QMD repair package until upstream Darwin packaging is fixed. Keep both pinned to the same QMD release unless there is a tested reason to diverge.

What happens next:

  1. Your agent sets everything up and runs home-manager switch
  2. You message your Telegram bot for the first time
  3. OpenClaw starts with the workspace context declared in your flake
  4. To change identity or operating context later, edit the source files and run Home Manager again

How It Works

You (Telegram/Discord) --> Gateway --> Tools --> Your machine does things

Gateway: The brain. A service running on your machine that receives messages and decides what to do. Managed by launchd on macOS and a systemd user service on Linux.

nix-openclaw plugins: Nix-managed bundles that contain two things:

  1. CLI tools - actual programs that do stuff (take screenshots, control Spotify, transcribe audio)
  2. Skills - markdown files that teach the AI how to use those tools

When you enable a nix-openclaw plugin, Nix installs the tools and wires up the skills to OpenClaw automatically - the gateway learns what it can do.

Skills: Instructions for the AI. A skill file says “when the user wants X, run this command.” The AI reads these to know what it can do.


Plugins

Note: Complete the Quick Start first to get OpenClaw running. Then come back here to add plugins.

These docs are for nix-openclaw tool plugins only. For OpenClaw runtime plugins such as Slack or Discord, use programs.openclaw.runtimePlugins; see OpenClaw Runtime Plugins.

Bundled plugins

These ship with nix-openclaw. Toggle them in your config:

programs.openclaw.bundledPlugins = {
  summarize.enable = true;   # Summarize web pages, PDFs, videos
  discrawl.enable = false;    # Discord archive/search
  wacrawl.enable = false;     # WhatsApp archive/search
  peekaboo.enable = true;    # Take screenshots
  poltergeist.enable = false; # File watching and automation
  sag.enable = false;        # Text-to-speech
  camsnap.enable = false;    # Camera snapshots
  gogcli.enable = false;     # Google Workspace CLI
  goplaces.enable = true;    # Google Places API
  sonoscli.enable = false;   # Sonos control
  imsg.enable = false;       # iMessage
};

# Optional config for bundled plugins
programs.openclaw.bundledPlugins.goplaces = {
  enable = true;
  config.env.GOOGLE_PLACES_API_KEY = "/run/agenix/google-places-api-key";
};
Plugin What it does
summarize Summarize URLs, PDFs, YouTube videos
discrawl Archive and search Discord history
wacrawl Archive and search WhatsApp Desktop history
peekaboo Screenshot your screen
poltergeist File watching and automation
sag Text-to-speech
camsnap Take photos from connected cameras
gogcli Google Workspace CLI
goplaces Google Places API (New) CLI
sonoscli Control Sonos speakers
imsg Send/read iMessages

Gmail hooks with gogcli

Enabling bundledPlugins.gogcli installs gog and its OpenClaw skill. It does not run openclaw hooks gmail setup or mutate the Nix-owned openclaw.json. Declare the upstream Gmail hook configuration under programs.openclaw.config.hooks instead.

The Gmail preset uses a templated hook:gmail: session key. OpenClaw rejects that callback with HTTP 400 unless request session keys are enabled and bounded to the expected prefix:

programs.openclaw.config.hooks = {
  enabled = true;
  presets = [ "gmail" ];
  defaultSessionKey = "hook:gmail:default";
  allowRequestSessionKey = true;
  allowedSessionKeyPrefixes = [ "hook:gmail:" ];

  # Add the upstream gmail block here.
};

Keep the prefix narrow: this option lets the authenticated hook caller select the target session. Follow the upstream Gmail Pub/Sub setup for Google credentials, hook tokens, and public-ingress security.

Adding custom nix-openclaw plugins

Tell your agent: “Add the plugin from github:owner/repo-name and pin it.”

Or add it manually to your config:

customPlugins = [
  { source = "github:owner/repo-name?rev=&narHash="; }
];

Then run home-manager switch to install.

Plugins with configuration

Some nix-openclaw plugins need settings (auth files, preferences). Here’s a simplified example:

# Example: a padel court booking plugin (simplified for illustration)
customPlugins = [
  {
    source = "github:example/padel-cli?rev=&narHash=";
    config = {
      env = {
        PADEL_AUTH_FILE = "/run/agenix/padel-auth";  # where your login token lives
      };
      settings = {
        default_city = "Barcelona";
        preferred_times = [ "18:00" "20:00" ];
      };
    };
  }
];
  • config.env - paths to secrets/auth files the plugin needs
  • config.settings - preferences (rendered to config.json for the plugin)

Configuration

Note: You probably don’t need to write this yourself. Your AI agent handles this when you use the Quick Start copypasta. These examples are here for reference.

Breaking change: nix-openclaw no longer exposes provider/routing/agent shortcut options. Put OpenClaw runtime config under programs.openclaw.config / instances..config, using the upstream OpenClaw config shape.

Breaking change: programs.openclaw.documents is removed. Use programs.openclaw.workspace.bootstrapFiles with explicit file paths for AGENTS.md, SOUL.md, TOOLS.md, IDENTITY.md, and USER.md; use programs.openclaw.workspace.files for extra managed workspace files. When migrating from documents, re-declare every old extra file you still want Nix-managed, e.g. LORE.md, PROMPTING-EXAMPLES.md, or private companion docs. Files not declared under workspace.bootstrapFiles or workspace.files intentionally stop being managed by nix-openclaw. See CHANGELOG.md for all current breaking changes, before/after config, and file mapping. When bootstrap files are configured, nix-openclaw forces agents.defaults.skipBootstrap = true so OpenClaw never seeds workspace bootstrap files from bundled templates. BOOTSTRAP.md and MEMORY.md are runtime-owned, not managed by workspace.files.

Workspace activation replaces declared files and removes stale managed paths only below currently enabled instances’ configured workspaceDir roots. Paths that escape through a parent symlink are rejected. If you move a workspace or remove an instance, old paths outside the remaining roots are preserved with a warning and retained in ~/.local/state/nix-openclaw/managed-workspace-files for manual inspection and cleanup. An empty set of declared files still cleans up stale files within configured workspaces.

What OpenClaw needs (minimum)

  1. Telegram bot token file - create via @BotFather, set channels.telegram.tokenFile
  2. Your Telegram user ID - get from @userinfobot, set channels.telegram.allowFrom
  3. Gateway auth token - set gateway.auth.token (or OPENCLAW_GATEWAY_TOKEN) for the local gateway
  4. Provider API keys - set via environment (e.g., ANTHROPIC_API_KEY) or config.env.vars (avoid secrets in store)

That’s it. Everything else has sensible defaults.

Secrets And OpenClaw Exec SecretRefs

nix-openclaw is part of the OpenClaw org and renders upstream OpenClaw config, including OpenClaw SecretRefs. The supported nix-openclaw secret path is still Nix-shaped: materialize secrets outside the Nix store, then pass them to OpenClaw as env/file-backed runtime config.

Good:

programs.openclaw.environment.GROQ_API_KEY =
  config.age.secrets.openclaw-groq-api-key.path;

programs.openclaw.config.models.providers.groq.apiKey = {
  source = "env";
  provider = "default";
  id = "GROQ_API_KEY";
};

Same shape with sops-nix:

programs.openclaw.environment.GROQ_API_KEY =
  config.sops.secrets.openclaw-groq-api-key.path;

You can do this, but you should not:

programs.openclaw.config.secrets.providers.aws = {
  source = "exec";
  command = "/run/current-system/sw/bin/aws";
  args = [ "secretsmanager" "get-secret-value" "--secret-id" "openclaw/groq-api-key" ];
};

programs.openclaw.config.models.providers.groq.apiKey = {
  source = "exec";
  provider = "aws";
  id = "value";
};

nix-openclaw will render this because upstream OpenClaw supports it. That is pass-through compatibility, not support, and nix-openclaw emits a Nix warning when it sees this shape. Exec SecretRefs move secret retrieval into OpenClaw runtime config, where Nix cannot evaluate it, build-check it, order it, permission it, reproduce it, or verify IAM/session/network/output failure modes. It also makes the OpenClaw process responsible for secret fetching instead of the host service layer that normally owns startup ordering, identity, logs, retries, and file permissions.

Better for AWS Secrets Manager, 1Password, Vault, etc.: have the host fetch the secret into a runtime-only file, then wire OpenClaw to that file/env value.

# Your systemd/launchd/host config writes this before OpenClaw starts.
programs.openclaw.environment.GROQ_API_KEY =
  "/run/openclaw-secrets/groq-api-key";

programs.openclaw.config.models.providers.groq.apiKey = {
  source = "env";
  provider = "default";
  id = "GROQ_API_KEY";
};

That keeps nix-openclaw responsible for stable config and service wiring, keeps secrets out of the Nix store, and leaves dynamic secret-manager integration to the host layer that owns credentials and runtime side effects.

Runtime environment values are literal strings: shell variables and command substitutions are not expanded. Values naming existing files are read at startup, with an optional matching NAME= prefix removed; variables ending in _FILE retain the file path. Use absolute secret paths or resolve home paths in Nix with config.home.homeDirectory.

Minimal config (single instance)

The simplest setup:

{
  programs.openclaw = {
    enable = true;
    config = {
      gateway = {
        mode = "local";
        auth = {
          token = ""; # or set OPENCLAW_GATEWAY_TOKEN
        };
      };

      channels.telegram = {
        tokenFile = "/run/agenix/telegram-bot-token"; # any file path works
        allowFrom = [ 12345678 ];  # your Telegram user ID
      };
    };

    bundledPlugins.summarize.enable = true;
  };
}

Then: home-manager switch --flake .#youruser

Sensible defaults config

Uses instances.default to unlock per-group mention rules. If instances is set, you don’t need programs.openclaw.enable.

{
  programs.openclaw = {
    workspace.bootstrapFiles = {
      agents = ./workspace/AGENTS.md;
      soul = ./workspace/SOUL.md;
      tools = ./workspace/TOOLS.md;
      identity = ./workspace/IDENTITY.md;
      user = ./workspace/USER.md;
    };

    config = {
      gateway = {
        mode = "local";
        auth = {
          token = ""; # or set OPENCLAW_GATEWAY_TOKEN
        };
      };

      channels.telegram = {
        tokenFile = "/run/agenix/telegram-bot-token";
        allowFrom = [
          12345678         # you (DM)
          -1001234567890   # couples group (no @mention required)
          -1002345678901   # noisy group (require @mention)
        ];
        groups = {
          "*" = { requireMention = true; };
          "-1001234567890" = { requireMention = false; }; # couples group
          "-1002345678901" = { requireMention = true; };  # noisy group
        };
      };
    };

    bundledPlugins.peekaboo.enable = true;
    customPlugins = [
      { source = "github:joshp123/xuezh?rev=&narHash="; }
      {
        source = "github:joshp123/padel-cli?rev=&narHash=";
        config = {
          env = { PADEL_AUTH_FILE = "/run/agenix/padel-auth"; };
          settings = {
            default_location = "CITY_NAME";
            preferred_times = [ "18:00" "20:00" ];
            preferred_duration = 90;
            venues = [
              {
                id = "VENUE_ID";
                alias = "VENUE_ALIAS";
                name = "VENUE_NAME";
                indoor = true;
                timezone = "TIMEZONE";
              }
            ];
          };
        };
      }
    ];

    instances.default = {
      enable = true;
      package = pkgs.openclaw; # batteries-included
      stateDir = "~/.openclaw";
      workspaceDir = "~/.openclaw/workspace";
      launchd.enable = true;
    };
  };
}

Advanced

Dual-instance setup (prod + dev)

Use named instances when you need two local gateways. Keep the default package unless you are actively debugging a local gateway checkout.

Config activation supports paths containing spaces, including a custom instances..configPath. A leading ~/ is resolved against the configured Home Manager home directory for activation, home.file destinations, the workspace pin, and launchd/systemd WorkingDirectory and environment paths. Systemd environment entries preserve spaces in the config and state paths.

Home Manager follows the pinned schema’s agent roster shape. On schemas with agents.entries, missing or empty entries without explicit ownership emit agents.entries.main = {}. This also applies with workspace.pinAgentDefaults = false, without setting agents.defaults.workspace. Nonempty rosters and explicit ownership remain unchanged; explicit ownership with missing or empty entries remains invalid. Keyed IDs are validated and lowercased for runtime profile paths.

programs.openclaw = {
  workspace = {
    bootstrapFiles = {
      agents = ./workspace/AGENTS.md;
      soul = ./workspace/SOUL.md;
      tools = ./workspace/TOOLS.md;
      identity = ./workspace/IDENTITY.md;
      user = ./workspace/USER.md;
    };
    files."LORE.md" = ./workspace/LORE.md;
  };

  instances = {
    prod = {
      enable = true;
      gatewayPort = 18789;
      config.channels.telegram = {
        tokenFile = "/run/agenix/telegram-prod";
        allowFrom = [ 12345678 ];
      };
      plugins = [
        { source = "github:owner/your-plugin?rev=&narHash="; }
      ];
    };

    dev = {
      enable = true;
      gatewayPort = 18790;
      gatewayPath = "/Users/you/code/openclaw";
      config.channels.telegram = {
        tokenFile = "/run/agenix/telegram-dev";
        allowFrom = [ 12345678 ];
      };
      plugins = [
        { source = "path:/Users/you/code/your-plugin"; }
      ];
    };
  };
};

Plugin collisions

Plugins are keyed by their declared name. If two plugins declare the same name, the last entry wins (use this to override a prod plugin with a local dev one).

Packaging & Updates

MCP OAuth credentials after upgrading

The stable npm gateway packages MCP SDK 1.32.0, including the fix for CVE-2026-111769, which prevents cross-origin redirects from forwarding custom headers and OAuth request bodies. Configure the final URL for HTTP MCP servers and authorization servers that previously redirected to a different origin. Saved OAuth clients and tokens must identify their authorization server. Older credentials without that issuer are ignored; run openclaw mcp logout and openclaw mcp login for each affected HTTP MCP server. Complete the login only for servers you trust. If credentials may already have been exposed, revoke the tokens and rotate the client secret at the authorization server. Explicit source overrides use their upstream dependency locks and must include the corresponding SDK fix themselves.

Goal: nix-openclaw is a great Nix package. Automation, promotion, and fleet rollout live elsewhere.

Stable release mirroring

We ship one default package: .#openclaw.

The gateway tracks the newest upstream stable OpenClaw source release that satisfies the Nix package contract:

  • gateway builds on Linux and macOS
  • gateway starts and answers local health checks

The macOS app is pinned separately to the newest stable public OpenClaw-*.zip artifact. If upstream has not promoted desktop assets for the latest source release yet, openclaw-app may lag; that must not block Linux users or macOS gateway users from getting the latest packaged OpenClaw gateway.

The macOS package preserves the signed upstream app bundle without rewriting its contents. CI compares installed bytes and symlinks with the pinned archive and verifies the signature and universal executable architectures.

The Nix gate is deliberately package-focused. It does not make the full upstream Vitest suite a hard promotion gate; upstream owns source test health, while nix-openclaw verifies the npm-shrinkwrapped gateway package, package contents, smoke startup, module activation, generated config materialization during pin updates, and newest available macOS app artifact.

Outputs:

.#openclaw
.#openclaw-gateway
.#openclaw-app   # Darwin only

.#openclaw-gateway and .#openclaw-app are component outputs for modules, CI, debugging, and advanced use. Start with .#openclaw.

Pins live in:

  • nix/sources/openclaw-source.nix
  • nix/packages/openclaw-app.nix

Responsibilities (who owns what)

  • openclaw (upstream): source code, tests, releases.
  • nix-openclaw: Nix packaging, pins, CI builds.
  • release automation: update cadence, smoke tests, promotion, rollout/rollback.

Automated pipeline

  1. Hourly Pin Stable OpenClaw Version polls upstream stable OpenClaw releases.
  2. It selects the newest stable source release and newest stable public macOS app zip independently.
  3. Newer source releases that lack public macOS app assets are reported as app lag, not skipped.
  4. The stable pin workflow materializes the source pin from the newest source tag ref, updates the app asset pin from the newest public app zip, and regenerates config options from the selected source.
  5. The stable pin workflow validates that source/app pin set on the same Linux + macOS contract as repository CI.
  6. Only after both validations pass does the workflow push one release-mirroring commit to main.

Mirrored tags

When a complete package state is proven on main, nix-openclaw publishes a matching v tag and lightweight GitHub Release. The tag points at the validated nix-openclaw commit, so users can install the same upstream OpenClaw version through Nix:

nix run github:openclaw/nix-openclaw/v2026.5.28#openclaw

Each generated GitHub Release links back to the matching upstream OpenClaw release so users can click through from the Nix package state to the source release notes and artifacts.

Mirrored tags are only created when the source pin and macOS app pin both match the same upstream stable OpenClaw version and repository CI is green on Linux and macOS for that exact commit. If a source release is packageable but the matching public macOS app zip is missing, main may still carry the packaging work, but no mirrored v tag is published until the full user-facing package state is complete.


Reference

Commands

# macOS: check service
launchctl print gui/$UID/com.steipete.openclaw.gateway | grep state

# macOS: view logs
tail -50 /tmp/openclaw/openclaw-gateway.log

# macOS: restart
launchctl kickstart -k gui/$UID/com.steipete.openclaw.gateway

# Linux: check service
systemctl --user status openclaw-gateway

# Linux: view logs
journalctl --user -u openclaw-gateway -f

# Linux: restart
systemctl --user restart openclaw-gateway

# Rollback only after verifying state compatibility and retaining a compatible backup
home-manager generations  # list
home-manager switch --rollback  # reactivate the previous package/configuration generation

Reactivate an earlier generation only after verifying OpenClaw state compatibility and retaining a compatible backup. An older gateway can reject a database written by a newer release; Home Manager does not restore that database from backup.

Packages

Package Contents
openclaw (default) Canonical package. Exposes openclaw; keeps runtime tools internal. macOS also links the app.
openclaw-gateway Component output: gateway CLI/service only
openclaw-app Component output: macOS app only

The default overlay preserves Nixpkgs’ pnpm and versioned pnpm attributes. OpenClaw’s private pnpm pins are available as pkgs.openclawPackages.pnpm_11 and pkgs.openclawPackages.pnpm_12 when needed for packaging or debugging.

Local memory

QMD backend integration is legacy-only. It is available when the pinned OpenClaw generated schema accepts memory.backend = "qmd", as in OpenClaw 2026.7.1-2. OpenClaw 2026.9.3 has retired this backend.

On a legacy schema, opt in through normal OpenClaw config:

programs.openclaw.config = {
  memory.backend = "qmd";
};

Legacy opt-in keeps QMD inside the OpenClaw runtime PATH. The default package does not include QMD without opt-in. Linux uses upstream tobi/qmd; Darwin uses the repaired nix-openclaw-tools package.

Before removing QMD settings, carry custom paths and extra collections (including { path, pattern } globs) into memory.search.extraPaths in your Nix source. Also carry over any enabled session-indexing settings and sources, following the upstream QMD migration guide.

After carrying those settings over, remove the retired memory.backend, memory.qmd, and memory.search.qmd from your Nix-authored OpenClaw config, then rebuild. Home Manager rejects these retired generated options; the NixOS module also rejects QMD opt-in and the two QMD subtrees in its raw config. Nix does not silently rewrite configuration or run Doctor during activation. An external NixOS configFile remains opaque and must be updated by its owner.

Standalone QMD is still available as pkgs.openclawPackages.qmd, including explicit home.packages or NixOS services.openclaw-gateway.servicePath use. Installing the CLI does not restore OpenClaw backend integration or migrate existing memory data.

Plugin CLIs are also kept on the OpenClaw runtime PATH by default, not on the user’s login shell PATH. Set programs.openclaw.exposePluginPackages = true only when you explicitly want plugin CLIs in home.packages.

Optional standalone model prewarming remains declarative on both schemas:

programs.openclaw.qmd.prewarmModels.enable = true;

That runs a temporary QMD collection through qmd update, qmd embed, and qmd query during Home Manager activation, which warms the default embedding, expansion, and reranking models in the user’s QMD cache. Expect about 2.25GB of cache use.

What we manage vs what you manage

Component Nix manages You manage
Gateway binary ✓
macOS app ✓
Service (launchd/systemd) ✓
Runtime tools and legacy QMD opt-in ✓
Telegram bot token ✓
Anthropic API key ✓
Chat IDs ✓

Runtime tools

Platform note: the toolchain is filtered per platform. macOS-only tools are skipped on Linux.

The default openclaw package uses these tools internally and does not expose them as separate user commands.

Core: nodejs, pnpm, git, curl, jq, python3, ffmpeg, sox, ripgrep

Legacy local memory: QMD, pulled in only when the generated schema accepts memory.backend = "qmd" and the instance opts in. Standalone CLI/prewarm use remains separate from backend support.

Default first-party tools come from nix-openclaw-tools: gogcli (gog), goplaces, summarize, camsnap, sonoscli.

Optional bundled plugins add their own packages when enabled: discrawl, wacrawl, peekaboo, poltergeist, sag, imsg.


Philosophy

The Zen of Python OpenClaw, by shamelessly stolen from Tim Peters

Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren’t special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you’re Dutch. Now is better than never. Although never is often better than right now. If the implementation is hard to explain, it’s a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea – let’s do more of those!


Upstream

Wraps OpenClaw by Peter Steinberger.

License

AGPL-3.0

View the full README on GitHub

Recommended Tools

Try a different keyword or remove a filter.

Install

npx skillfish add openclaw/nix-openclaw
New to MCP? Start with the mcpskill.org guide →