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

Code routines

Routines whose work is done by scripts. The model runs exact commands and reports; scripts fetch, filter, count and write. When to choose one, the rules a script follows, and weekly-marketing-report in full.

What it is

A code routine does its work in scripts under lib/, plus the bundled routineos.py. The prompt is a list of exact commands. The model runs them, reads their output, and writes a short report. The model’s own work shrinks to what no script can do: building the run input from a script’s output and phrasing the Inbox.

Nothing in routine.toml says “this is a code routine”. It is a design pattern: a prompt made of commands, and, in a pack, stages with kind = "code". See Routine kinds.

Why it exists

Counting, comparing, filtering and deduplicating are cheap and exact in code, and slow, costly and occasionally wrong in a model. A model asked for a percentage change can round differently on two runs; a script cannot. A routine that is mostly arithmetic should spend almost no tokens, and give the same answer for the same input every time.

Code also makes a routine testable on your machine. You can run lib/week_over_week.py against two JSON files before any cloud run and see exactly what the run will see.

When to choose it

Choose code for a step when:

  • The answer is arithmetic, a comparison or a filter, and must be the same on every run.
  • The input is structured: an export, an API response, a CSV, JSON from a connector.
  • A person will ask “why did this number change?” and the answer must be traceable.

Do not choose code when:

  • The step needs reading or judgement: “is this a real lead”, “would a visitor notice this page is broken”. Make it a hybrid.
  • The step is a style or a rubric applied to prose. Use a skill.
  • The script would need a host the run’s network blocks, or a runtime the cloud run does not have. See Compute sandboxes.

Use cases

Each code-heavy shipped template keeps its arithmetic in one small standard-library script:

Template Script What it computes Maturity
weekly-marketing-report lib/week_over_week.py Change per channel and metric, this week against last Unpiloted
candidate-shortlist lib/score_rubric.py A rubric score per candidate Unpiloted
engineering-digest lib/stale_items.py Items with no recent activity Unpiloted
invoice-exceptions lib/invoice_checks.py Invoices that break a rule Unpiloted
support-gap-digest lib/group_questions.py Questions grouped by topic Unpiloted

“Unpiloted” means no real run has checked the template yet. See the catalog.

How it works

Where scripts live

  • Scripts live in lib/ next to routine.toml and are delivered with the repo the routine clones. A script on your machine does not exist in a cloud run.
  • routineos.py (under skills/routineos/scripts/) does learn and write. Every routine’s store write goes through it, so the same rules hold as for any other routine.
  • lib/budget.py (copied from assets/lib/budget.py when you scaffold with new --app) is the budget check a run calls first.

How the prompt calls a script

The prompt gives each step as a runnable command with every flag, never “call function X from file Y”. references/prompt-craft.md records why: a pilot run could not import budget under python3 -I, because isolated mode ignores the working directory, and the run typed the state by hand instead (registry python-isolated-ignores-cwd, pilot-verified). Run each script by its path.

From templates/weekly-marketing-report/prompt.md, step 2:

Run python3 lib/week_over_week.py --this this-week.json --last last-week.json. Use its output as the only source of change figures; do not compute percentages yourself.

The second sentence matters as much as the first. Without it, a model may restate a change it worked out itself.

What a script follows

The shipped scripts share a shape. Follow it in your own:

  • Standard library only. No pip install, so no setup script is needed.
  • JSON in, JSON out. Read files named on the command line; print JSON to stdout.
  • Fail loudly, exit 2. On bad input, print error: ... to stderr and exit 2. Never echo the input values.
  • No store access. A script returns results to the run; routineos.py write produces the store batches. A script reaching the store directly from a run is owner-reported, not re-tested (registry script-writes-store, unverified). If it fails, the budget check and the final health write become the first and last prompt steps, and the Desk labels the cap “prompt-enforced, not guaranteed”.
  • No secrets. A script that calls an API reaches it through a host with a network secret, never a key in a file or an environment variable. See Compute sandboxes.

The budget check

lib/budget.py has two commands:

python3 lib/budget.py decide --max-runs 1 --state budget.json
python3 lib/budget.py record --steps 4 --state budget.json --out budget-next.json

decide prints {"allow": ..., "runs": ..., "reason": ...}, for example "daily cap reached (1 of 1 runs)". record writes the new state/budget document to --out, ready for an ArtifactData set. --state - reads the state from stdin; omit --state when the document does not exist yet.

In a pack

