# Proof your scheduled jobs and agents ran, and signed proof when they did not

Agents increasingly do work for you over time: nightly, hourly, after N days. Rightshold run monitors give every reported run a tamper-evident receipt, and when an expected run does not report in time, Rightshold writes the missed-run receipt itself.

## 1. Describe the schedule

In the operations console, open Monitors and add one per job or agent: a cron expression in its time zone (or every N seconds), a grace period, and optionally a maximum runtime. You get a secret ping URL, shown once.

## 2. Report each run

The job calls the ping URL when it starts and when it finishes, or once at the end. Pass the scheduler's run id as ?rid= and a redelivered call is recorded once. Every report becomes a hash-chained receipt committed to the public ledger.

## 3. Rightshold watches the clock

When an expected run's grace period closes with no report, Rightshold writes the missed-run receipt itself. A run that starts and never finishes within its maximum runtime gets a timed-out receipt. Both are Rightshold's own observation, not a claim by the job, and both alert your endpoints with a signed delivery.

## 4. Show anyone

Every signed statement counts each monitor's expected runs: on time, late, missed, failed and timed out. Share a statement with a client, an auditor or an insurer through a reviewer link that re-verifies on every open.

## Report a run

### Any cron job

```
curl -fsS -m 10 --retry 3 <ping URL>/start && ./nightly-export.sh && curl -fsS -m 10 --retry 3 <ping URL> || curl -fsS -m 10 --retry 3 <ping URL>/fail
```

### A scheduled GitHub Actions workflow

```
# Store the ping URL as the repository secret MONITOR_URL. Scheduled workflows can start late or be skipped,
# so give this monitor a grace period of 30 minutes or more; it records each run, and the ones that never came.
on:
  schedule:
    - cron: "0 2 * * *"
jobs:
  nightly:
    runs-on: ubuntu-latest
    steps:
      - run: curl -fsS -m 10 --retry 3 "${{ secrets.MONITOR_URL }}/start?rid=${{ github.run_id }}"
      - uses: actions/checkout@v4
      - run: ./nightly.sh
      - if: success()
        run: curl -fsS -m 10 --retry 3 "${{ secrets.MONITOR_URL }}?rid=${{ github.run_id }}"
      - if: failure()
        run: curl -fsS -m 10 --retry 3 "${{ secrets.MONITOR_URL }}/fail?rid=${{ github.run_id }}"
```

### Vercel Schedules (or any handler)

```
import { handleSchedule } from "@vercel/schedules";
import { monitored } from "./rightshold-runs.mjs"; // https://www.rightshold.com/sdks/rightshold-runs.mjs

export const POST = handleSchedule(monitored(process.env.MONITOR_URL, async (metadata) => {
  await exportRecords();
}));
```

### Python

```
# https://www.rightshold.com/sdks/rightshold_operations.py (standard library only)
from rightshold_operations import monitored

@monitored(os.environ["MONITOR_URL"], run_id=lambda run: run.id)
def nightly_export(run):
    export_records()
```

### An agent that works on its own schedule

```
// At the end of each unattended task:
await fetch(process.env.MONITOR_URL + "?rid=" + encodeURIComponent(taskId), { method: "POST" });
```

### An agent connected over MCP

```
Connect to https://www.rightshold.com/mcp?toolset=operations with your account key as a Bearer token, then call
report_scheduled_run {"workspace_id": "...", "monitor_id": "...", "status": "succeeded", "execution_id": "<task id>"}
```

### The API, with an account key

```
POST https://www.rightshold.com/v1/operations/workspaces/{workspace_id}/monitors/{monitor_id}/runs
Authorization: Bearer <api key>
{"status": "succeeded", "execution_id": "run-2026-11-06", "duration_ms": 8200}
```

The wrapper: https://www.rightshold.com/sdks/rightshold-runs.mjs (SHA-256 4e452fd9e0eea606ab4d47cdef6780acf12c68be39d3614b02bbf6daa0c19660). It never throws and never changes what your handler returns.

## Ping URL reference

- `<ping URL>/start`: The run started.
- `<ping URL>`: The run succeeded.
- `<ping URL>/fail`: The run failed.
- `<ping URL>/<exit code>`: 0 means success; 1 to 255 mean failure, and the code is kept.
- `?rid=<run id>`: The scheduler's execution or run id. A repeated call with the same id is recorded once, and a finish matches its start.
- `&scheduled_at=<ISO time>`: Which expected run this is, when the scheduler says so. Without it, a report belongs to the latest expected run at or before it.

Answers are plain text: OK, or a sentence saying why nothing was recorded.

## The secret

The ping URL carries a secret. Rightshold stores only its SHA-256, takes it out of the request path before anything is logged or counted, ignores any request body, and lets you rotate it at any time.

## What a receipt does not prove

- A run receipt proves what the job reported and when, and that the record has not changed since. It does not prove what the job did.
- A missed-run receipt proves that no report reached Rightshold within the grace period. It cannot tell a job that did not run from one that could not reach Rightshold.
- Expected runs are at least five minutes apart, and while a monitor stays down its missed runs are receipted and alerted once an hour, in one receipt covering the gap.
- Nothing is expected before a monitor is created or while it is paused. A local time that does not exist on a daylight saving change day is not expected; one that occurs twice is expected once.

Start in the operations console: https://www.rightshold.com/operations
