GitHunt

Cron

Table of Contents

Introduction

%cron is a Gall agent that fires pokes on a repeating timer. It exposes an HTTP API and a management UI at /apps/cron for creating, reading, updating, and deleting scheduled poke timers. All requests require urbauth cookie authentication.

Each timer stores a target poke specification (ship, agent, mark, body) along with a repeat interval in seconds. When the timer fires, %cron sends the poke to the target and reschedules itself. Failed pokes are logged but do not stop the timer.

Since timers are stored in agent state, these will survive restarts (whereas threads run with %spider do not).

Source files

File Description
/sur/cron Type definitions
/app/cron Agent implementation

Install

Via ames/docket:

|install ~maslev %cron

Locally:

|new-desk %cron
|mount %cron
(copy in everything from desk to $PIER/cron)
|commit %cron
|install our %cron

Types

Defined in /sur/cron:

$poke-spec

Target for a scheduled poke.

Face Type Description
ship @p Target ship
agent term Target agent
mark term Poke mark
body @t Poke body (cord), supports template substitution

$timer

A scheduled poke on a repeating interval.

Face Type Description
id @ud Auto-incrementing identifier
poke poke-spec Target poke specification
period @ud Repeat interval in seconds
active ? Whether the timer is active
created time Creation timestamp
version @ud Monotonically increasing counter, bumped on each fire or update

$timers

(map @ud timer) -- map of timers by sequential id.

HTTP API

All endpoints are served under /apps/cron via eyre. All requests require urbauth cookie authentication (403 if missing).

JSON API

Request and response bodies are JSON.

List timers

GET /apps/cron/api

Returns 200 with a JSON array of all timers.

Response:

[
  {
    "id": 0,
    "poke": { "ship": "~bus", "agent": "hood", "mark": "helm-hi", "body": "" },
    "period": 10,
    "active": true,
    "created": "~2026.3.10..22.42.26..8361",
    "version": 5
  }
]

Get timer

GET /apps/cron/api/:id

Returns 200 with a single timer object, 400 if the id is invalid, or 404 if not found.

Create timer

POST /apps/cron/new

Request body:

{
  "poke": {
    "ship": "~bus",
    "agent": "hood",
    "mark": "helm-hi",
    "body": ""
  },
  "period": 10
}
Field Type Required Description
poke.ship string yes Target ship (e.g. "~bus")
poke.agent string yes Target agent name (e.g. "hood")
poke.mark string yes Poke mark (e.g. "helm-hi")
poke.body string no Poke body, defaults to ""
period number yes Repeat interval in seconds

Returns 201 with the created timer object. The timer starts immediately -- the first poke fires after period seconds.

Update timer

PUT /apps/cron/api/:id

All fields are optional. Omitted fields keep their current values. The poke object supports partial updates -- you can change just poke.body without resending ship/agent/mark.

Request body:

{
  "poke": { "body": "{now}" },
  "period": 5,
  "active": false
}
Field Type Description
poke.ship string New target ship
poke.agent string New target agent
poke.mark string New poke mark
poke.body string New poke body
period number New repeat interval in seconds
active boolean Enable or disable the timer

Returns 200 with the updated timer. If the timer is active after the update, it is rescheduled with the new period. Setting active to false effectively pauses the timer -- stale behn wakes are ignored via version checking.

Delete timer

DELETE /apps/cron/api/:id

Returns 204 with no body on success, 400 if the id is invalid, or 404 if not found. Any pending behn wake for this timer is silently ignored when it fires (the timer no longer exists in state).

Form API

Used by the management UI. All form routes accept application/x-www-form-urlencoded bodies and redirect back to /apps/cron on success (POST-Redirect-GET).

Route Action
POST /apps/cron/new Create timer
POST /apps/cron/:id Update timer
POST /apps/cron/:id/pause Toggle pause
POST /apps/cron/:id/delete Delete timer

Error responses

All errors return a JSON object:

{ "error": "description of the problem" }
Status Meaning
400 Bad request (invalid id, missing fields, malformed JSON)
403 Forbidden (missing urbauth cookie)
404 Timer not found
405 Method not allowed

Management UI

Browse to /apps/cron to access the HTML management interface. The UI is server-rendered with no external dependencies (no JavaScript, no external CSS). Features:

  • Table of all timers with editable fields (ship, agent, mark, body, period)
  • Save button per row to update a timer
  • Pause/Resume button per row to toggle active state
  • Delete button per row
  • Add row at top to create new timers
  • All actions use standard HTML form submissions

Body Templates

The body field supports template substitution at fire time. Currently one token is supported:

Token Replacement
{now} Current @da timestamp (e.g. ~2026.3.10..23.02.24..xxxx)

Only the first occurrence of {now} is replaced. The substitution happens when the poke fires, not when the timer is created.

Timer Lifecycle

  1. Create: POST /apps/cron/new stores the timer and sets a behn wake for now + period seconds.
  2. Fire: When behn wakes the agent, it checks the timer is still active and the version matches (to ignore stale wakes from before an update or delete). If valid, it sends the poke to [ship agent] with the specified mark and body, then bumps the version and schedules the next wake.
  3. Ack handling: Poke acks arrive on the /fire/:id wire. Successful acks are silently ignored. Nacks are logged via slog with "cron: poke nacked".
  4. Update: PUT /apps/cron/api/:id bumps the version (invalidating any pending wake) and, if active, schedules a new wake with the updated period.
  5. Delete: DELETE /apps/cron/api/:id removes the timer from state. The next behn wake finds no timer and does nothing.

Rate Limiting

To prevent abuse, remote pokes (target ship != our ship) require a minimum period of 300 seconds (5 minutes). Local pokes have no minimum.

Examples

|hi our ship every 10 seconds

curl -sb ~/cookie -X POST http://localhost/apps/cron/new \
  -H 'Content-Type: application/json' \
  -d '{"poke":{"ship":"~zod","agent":"hood","mark":"helm-hi","body":""},"period":10}'

|hi our ship with current time every 3 seconds

curl -sb ~/cookie -X POST http://localhost/apps/cron/new \
  -H 'Content-Type: application/json' \
  -d '{"poke":{"ship":"~zod","agent":"hood","mark":"helm-hi","body":"{now}"},"period":3}'

Pause a timer

curl -sb ~/cookie -X PUT http://localhost/apps/cron/api/0 \
  -H 'Content-Type: application/json' \
  -d '{"active":false}'

Resume a timer with a new interval

curl -sb ~/cookie -X PUT http://localhost/apps/cron/api/0 \
  -H 'Content-Type: application/json' \
  -d '{"active":true,"period":60}'

Delete a timer

curl -sb ~/cookie -X DELETE http://localhost/apps/cron/api/0
jamesacklin/cron | GitHunt