---
title: Code routines
description: 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.
sidebar:
  order: 2
---

## 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](/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](/routine-kinds/hybrid).
- The step is a style or a rubric applied to prose. Use a [skill](/routine-kinds/skill).
- The script would need a host the run's network blocks, or a runtime the cloud run does not have. See [Compute sandboxes](/infrastructure/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](/templates/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](/infrastructure/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`:

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

   and `last-week.json`:

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

   ```json
   {"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](/infrastructure/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](/routine-kinds/hybrid).

## Related

- [Routine kinds](/routine-kinds)
- [Hybrid routines](/routine-kinds/hybrid)
- [Workflow routines](/routine-kinds/workflow)
- [CLI reference](/reference/cli)
- [Pack schema](/reference/pack-schema)