Packs (since 0.6.0) declare a code step as a stage with kind = "code" and a command. pack validate holds the command to a narrow shape:

  • One plain line that starts python3, with no shell operators (pipes, ;, &&, redirects).
  • The script comes straight after python3, so -m and -c are refused.
  • The script is skills/routineos/scripts/routineos.py or a lib/*.py file the pack ships: “command names lib/x.py, which is not in files”.

Install never runs pack code. It writes the files and prints a connector report.

How it compares

Code Skill Workflow Hybrid
Who does the work Scripts The model, guided by a skill Whatever each stage says The model reads, code rules
Same answer every run Yes No Per stage For the code half
Testable on your machine Yes, with sample JSON Partly Per code stage The code half
Token cost Lowest Medium Depends on stages Medium

Few routines are pure code: something has to fetch the numbers and phrase the result. Most are code-heavy hybrids, like weekly-marketing-report below.

Worked example

weekly-marketing-report compares this week’s marketing numbers with last week’s and checks a few key pages, every Sunday at 21:00 UTC (schedule = "0 21 * * 0", type watch). It is unpiloted.

  1. Install it with its script:

    python3 skills/routineos/scripts/routineos.py pack install site/templates/weekly-marketing-report.json --dir weekly-marketing-report

    The folder gets lib/week_over_week.py with the routine files. The connector report lists an analytics MCP connector (read, required) and the built-in web tool (read, optional).

  2. Read the stages. Two of five are the model; three are code:

    Stage Kind Does
    collect model Reads this week’s and last week’s totals from the analytics connector into this-week.json and last-week.json
    compare code python3 lib/week_over_week.py --this this-week.json --last last-week.json
    audit model Opens each page in pages_to_audit and records issues seen
    learn code routineos.py learn
    write code routineos.py write; writes runs, entities and inbox
  3. Test the script before any run. With this-week.json:

    {"search": {"clicks": 1320, "spend": 410.5}, "email": {"opens": 900}}

    and last-week.json:

    {"search": {"clicks": 1100, "spend": 410.5}, "email": {"opens": 0}}

    python3 lib/week_over_week.py --this this-week.json --last last-week.json prints:

    {"email": {"opens": {"value": 900, "previous": 0, "change_pct": null}}, "search": {"clicks": {"value": 1320, "previous": 1100, "change_pct": 20.0}, "spend": {"value": 410.5, "previous": 410.5, "change_pct": 0.0}}}

    change_pct is null when last week was 0 or absent. A non-number such as "lots" stops it with error: --this: every metric must be a number and exit code 2.

  4. The model records one metric per line of output and writes an alert for each change of at least change_alert_pct (default 20) up or down, inside max_inbox_per_run. With the default, search/clicks at 20.0 alerts; email/opens does not, because it has no change figure.

  5. Tune it on the Desk. A person who finds 20% too noisy raises change_alert_pct to 30. The next run’s alerts change; the script and the prompt do not.

A pure-code variant is possible when the numbers come from a file rather than a connector. Hiring Signals, for example, could read a job board’s export with a script instead of searching:

python3 lib/budget.py decide --max-runs 1 --state budget.json
python3 lib/fetch_postings.py --out postings.json
python3 skills/routineos/scripts/routineos.py learn --docs snapshot.json --run run.json --out learn
python3 skills/routineos/scripts/routineos.py write --docs snapshot.json --run run.json --out batches
python3 lib/budget.py record --steps 4 --state budget.json --out budget-next.json

lib/fetch_postings.py is illustrative; write your own. The model’s only work is to build run.json from postings.json and phrase the Inbox.

Common pitfalls

  • A script the cloud run cannot reach. Keep it in the repo, not on your machine.
  • Scaffolding with new --from-template and expecting lib/. It copies routine.toml, prompt.md, README.md and definition.json only. Use pack install to get the template’s scripts.
  • Calling a function instead of a script. “Call decide() from budget.py” failed under python3 -I in a pilot. Run the script by path with every flag.
  • Letting the model redo the arithmetic. Say the script’s output is the only source of the figure.
  • Treating a failed source as zero. A source that did not load is reported as dropped, and the run is partial.
  • Network from a script. A script’s fetch goes through the run’s network; check the host is reachable at the environment’s level. See Compute sandboxes.
  • Third-party packages. A pip install needs a setup script, and setup scripts have their own rules (registry setup-script-rules). Standard library first.
  • Forcing judgement into code. “Is this a real lead” is not a regex. Make it a hybrid.

Was this page helpful?