Skip to content
RoutineOS
Esc
↑↓navigate↵open⌘Jpreview
On this page

Quickstart

Build the Hiring Signals routine from the jobs-watch template, lint it, seed its Desk, and plan a first run's writes on your own machine.

This walk-through builds Hiring Signals, a hybrid routine that watches public job boards for commerce roles. You will scaffold it from the jobs-watch template, lint it, read its permission screen, seed its Desk, and plan the store writes of a sample run, all locally. Every output below is what the CLI printed when these commands were run.

You need the plugin installed and Python 3.11 or later. See Installation.

To keep commands short, set a variable for the CLI path (adjust it to where your checkout or plugin lives):

RO=skills/routineos/scripts/routineos.py

1. Scaffold the routine

python3 $RO new hiring-signals --from-template jobs-watch --app
created hiring-signals

--from-template jobs-watch copies templates/jobs-watch/ and renames the routine to hiring-signals. --app adds the app layer: app.toml, risks.md, CHANGELOG.md and lib/. The folder now holds:

File What it is
routine.toml Type, schedule, store, settings and the gate
prompt.md The routine prompt, with the untrusted-data clause and the push rule
definition.json Record types (company, role), identity rules, setting filters, views and learning
dashboard/index.html The Desk page, ready to publish
app.toml The app manifest: routines, store, budget, integrations, environment
risks.md Known risks and what the routine relies on
CHANGELOG.md Where improvements are recorded
lib/budget.py The run budget helper
README.md What the routine does

The routine name becomes the producer id. Every document this routine writes carries producer: "hiring-signals", and its settings live at settings/hiring-signals.

2. Lint it

python3 $RO lint hiring-signals
ok: no problems

Lint checks the routine type, the schedule (at least one hour apart), every setting’s shape, that a gate exists before any send action, and that the prompt carries the untrusted-data clause and a Push rule: line. With a definition, it also checks the record types, views and learning block.

3. Read the permission screen

python3 $RO permissions hiring-signals

The screen lists everything the routine will touch before anything is created. An excerpt:

Permission screen: hiring-signals

Schedule
- hiring-signals: UTC cron 0 22 * * 1,4, 1 run a day
- Total: 1 run a day against your cap of 1 run a day.
- The shortest allowed schedule interval is 1 hour.

Store
- Kind: artifact-db

First run
- The first run is report-only: it reads and reports what it would do, and nothing is sent or written outside the store.

Nothing is created until you say yes. A change to any line above makes a new screen.

The full screen also covers apps and hosts, connectors, tools, delivery, environment and secrets, budget, and what is not yet checked. See CLI reference.

4. Seed the Desk

python3 $RO seed hiring-signals --out hiring-signals/seed
ok: wrote 1 batch file(s) to hiring-signals/seed; lint passed. Apply them in order with ArtifactData batch.

hiring-signals/seed/batch-001.json has phase seed and four writes:

Document Holds
meta/workspace Workspace name, producers, template and lint result
meta/definition The compiled definition the Desk and the write step read
settings/hiring-signals The setting defaults from routine.toml
settings/workspace Workspace settings: merge policy per type, notifications inbox, empty quiet hours

Seed writes settings only when they are absent, so reseeding never overwrites a value a person saved.

5. Publish the Desk

Ask Claude to publish hiring-signals/dashboard/index.html as a private artifact with the capabilities {"db": {}, "user": {}, "downloads": true}, then to apply seed/batch-001.json with one ArtifactData batch call. db is required; user turns on per-person state (read, done, snooze); downloads turns on file export. The page reads its store from a config block already filled in by new:

<script type="application/json" id="desk-config">{"store": "db", "workspace": "hiring-signals"}</script>

Open the artifact. The Desk shows the first-run state: settings are in place and no run has written yet. See The Desk.

6. Plan a run’s writes

A deployed run does the searching and reading. You can see what the write contract does with its output without deploying anything. Save this as run.json; it is the run input a run builds in step 5 of its prompt:

{
  "producer": "hiring-signals",
  "runId": "2026-11-02-2200",
  "startedAt": "2026-11-02T22:00:04Z",
  "endedAt": "2026-11-02T22:09:41Z",
  "claimed": 3,
  "parsed": 3,
  "records": [
    {"type": "company", "values": {"name": "Northwind Goods", "domain": "northwind.example", "industry": "homewares", "country": "AU"}},
    {"type": "role", "values": {"title": "Senior Shopify Engineer", "company": "Northwind Goods", "company_domain": "northwind.example", "market": "AU", "posted": "2026-11-01", "url": "https://jobs.example/au/1001", "seniority": "senior"}, "links": [{"type": "company", "key": "northwind.example"}]},
    {"type": "role", "values": {"title": "Graduate Data Analyst", "company": "Northwind Goods", "company_domain": "northwind.example", "market": "AU", "posted": "2026-10-31", "url": "https://jobs.example/au/1002"}, "links": [{"type": "company", "key": "northwind.example"}]}
  ],
  "inbox": [
    {"kind": "match", "title": "Northwind Goods is hiring a Senior Shopify Engineer", "why": "Senior commerce role in AU, posted yesterday.", "refs": [{"type": "role", "key": "https://jobs.example/au/1001"}]}
  ]
}

The write step needs a snapshot of the store as {path: {version, data}}. A deployed run reads it from the artifact database. Locally, build it from the seed batch:

python3 - <<'EOF'
import json
b = json.load(open("hiring-signals/seed/batch-001.json"))
snap = {f"{w['collection']}/{w['doc_id']}": {"version": 1, "data": w["data"]} for w in b["writes"]}
json.dump(snap, open("snapshot.json", "w"))
EOF

Run the learning step, then the write step, in that order:

python3 $RO learn --docs snapshot.json --run run.json --out learn
python3 $RO write --docs snapshot.json --run run.json --out out
ok: 0 proposal(s) in proposals.json; apply learning.json as one ArtifactData batch
ok: wrote 5 batch file(s) to out

No proposals yet: nobody has made a decision for learning to count. The write step planned five batches, one per phase:

Batch Phase Writes
batch-001.json started runs/2026-11-02-2200
batch-002.json entities entities/company--northwind-example, entities/role--https-jobs-example-au-1001, entities/role--https-jobs-example-au-1002
batch-003.json summary runs/2026-11-02-2200
batch-004.json inbox inbox/2026-11-02-2200--1
batch-005.json done runs/2026-11-02-2200

Look at the run summary in batch-003.json. The graduate role does not contain any of the title keywords, so code dropped it and said why:

"counts": {"dropped": 1, "new": 3, "unchanged": 0, "updated": 0},
"dropped": [{"id": "role--https-jobs-example-au-1002", "reason": "not in title_keywords"}],
"droppedByReason": {"not in title_keywords": 1},
"inboxWritten": 1,
"status": "ok"

The dropped role is still written, with status: "dropped" and dropReason: "not in title_keywords", so a person can see what the settings cut and keep it if the settings were wrong.

What just happened

  • The model’s job (in a real run) was to find three postings, read them, and judge that one is a strong match.
  • Code’s job was everything else: it minted ids from the identity rules (domain for companies, field:url for roles), linked each role to its company, applied the title_keywords filter, counted, capped the Inbox, and planned the writes in order.
  • Nothing was written to settings/, decisions/ or any other people-owned path. The write contract refuses those paths.

Next

Was this page helpful?