> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Async Jobs

> Submit and manage long-running agent jobs and recipes via HTTP API

The `run` command manages async job execution for agents and recipes via a jobs server.

<Note>
  **`run` is overloaded.** `praisonai run <yaml-file>` runs the team through the modern agent runner (see [Run CLI](/docs/cli/run)); `praisonai run <verb>` — where `<verb>` is `submit`, `status`, `result`, `cancel`, `list`, or `stream` — reaches the jobs API documented below. Reserved job verbs **win** over a same-named file in the current directory, so `praisonai run submit "…"` always reaches jobs even if a file called `submit` exists in cwd. Use an explicit path (`praisonai run ./submit`) to force the agent runner in that case.
</Note>

## Quick Start

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Start the jobs server
praisonai serve jobs --port 8005

# Or, using uvicorn directly (factory mode)
python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory

# Submit a job
praisonai run submit "Analyze this data"

# Submit a recipe as job
praisonai run submit "Analyze AI trends" --recipe news-analyzer
```

## Commands Overview

| Command                     | Description         |
| --------------------------- | ------------------- |
| `praisonai run submit`      | Submit a new job    |
| `praisonai run status <id>` | Get job status      |
| `praisonai run result <id>` | Get job result      |
| `praisonai run stream <id>` | Stream job progress |
| `praisonai run list`        | List all jobs       |
| `praisonai run cancel <id>` | Cancel a job        |

## How routing works inside `run`

`praisonai run` inspects its first positional token to decide between the async-jobs API and the modern agent runner.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start[📋 praisonai run FIRST-TOKEN] --> Flag{🔍 starts<br/>with -?}
    Flag -->|Yes| Jobs[🤖 Jobs API<br/>argparse]
    Flag -->|No| Verb{🔍 reserved job verb?<br/>submit/status/result/<br/>cancel/list/stream}
    Verb -->|Yes| Jobs
    Verb -->|No| File{🔍 ends in .yaml/.yml<br/>or existing file?}
    File -->|Yes| Runner[✅ Modern agent runner<br/>run_app]
    File -->|No| Jobs

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef check fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef jobs fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef runner fill:#10B981,stroke:#7C90A0,color:#fff

    class Start input
    class Flag,Verb,File check
    class Jobs jobs
    class Runner runner
```

| You type                                                  | Route                                  | Result                                                               |
| --------------------------------------------------------- | -------------------------------------- | -------------------------------------------------------------------- |
| `praisonai run submit "task"`                             | Jobs API                               | Submit a new job                                                     |
| `praisonai run status run_abc123`                         | Jobs API                               | Get job status                                                       |
| `praisonai run agents.yaml`                               | Agent runner                           | Runs the YAML workflow                                               |
| `praisonai run ./my_agents.yaml`                          | Agent runner                           | Same — explicit path                                                 |
| `praisonai run existing_file.py`                          | Agent runner                           | Existing-file predicate wins                                         |
| `praisonai run ./submit` (file named `submit`)            | Agent runner                           | Path separator dodges the reserved-verb guard                        |
| `praisonai run submit` (file `submit` also exists in cwd) | Jobs API                               | Reserved verb wins over same-named file                              |
| `praisonai run "some prompt"`                             | Jobs API → **exit 2 `invalid choice`** | Free-text prompts belong to `praisonai "…"`, not `praisonai run "…"` |

<Note>
  Free-text prompts belong to the bare-prompt shape `praisonai "…"` (which the [CLI Dispatcher](/docs/features/cli-dispatcher) rewrites to a modern `run` invocation), not to `praisonai run "…"`. Typing `run` explicitly with a non-file, non-verb token still falls through to the jobs argparse parser and errors with `invalid choice`.
</Note>

## Starting the Jobs Server

Before using job commands, start the jobs server:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Start server on default port (8005)
praisonai serve jobs --port 8005

# With custom host/port
praisonai serve jobs --host 0.0.0.0 --port 8080

# Or, using uvicorn directly (factory mode)
python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory
```

## Submit a Job

### Basic Submission

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Basic submission with prompt
praisonai run submit "Analyze this data"

# Wait for completion
praisonai run submit "Quick task" --wait

# Stream progress after submission
praisonai run submit "Long task" --stream
```

### Submit with Recipe

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Submit with recipe
praisonai run submit "Analyze AI trends" --recipe news-analyzer

# With recipe config
praisonai run submit "Analyze AI trends" --recipe news-analyzer --recipe-config '{"format": "json"}'
```

### Submit with Agent File

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# With agent file
praisonai run submit "Process task" --agent-file agents.yaml
```

### Advanced Options

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# With timeout
praisonai run submit "Complex task" --timeout 7200

# With webhook
praisonai run submit "Task" --webhook-url https://example.com/callback

# With idempotency
praisonai run submit "Task" --idempotency-key order-123 --idempotency-scope session

# With metadata
praisonai run submit "Task" --metadata user=john --metadata priority=high

# JSON output
praisonai run submit "Task" --json

