---
title: Tune learning
description: Change how readily Hiring Signals suggests setting changes. Thresholds, turning signals off, declaring limits, rejecting versus switching learning off, and getting a new definition onto the Desk.
sidebar:
  order: 4
---

Hiring Signals ships with `"learning": {"enabled": true}` and every default. This guide changes those defaults, shows what `lint` says about each change, and gets the new values to the routine's runs. For what learning is and how it decides, read [Learning](/concepts/learning) first.

```
RO=skills/routineos/scripts/routineos.py
```

## Where the values live

| What | Where | Who changes it |
|---|---|---|
| Thresholds, signals, limits | `learning` block in `definition.json`, compiled into `meta/definition` | The routine's builder, then recompile and reseed |
| Whole-Desk off switch | `values.learning: false` in `settings/workspace` | People. As of 0.6.0 the Desk has no control for it |
| One suggestion | Reject or Snooze on Suggestions | People |
| Setting values | `settings/hiring-signals` | People, on Settings or by accepting a suggestion |

`learn` and `write` read the compiled definition from `meta/definition` in the store snapshot (unless `--definition` is given). Editing `definition.json` changes nothing until the new definition is in the store.

## 1. Decide what to change

Start from what people see on the Desk:

| What you see | Likely change |
|---|---|
| No suggestions after weeks of people keeping dropped roles | Lower `minSample` or `minEffect` |
| A suggestion after two or three keeps that turned out to be noise | Raise `minSample` |
| The same rejected idea comes back too soon | Raise `cooldownDays` |
| "Remove X from markets" for a market that is just quiet | Raise `deadAfterRuns`, or turn `dead_item` off |
| More than a couple of suggestions per run | Lower `maxPerRun` |
| Inbox overflows every run and nothing suggests a higher cap | Declare `max_inbox_per_run` in `limits` |

## 2. Edit the learning block

For Hiring Signals: suggest sooner, hold rejected ideas back for a month, stop dead-item suggestions, and let learning tune the Inbox cap.

```json
"learning": {
  "enabled": true,
  "minSample": 3,
  "cooldownDays": 30,
  "signals": {"dead_item": false},
  "limits": ["max_inbox_per_run"]
}
```

```
python3 $RO lint hiring-signals
```

```
ok: no problems
```

`max_inbox_per_run` can be a limit because Hiring Signals declares it as a number setting (default 5, min 0, max 20). `limit_pressure` proposes the cap plus the largest overflow seen, never above `max`.

## 3. What lint refuses

Each line below is the real output for the change shown.

A misspelled key:

```json
"learning": {"enabled": true, "minsample": 3}
```

```
FAIL definition.json: learning: unknown key 'minsample'
```

A signal that does not exist:

```json
"learning": {"enabled": true, "signals": {"people_dropped": true}}
```

```
FAIL definition.json: learning.signals: unknown signal 'people_dropped' (known: override, uncaptured_term, dead_item, source_health, limit_pressure, connections, edit_drift, manual_repetition, people_drop)
```

> Since 0.5.0, `people_drop` is the ninth signal, with `excluded_title_words` in the jobs-watch template.

Numbers out of range:

```json
"learning": {"enabled": true, "maxPerRun": 12, "minEffect": 1.5}
```

```
FAIL definition.json: learning.maxPerRun must be a whole number of 0 to 10
FAIL definition.json: learning.minEffect must be a share above 0 and at most 1
```

A limit learning cannot tune:

```json
"learning": {"enabled": true, "limits": ["lookback_days"]}
```

```
FAIL definition.json: learning.limits: 'lookback_days' is not a limit learning can tune (known: max_inbox_per_run)
```

A setting bound in `settingFilters` whose name contains push, gate, send, alert, permission, budget, maxruns or capoverride is refused with `learning: <name> is never learnable; remove its binding or turn learning off`.

## 4. Get the new definition into the store

```
python3 $RO compile hiring-signals
python3 $RO seed hiring-signals --out hiring-signals/seed --docs snapshot.json
```

`compile` prints a new version id. `seed` rewrites `meta/definition`; with `--docs` holding the current store documents, it keeps every setting people saved and writes settings defaults only where absent. Apply the batch to the Desk's artifact. The next run reads the new values.

Turning a signal off takes effect from the next run that reads the new definition.

## 5. Reject, snooze or switch off

| Want | Do | Effect |
|---|---|---|
| Stop one idea for a while | Reject on Suggestions (or Dismiss on the Inbox) | Its key is blocked for `cooldownDays` |
| Stop one idea until later | Snooze | Blocked until `until`, 7 days on the Desk |
| Stop one kind of idea | Turn its signal off in the definition | That signal stops proposing |
| Stop all ideas on this Desk | `values.learning: false` in `settings/workspace` | Tallies still count; nothing is proposed; `write` refuses every proposal message |
| Stop learning for one routine | `"enabled": false` in its definition | That producer proposes nothing |

Prefer Reject. It keeps the rest of learning working and its memory is per key. A `set` proposal is keyed by setting alone, so rejecting "set to 8" also blocks "set to 9".

The Desk has no switch for `values.learning: false` as of 0.6.0. Changing it means writing `settings/workspace` from a session, and that document is people-owned: a run must never write it.

## 6. Check it worked

After the next run, look at the Suggestions page and the run record (`runs/<runId>`):

- With `minSample` at 3, three kept roles sharing a word the include filter missed raise an `uncaptured_term` suggestion, if their share is at least `minEffect` (0.6 by default).
- A rejected key does not return within 30 days.
- No `dead_item` suggestions appear.

If nothing changes, check that the new `meta/definition` was applied: the run record carries `definitionVersion`.

## Common pitfalls

- **Editing `definition.json` and expecting the next run to see it.** Recompile and reseed.
- **Seeding without `--docs`.** Seed keeps saved settings only when it can see them. Without a snapshot it cannot tell which settings exist, so it plans defaults for all of them.
- **Setting `minSample` to 1.** One keep becomes a suggestion. People stop reading the Suggestions page.
- **Turning learning off to stop one suggestion.** Reject it.
- **Binding a send or gate setting to a filter.** It is never learnable, and lint refuses the binding while learning is on.

## Related

- [Learning](/concepts/learning)
- [The Desk: Suggestions](/desk/suggestions)
- [Settings](/concepts/settings)
- [Definition schema](/reference/definition-schema)
