Settings
Typed values people tune on the Desk without editing the prompt. How they are declared, stored, snapshotted on each run, and used as filters.
What it is
A setting is a typed value a person can change without touching the prompt: which markets to search, which title keywords count, which companies to ignore, how many Inbox messages to allow per run. Settings are declared in routine.toml, stored in the store, and edited on the Desk.
Why it exists
People steer routines all the time. If steering means editing a prompt, only the builder can do it, every change risks breaking something else, and nobody can say later what a run was told. Settings make steering safe: each value has a type and a range, lint checks it, the Desk shows a proper control, and each run records exactly which values it used.
Settings are owned by people. A run never writes them. If a run thinks a value should change, it proposes the change in the Inbox; see Learning.
How it works
Three layers
- Static: the defaults in
routine.toml, versioned with the routine. - Per routine: the values in
settings/<producerId>undervalues, edited on the Desk. - Per run: one-off overrides when a run is triggered by a request.
The prompt reads layer 2, falls back to layer 1 when the document is missing (a missing document is treated as a first run), and the write step snapshots the values used onto the run document as settingsUsed. A later edit never changes what an earlier run is shown to have used.
Seeding
seed writes the defaults to settings/<producerId> and settings/workspace only when they are absent. Reseeding never overwrites a saved value.
Filters
A list setting with role = "include" or role = "exclude" filters records, through a settingFilters entry in the definition that names the field it checks.
- Include keeps only records whose field matches an entry. An empty include list keeps everything.
- Exclude drops records whose field matches an entry.
- Text fields match case-insensitive substrings:
commercematchesEcommerce Developer. Other field types match whole values, case-insensitive for strings.
A filtered record is not deleted. It is written with status: "dropped" and a reason: not in <setting> for an include miss, excluded by <setting> for an exclude hit. A record a person chose to keep is never dropped by a filter.
The substring rule came from a pilot run (2026-10-29-0438) in which whole-word matching dropped two Ecommerce roles.
Changing a setting
On the Desk Settings page a person edits a value and saves. The page writes settings/<producerId> and shows “Saved. Applies from the next run.” Page writes are last-writer-wins, and the page says so.
Worked example
Hiring Signals declares five settings:
[[setting]]
name = "title_keywords"
type = "list"
role = "include"
label = "Title keywords"
default = ["Shopify", "headless", "commerce", "AI automation"]
cost_note = "each keyword adds a search per market"
[[setting]]
name = "max_inbox_per_run"
type = "number"
label = "Inbox messages per run"
default = 5
min = 0
max = 20
cost_note = "no extra cost; more messages ask more of your attention"
| Setting | Type | Default | Filters |
|---|---|---|---|
markets |
list, include | AU, US, CA, NZ, PH | role market |
title_keywords |
list, include | Shopify, headless, commerce, AI automation | role title |
excluded_companies |
list, exclude | none (example northwind.example) |
company domain, role company_domain |
lookback_days |
number, 1 to 30 | 7 | none |
max_inbox_per_run |
number, 0 to 20 | 5 | caps the Inbox |
In the Quickstart run, “Graduate Data Analyst” contains none of the title keywords, so it was written as dropped with the reason not in title_keywords, and the run recorded droppedByReason: {"not in title_keywords": 1}.
Reference
Setting keys
| Key | Applies to | Notes |
|---|---|---|
name |
all | Required. Name by effect, not mechanism |
type |
all | Required. toggle, number, choice or list |
default |
all | Required. Number defaults must sit inside min and max; choice defaults must be in choices |
label |
all | Text the Desk shows next to the control |
example |
all | A sample value shown as a hint |
cost_note |
all | What turning it up costs |
min, max |
number | Range; lint enforces |
choices |
choice | The allowed values |
role |
list | include or exclude; makes the list a filter |
Workspace settings
settings/workspace holds settings for the whole Desk. Seed writes:
{"values": {"notifications": "inbox", "quietHours": {"from": "", "to": ""}, "mergePolicy": {"company": "merge", "role": "merge"}},
"updatedAt": "<seed time>", "updatedBy": "seed"}
Setting values.learning: false in it turns learning off for the workspace; see Learning.
Never learnable
A setting whose name, with case and separators removed, contains push, gate, send, alert, permission, budget, maxruns or capoverride can never be changed by a proposal of any shape.
Common pitfalls
- Rules in the prompt instead of a setting. If a person will ever change it, it is a setting, and if it filters records, it is a filter. The model does not reliably apply prose rules on every record.
- An include list emptied by accident. An empty include list keeps everything. The Desk refuses to accept a proposal that would empty one.
- Expecting a change mid-run. A saved value applies from the next run.
- Two people editing at once. Page writes are last-writer-wins. The second save replaces the first.
- Naming a setting
alert_thresholdand expecting learning to tune it. The name containsalert, so it is never learnable. Rename it if learning should touch it.