Guides
Jobs & pipelines
A job runs a command, or a whole DAG of tasks with dependencies, retries and timeouts. Each execution is a run; the scheduler dispatches ready tasks in topological order and rolls the result up to the run.
Single command vs DAG
Declare no tasks and a job runs one command (legacy mode). Declare tasks with depends_on edges and Polnor fans out a graph.
POST /api/v1/jobs
{
"name": "daily-pipeline",
"tasks": [
{ "key": "ingest", "command": "python ingest.py" },
{ "key": "transform", "command": "python tx.py",
"depends_on": ["ingest"], "max_retries": 2, "timeout_seconds": 1800 },
{ "key": "report", "command": "python report.py",
"depends_on": ["transform"] }
]
} How a run executes
- At dispatch, each task is snapshotted into a task run, editing the job later never rewrites past runs.
- Tasks whose dependencies are all
successare claimed atomically and sent to the agent. - On a terminal status, the DAG advances: retry-eligible failures flip back to
pending; non-retryable failures cascadeskippeddownstream; the run finalises when nothing is pending or running.
Task states
| State | Meaning |
|---|---|
pending → running | Queued, then claimed by an agent. |
success / failed | Terminal outcome. |
skipped | An upstream failed/cancelled, so this never ran. |
cancelled | The run was cancelled. |
Retries & timeouts
Set max_retries and timeout_seconds per task. A reconciler enforces both run-level and task-level timeouts; an expired task fails and the run finalises rather than hanging.
Cancel & logs
polnor runs cancel run_…
polnor runs logs run_… --task transform --follow Logs are tagged per task, so GET /api/v1/runs/{id}/tasks/{key}/logs returns just that task's output. Cancelling SIGTERMs then SIGKILLs running task processes on the agent.