@kky42/pievo
v0.5.6
Published
Durable loops for AI agents — run pi-flow workflows on a cadence, with daily quotas, an append-only ledger, and a live dashboard
Downloads
4,288
Maintainers
Readme
pievo
Durable loops for AI agents — run pi-flow workflows on a cadence, with daily quotas, an append-only ledger, and a live dashboard.

Pievo runs agent work that has to keep happening while you're not looking — knowledge-base refreshes, morning digests, issue triage, monitoring. You register a loop (one named workflow plus a cadence), and pievo runs it on schedule, records every run durably, and shows you exactly what happened.
- Durable by construction. Every accepted run is committed to an append-only ledger before it executes and ends with exactly one terminal record —
complete,blocked,error, orinterrupted— even across crashes and daemon restarts. Startup recovery confirms stale workers are gone and closes out orphaned runs; it never guesses and never resumes half-finished work. - Spend under control. Per-loop daily quotas on runs, tokens, and cost; same-loop overlap is impossible; an error breaker blocks a loop after N consecutive failures instead of burning tokens all night.
- Agent-first CLI, human-first dashboard. Every command prints one JSON envelope with stable shapes and exit codes, so agents and scripts can operate pievo directly. Humans get loop cards, statistics, run history, and live logs in the browser.
- Honest state. The ledger is the only source of truth; everything else is a rebuildable copy. Unknown token counts display as unknown, never as a made-up zero.
Install
npm install -g @kky42/pievoRequires Node.js >= 22.19. Workflows execute pi coding agents through pi-flow (both bundled as dependencies) and use your existing pi provider setup — if pi already works on your machine, pievo runs are ready to go.
Quickstart
1. Write a workflow. Workflows are plain JavaScript files in your project at .pi/workflows/<name>.js, with agent() calls doing the real work:
// ~/loops/.pi/workflows/kb-update.js
export const meta = { name: "kb-update", description: "Keep the knowledge base current." };
phase("Review sources");
const notes = await agent("Review notes changed since the last run and list stale entries.");
phase("Update entries");
await agent(`Update the knowledge base accordingly:\n${notes}`);
return { status: "complete", message: "Knowledge base refreshed." };A workflow returns { status: "complete" | "blocked", message?, data? }. blocked is a strict, sticky intervention gate: use it only when authoritative evidence proves that an indispensable external action is outside the workflow's authority and rerunning cannot reasonably resolve it. Transient failures should error; safe no-ops and retry/reconciliation checkpoints should complete. See pi-flow for the full workflow API (agent, phase, parallel, schemas, subagent types).
2. Describe the loop.
# kb-update.yaml
version: 1
name: kb-update
objective: Keep the personal knowledge base current.
cwd: /Users/you/loops # absolute path; workflows live under it
cadence:
kind: delay # re-run 6h after each run finishes
after_completion: 6h
workflow:
name: kb-update
timeout: 60m
quotas:
max_runs_per_day: 8
max_cost_usd_per_day: 53. Start the daemon and the loop.
pievo daemon start # prints the dashboard URL
pievo loop register kb-update.yaml
pievo loop start kb-update # new loops register paused; this activates itA due loop accepts a run immediately — the run_id is right there in the response. Commands never wait for execution: run-starting commands return at acceptance, and you poll for the outcome.
4. Watch it work.
pievo loop show kb-update # config, quota usage, next due, recent runs
pievo run show <run-id> # one run in full: state, result, usage
pievo run logs <run-id> # lifecycle + workflow logs, live while executingOr just open the dashboard:

Cadences
delay— level-triggered: re-runafter_completionafter each run's terminal event, whatever the outcome. A fresh loop with no history is due immediately once started.cron— edge-triggered: five fields, each*, a number, or a comma list (30 8 * * 1,2,3,4,5), evaluated in a configured IANA timezone. Missed ticks (downtime, paused, quota) are skipped, never replayed as a backlog.
Manual pievo loop run <name> works from any status and still respects overlap and quota (--ignore-quota to override quota once).
CLI
Every command prints exactly one JSON envelope: { ok, command, data, diagnostics, ... }. Exit codes: 0 ok, 1 expected failure, 2 usage error, 70 internal error. Reads work even with no daemon; mutations require it and never fall back to local writes.
| Command | What it does |
|---|---|
| pievo daemon start | stop | restart | status | Manage the per-home daemon (--foreground for supervisors). |
| pievo doctor | Environment and state-root health checks. |
| pievo loop register <config.yaml> | Create or update a loop by content hash. |
| pievo loop list | Triage view: status, active/latest run, next due. |
| pievo loop show <name> | Full view: config, quota usage, consecutive errors, recent runs. |
| pievo loop start | pause <name> | Activate / stop future scheduling (pause never kills a running run). |
| pievo loop run <name> | Accept one manual run now; returns its run_id. |
| pievo loop interrupt <name> | Interrupt the active run (cooperative, then SIGTERM/SIGKILL). |
| pievo loop remove <name> | Remove from current state; ledger and run history survive. |
| pievo loop runs <name> | Recent run summaries, newest first. |
| pievo run show <run-id> | One run in full, including the workflow's result.data. |
| pievo run logs <run-id> | Tail lifecycle and workflow logs, live while it executes. |
Loop config reference
version: 1 # schema version, always 1
name: kb-update # stable key: ^[a-z0-9][a-z0-9_-]{0,63}$
objective: Keep the knowledge base current.
cwd: /abs/path/to/project # must exist; workflow file lives under it
cadence:
kind: delay # …or: kind: cron
after_completion: 6h # expr: "0 9 * * *"
# timezone: Asia/Taipei
workflow:
name: kb-update # must match .pi/workflows/<name>.js meta.name
args: {} # JSON args passed to the workflow
timeout: 240m # wall-clock cap enforced outside the worker (default 240m)
quotas: # optional daily gates, machine-local calendar day
max_runs_per_day: 24
max_tokens_per_day: 1000000
max_cost_usd_per_day: 10
policy:
max_consecutive_errors: 3 # errors in a row before the loop blocks (default 3)Durations are <N>s|m|h|d strings. Unknown keys are rejected at every level, so a misspelled quota can never silently disappear. Registration is create-or-update by content hash — edit the YAML and register again; running runs keep the config they started with.
Loop lifecycle
A loop is active (scheduler may start runs), paused (operator said stop), or blocked (scheduling stopped for operator attention). Workflow-authored blocked is intentionally strict and sticky: transient failures should become error runs, while safe no-ops and retry/reconciliation checkpoints remain complete. The error breaker can also block a loop after max_consecutive_errors failures. pievo loop start clears blocked and resumes. To stop everything now: pause (future runs), then interrupt (the current one).
Dashboard
pievo daemon start serves the dashboard on loopback (127.0.0.1) by default — local access needs no token. To reach it from other machines, bind a host and set a token:
PIEVO_DASHBOARD_TOKEN=<secret> pievo daemon start --host 0.0.0.0 --port 7842The token is env-only by design (flags leak into ps), and clients on a verified Tailscale tunnel get tokenless access. PIEVO_HOME (default ~/.pievo) selects the state root; one daemon owns one home.
Durability, briefly
PIEVO_HOME/ledger.jsonl is an append-only event log and the single source of truth — state.json, loops/*.yaml, and runs/<run_id>/result.json are rebuildable copies, and runs/<run_id>/logs.jsonl streams while the run executes. Core best-effort records workflow_started for invoked workflows and workflow_finished after normal terminal commit, including terminal state, concise message or diagnostic, and observed usage, even when the workflow emits no custom logs. Finalization failure is logged separately and never masquerades as a committed terminal outcome. Events are validated and fsynced before a command is acknowledged; torn tails are truncated, real corruption fails loudly rather than being "repaired". A new daemon epoch never adopts unfinished work: recovery kills confirmed-stale workers, writes their terminal events, and refuses to schedule anything if it cannot prove a worker is gone.
Upgrades are boring on purpose: pievo daemon stop, update the package, pievo daemon start, then compare pievo --version with pievo daemon status.
The full specification — terminology, event schemas, invariants — lives in CONTEXT.md.
Let your agent drive
The package ships an agent skill at skills/pievo/SKILL.md that teaches a coding agent to operate pievo through the JSON CLI — creating loops, polling runs, diagnosing blocked or over-quota loops. Load it with pi --skill <path-to>/skills/pievo, or copy it into your harness's skills directory (Claude Code: .claude/skills/pievo/).
License
MIT