# Custom API URL
praisonai run submit "Task" --api-url http://localhost:8080
```

### Submit Options

| Option                | Description                                                            |
| --------------------- | ---------------------------------------------------------------------- |
| `--agent-file`        | Path to agents.yaml                                                    |
| `--recipe`            | Recipe name (mutually exclusive with --agent-file)                     |
| `--recipe-config`     | Recipe config as JSON string                                           |
| `--framework`         | Framework to use (default: praisonai)                                  |
| `--timeout`           | Timeout in seconds (default: 3600)                                     |
| `--wait`              | Wait for completion                                                    |
| `--stream`            | Stream progress after submission                                       |
| `--idempotency-key`   | Key to prevent duplicates                                              |
| `--idempotency-scope` | Scope: none, session, global                                           |
| `--webhook-url`       | Webhook URL for completion                                             |
| `--session-id`        | Session ID for grouping                                                |
| `--metadata`          | Custom metadata (KEY=VALUE, repeatable)                                |
| `--json`              | Output JSON for scripting                                              |
| `--api-url`           | Jobs API URL (default: [http://127.0.0.1:8005](http://127.0.0.1:8005)) |

## Check Job Status

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Get status
praisonai run status run_abc123

# JSON output
praisonai run status run_abc123 --json
```

### Status Output

```
Job: run_abc123
Status: running
Progress: 45%
Step: Processing data
Created: 2024-01-15 10:30:00
Duration: 45.2s
```

## Get Job Result

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Get result
praisonai run result run_abc123

# JSON output
praisonai run result run_abc123 --json
```

## Stream Job Progress

Stream real-time progress updates via SSE:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Stream progress
praisonai run stream run_abc123

# Raw JSON events
praisonai run stream run_abc123 --json
```

### Stream Output

```
[10%] Initializing agent
[20%] Loading recipe
[50%] Processing data
[90%] Finalizing
[100%] Completed
```

## List Jobs

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# List all jobs
praisonai run list

# Filter by status
praisonai run list --status running
praisonai run list --status succeeded
praisonai run list --status failed

# Pagination
praisonai run list --page 1 --page-size 20

# JSON output
praisonai run list --json
```

## Cancel a Job

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Cancel job
praisonai run cancel run_abc123

# JSON output
praisonai run cancel run_abc123 --json
```

## Idempotency

Prevent duplicate job submissions:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# First submission creates job
praisonai run submit "Process order" --idempotency-key order-123 --json

# Second submission returns same job (no duplicate)
praisonai run submit "Process order" --idempotency-key order-123 --json
```

### Idempotency Scopes

| Scope     | Description                |
| --------- | -------------------------- |
| `none`    | No idempotency (default)   |
| `session` | Unique within session      |
| `global`  | Unique across all sessions |

## Webhooks

Configure webhooks to receive notifications when jobs complete:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai run submit "Task" --webhook-url https://example.com/callback
```

Webhook payload:

```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
{
  "job_id": "run_abc123",
  "status": "succeeded",
  "result": {"output": "..."},
  "completed_at": "2024-01-15T10:30:00Z",
  "duration_seconds": 45.2
}
```

## Session Grouping

Group related jobs by session:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai run submit "Task 1" --session-id project-alpha
praisonai run submit "Task 2" --session-id project-alpha
```

## Python API

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai import recipe

# Submit recipe as async job
job = recipe.submit_job(
    "my-recipe",
    input={"query": "What is AI?"},
    config={"max_tokens": 1000},
    session_id="session_123",
    timeout_sec=3600,
    webhook_url="https://example.com/webhook",
    idempotency_key="unique-key-123",
    api_url="http://127.0.0.1:8005",
)

print(f"Job ID: {job.job_id}")
print(f"Status: {job.status}")

# Wait for completion
result = job.wait(poll_interval=5, timeout=300)
print(f"Result: {result}")
```

## Complete Workflow Example

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# 1. Start the server (in another terminal)
praisonai serve jobs --port 8005

# 2. Submit a job with recipe
praisonai run submit "Analyze AI news" --recipe news-analyzer --json
# Output: {"job_id": "run_abc123", "status": "queued", ...}

# 3. Check status
praisonai run status run_abc123

# 4. Stream progress
praisonai run stream run_abc123

# 5. Get result when done
praisonai run result run_abc123

# 6. List all jobs
praisonai run list
```

## Scripting with JSON

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
#!/bin/bash

API_URL="http://127.0.0.1:8005"

# Submit job
RESULT=$(praisonai run submit "Analyze data" --recipe analyzer --json --api-url $API_URL)
JOB_ID=$(echo $RESULT | jq -r '.job_id')

echo "Submitted job: $JOB_ID"

# Poll for completion
while true; do
    STATUS=$(praisonai run status $JOB_ID --json --api-url $API_URL | jq -r '.status')
    echo "Status: $STATUS"
    
    if [ "$STATUS" = "succeeded" ] || [ "$STATUS" = "failed" ]; then
        break
    fi
    
    sleep 5
done

# Get result
praisonai run result $JOB_ID --json --api-url $API_URL
```

## See Also

* [Async Jobs Feature](/docs/features/async-jobs)
* [Background Tasks CLI](/docs/cli/background)
* [Scheduler CLI](/docs/cli/scheduler)
* [Async Jobs SDK](/docs/sdk/praisonai/async-jobs)
* [Jobs API Reference](/docs/deploy/api/async-jobs/index)
