Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.praison.ai/llms.txt

Use this file to discover all available pages before exploring further.

Custom actions turn repeating logic into reusable building blocks for job workflows. Define them inline in YAML, as standalone Python files, or use built-in actions — all referenced with action: my-action.
steps:
  - name: Say hello
    action: greet
That single line triggers a 3-tier resolution chain to find and run the action.

How Actions Resolve

PriorityTypeWhere it lives
1stYAML-definedactions: block in the workflow file
2ndFile-basedactions/my_action.py or .praison/actions/my_action.py
3rdBuilt-inShipped with PraisonAI (e.g., bump-version)
[!TIP] YAML-defined actions always win. This lets you override any file-based or built-in action per-workflow.

YAML-Defined Actions

Self-contained in the workflow file — no external files needed. Define them in the top-level actions: block.
yaml-actions-demo.yaml
type: job
name: yaml-actions-demo
description: Demonstrates YAML-defined custom actions

actions:
  check-python:
    run: python3 --version

  count-files:
    script: |
      import os
      cwd = os.getcwd()
      py_files = [f for f in os.listdir(cwd) if f.endswith('.py')]
      yaml_files = [f for f in os.listdir(cwd) if f.endswith('.yaml') or f.endswith('.yml')]
      result = f"Python: {len(py_files)}, YAML: {len(yaml_files)}"

  timestamp:
    script: |
      from datetime import datetime
      result = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

steps:
  - name: Check Python version
    action: check-python

  - name: Count project files
    action: count-files

  - name: Get timestamp
    action: timestamp
praisonai workflow run yaml-actions-demo.yaml

YAML Action Types

Each action defines one key — the same types available in regular steps:
KeyWhat it does
run:Shell command
script:Inline Python (set result variable for output)
python:Run a Python script file

File-Based Actions

Reusable .py files that can be shared across multiple workflows.

Directory Structure

my-project/
├── deploy.yaml             ← workflow file
├── actions/                ← per-workflow actions
│   ├── system_info.py
│   └── line_count.py
└── .praison/               ← project-level actions
    └── actions/
        └── deploy_k8s.py

The Action Contract

Every action file must export a run function:
def run(step, flags, cwd):
    """
    Args:
        step:  dict — the full YAML step (access custom keys)
        flags: dict — CLI flags (e.g., {"major": True})
        cwd:   str  — workflow working directory

    Returns:
        dict with:
          {"ok": True, "output": "..."} on success
          {"ok": False, "error": "..."} on failure
    """
    return {"ok": True, "output": "Hello from my action!"}

Example: System Info Action

actions/system_info.py
import platform
import os

def run(step, flags, cwd):
    """Collect system information."""
    info_parts = [
        f"OS: {platform.system()} {platform.release()}",
        f"Python: {platform.python_version()}",
        f"Machine: {platform.machine()}",
        f"CWD: {cwd}",
    ]

    # Read custom config from the step YAML
    if step.get("show_env"):
        for var in step["show_env"].split(","):
            var = var.strip()
            info_parts.append(f"{var}: {os.environ.get(var, '(not set)')}")

    return {"ok": True, "output": " | ".join(info_parts)}

Example: Line Count Action

actions/line_count.py
from pathlib import Path

def run(step, flags, cwd):
    """Count lines in files matching a glob pattern."""
    pattern = step.get("pattern", "*.py")
    directory = step.get("directory", ".")
    target = Path(cwd) / directory

    if not target.exists():
        return {"ok": False, "error": f"Directory not found: {target}"}

    total_lines = 0
    file_count = 0

    for filepath in target.rglob(pattern):
        if filepath.is_file():
            try:
                total_lines += len(filepath.read_text().splitlines())
                file_count += 1
            except (UnicodeDecodeError, PermissionError):
                continue

    return {
        "ok": True,
        "output": f"{file_count} files, {total_lines} lines ({pattern} in {directory})",
    }

Using File-Based Actions

Reference them by name — dashes become underscores when looking up the .py file:
file-actions-demo.yaml
type: job
name: file-actions-demo
description: Demonstrates file-based custom actions

steps:
  - name: System info
    action: system-info

  - name: Count Python files
    action: line-count
    pattern: "*.py"
    directory: "."

  - name: Count YAML files
    action: line-count
    pattern: "*.yaml"
    directory: "."
praisonai workflow run file-actions-demo.yaml
[!NOTE] action: system-info resolves to actions/system_info.py. Dashes in the action name become underscores in the filename.

Built-in Actions

Shipped with PraisonAI, no files needed:
ActionDescription
bump-versionBump version in pyproject.toml (patch, minor, or major)
- name: Bump version
  action: bump-version
  file: pyproject.toml
  strategy: patch
Flags --major and --minor override the strategy at runtime:
praisonai workflow run release.yaml --major

Passing Data to Actions

Custom keys on the step are passed through to the action via the step dict:
steps:
  - name: Deploy to staging
    action: deploy
    environment: staging       # ← custom key
    region: us-east-1          # ← custom key
    skip_health_check: false   # ← custom key
actions/deploy.py
def run(step, flags, cwd):
    env = step.get("environment", "production")
    region = step.get("region", "us-west-2")
    skip_health = step.get("skip_health_check", False)

    # ... deploy logic ...
    return {"ok": True, "output": f"Deployed to {env} in {region}"}

Resolution Priority Demo

When the same action name exists in both YAML and as a file, YAML wins:
type: job

actions:
  greet:
    script: |
      result = "Hello from YAML!"    # ← this runs

steps:
  - name: Greet
    action: greet
    # Even if actions/greet.py exists, the YAML definition takes priority

Best Practices

If the action is only used in one workflow, define it inline. This keeps the workflow portable — a single file you can copy or share.
If multiple workflows need the same action, put it in actions/ (per-project) or .praison/actions/ (project-level shared).
File-based actions must return {"ok": True, "output": "..."} or {"ok": False, "error": "..."}. The workflow executor checks ok to determine pass/fail.
Dry run shows all steps and their action names without executing:
praisonai workflow run my-workflow.yaml --dry-run

Job Workflows

Core job workflow syntax and step types

YAML Workflows

Agent-based YAML workflows