> For the complete documentation index, see [llms.txt](https://kerno.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kerno.gitbook.io/docs/core-concepts/custom-rules.md).

# Custom Rules

Understand how to customize Kerno with custom rules, how to create them, and how they apply to the tests it generates.

Every codebase has rules that are obvious to your team and invisible in the source. Custom rules put those in your repository, so Kerno applies them on every run without you restating them.

Rules are guidance to the planner, not enforced constraints. Kerno weighs them against your source code, so a rule that contradicts verified behaviour never overrides it.

{% hint style="warning" %}
**Editing your config does not rewrite existing tests.** New rules shape the next generate for entry points that have no tests yet. For an entry point that already has tests, the existing tests are kept.

To apply new rules to an entry point that already has tests, ask your agent to generate it again with guidance to apply the new rules.
{% endhint %}

### Where rules live

Rules live in `.kerno/config.yaml`, under `test-generation`.

| File                 | Committed | Purpose                                                                     |
| -------------------- | --------- | --------------------------------------------------------------------------- |
| `.kerno/config.yaml` | yes       | Your rules and configuration, reviewed in pull requests like any other code |

`config.yaml` is committed, so a teammate who clones the repository inherits your rules, and changing one is a reviewable diff. Secret values stay out of it. Those live in `.kerno/.env`, which Kerno gitignores for you. See [Ports and file paths](/docs/references/ports-and-file-paths.md).

Calibration writes rules here too. When your agent [calibrates Kerno](/docs/core-concepts/environment-setup.md#calibration), it adds the confirmed facts, such as how to sign in and how to seed test data, to the workspace rules. Expect rules under `test-generation` that nobody on your team wrote by hand. They work like any other rule, and you can edit them the same way.

{% hint style="info" %}
Earlier versions of Kerno split rules across a committed `default.config.yaml` and a gitignored `config.yaml`. That split is gone. A workspace still holding a `default.config.yaml` has it folded into `config.yaml` automatically the first time the agent reads it, keeping the `config.yaml` value where both set the same field, and the old file is then removed.
{% endhint %}

### How rules are applied

Rules apply from broadest to most specific, and everything that matches is included, so a specific rule adds to the general ones.

```mermaid
flowchart LR
    A[Workspace context] --> B[Workspace entry point rules]
    B --> C[Application context]
    C --> D[Application entry point rules]
    D --> E[Merged guidance]
    F["Per-call guidance<br/>(wins on conflict)"] --> E
    E --> G[Planner]
```

For `POST /api/v1/payments` on the `payments-api` application, all four apply, in that order. For `GET /api/v1/invoices`, only the error-envelope and tenant rules apply; the payments-api rules do not.

The planner also reads two sources that rank below your rules:

* Context Kerno distills from your repository's documentation, such as `AGENTS.md`, `CLAUDE.md` and `README.md`. Text between `<!-- kerno instructions start -->` and `<!-- kerno instructions end -->` in those files is left out.
* Lessons Kerno learned from earlier runs. See [Memory and learning](/docs/core-concepts/memory-and-learning.md).

```yaml
# .kerno/config.yaml

test-generation:
  context: |
    Error responses use our standard envelope. On every failure, assert the
    body matches { error: { code, message } }, not only the status code.
  endpoints:
    "/api/v1/**": |
      Every v1 route is tenant-scoped. Always assert that a request
      authenticated as tenant A cannot read or modify tenant B's records.

applications:
  payments-api:
    test-generation:
      context: |
        Money amounts are integer minor units, never floats. Idempotency keys
        are required on every mutating request.
      endpoints:
        "POST /api/v1/payments": |
          Cover the duplicate-idempotency-key case explicitly: the second
          request must return the original payment, not create a second one.
```

#### Matching entry points

| Pattern                         | Matches                                  |
| ------------------------------- | ---------------------------------------- |
| `*`                             | Every entry point                        |
| `/api/users`                    | That exact path, any method              |
| `GET /api/users`                | That path and method only                |
| `/api/v1/*`                     | One path segment, e.g. `/api/v1/users`   |
| `/api/v1/**`                    | Any depth, e.g. `/api/v1/users/42/roles` |
| `regex:^/api/v[0-9]+/users/.+$` | Anything the expression matches          |
