---
title: "@henryqw/pi-cron"
seo:
  description: "Run scheduled job prompts in new Pi sessions with per-job interval, Role, model, and thinking level."
---

<div class="not-prose my-6 flex flex-wrap items-center gap-3"><a href="https://www.npmjs.com/package/@henryqw/pi-cron" aria-label="View @henryqw/pi-cron version v0.1.0 on npm"><img alt="Version v0.1.0" height="20" src="https://img.shields.io/static/v1?cacheSeconds=7200&amp;color=1d4ed8&amp;label=version&amp;labelColor=6a7282&amp;style=flat-square&amp;message=v0.1.0" width="96"></a><div class="ml-auto flex flex-wrap items-center justify-end gap-3"><a href="https://www.npmjs.com/package/@henryqw/pi-cron" aria-label="View @henryqw/pi-cron on npm"><img alt="Monthly npm downloads" height="20" src="https://img.shields.io/npm/dm/%40henryqw%2Fpi-cron?cacheSeconds=7200&amp;color=1d4ed8&amp;label=downloads&amp;labelColor=6a7282&amp;style=flat-square" width="144"></a><a href="https://github.com/HenryQW/pi-harness/blob/main/extensions/pi-cron/LICENSE" aria-label="View the MIT license for @henryqw/pi-cron"><img alt="MIT license" height="20" src="https://img.shields.io/npm/l/%40henryqw%2Fpi-cron?cacheSeconds=7200&amp;color=1d4ed8&amp;label=license&amp;labelColor=6a7282&amp;style=flat-square" width="78"></a></div></div>

Run a prompt on a schedule in a fresh Pi session, with the Role, model, and thinking level chosen per job. Jobs fire while any Pi session with this extension is open, and created session files can be reopened.

## Install

```bash
pi install npm:@henryqw/pi-task-models
pi install npm:@henryqw/pi-subagent
pi install npm:@henryqw/pi-cron
```

Run `/task-models` and configure the profiles your jobs use. Create at least one job in the config file below, then run `/cron` to see its next run time.

## Works with

