Skip to content

Jobs and schedules

Jobs make Push useful while you are not in a conversation. Each job is a user-owned Markdown runbook in the configured assistant repository's jobs/ directory. TOML frontmatter defines execution policy; the Markdown body is sent verbatim to a fresh backend session.

The schedule definition lives in the same Markdown file as the job. Push evaluates that definition and stores its run and delivery state. For every run:

  • SOUL.md is supplied automatically as identity and working instructions;
  • files under context/ are optional shared information and are not inserted automatically; keep task-specific instructions in the job;
  • the job body is the fresh request, without chat history or template expansion;
  • primary_delivery in Push configuration selects where scheduled results go.

Create a job

Create <assistant_root>/jobs/repo-review.md:

+++
version = 1
timeout = "5m"
backend = "codex"
+++

Review repositories with uncommitted work. Summarize the risk and the next
useful action. Do not change files or remote state.

Job names are lowercase ASCII slugs made from letters, digits, and hyphens. Files must be regular UTF-8 Markdown files directly inside the derived <assistant_root>/jobs directory. Subdirectories and symlinks are rejected.

Frontmatter fields:

Field Required Meaning
version yes Format version, currently 1
timeout yes Positive duration no greater than jobs_max_timeout
workdir no Existing working directory for the backend; defaults to assistant_root
backend no claude, codex, or pi; defaults to jobs_agent, then root agent
evals no Reusable Markdown agent eval names from <assistant_root>/evals/
triggers no One or more cron trigger tables

Unknown fields are errors. The assistant repository is a valid work directory. A job work directory may not overlap Push state, database, audit log, job lock paths, or a loaded config stored outside the assistant repository.

Validate and inspect jobs

push job validate
push job list
push job show repo-review

Validation reports every valid and invalid file. An invalid job is disabled individually and does not stop messaging or other valid jobs.

Evaluate completed work

Jobs can assign one or more reusable agent evals:

evals = ["writing-style", "task-completion"]

Each name resolves to one non-empty regular Markdown file directly under <assistant_root>/evals/. Names use the same lowercase ASCII slug format as jobs. Symlinks, missing files, duplicate names, invalid UTF-8, more than 16 assigned evals, files larger than 64 KiB, and assigned evals larger than 256 KiB in total are rejected during job validation. Assigned eval contents are included in the validated job snapshot, so changing an eval changes the snapshot used for future claims.

For example, create <assistant_root>/evals/writing-style.md:

# Writing style

Fail work that uses em dashes, unsupported claims, or needlessly complex words.

After a job returns successfully, Push starts one fresh evaluator session using the same backend, timeout, and work directory. The evaluator receives the original job, final response, and every assigned eval, then must finish with VERDICT: PASS or VERDICT: FAIL. Push disables evaluator shell access, external MCP tools, extensions, browser integrations, and session persistence. Codex project instructions are also disabled. Some backends may retain non-mutating built-in utility tools. The first version evaluates the returned response and does not inspect work-directory artifacts.

Evaluation is recorded separately as running, passed, failed, error, or not_requested. A failed or malformed evaluation does not rewrite the result, rerun the job, or change a successful execution into a failed execution. Scheduled delivery includes the evaluation verdict and failure details.

Run a job manually

push job run repo-review
push job runs repo-review

A manual run executes in the invoking CLI process and prints its result there. It does not proactively message a channel. Push records and claims the run in SQLite before starting the backend and holds a non-blocking per-job advisory lock for the run's lifetime.

If the same job is already active, the new attempt is recorded as skipped_overlap. A fresh claim can recover a stale claim only after acquiring the released OS lock, so a live process is not reclaimed from database state alone.

Schedule a job

Add one or more five-field cron triggers:

[[triggers]]
id = "weekday-morning"
kind = "cron"
schedule = "0 8 * * 1-5"
timezone = "Europe/London"
enabled = true

Then configure a delivery destination:

[primary_delivery]
channel = "telegram"
target = "123456789"

Scheduling starts only when the primary destination is enabled and allowlisted. A missing or invalid destination disables new scheduled starts without affecting conversations or manual jobs.

Push runs at most jobs_max_workers scheduled jobs concurrently. It does not catch up cron occurrences missed while offline. Daylight-saving gaps are skipped; repeated local times run once at their first instant. Cron expressions whose selected months and days can never form a calendar date are rejected during job validation.

Complete assistant example

The daily inbox triage job keeps global identity separate while making the scheduled runbook self-contained:

SOUL.md
jobs/daily-inbox-triage.md

That one job file contains its schedule, triage priorities, output format, and safety rules. It uses email tools configured in the selected agent, drafts no replies, and performs no external side effects. Its schedule is disabled by default. Set enabled = true in the job only after configuring the email tools and primary delivery destination.

Execution and delivery guarantees

  • Every job and evaluator run uses a fresh backend session, without chat history.
  • Codex and Claude jobs bypass interactive permissions so unattended work can complete. Evaluators remain restricted.
  • Push does not retry failed or timed-out backend execution because the agent may have completed external side effects before failing.
  • Success, failure, timeout, overlap, and delivery state are stored separately.
  • Scheduled output is persisted before delivery.
  • Delivery retries use the stored result and never rerun the backend.
  • Delivery is claimed across gateway processes. Normal partial-message retries resume from the first unsent chunk. Push checkpoints each successful chunk and bounds a delivery attempt below its claim lease so an active worker cannot be reclaimed. Delivery can still produce an at-least-once duplicate after a process crash between a channel send and its checkpoint, or after a delivery-state persistence failure, because channel APIs do not provide atomic delivery.
  • Queued runs and pending delivery survive restart. Interrupted execution is not automatically replayed.

Use push job runs [<name>] to inspect execution state, evaluation state, delivery attempts, destination, bounded results, and error details.

Agent-created jobs

When a user asks for a job, the assistant writes the complete runbook directly to <assistant_root>/jobs/<lowercase-slug>.md and runs push job validate. There is no separate draft or approval step. The selected agent's filesystem permissions control whether it can change the assistant repository.

For an assistant repository created before this change, replace any AGENTS.md instruction that says to propose jobs through approval with the direct-write rule above. The gateway's runtime instruction overrides that old rule, but updating the repository keeps its checked-in guidance accurate.

Pending job approvals from older Push versions are cancelled during database migration. Replying to one explains that the job must be requested again.

Warning

Jobs have no interactive approval path. Push runs Codex jobs with full access and no prompts and Claude jobs in bypassPermissions mode. Treat every enabled job as code execution by the Push service user, review changes to the assistant repository, and allow only trusted senders.