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

  1. At dispatch, each task is snapshotted into a task run, editing the job later never rewrites past runs.
  2. Tasks whose dependencies are all success are claimed atomically and sent to the agent.
  3. On a terminal status, the DAG advances: retry-eligible failures flip back to pending; non-retryable failures cascade skipped downstream; the run finalises when nothing is pending or running.

Task states

StateMeaning
pending → runningQueued, then claimed by an agent.
success / failedTerminal outcome.
skippedAn upstream failed/cancelled, so this never ran.
cancelledThe 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.