> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mibyanai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Nix & NixOS Setup

> Install and deploy Mibyan with Nix — from quick `nix run` to fully declarative NixOS module with container mode

<Info>
  Commands, package names, and image names on this page come from the open-source project that Mibyan Desktop is built on, and can differ from the Mibyan Desktop installer. For the supported Mibyan install and update path, see [Install and update](/products/desktop-guide/install-and-update).
</Info>

<Warning>
  **Tier 2 platform**

  Nix and NixOS are [Tier 2 platforms](/desktop/getting-started/platform-support#tier-2). The flake and NixOS module documented here are maintained on a best-effort basis only. Commits to `main` may break these packages at any point in time.

  For a supported setup, use one of the standard [installation](/desktop/getting-started/installation) paths - either Docker or an FHS environment.
</Warning>

Mibyan ships a Nix flake, a NixOS module, and a Home Manager module.

| Level | Who it's for | What you get |
| - | - | - |
| **`nix run` / `nix profile install`** | Any Nix user (macOS, Linux) | Pre-built binary with all deps — then use the standard CLI workflow |
| **Home Manager module** | An agent for one person, on any distribution or on macOS | Declarative configuration and a user service, without root |
| **NixOS module (native)** | NixOS server deployments | Declarative config, hardened systemd service, managed secrets |
| **NixOS module (container)** | Agents that need self-modification | Everything above, plus a persistent Ubuntu container where the agent can `apt`/`pip`/`npm install` |

<Info>
  **What's different from the standard install**

  The `curl | bash` installer manages Python, Node, and dependencies itself. The Nix flake replaces all of that — every Python dependency is a Nix derivation built by [uv2nix](https://github.com/pyproject-nix/uv2nix), and runtime tools (Node.js, git, ripgrep, ffmpeg) are wrapped into the binary's PATH. There is no runtime pip, no venv activation, no `npm install`.

  **For non-NixOS users**, this only changes the install step. Everything after (`mibyan setup`, `mibyan gateway install`, config editing) works identically to the standard install.

  **For NixOS module users**, the entire lifecycle is different: configuration lives in `configuration.nix`, secrets go through sops-nix/agenix, the service is a systemd unit, and CLI config commands are blocked. You manage mibyan the same way you manage any other NixOS service.
</Info>

## Runtime pins

PM's tool lock is also a Nix build input. `nix/npm-pinned.nix` reads the npm
pin, and `nix/pm-packages.nix` exposes matching archives as `pm-NAME` derivations:

```bash theme={null}
nix build .#pm-ripgrep
```

These outputs unpack the pinned archives. They are not the complete Mibyan
wrapper or a guarantee that each archive runs without platform integration.
The application still uses the uv2nix environment and Nix wrapper.

`nix/pythonLock.nix` reads Python's major/minor from `pm/lock.json`.
The uv2nix environment, package overrides, plugin packages, and developer shell
use that interpreter family. If the pinned nixpkgs lacks that family, evaluation
stops instead of selecting a different Python.

Native Nix evaluation and builds remain CI gates. Update through Nix. Do not
repair a Nix store path with pip.

## Prerequisites

* **Nix with flakes enabled** — [Determinate Nix](https://install.determinate.systems) recommended (enables flakes by default)
* **API keys** for the services you want to use (at minimum: an OpenRouter or Anthropic key)

***

## Quick Start (Any Nix User)

No clone needed. Nix fetches, builds, and runs everything:

```bash theme={null}
# Run the desktop app
nix run github:NousResearch/hermes-agent#desktop

# Or install persistently
nix profile install github:NousResearch/hermes-agent#desktop

# run the tui
nix run github:NousResearch/hermes-agent -- setup
nix run github:NousResearch/hermes-agent -- --tui

# or install it in your profile
nix profile install github:NousResearch/hermes-agent
mibyan setup
mibyan --tui
```

After `nix profile install`, `mibyan`, `mibyan-agent`, and `mibyan-acp` are on your PATH. From here, the workflow is identical to the [standard installation](/desktop/getting-started/installation) — `mibyan setup` walks you through provider selection, `mibyan gateway install` sets up a launchd (macOS) or systemd user service, and config lives in `~/.mibyan/`.

<Warning>
  **Messaging platforms (Discord, Telegram, Slack)**

  The default package includes ALL libraries mibyan-agent might need. if you want a smaller variant, check the other flake outputs.

  The `default` package adds \~700 MB to the closure. If you only need messaging platforms, `#messaging` adds just \~33 MB.
</Warning>

<details>
  <summary><strong>Running from a local clone</strong></summary>

  ```bash theme={null}
  git clone https://github.com/NousResearch/hermes-agent.git
  cd mibyan-agent
  nix develop
  mibyan setup
  ```
</details>

***

## NixOS Module

The flake exports `nixosModules.default` — a full NixOS service module that declaratively manages user creation, directories, config generation, secrets, documents, and service lifecycle.

<Note>
  This module needs NixOS. Mibyan is an agent for one person. If you want an agent for one person and not a system service, use the [Home Manager module](#home-manager-module). That module runs on NixOS and on each other system that Home Manager supports.
</Note>

### Add the Flake Input

```nix theme={null}
# /etc/nixos/flake.nix (or your system flake)
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    mibyan-agent.url = "github:NousResearch/hermes-agent";
  };

  outputs = { nixpkgs, mibyan-agent, ... }: {
    nixosConfigurations.your-host = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        mibyan-agent.nixosModules.default
        ./configuration.nix
      ];
    };
  };
}
```

### Minimal Configuration

```nix theme={null}
# configuration.nix
{ config, ... }: {
  services.mibyan-agent = {
    enable = true;
    settings.model.default = "anthropic/claude-sonnet-4";
    environmentFiles = [ config.sops.secrets."mibyan-env".path ];
    addToSystemPackages = true;
  };
}
```

That's it. `nixos-rebuild switch` creates the `mibyan` user, generates `config.yaml`, wires up secrets, and starts the gateway — a long-running service that connects the agent to messaging platforms (Telegram, Discord, etc.) and listens for incoming messages.

<Warning>
  **Secrets are required**

  The `environmentFiles` line above assumes you have [sops-nix](https://github.com/Mic92/sops-nix) or [agenix](https://github.com/ryantm/agenix) configured. The file should contain at least one LLM provider key (e.g., `OPENROUTER_API_KEY=sk-or-...`). See [Secrets Management](#secrets-management) for full setup. If you don't have a secrets manager yet, you can use a plain file as a starting point — just ensure it's not world-readable:

  ```bash theme={null}
  echo "OPENROUTER_API_KEY=sk-or-your-key" | sudo install -m 0600 -o mibyan /dev/stdin /var/lib/mibyan/env
  ```

  ```nix theme={null}
  services.mibyan-agent.environmentFiles = [ "/var/lib/mibyan/env" ];
  ```
</Warning>

<Tip>
  **addToSystemPackages**

  Setting `addToSystemPackages = true` does two things: puts the `mibyan` CLI on your system PATH **and** sets `mibyan_HOME` system-wide so the interactive CLI shares state (sessions, skills, cron) with the gateway service. Without it, running `mibyan` in your shell creates a separate `~/.mibyan/` directory.
</Tip>

### Container-aware CLI

<Info>
  When `container.enable = true` and `addToSystemPackages = true`, **every** `mibyan` command on the host automatically routes into the managed container. This means your interactive CLI session runs inside the same environment as the gateway service — with access to all container-installed packages and tools.

  * The routing is transparent: `mibyan chat`, `mibyan sessions list`, `mibyan --version`, etc. all exec into the container under the hood
  * All CLI flags are forwarded as-is
  * If the container isn't running, the CLI retries briefly (5s with a spinner for interactive use, 10s silently for scripts) then fails with a clear error — no silent fallback
  * For developers working on the mibyan codebase, set `mibyan_DEV=1` to bypass container routing and run the local checkout directly

  Set `container.hostUsers` to create a `~/.mibyan` symlink to the service state directory, so the host CLI and the container share sessions, config, and memories:

  ```nix theme={null}
  services.mibyan-agent = {
    container.enable = true;
    container.hostUsers = [ "your-username" ];
    addToSystemPackages = true;
  };
  ```

  Users listed in `hostUsers` are automatically added to the `mibyan` group for file permission access.

  **Podman users:** The NixOS service runs the container as root. Docker users get access via the `docker` group socket, but Podman's rootful containers require sudo. Grant passwordless sudo for your container runtime:

  ```nix theme={null}
  security.sudo.extraRules = [{
    users = [ "your-username" ];
    commands = [{
      command = "/run/current-system/sw/bin/podman";
      options = [ "NOPASSWD" ];
    }];
  }];
  ```

  The CLI auto-detects when sudo is needed and uses it transparently. Without this, you'll need to run `sudo mibyan chat` manually.
</Info>

### Verify It Works

After `nixos-rebuild switch`, check that the service is running:

```bash theme={null}
# Check service status
systemctl status mibyan-agent

# Watch logs (Ctrl+C to stop)
journalctl -u mibyan-agent -f

# If addToSystemPackages is true, test the CLI
mibyan --version
mibyan config       # shows the generated config
```

### Choosing a Deployment Mode

The module supports two modes, controlled by `container.enable`:

| | **Native** (default) | **Container** |
| - | - | - |
| How it runs | Hardened systemd service on the host | Persistent Ubuntu container with `/nix/store` bind-mounted |
| Security | `NoNewPrivileges`, `ProtectSystem=strict`, `PrivateTmp` | Container isolation, runs as unprivileged user inside |
| Agent can self-install packages | No — only tools on the Nix-provided PATH | Yes — `apt`, `pip`, `npm` installs persist across restarts |
| Config surface | Same | Same |
| When to choose | Standard deployments, maximum security, reproducibility | Agent needs runtime package installation, mutable environment, experimental tools |

To enable container mode, add one line:

```nix theme={null}
{
  services.mibyan-agent = {
    enable = true;
    container.enable = true;
    # ... rest of config is identical
  };
}
```

<Info>
  Container mode auto-enables `virtualisation.docker.enable` via `mkDefault`. If you use Podman instead, set `container.backend = "podman"` and `virtualisation.docker.enable = false`.
</Info>

<Note>
  **Cron on a native install needs a lingering service user**

  Scheduled cron jobs are launched in a transient `systemd-run --user --scope` so a gateway restart cannot kill a running job. That needs a systemd user manager for the service uid, which a system service only gets when the uid lingers. With `createUser = true` the module sets `users.users.<user>.linger = true` (nixpkgs ≥ 25.05), orders the gateway after `linger-users.service`, and waits briefly for `/run/user/<uid>/bus` before starting. If you declare the user yourself (`createUser = false`), set `linger = true` on it or run `sudo loginctl enable-linger <user>` once; otherwise cron degrades to unscoped workers (or fails closed under `cron.require_restart_safe_scope: true`).
</Note>

***

## Configuration

### Declarative Settings

The `settings` option accepts an arbitrary attrset that is rendered as `config.yaml`. It supports deep merging across multiple module definitions (via `lib.recursiveUpdate`), so you can split config across files:

```nix theme={null}
# base.nix
services.mibyan-agent.settings = {
  model.default = "anthropic/claude-sonnet-4";
  toolsets = [ "all" ];
  terminal = { backend = "local"; timeout = 180; };
};

# personality.nix
services.mibyan-agent.settings = {
  display = { compact = false; personality = "kawaii"; };
  memory = { memory_enabled = true; user_profile_enabled = true; };
};
```

Both are deep-merged at evaluation time. Nix-declared keys always win over keys in an existing `config.yaml` on disk, but **user-added keys that Nix doesn't touch are preserved**. This means if the agent or a manual edit adds keys like `skills.disabled` or `streaming.enabled`, they survive `nixos-rebuild switch`.

<Note>
  **Model naming**

  `settings.model.default` uses the model identifier your provider expects. With [OpenRouter](https://openrouter.ai) (the default), these look like `"anthropic/claude-sonnet-4"` or `"google/gemini-3-flash"`. If you're using a provider directly (Anthropic, OpenAI), set `settings.model.base_url` to point at their API and use their native model IDs (e.g., `"claude-sonnet-4-20250514"`). When no `base_url` is set, Mibyan defaults to OpenRouter.
</Note>

<Tip>
  **Discovering available config keys**

  Run `nix build .#configKeys && cat result` to see every leaf config key extracted from Python's `DEFAULT_CONFIG`. You can paste your existing `config.yaml` into the `settings` attrset — the structure maps 1:1.
</Tip>

<details>
  <summary><strong>Full example: all commonly customized settings</strong></summary>

  ```nix theme={null}
  { config, ... }: {
    services.mibyan-agent = {
      enable = true;
      container.enable = true;

      # ── Model ──────────────────────────────────────────────────────────
      settings = {
        model = {
          base_url = "https://openrouter.ai/api/v1";
          default = "anthropic/claude-opus-4.6";
        };
        toolsets = [ "all" ];
        max_turns = 100;
        terminal = { backend = "local"; cwd = "."; timeout = 180; };
        compression = {
          enabled = true;
          threshold = 0.85;
          summary_model = "google/gemini-3-flash-preview";
        };
        memory = { memory_enabled = true; user_profile_enabled = true; };
        display = { compact = false; personality = "kawaii"; };
        agent = { max_turns = 60; verbose = false; };
      };

      # ── Secrets ────────────────────────────────────────────────────────
      environmentFiles = [ config.sops.secrets."mibyan-env".path ];

      # ── Documents ──────────────────────────────────────────────────────
      # USER.md is memory, so it goes to mibyan_HOME. Workspace files use
      # `documents`, and that option needs an explicit `workingDirectory`.
      mibyanHomeFiles = {
        "memories/USER.md" = ./documents/USER.md;
      };

      # ── MCP Servers ────────────────────────────────────────────────────
      mcpServers.filesystem = {
        command = "npx";
        args = [ "-y" "@modelcontextprotocol/server-filesystem" "/data/workspace" ];
      };

      # ── Container options ──────────────────────────────────────────────
      container = {
        image = "ubuntu:24.04";
        backend = "docker";
        hostUsers = [ "your-username" ];
        extraVolumes = [ "/home/user/projects:/projects:rw" ];
        extraOptions = [ "--gpus" "all" ];
      };

      # ── Service tuning ─────────────────────────────────────────────────
      addToSystemPackages = true;
      extraArgs = [ "--verbose" ];
      restart = "always";
      restartSec = 5;
    };
  }
  ```
</details>

### Escape Hatch: Bring Your Own Config

If you'd rather manage `config.yaml` entirely outside Nix, use `configFile`:

```nix theme={null}
services.mibyan-agent.configFile = /etc/mibyan/config.yaml;
```

This bypasses `settings` entirely — no merge, no generation. The file is copied as-is to `$mibyan_HOME/config.yaml` on each activation.

### Customization Cheatsheet

Quick reference for the most common things Nix users want to customize:

| I want to... | Option | Example |
| - | - | - |
| Change the LLM model | `settings.model.default` | `"anthropic/claude-sonnet-4"` |
| Use a different provider endpoint | `settings.model.base_url` | `"https://openrouter.ai/api/v1"` |
| Add API keys | `environmentFiles` | `[ config.sops.secrets."mibyan-env".path ]` |
| Give the agent an identity | `mibyanHomeFiles."SOUL.md"` | `"You are a terse ops assistant."` |
| Add project context to the workspace | `documents."AGENTS.md"` | `./documents/AGENTS.md` |
| Run the backend for the desktop app or the dashboard | `backend.mode` | `"serve"` or `"dashboard"` |
| Add MCP tool servers | `mcpServers.<name>` | See [MCP Servers](#mcp-servers) |
| Enable Discord/Telegram/Slack | `extraDependencyGroups` | `[ "messaging" ]` |
| Mount host directories into container | `container.extraVolumes` | `[ "/data:/data:rw" ]` |
| Pass GPU access to container | `container.extraOptions` | `[ "--gpus" "all" ]` |
| Use Podman instead of Docker | `container.backend` | `"podman"` |
| Share state between host CLI and container | `container.hostUsers` | `[ "sidbin" ]` |
| Make extra tools available to the agent | `extraPackages` | `[ pkgs.pandoc pkgs.imagemagick ]` |
| Use a custom base image | `container.image` | `"ubuntu:24.04"` |
| Override the mibyan package | `package` | `inputs.mibyan-agent.packages.${system}.default.override { ... }` |
| Change state directory | `stateDir` | `"/opt/mibyan"` |
| Set the agent's working directory | `workingDirectory` | `"/home/user/projects"` |

***

## Secrets Management

<Warning>
  **Never put API keys in `settings` or `environment`**

  Values in Nix expressions end up in `/nix/store`, which is world-readable. Always use `environmentFiles` with a secrets manager.
</Warning>

Both `environment` (non-secret vars) and `environmentFiles` (secret files) are merged into `$mibyan_HOME/.env` at activation time (`nixos-rebuild switch`). Mibyan reads this file on every startup, so changes take effect with a `systemctl restart mibyan-agent` — no container recreation needed.

### sops-nix

```nix theme={null}
{
  sops = {
    defaultSopsFile = ./secrets/mibyan.yaml;
    age.keyFile = "/home/user/.config/sops/age/keys.txt";
    secrets."mibyan-env" = { format = "yaml"; };
  };

  services.mibyan-agent.environmentFiles = [
    config.sops.secrets."mibyan-env".path
  ];
}
```

The secrets file contains key-value pairs:

```yaml theme={null}
# secrets/mibyan.yaml (encrypted with sops)
mibyan-env: |
    OPENROUTER_API_KEY=sk-or-...
    TELEGRAM_BOT_TOKEN=123456:ABC...
    ANTHROPIC_API_KEY=sk-ant-...
```

### agenix

```nix theme={null}
{
  age.secrets.mibyan-env.file = ./secrets/mibyan-env.age;

  services.mibyan-agent.environmentFiles = [
    config.age.secrets.mibyan-env.path
  ];
}
```

### OAuth / Auth Seeding

For platforms requiring OAuth (e.g., Discord), use `authFile` to seed credentials on first deploy:

```nix theme={null}
{
  services.mibyan-agent = {
    authFile = config.sops.secrets."mibyan/auth.json".path;
    # authFileForceOverwrite = true;  # overwrite on every activation
  };
}
```

The file is only copied if `auth.json` doesn't already exist (unless `authFileForceOverwrite = true`). Runtime OAuth token refreshes are written to the state directory and preserved across rebuilds.

***

## Documents

Mibyan reads files from two directories. Thus there are two options. Use the option for the directory that the file must go into.

`documents` installs into the **working directory** of the agent, which is `workingDirectory`. The agent reads its project context from that workspace:

```nix theme={null}
{
  services.mibyan-agent = {
    # documents needs this option. Read the note below.
    workingDirectory = "/var/lib/mibyan/workspace";
    documents = {
      "AGENTS.md" = ./documents/AGENTS.md;   # path reference, copied from Nix store
      "notes/oncall.md" = "Page #infra before restarting anything.";
    };
  };
}
```

<Warning>
  **documents needs an explicit workingDirectory**

  The module refuses `documents` until you set `workingDirectory`. The default of
  that option is different on each module. It is your home directory on Home
  Manager, and `${stateDir}/workspace` on NixOS. Thus an unset default puts the
  files in a directory that you did not select. A directory with the same path as
  the default is a correct selection, and it satisfies the rule.
</Warning>

`mibyanHomeFiles` installs into **`mibyan_HOME`**. Mibyan reads the identity file and the memory files of the agent from that directory. `SOUL.md` and `memories/` work only from there. A `SOUL.md` in `documents` makes a workspace file. Mibyan does not load that file as the identity:

```nix theme={null}
{
  services.mibyan-agent.mibyanHomeFiles = {
    "SOUL.md" = "You are a helpful AI assistant.";
    "memories/USER.md" = ./documents/USER.md;
  };
}
```

Each value is a string or a path. A key in either option can contain subdirectories, and the module makes the parent directories. Each activation installs the files again.

`mibyanHomeFiles` needs no `workingDirectory`, because the module owns the `mibyan_HOME` directory. Most users want `mibyanHomeFiles`.

***

## MCP Servers

The `mcpServers` option declaratively configures [MCP (Model Context Protocol)](https://modelcontextprotocol.io) servers. Each server uses either **stdio** (local command) or **HTTP** (remote URL) transport.

### Stdio Transport (Local Servers)

```nix theme={null}
{
  services.mibyan-agent.mcpServers = {
    filesystem = {
      command = "npx";
      args = [ "-y" "@modelcontextprotocol/server-filesystem" "/data/workspace" ];
    };
    github = {
      command = "npx";
      args = [ "-y" "@modelcontextprotocol/server-github" ];
      env.GITHUB_PERSONAL_ACCESS_TOKEN = "\${GITHUB_TOKEN}"; # resolved from .env
    };
  };
}
```

<Tip>
  Environment variables in `env` values are resolved from `$mibyan_HOME/.env` at runtime. Use `environmentFiles` to inject secrets — never put tokens directly in Nix config.
</Tip>

### HTTP Transport (Remote Servers)

```nix theme={null}
{
  services.mibyan-agent.mcpServers.remote-api = {
    url = "https://mcp.example.com/v1/mcp";
    headers.Authorization = "Bearer \${MCP_REMOTE_API_KEY}";
    timeout = 180;
  };
}
```

### HTTP Transport with OAuth

Set `auth = "oauth"` for servers using OAuth 2.1. Mibyan implements the full PKCE flow — metadata discovery, dynamic client registration, token exchange, and automatic refresh.

```nix theme={null}
{
  services.mibyan-agent.mcpServers.my-oauth-server = {
    url = "https://mcp.example.com/mcp";
    auth = "oauth";
  };
}
```

Tokens are stored in `$mibyan_HOME/mcp-tokens/<server-name>.json` and persist across restarts and rebuilds.

<details>
  <summary><strong>Initial OAuth authorization on headless servers</strong></summary>

  The first OAuth authorization requires a browser-based consent flow. In a headless deployment, Mibyan prints the authorization URL to stdout/logs instead of opening a browser.

  **Option A: Interactive bootstrap** — run the flow once via `docker exec` (container) or `sudo -u mibyan` (native):

  ```bash theme={null}
  # Container mode
  docker exec -it mibyan-agent \
    mibyan mcp add my-oauth-server --url https://mcp.example.com/mcp --auth oauth

  # Native mode
  sudo -u mibyan mibyan_HOME=/var/lib/mibyan/.mibyan \
    mibyan mcp add my-oauth-server --url https://mcp.example.com/mcp --auth oauth
  ```

  The container uses `--network=host`, so the OAuth callback listener on `127.0.0.1` is reachable from the host browser.

  **Option B: Pre-seed tokens** — complete the flow on a workstation, then copy tokens:

  ```bash theme={null}
  mibyan mcp add my-oauth-server --url https://mcp.example.com/mcp --auth oauth
  scp ~/.mibyan/mcp-tokens/my-oauth-server{,.client}.json \
      server:/var/lib/mibyan/.mibyan/mcp-tokens/
  # Ensure: chown mibyan:mibyan, chmod 0600
  ```
</details>

### Sampling (Server-Initiated LLM Requests)

Some MCP servers can request LLM completions from the agent:

```nix theme={null}
{
  services.mibyan-agent.mcpServers.analysis = {
    command = "npx";
    args = [ "-y" "analysis-server" ];
    sampling = {
      enabled = true;
      model = "google/gemini-3-flash";
      max_tokens_cap = 4096;
      timeout = 30;
      max_rpm = 10;
    };
  };
}
```

***

## Managed Mode

When mibyan runs via the NixOS module, the following CLI commands are **blocked** with a descriptive error pointing you to `configuration.nix`:

| Blocked command | Why |
| - | - |
| `mibyan setup` | Config is declarative — edit `settings` in your Nix config |
| `mibyan config edit` | Config is generated from `settings` |
| `mibyan config set <key> <value>` | Config is generated from `settings` |
| `mibyan gateway install` | The systemd service is managed by NixOS |
| `mibyan gateway uninstall` | The systemd service is managed by NixOS |

This prevents drift between what Nix declares and what's on disk. Detection uses two signals:

1. **The `mibyan_MANAGED` environment variable.** The service sets it, and the gateway process reads it.
2. **The `.managed` marker file** in `mibyan_HOME`. The activation script writes it, and an interactive shell reads it. Thus the CLI also blocks a command such as `docker exec -it mibyan-agent mibyan config set ...`.

Both signals hold the name of the system that manages the install. Thus the refusal names the correct rebuild command. The NixOS module gives `sudo nixos-rebuild switch`. The Home Manager module gives `home-manager switch`.

***

## Home Manager Module

The flake also exports `homeManagerModules.default`. Mibyan is an agent for one person. The credentials, the memory, the sessions and the cron jobs all belong to that person. Thus a user service is the correct shape on a personal machine. It runs on each distribution that Home Manager supports, and not only on NixOS.

The option set is the same set that the NixOS module uses. It is `services.mibyan-agent`, with the same `settings`, `environmentFiles`, `documents`, `mcpServers`, `extraPlugins` and `backend` options. Each example above works here without a change. Only the necessary parts are different:

| | NixOS module | Home Manager module |
| - | - | - |
| Runs as | a system user that you declare, with `user`, `group` and `createUser` | you |
| State directory | `stateDir` and `/.mibyan` | `mibyanHome`, set directly. The default is `~/.mibyan`. |
| Service | `systemd.services` | `systemd.user.services` on Linux, `launchd.agents` on macOS |
| CLI on the PATH | `addToSystemPackages`, which exports `mibyan_HOME` for the full system | `programs.mibyan-agent.enable`, which exports it for your session only |
| Desktop application | not supported, because a system service cannot own a user session | `programs.mibyan-agent.desktop.enable` |
| Container mode | supported | not supported, because it needs root and the Docker socket |

### Add the Flake Input

```nix theme={null}
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    home-manager.url = "github:nix-community/home-manager";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
    mibyan-agent.url = "github:NousResearch/hermes-agent";
  };
}
```

Then import the module into your Home Manager configuration. The configuration can be standalone. It can also be under `home-manager.users.<name>` in a NixOS or nix-darwin configuration:

```nix theme={null}
{
  imports = [ mibyan-agent.homeManagerModules.default ];

  services.mibyan-agent = {
    enable = true;
    gateway.enable = true;
    settings.model.default = "anthropic/claude-sonnet-4";
    environmentFiles = [ config.sops.secrets."mibyan-env".path ];
  };
}
```

`home-manager switch` makes `~/.mibyan`, writes `config.yaml`, builds `.env` and starts the gateway as a user service.

<Warning>
  **Enable linger, or the service stops at logout**

  CAUTION: Enable linger for your account. Without linger, systemd stops the user manager when your last session ends, and the gateway stops with it. Home Manager cannot set linger, because linger is a property of the account:

  ```nix theme={null}
  # NixOS
  users.users.your-username.linger = true;
  ```

  ```bash theme={null}
  # anywhere else
  sudo loginctl enable-linger your-username
  ```

  macOS has no equivalent option. A `launchd` agent with `RunAtLoad` starts at login and continues to run.
</Warning>

### Running the Desktop / Dashboard Backend

`gateway.enable` runs the messaging gateway for Telegram, Discord, Slack and the other platforms. Mibyan Desktop and the web dashboard connect to a *different* process, which is `mibyan serve` or `mibyan dashboard`. `backend.mode` runs that process with the gateway:

```nix theme={null}
{
  services.mibyan-agent = {
    enable = true;
    gateway.enable = true;      # messaging platforms
    backend.mode = "dashboard"; # + the browser dashboard on 127.0.0.1:9119
    backend.port = 9119;
  };
}
```

`serve` runs without a user interface. It gives the `/api/ws` and `/api/pty` sockets that Mibyan Desktop connects to, and it does not build the web application. `dashboard` gives all of that, and also serves the browser admin panel. Both processes use one `mibyan_HOME` with the gateway. Thus the sessions, the skills, the memory and the cron jobs are the same for all of them. `backend.mode` works in the same way on the NixOS module, but not in container mode.

<Warning>
  **Binding to an address other than loopback**

  The default address is `127.0.0.1`. Each other address starts the authentication gate of the dashboard. The server also refuses each request with a `Host` header that is different from the address that the server bound to. This is a defence against DNS rebinding. Bind to the name or the address that your client uses.
</Warning>

### Verify It Works

```bash theme={null}
# Linux
systemctl --user status mibyan-agent
journalctl --user -u mibyan-agent -f

# macOS
launchctl list | grep mibyan
tail -f ~/Library/Logs/mibyan-agent.log

mibyan --version
mibyan config     # shows the configuration that Nix wrote
```

***

## Container Architecture

<Info>
  This section is only relevant if you're using `container.enable = true`. Skip it for native mode deployments.
</Info>

When container mode is enabled, mibyan runs inside a persistent Ubuntu container with the Nix-built binary bind-mounted read-only from the host:

```
Host                                    Container
────                                    ─────────
/nix/store/...-mibyan-agent-0.1.0  ──►  /nix/store/... (ro)
~/.mibyan -> /var/lib/mibyan/.mibyan       (symlink bridge, per hostUsers)
/var/lib/mibyan/                    ──►  /data/          (rw)
  ├── current-package -> /nix/store/...    (symlink, updated each rebuild)
  ├── .gc-root -> /nix/store/...           (prevents nix-collect-garbage)
  ├── .container-identity                  (sha256 hash, triggers recreation)
  ├── .mibyan/                             (mibyan_HOME)
  │   ├── .env                             (merged from environment + environmentFiles)
  │   ├── config.yaml                      (Nix-generated, deep-merged by activation)
  │   ├── .managed                         (marker file)
  │   ├── .container-mode                  (routing metadata: backend, exec_user, etc.)
  │   ├── state.db, sessions/, memories/   (runtime state)
  │   └── mcp-tokens/                      (OAuth tokens for MCP servers)
  ├── home/                                ──►  /home/mibyan    (rw)
  └── workspace/                           (agent working directory)
      ├── AGENTS.md                        (from the documents option)
      └── (agent-created files)

Container writable layer (apt/pip/npm):   /usr, /usr/local, /tmp
```

The Nix-built binary works inside the Ubuntu container because `/nix/store` is bind-mounted — it brings its own interpreter and all dependencies, so there's no reliance on the container's system libraries. The container entrypoint resolves through a `current-package` symlink: `/data/current-package/bin/mibyan gateway run --replace`. On `nixos-rebuild switch`, only the symlink is updated — the container keeps running.

### What Persists Across What

| Event | Container recreated? | `/data` (state) | `/home/mibyan` | Writable layer (`apt`/`pip`/`npm`) |
| - | - | - | - | - |
| `systemctl restart mibyan-agent` | No | Persists | Persists | Persists |
| `nixos-rebuild switch` (code change) | No (symlink updated) | Persists | Persists | Persists |
| Host reboot | No | Persists | Persists | Persists |
| `nix-collect-garbage` | No (GC root) | Persists | Persists | Persists |
| Image change (`container.image`) | **Yes** | Persists | Persists | **Lost** |
| Volume/options change | **Yes** | Persists | Persists | **Lost** |
| `environment`/`environmentFiles` change | No | Persists | Persists | Persists |

The container is only recreated when its **identity hash** changes. The hash covers: schema version, image, `extraVolumes`, `extraOptions`, and the entrypoint script. Changes to environment variables, settings, documents, or the mibyan package itself do **not** trigger recreation.

<Warning>
  **Writable layer loss**

  When the identity hash changes (image upgrade, new volumes, new container options), the container is destroyed and recreated from a fresh pull of `container.image`. Any `apt install`, `pip install`, or `npm install` packages in the writable layer are lost. State in `/data` and `/home/mibyan` is preserved (these are bind mounts).

  If the agent relies on specific packages, consider baking them into a custom image (`container.image = "my-registry/mibyan-base:latest"`) or scripting their installation in the agent's SOUL.md.
</Warning>

### GC Root Protection

The `preStart` script creates a GC root at `${stateDir}/.gc-root` pointing to the current mibyan package. This prevents `nix-collect-garbage` from removing the running binary. If the GC root somehow breaks, restarting the service recreates it.

***

## Plugins

The NixOS module supports declarative plugin installation — no imperative `mibyan plugins install` needed.

### Directory Plugins (`extraPlugins`)

For plugins that are just a source tree with `plugin.yaml` + `__init__.py` (e.g., [mibyan-lcm](https://github.com/stephenschoettler/hermes-lcm)):

```nix theme={null}
services.mibyan-agent.extraPlugins = [
  (pkgs.fetchFromGitHub {
    owner = "stephenschoettler";
    repo = "mibyan-lcm";
    rev = "v0.7.0";
    hash = "sha256-...";
  })
];
```

Plugins are symlinked into `$mibyan_HOME/plugins/` at activation time. Mibyan discovers them via its normal directory scan. Removing a plugin from the list and running `nixos-rebuild switch` removes the symlink.

### Entry-Point Plugins (`extraPythonPackages`)

For pip-packaged plugins that register via `[project.entry-points."mibyan_agent.plugins"]` (e.g., [rtk-mibyan](https://github.com/ogallotti/rtk-hermes)):

```nix theme={null}
services.mibyan-agent.extraPythonPackages = [
  (config.services.mibyan-agent.package.python.pkgs.buildPythonPackage {
    pname = "rtk-mibyan";
    version = "1.0.0";
    src = pkgs.fetchFromGitHub {
      owner = "ogallotti";
      repo = "rtk-mibyan";
      rev = "v1.0.0";
      hash = "sha256-...";
    };
    format = "pyproject";
    build-system = [ config.services.mibyan-agent.package.python.pkgs.setuptools ];
  })
];
```

The package's `site-packages` is added to PYTHONPATH in the mibyan wrapper. `importlib.metadata` discovers the entry point at session start.

### Optional Dependency Groups (`extraDependencyGroups`)

For optional extras declared in mibyan-agent's `pyproject.toml`, use `extraDependencyGroups` to include them in the sealed venv at build time. This is required for any extra not in the default `[all]` set — on Nix, runtime installation into the read-only store is not possible.

```nix theme={null}
# Enable Discord, Telegram, Slack
services.mibyan-agent.extraDependencyGroups = [ "messaging" ];
```

```nix theme={null}
# Enable a memory provider
services.mibyan-agent = {
  extraDependencyGroups = [ "honcho" ];
  settings.memory.provider = "honcho";
};
```

These groups join the core dependency resolution at build time. Conflicting
requirements can still fail that resolution. The table lists common groups;
`pyproject.toml` is authoritative for the complete list and platform markers.

| Group | What it enables |
| - | - |
| `messaging` | Discord, Telegram, Slack |
| `matrix` | Matrix/Element (mautrix with encryption; Linux only) |
| `dingtalk` | DingTalk |
| `feishu` | Feishu/Lark |
| `voice` | Local speech-to-text (faster-whisper) |
| `edge-tts` | Edge TTS provider |
| `tts-premium` | ElevenLabs TTS |
| `anthropic` | Native Anthropic SDK (not needed via OpenRouter) |
| `bedrock` | AWS Bedrock (boto3) |
| `azure-identity` | Azure Entra ID auth |
| `honcho` | Honcho memory provider |
| `modal` | Modal terminal backend |
| `daytona` | Daytona terminal backend |
| `exa` | Exa web search |
| `firecrawl` | Firecrawl web search |
| `fal` | FAL image generation |

Memory providers that live in the [plugin catalog](/desktop/user-guide/features/plugins) rather than in the Mibyan tree (e.g. Hindsight) are not extras. Install them like any catalog plugin with `mibyan plugins install hindsight`, or declaratively via [`extraPlugins`](#directory-plugins-extraplugins) pointing at the plugin's source tree.

Or use the pre-built `#messaging` or `#full` flake packages instead of per-extra configuration (see [Quick Start](#quick-start-any-nix-user)).

**When to use which:**

| Need | Option |
| - | - |
| Enable a pyproject.toml optional extra | `extraDependencyGroups` |
| Add an external Python plugin not in pyproject.toml | `extraPythonPackages` |
| Add a system binary (pandoc, jq, etc.) | `extraPackages` |
| Add a directory-based plugin source tree | `extraPlugins` |

### Combining Both

A directory plugin with third-party Python dependencies needs both options:

```nix theme={null}
services.mibyan-agent = {
  extraPlugins = [ my-plugin-src ];          # plugin source
  extraPythonPackages = [ config.services.mibyan-agent.package.python.pkgs.redis ];  # its Python dep
  extraPackages = [ pkgs.redis ];            # system binary it needs
};
```

### Using the Overlay

External flakes can override the package directly:

```nix theme={null}
{
  inputs.mibyan-agent.url = "github:NousResearch/hermes-agent";
  outputs = { mibyan-agent, nixpkgs, ... }: {
    nixpkgs.overlays = [ mibyan-agent.overlays.default ];
    # Then:
    #   pkgs.mibyan-agent.override { extraPythonPackages = [...]; }
    #   pkgs.mibyan-agent.override { extraDependencyGroups = [ "honcho" ]; }
  };
}
```

### Plugin Configuration

Plugins still need to be enabled in `config.yaml`. Add them via the declarative settings:

```nix theme={null}
services.mibyan-agent.settings.plugins.enabled = [
  "mibyan-lcm"
  "rtk-rewrite"
];
```

<Note>
  A build-time collision check prevents plugin packages from shadowing core mibyan dependencies. If a plugin provides a package already in the sealed venv, `nixos-rebuild` fails with a clear error.
</Note>

***

## Development

### Dev Shell

The flake provides an editable Python environment with the lock-derived interpreter
and the `dev` dependency group. `mibyan_PYTHON` points to its interpreter. It does not install
Python dependencies into a repository-local `.venv`. The shell also provides
Node.js and runtime tools. Its npm hook refreshes JS workspaces when their inputs change.

```bash theme={null}
cd mibyan-agent
nix develop
"$mibyan_PYTHON" -c "import sys; print(sys.executable); print(sys.version)"
mibyan setup
mibyan chat
```

### direnv (Recommended)

The included `.envrc` activates the dev shell automatically:

```bash theme={null}
cd mibyan-agent
direnv allow    # one-time
# Nix reuses its built Python environment; the npm hook checks JS inputs.
```

### Flake Checks

The flake includes build-time verification that runs in CI and locally:

```bash theme={null}
# Run all checks
nix flake check

# Individual checks
nix build .#checks.x86_64-linux.package-contents   # binaries exist + version
nix build .#checks.x86_64-linux.entry-points-sync  # pyproject.toml ↔ Nix package sync
nix build .#checks.x86_64-linux.cli-commands        # gateway/config subcommands
nix build .#checks.x86_64-linux.managed-guard       # mibyan_MANAGED blocks mutation
nix build .#checks.x86_64-linux.bundled-skills      # skills present in package
nix build .#checks.x86_64-linux.config-roundtrip    # merge script preserves user keys
```

<details>
  <summary><strong>What each check verifies</strong></summary>

  | Check | What it tests |
  | - | - |
  | `package-contents` | `mibyan` and `mibyan-agent` binaries exist and `mibyan --version` runs |
  | `entry-points-sync` | Every `[project.scripts]` entry in `pyproject.toml` has a wrapped binary in the Nix package |
  | `cli-commands` | `mibyan --help` exposes `gateway` and `config` subcommands |
  | `managed-guard` | `mibyan_MANAGED=true mibyan config set ...` prints the NixOS error |
  | `bundled-skills` | Skills directory exists, contains SKILL.md files, `mibyan_BUNDLED_SKILLS` is set in wrapper |
  | `config-roundtrip` | 7 merge scenarios: fresh install, Nix override, user key preservation, mixed merge, MCP additive merge, nested deep merge, idempotency |
</details>

***

## Options Reference

### Core

| Option | Type | Default | Description |
| - | - | - | - |
| `enable` | `bool` | `false` | Enable the mibyan-agent service |
| `package` | `package` | `mibyan-agent` | The mibyan-agent package to use |
| `user` | `str` | `"mibyan"` | System user |
| `group` | `str` | `"mibyan"` | System group |
| `createUser` | `bool` | `true` | Auto-create user/group |
| `stateDir` | `str` | `"/var/lib/mibyan"` | State directory (`mibyan_HOME` parent) |
| `workingDirectory` | `str` | `"${stateDir}/workspace"` | Agent working directory |
| `addToSystemPackages` | `bool` | `false` | Add `mibyan` CLI to system PATH and set `mibyan_HOME` system-wide |

### Configuration

| Option | Type | Default | Description |
| - | - | - | - |
| `settings` | `attrs` (deep-merged) | `{}` | Declarative config rendered as `config.yaml`. Supports arbitrary nesting; multiple definitions are merged via `lib.recursiveUpdate` |
| `configFile` | `null` or `path` | `null` | Path to an existing `config.yaml`. Overrides `settings` entirely if set |

### Secrets & Environment

| Option | Type | Default | Description |
| - | - | - | - |
| `environmentFiles` | `listOf str` | `[]` | Paths to env files with secrets. Merged into `$mibyan_HOME/.env` at activation time |
| `environment` | `attrsOf str` | `{}` | Non-secret env vars. **Visible in Nix store** — do not put secrets here |
| `authFile` | `null` or `path` | `null` | OAuth credentials seed. Only copied on first deploy |
| `authFileForceOverwrite` | `bool` | `false` | Always overwrite `auth.json` from `authFile` on activation |

### Documents

| Option | Type | Default | Description |
| - | - | - | - |
| `documents` | `attrsOf (either str path)` | `{}` | Workspace files. Each key is a path relative to `workingDirectory`. You must set that option to use this one. |
| `mibyanHomeFiles` | `attrsOf (either str path)` | `{}` | Files that go into `mibyan_HOME`. `SOUL.md` and `memories/` must be here, or Mibyan does not load them. |

### MCP Servers

| Option | Type | Default | Description |
| - | - | - | - |
| `mcpServers` | `attrsOf submodule` | `{}` | MCP server definitions, merged into `settings.mcp_servers` |
| `mcpServers.<name>.command` | `null` or `str` | `null` | Server command (stdio transport) |
| `mcpServers.<name>.args` | `listOf str` | `[]` | Command arguments |
| `mcpServers.<name>.env` | `attrsOf str` | `{}` | Environment variables for the server process |
| `mcpServers.<name>.url` | `null` or `str` | `null` | Server endpoint URL (HTTP/StreamableHTTP transport) |
| `mcpServers.<name>.headers` | `attrsOf str` | `{}` | HTTP headers, e.g. `Authorization` |
| `mcpServers.<name>.auth` | `null` or `"oauth"` | `null` | Authentication method. `"oauth"` enables OAuth 2.1 PKCE |
| `mcpServers.<name>.enabled` | `bool` | `true` | Enable or disable this server |
| `mcpServers.<name>.timeout` | `null` or `int` | `null` | Tool call timeout in seconds (default: 120) |
| `mcpServers.<name>.connect_timeout` | `null` or `int` | `null` | Connection timeout in seconds (default: 60) |
| `mcpServers.<name>.tools` | `null` or `submodule` | `null` | Tool filtering (`include`/`exclude` lists) |
| `mcpServers.<name>.sampling` | `null` or `submodule` | `null` | Sampling config for server-initiated LLM requests |

### Service Behavior

| Option | Type | Default | Description |
| - | - | - | - |
| `extraArgs` | `listOf str` | `[]` | Extra args for `mibyan gateway` |
| `extraPackages` | `listOf package` | `[]` | Extra packages available to the agent. Added to the mibyan user's per-user profile so terminal commands, skills, and cron jobs all see them |
| `extraPlugins` | `listOf package` | `[]` | Directory plugin packages to symlink into `$mibyan_HOME/plugins/`. Each must contain `plugin.yaml` |
| `extraPythonPackages` | `listOf package` | `[]` | Python packages added to PYTHONPATH for entry-point plugin discovery. Use the selected package’s `python.pkgs` |
| `extraDependencyGroups` | `listOf str` | `[]` | pyproject.toml optional extras to include in the sealed venv (e.g. `["honcho"]`). Resolved by uv — no collisions |
| `restart` | `str` | `"always"` | The systemd `Restart=` policy. macOS does not use it. |
| `restartSec` | `int` | `5` | The systemd `RestartSec=` value. macOS does not use it. |

### Backend (`mibyan serve` / `mibyan dashboard`)

This option runs the process that Mibyan Desktop and the web dashboard connect to, with the gateway. You cannot use it with `container.enable`.

| Option | Type | Default | Description |
| - | - | - | - |
| `backend.mode` | `enum ["none" "serve" "dashboard"]` | `"none"` | `serve` runs without a user interface and gives `/api/ws` and `/api/pty`. `dashboard` also serves the browser panel. |
| `backend.host` | `str` | `"127.0.0.1"` | The address to bind to. Each address other than loopback starts the authentication gate. |
| `backend.port` | `port` | `9119` | The port to bind to |
| `backend.extraArgs` | `listOf str` | `[]` | More arguments for the backend command |

### Home Manager only

| Option | Type | Default | Description |
| - | - | - | - |
| `mibyanHome` | `str` | `"${config.home.homeDirectory}/.mibyan"` | `mibyan_HOME` directly. The NixOS module builds it from `stateDir`. |
| `gateway.enable` | `bool` | `false` | Run the messaging gateway. On the NixOS module the gateway is the service, so that module has no such option. |

### `programs.mibyan-agent` (Home Manager only)

Home Manager separates "install this application for me" from "run this
daemon". `services.mibyan-agent` keeps the state, the configuration and the
daemons. `programs.mibyan-agent` installs what you use, and reads
`mibyanHome` and the backend address from the services.

| Option | Type | Default | Description |
| - | - | - | - |
| `enable` | `bool` | `false` | Add the `mibyan` CLI to `home.packages`, and export `mibyan_HOME` for your shells |
| `package` | `package` | `services.mibyan-agent.package` | The package to install. The default applies `extraPythonPackages` and `extraDependencyGroups` from the services, so both are one build. |
| `desktop.enable` | `bool` | `false` | Add the Mibyan Desktop application, with a launcher entry on Linux |
| `desktop.package` | `package` | `package.mibyanDesktop` | The desktop package. The default follows `package`, so the application and the services run one Mibyan runtime. |

```nix theme={null}
programs.mibyan-agent = {
  enable = true;
  desktop.enable = true;
};

services.mibyan-agent = {
  enable = true;
  backend.mode = "serve";
  backend.sessionTokenFile = config.sops.secrets."mibyan/desktop-token".path;
};
```

The launcher carries `mibyan_HOME` itself. A desktop menu reads no shell
profile, so the value that `programs.mibyan-agent.enable` exports with
`home.sessionVariables` reaches an interactive shell only. Without the
value in the launcher, the application opens `~/.mibyan` while the
services use `mibyanHome`, and you see no sessions and no keys.

With `backend.sessionTokenFile`, the application connects to the backend
of the service instead of starting one of its own. Both sides read the
file at start time, so the token enters no Nix store path. Without the
option, each side runs its own backend.

`services.mibyan-agent.installPackage` was removed by this split. A
configuration that still sets it gets an error that names the
replacement.

### Container (NixOS only)

| Option | Type | Default | Description |
| - | - | - | - |
| `container.enable` | `bool` | `false` | Enable OCI container mode |
| `container.backend` | `enum ["docker" "podman"]` | `"docker"` | Container runtime |
| `container.image` | `str` | `"ubuntu:24.04"` | Base image (pulled at runtime) |
| `container.extraVolumes` | `listOf str` | `[]` | Extra volume mounts (`host:container:mode`) |
| `container.extraOptions` | `listOf str` | `[]` | Extra args passed to `docker create` |
| `container.hostUsers` | `listOf str` | `[]` | Interactive users who get a `~/.mibyan` symlink to the service stateDir and are auto-added to the `mibyan` group |

***

## Directory Layout

### Native Mode

```
/var/lib/mibyan/                     # stateDir (owned by mibyan:mibyan, 0750)
├── .mibyan/                         # mibyan_HOME
│   ├── SOUL.md                      # from mibyanHomeFiles: the agent identity
│   ├── config.yaml                  # Nix-generated (deep-merged each rebuild)
│   ├── .managed                     # Marker: CLI config mutation blocked
│   ├── .env                         # Merged from environment + environmentFiles
│   ├── auth.json                    # OAuth credentials (seeded, then self-managed)
│   ├── gateway.pid
│   ├── state.db
│   ├── mcp-tokens/                  # OAuth tokens for MCP servers
│   ├── sessions/
│   ├── memories/
│   ├── skills/
│   ├── cron/
│   └── logs/
├── home/                            # Agent HOME
└── workspace/                       # Agent working directory
    ├── AGENTS.md                    # from the documents option
    └── (agent-created files)
```

### Home Manager

```
~/.mibyan/                           # mibyanHome (mibyan_HOME), 0700
├── SOUL.md                          # from mibyanHomeFiles
├── config.yaml                      # written by Nix, merged at each activation
├── .managed                         # marker: names the system that manages this
├── .env                             # written again from environment + environmentFiles
├── auth.json                        # OAuth credentials: seeded, then Mibyan owns it
├── memories/  sessions/  skills/  cron/  logs/  plugins/
└── (runtime state)

~/                                   # workingDirectory, your home by default
└── AGENTS.md                        # from the documents option
```

### Container Mode

Same layout, mounted into the container:

| Container path | Host path | Mode | Notes |
| - | - | - | - |
| `/nix/store` | `/nix/store` | `ro` | Mibyan binary + all Nix deps |
| `/data` | `/var/lib/mibyan` | `rw` | All state, config, workspace |
| `/home/mibyan` | `${stateDir}/home` | `rw` | Persistent agent home — `pip install --user`, tool caches |
| `/usr`, `/usr/local`, `/tmp` | (writable layer) | `rw` | `apt`/`pip`/`npm` installs — persists across restarts, lost on recreation |

***

## Updating

```bash theme={null}
# Update the flake input (run from the directory containing flake.nix)
cd /etc/nixos && nix flake update mibyan-agent

# Rebuild
sudo nixos-rebuild switch          # for the NixOS module
home-manager switch                # for the Home Manager module
```

In container mode, the `current-package` symlink is updated and the agent picks up the new binary on restart. No container recreation, no loss of installed packages.

***

## Troubleshooting

<Tip>
  **Podman users**

  All `docker` commands below work the same with `podman`. Substitute accordingly if you set `container.backend = "podman"`.
</Tip>

### Service Logs

```bash theme={null}
# Both modes use the same systemd unit
journalctl -u mibyan-agent -f

# Container mode: also available directly
docker logs -f mibyan-agent
```

### Container Inspection

```bash theme={null}
systemctl status mibyan-agent
docker ps -a --filter name=mibyan-agent
docker inspect mibyan-agent --format='{{.State.Status}}'
docker exec -it mibyan-agent bash
docker exec mibyan-agent readlink /data/current-package
docker exec mibyan-agent cat /data/.container-identity
```

### Force Container Recreation

If you need to reset the writable layer (fresh Ubuntu):

```bash theme={null}
sudo systemctl stop mibyan-agent
docker rm -f mibyan-agent
sudo rm /var/lib/mibyan/.container-identity
sudo systemctl start mibyan-agent
```

### Verify Secrets Are Loaded

If the agent starts but can't authenticate with the LLM provider, check that the `.env` file was merged correctly:

```bash theme={null}
# Native mode
sudo -u mibyan cat /var/lib/mibyan/.mibyan/.env

# Container mode
docker exec mibyan-agent cat /data/.mibyan/.env
```

### GC Root Verification

```bash theme={null}
nix-store --query --roots $(docker exec mibyan-agent readlink /data/current-package)
```

### Common Issues

| Symptom | Cause | Fix |
| - | - | - |
| `Cannot save configuration: managed by NixOS` | CLI guards active | Edit `configuration.nix` and `nixos-rebuild switch` |
| `No adapter available for discord` (or telegram/slack) | Messaging deps missing from the sealed Nix venv | Install `#messaging` variant: `nix profile install ...#messaging`. For NixOS module: `extraDependencyGroups = [ "messaging" ]`. Read `journalctl -u mibyan-agent` for `InstallError` or `requirements not met` and the underlying cause. |
| Container recreated unexpectedly | `extraVolumes`, `extraOptions`, or `image` changed | Expected — writable layer resets. Reinstall packages or use a custom image |
| `mibyan --version` shows old version | Container not restarted | `systemctl restart mibyan-agent` |
| Permission denied on `/var/lib/mibyan` | State dir is `0750 mibyan:mibyan` | Use `docker exec` or `sudo -u mibyan` |
| `nix-collect-garbage` removed mibyan | GC root missing | Restart the service (preStart recreates the GC root) |
| `no container with name or ID "mibyan-agent"` (Podman) | Podman rootful container not visible to regular user | Add passwordless sudo for podman (see [Container Mode](#container-mode) section) |
| `unable to find user mibyan` | Container still starting (entrypoint hasn't created user yet) | Wait a few seconds and retry — the CLI retries automatically |
| Tool added via `extraPackages` not found in terminal | Requires `nixos-rebuild switch` to update the per-user profile | Rebuild and restart: `nixos-rebuild switch && systemctl restart mibyan-agent` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.