# Layer

One reusable slice. The file is `layers/<name>/default.nix`. The name is the directory or the `rig add` input. The file is the module body.

```
{ pkgs, ... }:
{
  description = "What this slice is for.";
  packages = [ pkgs.jq ];
  files."AGENTS.md" = {
    source = ./AGENTS.md;
    mergeable = true;
  };
}
```

A layer that needs another layer lists modules, not strings:

```
{ layers, pkgs, ... }:
{
  description = "GitLab CLI and forge conventions.";
  compose = [ layers.git ];
  packages = [ pkgs.glab ];
}
```

`compose = [ layers.git ]` fails eval unless `git` is a workspace input or a local layer. `rig add` does not add those dependencies for you.

## Fields

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| description | string | empty | Human-readable description. |
| image | null or string | `null` | Canonical published image as `host/name:tag`. |
| compose | list of module | `[]` | Layer modules composed before this one. A cycle fails eval. |
| packages | list of package | `[]` | Nix packages on PATH in the session root. Unioned in compose order. |
| skills | list of skill | `[]` | Native skills. Later compose entries win on the same skill name. First-seen order is kept. |
| models | submodule | empty | `default` and `providers.<id>`. Later compose entries override the same provider id and a non-null default. |
| mcp | attrs | `{}` | stdio servers. Later compose entries win on the same server name. |
| services | attrs of service | `{}` | Session processes. Later compose entries win on the same name. |
| skillLinks | list of string | `[]` | Relative paths under the agent root. Each path becomes a symlink to `skills/`. |
| secrets | attrs of secret | `{}` | SecretSpec keys. |
| privileged | list of string | `[]` | Process ids such as `harness:`, `service:`, `mcp:` that get real secret env. Only `agent.layers.secretGuard.privileged` is consumed. Other layers append. `lib.mkForce` replaces. |
| sandbox.network.allow | list of attr | `[]` | Egress allows. Each item has exactly one of `domain`, `ip`, or `cidr`. Default deny. |
| sandbox.network.deny | list of attr | `[]` | Egress denies. Applied after allows. A deny removes a matching allow key. |
| sandbox.fs.writeAllow | list of string | `[]` | Filesystem write allow prefixes. Default deny. |
| sandbox.fs.writeDeny | list of string | `[]` | Filesystem write denies. A deny removes a matching allow path. |
| files | attrs | `{}` | Files in the agent home. Each value has `source` path and `mergeable` bool, default `false`. Same path with different bytes fails the build unless the path is mergeable. `AGENTS.md` is mergeable even when unset. Mergeable files concatenate with a `<!-- rig:layer:<name> -->` marker. Paths under `triggers/` are rejected. |
| hooks | list | `[]` | `id` plus `command`. Concatenated in stack order. Each harness maps the ids it knows. |
| tools | attrs | `{}` | Harness tools. Attr key is the tool name and must match `[a-zA-Z0-9_-]+`. Each value has `description` and a TypeScript `source` path. Later compose entries win on the same name. The `opencode` layer writes `.opencode/tools/<name>.ts`. |
| disableNativeTools | bool | `false` | Deny built-in harness tools. ORed across the stack and the agent. |
| triggerProvider | null or submodule | `null` | Webhook ingress. |
| triggers | attrs of trigger | `{}` | Triggers this layer contributes. Later compose entries win on the same name. |
| scripts | attrs | `{}` | devenv `scripts.<name>.exec` when this layer is composed. |
| module | deferred module | empty | Extra devenv module. Not auto-imported. |

## Skill

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| name | string | required | Directory and display name under `skills/<name>/`. |
| path | null or path | `null` | Local `SKILL.md` or skill directory. |
| repo | null or string | `null` | `owner/repo` or `https://github.com/owner/repo`. Built with `fetchFromGitHub`. |
| skill | null or string | `null` | Subdirectory in the repo, `skills/<name>` or `<name>`. |
| rev | null or string | `null` | Git revision. Required when `repo` is set. |
| hash | null or string | `null` | SRI hash. Required when `repo` is set. A changed tree fails the build. |

## Models

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| default | null or string | `null` | Default model id. |
| providers.<id>.name | string | the attr key | Display name. |
| providers.<id>.kind | enum | `openai-compatible` | Only `openai-compatible`. |
| providers.<id>.baseURL | null or string | `null` | Provider base URL. |
| providers.<id>.apiKeyEnv | null or string | `null` | Environment variable that holds the API key. |
| providers.<id>.models.<id>.name | string | required | Display name for that model id. |

## MCP

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| command | string | required | Executable that speaks MCP over stdio. |
| args | list of string | `[]` | Arguments. |
| secretsScope | null or string | `null` | secretspec scope for the process wrapper. |
| env | attrs of string | `{}` | Extra environment. |
| description | string | empty | Human-readable description. |

## Services

The attr is `services.<id>` on a layer. Later compose entries win on the same `<id>`. Only the last composed service that sets `work` keeps it. Earlier `work` is dropped. A non-empty collected set writes `services.json`. A collected `work` also writes `harness.json` with `mode` set to `service`. The runtime starts each command inside the session root. The process id is `service:<id>`.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| command | list of string | required | argv started inside the session root. |
| env | attrs of string | `{}` | Extra environment. |
| ready | null or submodule | `null` | Optional TCP wait before the session is ready. |
| ready.tcp | string | required when ready is set | Listen address as `host:port`. |
| ready.timeoutMs | positive integer | `60000` | Milliseconds to wait for `ready.tcp`. |
| work | null or submodule | `null` | How session prompts reach this process. |
| work.kind | enum | required when work is set | `acp` or `http`. |
| work.url | null or string | `null` | Required when `kind` is `http`. |

`work.kind = "http"` posts the prompt to `work.url`. `work.kind = "acp"` sends the prompt over the child stdin and stdout.

## Invariants

- Default deny for network and filesystem writes. No grant, no access.
- Each sandbox.network allow or deny item has exactly one of `domain`, `ip`, or `cidr`.
- `AGENTS.md` is mergeable even when `mergeable` is unset.
- Paths under `triggers/` in `files` are rejected.
- Tool names must match `[a-zA-Z0-9_-]+`.
- `rev` and `hash` are required when `repo` is set.

## Failure modes

- A cycle in `compose` fails eval.
- Same `files` path with different bytes fails the build unless the path is mergeable.
- A changed skill tree fails the build when `hash` no longer matches.
- Later compose entries win on the same skill name, MCP server name, service id, and trigger name.
- Only the last composed service that sets `work` keeps it.

## How to check

Needs a workspace whose flake calls `rig.lib.workspace`.

```
rig inspect notes
rig build notes
```

A layer block from `rig inspect` starts with `=== layer: <name> ===`.

## Related

- agent: https://rig.slopkit.dev/raw/agent.md
- Create an agent: https://rig.slopkit.dev/raw/create-agent.md
- Secrets: https://rig.slopkit.dev/raw/secrets.md
- Triggers: https://rig.slopkit.dev/raw/triggers.md
