Handler Tasks

Run Python code as a task step — no agent, no LLM call

A handler task runs your Python function instead of an LLM.

Diagram

🔍 Agent Task

⚡ Handler Task

✍️ Agent Task

Quick Start

  1. Handler only

    A task with a handler runs your function directly — no agent required.

    python
    from praisonaiagents import Agent, Task, PraisonAIAgents
    
    score = Task(name="score", handler=lambda ctx: {"score": 92})
    
    team = PraisonAIAgents(tasks=[score])
    team.start()
  2. Handler in a team with agents

    An agent researches, a handler post-processes, and another agent writes the report.

    python
    from praisonaiagents import Agent, Task, PraisonAIAgents
    
    researcher = Agent(name="Researcher", instructions="Research the topic and list key facts.")
    writer     = Agent(name="Writer",     instructions="Write a short report from the facts.")
    
    research = Task(
        name="research",
        description="Research renewable energy trends",
        agent=researcher,
        output_variable="research_data",
    )
    
    def summarise(ctx):
        facts = ctx.variables.get("research_data", "")
        return {"summary": facts[:500]}
    
    process = Task(name="process", handler=summarise, context=[research])
    
    report = Task(
        name="report",
        description="Write a report using {{summary}}",
        agent=writer,
        context=[process],
    )
    
    team = PraisonAIAgents(
        agents=[researcher, writer],
        tasks=[research, process, report],
    )
    team.start()

How It Works

The team runs each task in order, calling your function for handler tasks and the model for agent tasks.

Diagram
AgentHandlerPraisonAIAgentsUserAgentHandlerPraisonAIAgentsUserstart()run agent taskoutputrun handler(ctx)dict / stringfinal result

The context argument

The handler receives a WorkflowContext with .input, .current_step, and .variables.

python
from praisonaiagents import Task

def enrich(ctx):
    upstream = ctx.variables["research_data"]
    return {"enriched": upstream.upper()}

Task(name="enrich", handler=enrich)
FieldTypeDescription
inputstrOriginal run input
current_stepstrName of the current task
variablesdictShared variables from upstream tasks (keyed by output_variable)

Async handlers

An async def handler works too — the SDK awaits it under both start() and astart().

python
from praisonaiagents import Task

async def fetch_price(ctx):
    return {"price": 42.0}

Task(name="fetch_price", handler=fetch_price)

Common Patterns

A deterministic data step between two LLM steps keeps parsing predictable.

python
from praisonaiagents import Agent, Task, PraisonAIAgents

analyst = Agent(name="Analyst", instructions="Return a JSON list of numbers.")

collect = Task(name="collect", description="List sales figures", agent=analyst, output_variable="figures")

def total(ctx):
    figures = ctx.variables.get("figures", "")
    return {"total": sum(int(x) for x in figures.split() if x.isdigit())}

sum_task = Task(name="sum", handler=total, context=[collect])

PraisonAIAgents(agents=[analyst], tasks=[collect, sum_task]).start()

Skip the LLM when the answer already lives in variables.

python
from praisonaiagents import Task

def use_cached(ctx):
    return ctx.variables.get("cached_answer", "no answer yet")

Task(name="answer", handler=use_cached)

Best Practices

Keep handlers pure

A handler should compute from its inputs and return a value — avoid hidden global state so runs stay repeatable.

Return strings or dicts

Return a plain string or a dict. A dict merges into the shared variables so later tasks can read each key.

Use output_variable to hand data to the next step

Set output_variable on the handler task to name its result, then read it downstream with {{name}} or ctx.variables["name"].

Prefer handlers for API and DB calls

Reach for a handler when a step is a deterministic API or database call that does not need an LLM.


Related