Skip to main content
A workflow with a trigger.schedule does not fire on its own - something has to be watching the clock. That something is px0d, a deliberately dumb scheduler: it polls workflows/, evaluates cron expressions in machine local time, spawns px0 workflows run <id> --quiet for anything due, and runs one housekeeping pass a day. Dumb is the goal here. There is no queue to inspect, no job state to reconcile, and nothing to recover from a corrupt scheduler database - the workflow files are the schedule.

1. Give a workflow a schedule

Standard five-field cron. If you described the timing in your original sentence - “every friday at 5pm” - the builder already wrote this for you. Invalid expressions are rejected when the workflow is validated, not silently ignored at fire time. A schedule that never fires is one of the worst failure modes an automation tool can have, so px0 refuses to save one.
A scheduled workflow must write to a file (output: {target: file, path: ...}). Nothing is watching stdout when the run fires, so px0 treats stdout-plus-schedule as a validation error rather than losing the output.

Or watch instead of scheduling

A workflow does not have to fire on the clock at all. trigger.watch polls a read-only tool and runs the workflow when an item it has not seen before turns up:
The first poll only records a baseline, so pointing a watch at a busy source does not immediately fire on everything already there. Composio’s own event triggers need a public endpoint to deliver to, which a laptop does not have - this is what a local-first tool does instead. See Workflow files.

2. Install the daemon

px0 picks the mechanism your OS actually wants: Force the cron fallback with --fallback-cron if you would rather not use the native one.
status tells you whether the process is genuinely alive - it signals the recorded pid rather than trusting a pidfile - plus the last fire per workflow and each scheduled workflow’s next upcoming fire. That last part is the quickest way to sanity-check a cron expression you are unsure about. To run it in the foreground instead, which is what you want while debugging a schedule:

3. What it does while it runs

The loop polls every 30 seconds. On each tick it fires anything whose cron schedule is due, and polls any trigger.watch whose interval has elapsed, firing on anything new. A workflow with enabled: false is skipped by both, and by the cron fallback’s generated crontab block. On the first tick of a new calendar day it also runs the nightly pass: Every one of those steps is fault-isolated. A broken index or an unreachable playlist is recorded in the report, and the rest of housekeeping still runs - one bad URL should not cost you a week of version checkpoints.

4. Missed fires

If your machine was asleep or the daemon was down, the fires it missed are not silently dropped. On startup and on every tick, px0 compares each workflow’s schedule against its last recorded fire, runs what it owes you, and marks anything discovered well after it was due as late in the run record. So a laptop that was closed on Friday afternoon still gets its Friday digest when it wakes up - and the record tells you it arrived late, so you are not left wondering why a 5pm digest is timestamped Saturday morning.

5. Watch what it is doing

Timestamps in the log are UTC; cron schedules are evaluated in machine local time. That is worth remembering when a fire looks an hour off from what you expected. The log lives at logs.path. There is no rotation yet, so if it ever gets unwieldy, truncate it - nothing reads it back. For one run rather than the daemon as a whole:
--follow on a run that has already finished prints what is there and returns immediately rather than hanging, so it is safe to use on any run id without checking first.

6. Scheduled runs are just runs

Everything a scheduled fire does is recorded exactly like a manual run, with trigger: schedule on the record:
That is the loop for debugging a schedule that misbehaved: find the run, read what it actually did, fix the workflow file, rerun it by hand to confirm, and let the next fire pick up the corrected file.

Running without the daemon

You do not have to install it. Without the daemon, trigger.schedule and trigger.watch are documentation, every run is one you started, and the nightly housekeeping simply does not happen - which mostly means hand edits are checkpointed on the next run instead of overnight, and you reindex the brain yourself with px0 brain reindex. If your machine already has a scheduler you trust, pointing it at px0 workflows run <id> --quiet is a perfectly reasonable substitute.

Uninstalling the scheduler

Removes whatever install put in place - the launchd plist or the systemd unit, unloaded first - and stops the daemon if it is running. Workflows keep their schedules; nothing fires on its own again until px0 daemon install runs again. On the cron fallback, it says that px0’s entries are still in your crontab rather than editing it - that file is yours. install.sh --uninstall also removes the scheduler unit, but it removes px0 along with it.

Retries and failure notifications

A workflow can be told how many times to retry itself and how to tell you when it still fails, in its own retry and on_failure frontmatter, or store-wide with px0 config set:
A workflow’s own on_failure block wins over these, so the noisy hourly job can stay quiet while the nightly report shouts. See Workflow files.