Loops are recurring agent jobs that run on a cron schedule. They let you set up an agent to periodically inspect your repository, make incremental progress on tasks, monitor CI, or generate summaries — without manual intervention.
PVYai’s cron system is foreground, file-backed — there is no daemon. You run pvyai cron run in a terminal (or under a process manager like systemd / launchd / tmux), and it ticks every 30 seconds, firing any due jobs.
┌─────────────────────────────────────────────────────┐
│ pvyai cron run (foreground loop, 30s tick) │
│ │
│ Job 20260707-150405: "0 9 * * *" → daily summary │
│ Job 20260707-160712: "*/30 * * * *" → CI watch │
│ Job 20260707-162100: "*/15 * * * *" → todo pulse │
│ │
│ ┌─── tick ───┐ │
│ │ check jobs │ → fire due → pvyai exec --prompt │
│ │ advance │ → record run → next run at │
│ └────────────┘ │
└─────────────────────────────────────────────────────┘
pvyai cron add <cron-expr> [--prompt P | --recipe R] [--cwd D] [--model M] [--run-now]
# Daily 9 AM summary
pvyai cron add "0 9 * * *" --prompt "Write a short progress summary of what changed in this repo over the last day."
# Every 30 minutes, check CI
pvyai cron add "*/30 * * * *" --prompt "Check CI status for the current branch and report failures with the failing step."
# Every 15 minutes, scan for new TODOs
pvyai cron add "*/15 * * * *" --prompt "Scan the working tree for new TODO/FIXME markers added recently and list them."
# Using a recipe
pvyai cron add --recipe daily-summary
# Override the recipe's schedule
pvyai cron add "0 10 * * *" --recipe daily-summary
# Run immediately after adding
pvyai cron add "0 9 * * *" --prompt "..." --run-now
# Specify a working directory and model
pvyai cron add "0 9 * * *" --prompt "..." --cwd /path/to/repo --model claude-sonnet-4.5
pvyai cron list # List all jobs
pvyai cron pause <id> # Pause a job
pvyai cron resume <id> # Resume a paused job
pvyai cron rm <id> # Remove a job
pvyai cron run # Start the foreground cron loop
pvyai cron run --once # Fire all due jobs once and exit (for external schedulers)
pvyai cron run --catch-up # Fire overdue jobs immediately (instead of skipping)
PVYai uses the standard 5-field Vixie cron format:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12 or jan-dec)
│ │ │ │ ┌───────────── day of week (0-6 or sun-sat, 7=sun)
│ │ │ │ │
* * * * *
| Pattern | Meaning | Example |
|---|---|---|
* |
Every unit | * * * * * = every minute |
5 |
At value 5 | 5 * * * * = at minute 5 |
*/15 |
Every 15 units | */15 * * * * = every 15 minutes |
0,30 |
At values 0 and 30 | 0,30 * * * * = at :00 and :30 |
0-10 |
Values 0 through 10 | 0-10 * * * * = minutes 0-10 |
0-30/5 |
Every 5 in range 0-30 | 0-30/5 * * * * = 0,5,10,15,20,25,30 |
mon-fri |
Weekday range | 0 9 * * mon-fri = 9 AM weekdays |
When both day-of-month and day-of-week are restricted (neither is *), a day matches if either matches. Example: 0 0 1 * mon = midnight on the 1st of the month OR any Monday.
Recipes are named presets that bundle a cron expression with a prompt:
| Recipe | Expression | Prompt |
|---|---|---|
git-recap |
*/30 * * * * |
Summarize the git commits since your last run; note anything risky. |
ci-watch |
*/15 * * * * |
Check CI status for the current branch and report failures with the failing step. |
todo-pulse |
0 * * * * |
Scan the working tree for new TODO/FIXME markers added recently and list them. |
daily-summary |
0 9 * * * |
Write a short progress summary of what changed in this repo over the last day. |
# Use a recipe directly
pvyai cron add --recipe ci-watch
# Override the schedule
pvyai cron add "*/10 * * * *" --recipe ci-watch
loop.md Design PatternInstead of embedding a long prompt in --prompt, you can write a loop.md file that the agent reads on every fire. This is the loop design pattern — the prompt lives in your repo, not on the command line.
When a cron job fires without --prompt, PVYai resolves the loop prompt from the first readable loop.md found:
| Priority | Path | Scope |
|---|---|---|
| 1 | <cwd>/.pvyai/loop.md |
Project-specific |
| 2 | <cwd>/.agents/loop.md |
Alternative location |
| 3 | ~/.pvyai/loop.md |
User-wide |
| 4 | ~/.agents/loop.md |
Alternative user location |
| 5 | Built-in fallback | See below |
If no loop.md is found, the default prompt is:
Continue the ongoing task in this repository.
Inspect recent changes, make incremental progress, and stop when you reach a natural checkpoint.
Do not take destructive actions without strong justification.
Report what you did succinctly.
loop.md.pvyai/loop.md:
# Ongoing Task: API Migration v2
We're migrating the API from v1 to v2. Each cycle:
1. Check `git log --oneline -5` for recent progress
2. Find the next un-migrated endpoint (grep for `v1/` in routes.go)
3. Migrate ONE endpoint per cycle — don't batch
4. Run `go test ./internal/api/...` after each migration
5. Update `MIGRATION_PROGRESS.md` with the completed endpoint
6. Commit with message `migrate(api): <endpoint> to v2`
7. Stop when you hit a natural checkpoint (test failure, ambiguity, or all done)
Rules:
- Never delete v1 routes — only add v2 alongside
- Always run tests before committing
- If a test fails, revert your change and report the error
- Don't touch files outside `internal/api/`
loop.md files over 25,000 bytes return an errorEach job lives in its own directory:
~/.local/share/pvyai/cron/
└── 20260707-150405/
├── metadata.json # Job definition + schedule state
└── runs.jsonl # Append-only run history
metadata.json{
"id": "20260707-150405",
"expr": "0 9 * * *",
"prompt": "Write a short progress summary...",
"cwd": "/path/to/repo",
"model": "claude-sonnet-4.5",
"status": "active",
"fireCount": 42,
"nextRunAt": "2026-07-08T09:00:00Z",
"createdAt": "2026-07-07T15:04:05Z"
}
runs.jsonl{"jobId":"20260707-150405","at":"2026-07-08T09:00:00Z","exitCode":0,"sessionTitle":"cron:daily-summary"}
{"jobId":"20260707-150405","at":"2026-07-09T09:00:00Z","exitCode":0,"sessionTitle":"cron:daily-summary"}
Each line is a RunRecord with the job ID, fire time, exit code, session title, and optional error.
pvyai cron run
This starts a 30-second tick loop. It fires any due jobs, runs them via pvyai exec, records the results, and advances the schedule.
pvyai cron run --once
Fires every currently-due job once and exits. Use this when an external scheduler (system cron, launchd, Kubernetes CronJob) handles the timing:
# /etc/cron.d/pvyai-cron
*/5 * * * * pvyai pvyai cron run --once >> /var/log/pvyai-cron.log 2>&1
pvyai cron run --catch-up
By default, overdue jobs are rescheduled to their next future slot (skip the backlog). With --catch-up, overdue jobs fire immediately.
When the cron loop fires a job, it atomically claims the fire under a per-job lock:
NextRunAt is advanced before the job runspvyai cron run instances) can’t both fire the same jobThe cron parser is robust to daylight saving time:
The search is bounded to 9 years (nextSearchYears = 9) to handle the worst-case gap between consecutive valid days (Feb 29 across a century non-leap-year).
.pvyai/loop.md:
# Database Migration: v1 → v2
Each cycle:
1. Read `MIGRATION_TODO.md` for the next pending migration
2. Write the migration script in `migrations/NNN_description.up.sql`
3. Run `make migrate-up` to apply it
4. Run `make test` to verify
5. Mark the migration as done in `MIGRATION_TODO.md`
6. Commit: `db(migrate): NNN description`
Schedule: pvyai cron add "0 */2 * * *" --cwd /path/to/project
.pvyai/loop.md:
# CI Failure Triage
1. Run `gh run list --status failure --limit 1` to find the latest failed run
2. Run `gh run view <run-id> --log-failed` to get the failure logs
3. Analyze the failure — is it a real bug, flaky test, or env issue?
4. If it's a real bug, create a minimal reproduction and open an issue:
`gh issue create --title "CI failure: <summary>" --body "<details>"`
5. If it's a flaky test, add a `t.Skip` with a comment and note in the PR
6. Report findings
Schedule: pvyai cron add "*/15 * * * *" --recipe ci-watch
.pvyai/loop.md:
# Dependency Watch
1. Run `go list -m -u all 2>/dev/null | grep '\['` to find outdated dependencies
2. For each outdated dependency, check:
- Is the update a major version? (breaking)
- Are there security advisories? (`govulncheck ./...`)
- Is the changelog relevant to our code?
3. Open issues for security-relevant updates
4. Report a summary of all outdated dependencies
Schedule: pvyai cron add "0 6 * * 1" --cwd /path/to/project (Mondays at 6 AM)
| Practice | Why |
|---|---|
Use loop.md instead of --prompt |
Prompt lives in the repo, version-controlled, editable |
| Keep loops single-step | “Migrate ONE endpoint per cycle” not “migrate all endpoints” |
| Always run tests before committing | Loops are autonomous — bad commits go unnoticed longer |
Set --cwd explicitly |
Don’t rely on the cron process’s current directory |
| Run under a process manager | tmux, screen, systemd, or launchd keep the loop alive |
Check runs.jsonl periodically |
Monitor for failures or unexpected behavior |
Use --once with system cron |
More reliable than a long-running foreground process |
| Don’t embed secrets in prompts | The prompt is stored in plaintext on disk |