---
title: Definition schema
description: Every key in definition.json (types, fields, identity, merge, links, setting filters, views, learning, inbox), what lint refuses, and the shape of the compiled meta/definition. Taken from definition.py.
sidebar:
  order: 3
---

`definition.json` declares what a routine records and how the Desk shows it. `definition.py` lints it and compiles one or more definitions into the `meta/definition` document the Desk and the write step read. See [Definition](/concepts/definition) for the concept.

## Top level

| Key | Type | Required | Meaning |
|---|---|---|---|
| `version` | integer | yes | 1 or more. Raise it when the definition changes |
| `label` | text | no | The producer's label on the Desk. Falls back to the routine name |
| `inbox` | bool | no | The producer writes Inbox messages. Then `max_inbox_per_run` must be a number setting |
| `types` | object | yes | Record types, keyed by type name |
| `views` | list | no | Views shipped with the routine |
| `learning` | object | no | Learning block. Absent means learning is off |

## Types

A type name is lowercase letters and digits with single hyphens (`^[a-z][a-z0-9]*(-[a-z0-9]+)*$`).

| Key | Type | Required | Meaning |
|---|---|---|---|
| `label` | text | no | Singular label |
| `plural` | text | no | Plural label |
| `identity` | text | yes | `domain`, `name` or `field:<field>` |
| `merge` | text | no | `merge` (default) or `separate` |
| `fields` | object | yes | At least one field |
| `links` | list | no | Type names this type links to. Each must be declared |
| `settingFilters` | list | no | `{setting, field}` pairs |

### Identity rules

| Rule | Key |
|---|---|
| `field:<name>` | The value of that field, for example `field:url` for a role |
| `domain` | The record's `key`, else `values.domain`, else `values.name`. A value that is not a domain once normalised uses the `name` rule |
| `name` | The normalised name |

The entity id is `<type>--<slug(key)>`. Slugs are lowercase `[a-z0-9-]`, at most 120 characters, with a SHA-1 suffix for longer keys. Two keys that slug the same fail the plan with both named. With merge policy `separate`, the id is `<type>--<slug(producerId--key)>`.

### Fields

| Key | Type | Required | Meaning |
|---|---|---|---|
| `type` | text | yes | `text`, `number`, `date`, `url`, `choice`, `list`, `bool` |
| `label` | text | no | Shown on the Desk |
| `summary` | bool | no | Shown in summaries |
| `choices` | list | choice only | Non-empty |

### Setting filters

```json
"settingFilters": [{"setting": "title_keywords", "field": "title"}]
```

The setting must be declared in `routine.toml`, be a `list`, and have a `role` of `include` or `exclude`. The field must be declared on the type. Text fields match case-insensitive substrings; other types match whole values, case-insensitive for strings. A filtered record is kept with `status: "dropped"` and a `dropReason` such as `not in title_keywords`.

## Views

| Key | Type | Values |
|---|---|---|
| `id` | text | `^[a-z0-9][a-z0-9-]*$` |
| `type` | text | A declared type |
| `name` | text | Shown on the Desk |
| `filters` | object | `q`, `status`, `starred`, `tag`, `source`, `reason`, `fields` |
| `filters.status` | text | `all`, `active`, `dropped` |
| `filters.fields` | object | `{field: value}`, the one value a declared field must equal |
| `window` | text | `all`, `week`, `month`, `last-month` |
| `sort` | object | `{field, dir}`, `dir` `asc` or `desc` |
| `group` | text | A field name |
| `columns` | list | Field names |

Besides declared fields, a view may name the meta fields `lastSeen`, `firstSeen`, `seenCount`, `status` and `dropReason`. A field filter at the top level of `filters` is an error. Views that people save and views the agent proposes use the same shape. See [Views](/concepts/views).

## Learning

| Key | Default | Range |
|---|---|---|
| `enabled` | required | bool |
| `signals` | all on | `{signal: bool}`; signals `override`, `uncaptured_term`, `dead_item`, `source_health`, `limit_pressure`, `connections`, `edit_drift`, `manual_repetition` |
| `limits` | `[]` | `max_inbox_per_run`, declared as a number setting |
| `minSample` | 5 | at least 1 |
| `minEffect` | 0.6 | above 0, at most 1 |
| `cooldownDays` | 14 | at least 0 |
| `maxPerRun` | 3 | 0 to 10 |
| `deadAfterRuns` | 10 | at least 2 |

> Since 0.5.0: a ninth signal, `people_drop`.

## Example: Hiring Signals

From `templates/jobs-watch/definition.json`, fields shortened:

```json
{
  "version": 1,
  "label": "Jobs watch",
  "inbox": true,
  "types": {
    "company": {
      "label": "Company", "plural": "Companies",
      "identity": "domain", "merge": "merge",
      "fields": {"name": {"label": "Name", "type": "text", "summary": true},
                 "domain": {"label": "Domain", "type": "url", "summary": true}},
      "settingFilters": [{"setting": "excluded_companies", "field": "domain"}]
    },
    "role": {
      "label": "Role", "plural": "Roles",
      "identity": "field:url", "merge": "merge",
      "links": ["company"],
      "fields": {"title": {"label": "Title", "type": "text", "summary": true},
                 "market": {"label": "Market", "type": "choice", "choices": ["AU", "US", "CA", "NZ", "PH", "GB", "DE"], "summary": true},
                 "url": {"label": "URL", "type": "url"}},
      "settingFilters": [{"setting": "markets", "field": "market"},
                         {"setting": "title_keywords", "field": "title"},
                         {"setting": "excluded_companies", "field": "company_domain"}]
    }
  },
  "views": [
    {"id": "new-roles", "type": "role", "name": "New roles", "filters": {"status": "active"},
     "sort": {"field": "firstSeen", "dir": "desc"}, "columns": ["title", "company", "market", "posted"]}
  ],
  "learning": {"enabled": true}
}
```

The full role type also has `company`, `company_domain`, `location`, `remote`, `salary_max`, `posted`, `seniority` and `summary`. The template ships two more views, `dropped-roles` and `hiring-companies`.

## What lint checks

- `version` is a whole number of 1 or more; `types` is a non-empty object.
- Each type: a valid name, at least one field, known field types, a `choices` list on choice fields, a valid identity rule naming a declared field, a known merge policy, links to declared types.
- Each setting filter names a declared list setting with a role, and a declared field.
- Each view passes the view rules above.
- The learning block has only known keys, known signals and in-range numbers, and `limits` names declared number settings it can tune.
- No never-learnable setting (a name containing push, gate, send, alert, permission, budget, maxruns or capoverride, ignoring case and separators) is bound while learning is on.
- `inbox: true` needs a number setting `max_inbox_per_run`.

Each problem prints as `FAIL definition.json: <problem>`.

## The compiled meta/definition

`compile` merges every producer's definition into one document with the keys `producers`, `schema`, `types`, `version` and `views`. Each entry in `producers` carries the producer's `id`, `label`, `schedule`, `definitionVersion`, `inbox`, its type names, its settings schema, and its learning config with every default filled in. `version` is a short hash of the compiled content, printed by `compile` (for example `eda3cea96ab1`). A compiled document over 256 KiB is refused.

## Related

- [Definition](/concepts/definition)
- [Records](/concepts/records)
- [Store collections](/reference/store-collections)
- [routine.toml and app.toml](/reference/routine-toml)