| Package | Relationship | Purpose |
| --- | --- | --- |
| [`@henryqw/pi-subagent`](https://pi.henry.wang/extensions/pi-subagent) | Required | Provides Roles, launch policy, and the bounded child executor that runs each job. |
| [`@henryqw/pi-task-models`](https://pi.henry.wang/extensions/pi-task-models) | Required | Resolves `modelClass` and explicit model routes. |

## Use

Add a job, keep a Pi session open, and the job runs at its next slot. You get a notification with the session file path, or a follow-up message in the current conversation when the job asks for one.

| Surface | Type | Purpose |
| --- | --- | --- |
| `/cron` | command | List jobs with next run and last outcome, then run one now, enable or disable it, or show its last result. Without a TUI it only prints the list. |
| `/cron run <job-id>` | command | Start one job immediately. The result arrives the same way a scheduled run would. |

A job is a Role plus a prompt. The Role decides which tools, extensions, Skills, and MCP servers the run may use, exactly as for `delegate_task`. Any configured Role is allowed, including write-capable ones, because the job entry is authored by you. Keep the prompt's instructions and the Role's tools as narrow as the task needs.

Each run launches a new Pi process in `cwd` with the Role's resources, the job's environment variables on top of the current session environment, and the prompt as its single task. The session is persisted under this extension's home so you can open it later with `pi --session <file>`.

## Flow

![An open Pi session claims due work in shared state, launches a bounded child, and delivers its result.](data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgwIiBoZWlnaHQ9IjcyMCIgdmlld0JveD0iMCAwIDEyODAgNzIwIiByb2xlPSJpbWciIGFyaWEtbGFiZWxsZWRieT0iY3Jvbi1mbG93LXRpdGxlIGNyb24tZmxvdy1kZXNjIiBpZD0iY3Jvbi1mbG93LXJvb3QiPgo8dGl0bGUgaWQ9ImNyb24tZmxvdy10aXRsZSI+UGkgQ3JvbiBzY2hlZHVsZXIgZmxvdzwvdGl0bGU+CjxkZXNjIGlkPSJjcm9uLWZsb3ctZGVzYyI+QW4gb3BlbiBQaSBzZXNzaW9uIHJlYWRzIGpvYiBjb25maWcsIGNsYWltcyBhIGR1ZSBqb2IgaW4gc2hhcmVkIHN0YXRlLCBsYXVuY2hlcyBhIGJvdW5kZWQgY2hpbGQsIGFuZCByZWNvcmRzIGl0cyBvdXRjb21lIHdpdGggYW4gb3B0aW9uYWwgcGVyc2lzdGVkIHNlc3Npb24gYmVmb3JlIGRlbGl2ZXJpbmcgdGhlIHJlc3VsdC48L2Rlc2M+CjxkZWZzPgogICAgICA8c3R5bGU+QGltcG9ydCB1cmwoJ2h0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9SW5zdHJ1bWVudCtTZXJpZjppdGFsQDA7MSZhbXA7ZmFtaWx5PUdlaXN0OndnaHRANDAwOzUwMDs2MDAmYW1wO2ZhbWlseT1HZWlzdCtNb25vOndnaHRANDAwOzUwMDs2MDAmYW1wO2ZhbWlseT1Ob3RvK1NlcmlmOml0YWxAMDsxJmFtcDtmYW1pbHk9Tm90bytTYW5zK0tSOndnaHRANDAwOzUwMDs2MDAmYW1wO2ZhbWlseT1Ob3RvK1NlcmlmK0tSOndnaHRANDAwJmFtcDtmYW1pbHk9Tm90bytTYW5zK1RDOndnaHRANDAwOzUwMDs2MDAmYW1wO2ZhbWlseT1Ob3RvK1NlcmlmK1RDOndnaHRANDAwJmFtcDtkaXNwbGF5PXN3YXAnKTsKICAgICAgI2Nyb24tZmxvdy1yb290IHsgY29sb3I6ICMxMDE4Mjg7IH0KICAgICAgI2Nyb24tZmxvdy1yb290IC5kaWFncmFtLWNvbnRhaW5lciB7IG92ZXJmbG93LXg6IGF1dG87IH08L3N0eWxlPgo8bWFya2VyIGlkPSJjcm9uLWZsb3ctYXJyb3ciIHZpZXdCb3g9IjAgMCA4IDgiIHJlZlg9IjciIHJlZlk9IjQiIG1hcmtlcldpZHRoPSI4IiBtYXJrZXJIZWlnaHQ9IjgiIG9yaWVudD0iYXV0byI+PHBhdGggZD0iTTAgMCBMOCA0IEwwIDhaIiBmaWxsPSIjNGM1NjY1Ii8+PC9tYXJrZXI+CjxtYXJrZXIgaWQ9ImNyb24tZmxvdy1hcnJvdy1ibHVlIiB2aWV3Qm94PSIwIDAgOCA4IiByZWZYPSI3IiByZWZZPSI0IiBtYXJrZXJXaWR0aD0iOCIgbWFya2VySGVpZ2h0PSI4IiBvcmllbnQ9ImF1dG8iPjxwYXRoIGQ9Ik0wIDAgTDggNCBMMCA4WiIgZmlsbD0iIzFkNGVkOCIvPjwvbWFya2VyPgo8L2RlZnM+CjxyZWN0IHdpZHRoPSIxMjgwIiBoZWlnaHQ9IjcyMCIgZmlsbD0iI2YwZWVlOSIvPgo8dGV4dCB4PSI0MCIgeT0iNTIiIGZvbnQtZmFtaWx5PSJJbnN0cnVtZW50IFNlcmlmLCBzZXJpZiIgZm9udC1zaXplPSIyOCIgZmlsbD0iIzEwMTgyOCI+U2NoZWR1bGVkIHdvcmssIG9uZSBib3VuZGVkIHJ1biBhdCBhIHRpbWU8L3RleHQ+CjwhLS0gQ29ubmVjdGlvbnMgcHJlY2VkZSBub2Rlcy4gRWFjaCBwYXRoIGhhcyBpdHMgb3duIGF0dGFjaG1lbnQgcG9pbnQuIC0tPgo8ZyBmaWxsPSJub25lIiBzdHJva2U9IiM0YzU2NjUiIHN0cm9rZS13aWR0aD0iMS4yIiBtYXJrZXItZW5kPSJ1cmwoI2Nyb24tZmxvdy1hcnJvdykiPgo8cGF0aCBkPSJNMjAwIDE5MiBWMjcyIi8+CjxwYXRoIGQ9Ik0zMjAgMjkyIEgzOTIgUTQwMCAyOTIgNDAwIDI4NCBWMjQwIFE0MDAgMjMyIDQwOCAyMzIgSDUxMiBRNTIwIDIzMiA1MjAgMjI0IFYyMDAiLz4KPHBhdGggZD0iTTEwMzIgMjcyIFYyNDAgUTEwMzIgMjMyIDEwMjQgMjMyIEg2ODggUTY4MCAyMzIgNjgwIDIyNCBWMjAwIi8+CjxwYXRoIGQ9Ik0xMDMyIDM5MiBWNDY0IiBzdHJva2UtZGFzaGFycmF5PSI1LDQiLz4KPHBhdGggZD0iTTk4NCAzOTIgVjQxNiBROTg0IDQyNCA5NzYgNDI0IEg2MDggUTYwMCA0MjQgNjAwIDQzMiBWNDY0Ii8+CjwvZz4KPHBhdGggZD0iTTMyMCAzMzIgSDkwMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMWQ0ZWQ4IiBzdHJva2Utd2lkdGg9IjEuNiIgbWFya2VyLWVuZD0idXJsKCNjcm9uLWZsb3ctYXJyb3ctYmx1ZSkiLz4KPCEtLSBMYWJlbCBtYXNrcyBzdGF5IGNsZWFyIG9mIHRoZWlyIGNvbm5lY3RvcnMgYW5kIGFsbCBub2RlIHJlY3RhbmdsZXMuIC0tPgo8ZyBmaWxsPSIjZjBlZWU5Ij4KPHJlY3QgeD0iMjA4IiB5PSIyMjQiIHdpZHRoPSI3MiIgaGVpZ2h0PSIxMiIvPgo8cmVjdCB4PSI0MDgiIHk9IjI0OCIgd2lkdGg9IjExMiIgaGVpZ2h0PSIxMiIvPgo8cmVjdCB4PSI3ODAiIHk9IjIxMCIgd2lkdGg9Ijk2IiBoZWlnaHQ9IjEyIi8+CjxyZWN0IHg9IjU0MCIgeT0iMzEyIiB3aWR0aD0iMTEyIiBoZWlnaHQ9IjEyIi8+CjxyZWN0IHg9IjEwNDAiIHk9IjQyNCIgd2lkdGg9IjEwNCIgaGVpZ2h0PSIxMiIvPgo8cmVjdCB4PSI3NjAiIHk9IjQwMiIgd2lkdGg9Ijk2IiBoZWlnaHQ9IjEyIi8+CjwvZz4KPGcgZm9udC1mYW1pbHk9IkdlaXN0IE1vbm8sIG1vbm9zcGFjZSIgZm9udC1zaXplPSI4IiBmaWxsPSIjNGM1NjY1Ij4KPHRleHQgeD0iMjE2IiB5PSIyMzMiPlJFQUQgSk9CUzwvdGV4dD4KPHRleHQgeD0iNDY0IiB5PSIyNTciIHRleHQtYW5jaG9yPSJtaWRkbGUiPkNMQUlNIERVRSBKT0I8L3RleHQ+Cjx0ZXh0IHg9IjgyOCIgeT0iMjE5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5GSU5JU0ggQ0xBSU08L3RleHQ+Cjx0ZXh0IHg9IjU5NiIgeT0iMzIxIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjMWQ0ZWQ4Ij5ST0xFICsgUFJPTVBUPC90ZXh0Pgo8dGV4dCB4PSIxMDQ4IiB5PSI0MzMiPklGIENSRUFURUQ8L3RleHQ+Cjx0ZXh0IHg9IjgwOCIgeT0iNDExIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5ERUxJVkVSIFJFU1VMVDwvdGV4dD4KPC9nPgo8IS0tIENvbmZpZyBhbmQgc2hhcmVkIGNsYWltIHN0YXRlIGFyZSBzZXBhcmF0ZSBmcm9tIHRoZSBhY3RpdmUgcHJvY2Vzcy4gLS0+CjxyZWN0IHg9IjgwIiB5PSI5NiIgd2lkdGg9IjI0MCIgaGVpZ2h0PSI5NiIgcng9IjgiIGZpbGw9IiNlNmU0ZGUiIHN0cm9rZT0iIzEwMTgyOCIgc3Ryb2tlLW9wYWNpdHk9IjAuMiIvPgo8cmVjdCB4PSI0NjAiIHk9Ijk2IiB3aWR0aD0iMjgwIiBoZWlnaHQ9IjEwNCIgcng9IjgiIGZpbGw9IiNlNmU0ZGUiIHN0cm9rZT0iIzEwMTgyOCIgc3Ryb2tlLW9wYWNpdHk9IjAuMiIvPgo8cmVjdCB4PSI4MCIgeT0iMjcyIiB3aWR0aD0iMjQwIiBoZWlnaHQ9IjEyMCIgcng9IjgiIGZpbGw9IiMxZDRlZDgiIGZpbGwtb3BhY2l0eT0iMC4wOCIgc3Ryb2tlPSIjMWQ0ZWQ4Ii8+CjxyZWN0IHg9IjkwMCIgeT0iMjcyIiB3aWR0aD0iMjg4IiBoZWlnaHQ9IjEyMCIgcng9IjgiIGZpbGw9IiMxZDRlZDgiIGZpbGwtb3BhY2l0eT0iMC4wOCIgc3Ryb2tlPSIjMWQ0ZWQ4Ii8+CjxyZWN0IHg9IjkwMCIgeT0iNDY0IiB3aWR0aD0iMjg4IiBoZWlnaHQ9Ijk2IiByeD0iOCIgZmlsbD0iI2U2ZTRkZSIgc3Ryb2tlPSIjMTAxODI4IiBzdHJva2Utb3BhY2l0eT0iMC4yIi8+CjxyZWN0IHg9IjQ2MCIgeT0iNDY0IiB3aWR0aD0iMjgwIiBoZWlnaHQ9IjEyMCIgcng9IjgiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iIzEwMTgyOCIgc3Ryb2tlLW9wYWNpdHk9IjAuMiIvPgo8ZyBmb250LWZhbWlseT0iR2Vpc3QsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSI2MDAiIGZpbGw9IiMxMDE4MjgiPgo8dGV4dCB4PSIxMDQiIHk9IjEyOCI+VXNlci1hdXRob3JlZCBqb2JzPC90ZXh0Pgo8dGV4dCB4PSI0ODQiIHk9IjEyOCI+U2hhcmVkIGNsYWltcyBhbmQgb3V0Y29tZXM8L3RleHQ+Cjx0ZXh0IHg9IjEwNCIgeT0iMzA0Ij5PcGVuIFBpIHNlc3Npb248L3RleHQ+Cjx0ZXh0IHg9IjkyNCIgeT0iMzA0Ij5GcmVzaCBib3VuZGVkIGNoaWxkPC90ZXh0Pgo8dGV4dCB4PSI5MjQiIHk9IjQ5NiI+UmVvcGVuYWJsZSBydW4gc2Vzc2lvbjwvdGV4dD4KPHRleHQgeD0iNDg0IiB5PSI0OTYiPlJlc3VsdCBkZWxpdmVyeTwvdGV4dD4KPC9nPgo8ZyBmb250LWZhbWlseT0iR2Vpc3QgTW9ubywgbW9ub3NwYWNlIiBmb250LXNpemU9IjgiIGZpbGw9IiM0YzU2NjUiPgo8dGV4dCB4PSIxMDQiIHk9IjE1MiI+Y29uZmlnL3BpLWNyb24vY29uZmlnLmpzb248L3RleHQ+Cjx0ZXh0IHg9IjEwNCIgeT0iMTcyIj5ldmVyeSAvIGF0IMK3IFJvbGUgwrcgcm91dGUgwrcgY3dkPC90ZXh0Pgo8dGV4dCB4PSI0ODQiIHk9IjE1MiI+Y29uZmlnL3BpLWNyb24vc3RhdGUuanNvbjwvdGV4dD4KPHRleHQgeD0iNDg0IiB5PSIxNzIiPmZpbGUgbG9jayDCtyBvd25lciArIHN0YXJ0IGlkZW50aXR5PC90ZXh0Pgo8dGV4dCB4PSIxMDQiIHk9IjMyOCI+c2Vzc2lvbl9zdGFydCArIDYwcyBjaGVja3M8L3RleHQ+Cjx0ZXh0IHg9IjEwNCIgeT0iMzUyIj5vbmUgbG9jYWwgcnVuIGFkbWl0dGVkIGJlZm9yZSBjbGFpbTwvdGV4dD4KPHRleHQgeD0iOTI0IiB5PSIzMjgiPnBpLXN1YmFnZW50IMK3IHBpLXRhc2stbW9kZWxzPC90ZXh0Pgo8dGV4dCB4PSI5MjQiIHk9IjM1MiI+bWF4VHVybnMgwrcgaWRsZU1zIMK3IG1heE1zPC90ZXh0Pgo8dGV4dCB4PSI5MjQiIHk9IjUyMCI+c2Vzc2lvbnMvJmx0O2pvYi1pZCZndDsvJmx0O3RpbWVzdGFtcCZndDsuanNvbmw8L3RleHQ+Cjx0ZXh0IHg9IjQ4NCIgeT0iNTIwIj5ub3RpZnkgLyBmb2xsb3dVcCAvIG5vbmU8L3RleHQ+CjwvZz4KPGcgZm9udC1mYW1pbHk9IkdlaXN0LCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBmaWxsPSIjNGM1NjY1Ij4KPHRleHQgeD0iNDg0IiB5PSI1NTIiPk9ubHkgdGhlIGNsYWltIG93bmVyIGNhbiBmaW5pc2guPC90ZXh0Pgo8dGV4dCB4PSI4MCIgeT0iNDkyIj5PdGhlciBQaSBzZXNzaW9ucyBzaGFyZSB0aGUgc2FtZSBjbGFpbXMuPC90ZXh0Pgo8dGV4dCB4PSI4MCIgeT0iNTIwIj5CdXN5IG1hbnVhbCByZXF1ZXN0cyBkbyBub3QgcXVldWUuPC90ZXh0Pgo8dGV4dCB4PSI4MCIgeT0iNTQ4Ij5EdWUgam9icyByZXRyeSBvbiBhIGxhdGVyIGNoZWNrLjwvdGV4dD4KPC9nPgo8cGF0aCBkPSJNNDAgNjQ0IEgxMjQwIiBzdHJva2U9IiMxMDE4MjgiIHN0cm9rZS1vcGFjaXR5PSIwLjEyIi8+Cjx0ZXh0IHg9IjQwIiB5PSI2ODAiIGZvbnQtZmFtaWx5PSJHZWlzdCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzRjNTY2NSI+UnVucyBvbmx5IHdoaWxlIFBpIGlzIG9wZW4uIFNodXRkb3duIGFib3J0cyB0aGUgY2hpbGQ7IG5vIGJhY2tncm91bmQgZGFlbW9uIGlzIGNyZWF0ZWQuPC90ZXh0Pgo8L3N2Zz4K)

1. On session start and every minute after, the extension checks the configured jobs. Before admitting each one, it reloads the config so edits, disablement, and removal made during an earlier run take effect.
2. A job seen for the first time only records a baseline; it first runs at its next slot. Use `/cron run` for an immediate run.
3. A local run slot is admitted before the due job is claimed in shared state, so claims never wait in a child queue. A fresh claim excludes other Pi sessions. A missed slot, for example while no Pi was open, produces one catch-up run, not a backlog.
4. The run is bounded by `limits`: turn count, idle timeout, and maximum runtime. The outcome, a bounded output summary, and the session path (when a file exists) are recorded.
5. Delivery follows the job's `notify`: `notify` shows a one-line notice, `followUp` sends the output into the current conversation and starts a turn, `none` stays quiet. Failures show a notice while a session UI is active. A rejected follow-up does not change the saved run outcome; synchronous delivery errors show a recovery notice.

## Config

Package-owned: `~/.pi/agent/config/pi-cron/config.json`

```json
{
  "jobs": [
    {
      "id": "digest",
      "at": "07:30",
      "timezone": "Asia/Hong_Kong",
      "role": "digest",
      "modelClass": "balanced",
      "cwd": "/Users/me/.config/miniflux",
      "promptFile": "/Users/me/.config/miniflux/digest.md"
    }
  ]
}
```

| Name | Description | Values | Default |
| --- | --- | --- | --- |
| `jobs` | Scheduled jobs. | Array of job objects with unique `id`. | — |
| `jobs[].id` | Job name used in `/cron` and state. | Lowercase letters, digits, and hyphens, up to 64 characters. | — |
| `jobs[].every` | Interval schedule. Exactly one of `every` or `at` is required. | `<n>m`, `<n>h`, or `<n>d` between `1m` and `7d`. | — |
| `jobs[].at` | Daily wall-clock schedule. | `HH:MM` in 24-hour time. | — |
| `jobs[].timezone` | Zone for `at`. Only valid with `at`. | IANA name such as `Asia/Hong_Kong`. | The system time zone. |
| `jobs[].role` | pi-subagent Role to run. | Name of a built-in or user Role. | — |
| `jobs[].modelClass` | Shared task-models profile. Not allowed with `model`. | `fast`, `balanced`, `frontier`, or `fav`. | The Role's `modelClass`, else the `pi-cron/job` task assignment (`fast`). |
| `jobs[].model` | Explicit model. Requires `thinking`. | `provider/model` available in the current session. | — |
| `jobs[].thinking` | Thinking level for `model`. | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`. | — |
| `jobs[].cwd` | Working directory of the run. | Absolute path. | — |
| `jobs[].env` | Extra environment variables for the run. | Object of variable name to string. | No extra variables. |
| `jobs[].prompt` | Inline task text. Exactly one of `prompt` or `promptFile` is required. | Non-empty text. | — |
| `jobs[].promptFile` | File whose contents are the task. Read at each run. | Absolute path to a UTF-8 file up to 256 KiB. | — |
| `jobs[].enabled` | Whether the scheduler considers the job. | `true` or `false`. | `true` |
| `jobs[].notify` | How a result reaches you. | `notify`, `followUp`, or `none`. | `notify` |
| `limits.maxTurns` | Provider-turn limit per run. | Safe integer ≥ 1. | `50` |
| `limits.idleMinutes` | Minutes without child activity before the run is stopped. | Positive number; converted milliseconds must be ≤ 2,147,483,647. | `10` |
| `limits.maxMinutes` | Hard runtime cap per run. | Greater than `idleMinutes`; converted milliseconds must be ≤ 2,147,483,647. | `30` |

Only you edit this file, except that `/cron` toggles a job's `enabled` flag after you choose Enable or Disable. Changes apply before the next scheduled admission without restarting Pi; newly added jobs wait until the next check. An already running job keeps its admitted definition and limits. Unknown keys, a job with both or neither schedule, an unknown time zone, a relative path, or an invalid route block all jobs with one error notice until the file is fixed; the file is never rewritten to recover.

Keep secrets out of this file. The run inherits the current session's environment, so point `env` at a private file or export the variable in the shell that starts Pi.

## State and storage

`~/.pi/agent/config/pi-cron/state.json` records each job's first sighting, last start, last outcome, a bounded output summary, the last session path, and any active run claim. Deleting it resets baselines, so every job waits for its next slot again. Invalid version 1 state, including malformed job records or run claims, is reported and left untouched. State writes above 1 MiB fail before replacing the existing readable file; remove inactive job records from `state.json` while Pi is closed to make room. Completion updates require the same claim owner and start time, so a stale run cannot overwrite its replacement's claim or outcome.

`~/.pi/agent/config/pi-cron/sessions/<job-id>/<timestamp>.jsonl` holds each run's Pi session. These files contain the prompt and the model's output; delete them when you no longer need them.

## Limits and recovery

Jobs run only while a Pi session with this extension is open; there is no background daemon. A slot missed while Pi was closed runs once at the next check.

A run cannot answer approval prompts or questions, so a Role that needs interactive confirmation will stall until the idle timeout. A failed or timed-out run records `failure` with the error text. Open the session file when one was created; pre-launch failures have no session and `/cron` says `Session: none created`. Use `/cron run <job-id>` to retry.

Each claim stores its expiration at admission: the original `limits.maxMinutes` plus five minutes after its start. It may be reclaimed only when that deadline is reached; lowering limits does not expire an active claim early. Runs are launched one at a time per Pi session. `/cron run` reports busy instead of queueing when another local job is active; due scheduled jobs are reconsidered on a later check. Shutdown aborts pending preparation and the active child, even if another Pi session starts in the same process. Results are still recorded, but are not delivered into a shutting-down session. Use `/cron` → Show last run to recover a result that could not be delivered.
