Skip to content

Scheduled local dogfood verification

The local scheduler runs configured public repository and private transcript suites through the installed praetorctl. It retains evidence for every admitted run and creates local repair plans for failed cases. Plans select eligible models using declared routing prices and explicit token estimates. They do not execute agents, apply patches, promote repository stages, or send messages.

Configure a bounded schedule

Create a private (0600) schedule JSON and suite JSON outside the public checkout. All paths are clean absolute paths. The state directory's parent must exist. The suite guide describes pinning public repositories and transcripts. Public cases require allow_remote: true explicitly.

{
  "version": 1,
  "runner_binary": "/home/example/.local/bin/praetorctl",
  "suite_config": "/home/example/.config/praetor/local-suite.json",
  "source_root": "/home/example/dev/praetor",
  "state_dir": "/home/example/.local/state/praetor/dogfood-local",
  "allow_remote": true,
  "interval_seconds": 86400,
  "retry_seconds": 3600,
  "max_consecutive_failures": 3,
  "max_runs": 32,
  "max_bytes": 2147483648,
  "retention": "block",
  "repair_policy": {
    "routing_config": "/home/example/dev/praetor/.config/models/routing.yaml",
    "task": "ci_debugging",
    "input_tokens": 8000,
    "output_tokens": 2000,
    "max_cost": 0.1
  }
}

The required repair_policy fields are routing_config, task, input_tokens, output_tokens, and max_cost. The register tuple (register, register_source, prompt_register, prompt_register_source, max_output_tokens, register_manifest_sha256) is resolved from the repository's canonical register manifest using task and is never taken from the caller: any values supplied for those fields are discarded and replaced with the manifest's resolution before the policy is validated or run, so status and admission use the canonical tuple regardless of what the config file contains for it.

Inspect the configuration and run one bounded tick:

praetorctl dogfood schedule status --config /absolute/schedule.json
praetorctl dogfood schedule run --config /absolute/schedule.json

Ticks use a process lock, persisted attempt state, retry cooldown, and a consecutive failure circuit. Changed build or configuration fingerprints become eligible for a new run. A failed or interrupted run remains visible. The retained run count, byte limit, and 200,000 entry cap are admission thresholds, not disk quotas: an admitted suite can grow beyond the threshold, after which subsequent admission stops. Configured retention: "block" (default) blocks admission with status: resource_blocked without error when max_runs, max_bytes, or 200,000 entries is exceeded. When configured with retention: "prune-oldest", the scheduler prunes the oldest completed attempt directory before admission when retained runs exceed max_runs, entries exceed 100,000 (half of the entry cap), or bytes reach max_bytes, persisting pruned_through with fsync before deletion and reporting pruned_runs. Retained attempt directories must exist gapless from pruned_through + 1 through attempts. Status reads current admission and reports what would be pruned without deleting evidence.

A due run waits for the host admission policy shared with the race stage (admission.HostCappedPolicy). A held tick returns status: host_held and host_reason; the next tick retries, with no attempt or failure counted. Status-only reads stay due without reading the host. Host read errors stop the tick.

Install the user timer

Install fresh local binaries with make dev-install, then run:

python3 -B scripts/dev_schedule.py install --config /absolute/schedule.json --activate
python3 -B scripts/dev_schedule.py status
systemctl --user start praetor-dogfood-local.service
journalctl --user -u praetor-dogfood-local.service -n 40 --no-pager

The installer validates the schedule and generated units before replacing managed files. It retains previous units and an installation manifest under ~/.local/state/praetor/dev-schedules/install-*, verifies activation, and attempts rollback if installation fails. An interrupted process leaves backups for review. Foreign units, overrides, symlinks, and concurrent installers are rejected.

The timer first checks after two minutes, then fifteen minutes after the previous service finishes. The scheduler independently enforces its daily interval and hourly retry policy. The oneshot service has a seventeen-minute start deadline; an active service is not started again. User timers run while the user service manager is available; the installer does not enable lingering or change system services.

Without --activate, installation preserves the previous timer activation state. To stop future scheduling while retaining units, an active run, and all evidence:

python3 -B scripts/dev_schedule.py disable

Service success means that the tick completed according to scheduler policy; inspect the schedule report to distinguish a verified suite from a cooldown or other skipped tick. Model selection is an estimate from the configured catalog. Provider capacity, current prices, and successful agent execution require later rollout evidence.

The development MCP exposes standards_dogfood_schedule_status for the same status read. Its config_path and every embedded read path obey the server root policy; it never creates schedule state or starts a run. Use an explicitly configured workstation root when inspecting private configuration outside the source checkout.