---
title: The definition
description: definition.json declares the record types a routine produces, how each record is identified, which settings filter which fields, the default views, and learning.
sidebar:
  order: 3
---

## What it is

`definition.json` is the data model of a routine. It declares the record types the routine writes, the fields on each, how a record is identified across runs, which settings filter which fields, the views the Desk shows by default, whether the routine uses the Inbox, and how it learns.

The Desk knows nothing about jobs, roles or companies. Everything it shows (navigation labels, columns, filters, setting controls) comes from the compiled definition.

## Why it exists

Without a declared model, every run invents its own shape and nothing can be compared week to week. A definition lets code do the exact work: mint the same id for the same role every run, apply the same filter every run, and show the same columns every run. It also gives learning its only lever: a setting filter is the binding learning may change.

## How it works

1. You write `definition.json` in the routine folder.
2. `lint` checks it: type names, identity rules, field types, views, learning keys.
3. `compile` (or `seed`, which compiles) merges the definitions of every producer on the workspace into one document, `meta/definition`, with a 12 character version hash.
4. The Desk and the write step read `meta/definition`. Each run records the `definitionVersion` it used.

When two producers share a workspace and declare the same type, compile requires the same identity rule, the same merge default and the same type for any shared field. Fields are unioned in declared order; links and setting filters are combined.

## Worked example

Hiring Signals declares two types. A trimmed copy:

```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}
}
```

Read it as: a company is the same company when its domain is the same; a role is the same role when its URL is the same. A role links to a company. Three settings filter roles. The Desk opens on new roles, newest first.

Compile it:

```
python3 skills/routineos/scripts/routineos.py compile hiring-signals --out md.json
ok: 1 producer(s), version eda3cea96ab1, wrote md.json
```

## Reference

### Top level

| Key | Type | Notes |
|---|---|---|
| `version` | number | Your version of the definition |
| `label` | string | Shown on the Desk for this producer |
| `inbox` | bool | Whether the producer writes Inbox messages |
| `types` | object | Type name to type; names match `^[a-z][a-z0-9]*(-[a-z0-9]+)*$` |
| `views` | list | Default views; see [Views](/concepts/views) |
| `learning` | object | See [Learning](/concepts/learning) |

### Type

| Key | Notes |
|---|---|
| `label`, `plural` | Display names; `plural` labels the Desk list |
| `identity` | `domain`, `name` or `field:<field>`; see [Records](/concepts/records#identity) |
| `merge` | `merge` (default) or `separate`; see [Merges](/concepts/merges) |
| `fields` | Field name to field |
| `links` | Types this type links to |
| `settingFilters` | `[{setting, field}]`: which setting filters which field |

### Field

| Key | Notes |
|---|---|
| `label` | Column and form label |
| `type` | `text`, `number`, `date`, `url`, `choice`, `list`, `bool` |
| `summary` | `true` shows the field on the record summary |
| `choices` | For `choice` fields |

## Common pitfalls

- **Identity on a field that changes.** A role keyed on title would split every time a title is reworded. Key on something stable: a URL, a domain.
- **A filter on an undeclared setting.** Each `settingFilters` entry must name a `[[setting]]` in `routine.toml` with a `role` of `include` or `exclude`.
- **Expecting a definition change to re-key records.** Ids are minted when a record is first written. Changing identity or merge policy affects new ids only; existing documents are never re-keyed.
- **A compiled definition over 256 KiB.** Compile refuses it. Very large choice lists belong in a setting, not a field.

## Related

- [Records](/concepts/records)
- [Settings](/concepts/settings)
- [Views](/concepts/views)
- [Definition schema reference](/reference/definition-schema)
