# Pipecat Multi-Task Examples

This directory contains example bots that use the multi-task framework in `pipecat.tasks`, `pipecat.pipeline.runner` (with `spawn()`), and the `TaskBus`. Each example shows a different cooperation pattern between tasks: hand-off, parallel fan-out, remote workers, etc.

## Setup

From the repo root:

```bash
uv sync --all-extras
source .venv/bin/activate
cd examples/multi-task
```

Copy the env template and fill in your API keys:

```bash
cp env.example .env
```

## Environment variables

| Variable           | Required by                             |
| ------------------ | --------------------------------------- |
| `OPENAI_API_KEY`   | LLM tasks                               |
| `DEEPGRAM_API_KEY` | STT                                     |
| `CARTESIA_API_KEY` | TTS                                     |
| `DAILY_API_KEY`    | Optional: only with `--transport daily` |

Additional, example-specific variables are listed below.

## Table of contents

**[Local](#local)** (single process)

- [Handoff between LLM tasks](#handoff-between-llm-tasks)
- [Parallel debate](#parallel-debate)
- [Voice code assistant with Claude Agent SDK](#voice-code-assistant)

**[Distributed](#distributed)** (multi-process)

- [Handoff via Redis](#handoff-via-redis)
- [Handoff via PGMQ (Postgres)](#handoff-via-pgmq-postgres)
- [LLM task via WebSocket proxy](#llm-task-via-websocket-proxy)

# Local

Examples where all tasks run in the same process on an `AsyncQueueBus`.

## Handoff between LLM tasks

Two LLM tasks (greeter + support) that transfer control to each other during a voice conversation. A main task owns the transport pipeline and bridges frames to the bus.

### Running

```bash
uv run local-handoff/local-handoff-two-agents.py
```

Open <http://localhost:7860/client> in your browser to talk to your bot.

To use Daily transport:

```bash
uv run local-handoff/local-handoff-two-agents.py --transport daily
```

### Overview

- **[`local-handoff-two-agents.py`](local-handoff/local-handoff-two-agents.py)** — Two LLM tasks (greeter + support) that hand off via `BaseTask.handoff_to(...)`. The main task owns STT, TTS, transport, and a `BusBridgeProcessor`.
- **[`local-handoff-two-agents-tts.py`](local-handoff/local-handoff-two-agents-tts.py)** — Same shape, but each child task ships with its own `CartesiaTTSService` in a custom pipeline. The main task has no TTS — audio comes from whichever child is active over the bus.

## Parallel debate

Parallel fan-out using `task.job_group(...)`. A voice bot takes a topic from the user, kicks off three worker tasks in parallel (advocate, critic, analyst), waits for all three to respond, and synthesizes a balanced answer. Each worker keeps its own LLM context across rounds.

### Running

```bash
uv run parallel-debate/parallel-debate.py
```

Open <http://localhost:7860/client> in your browser to talk to your bot.

To use Daily transport:

```bash
uv run parallel-debate/parallel-debate.py --transport daily
```

### Architecture

```
Main task (transport + LLM + `debate` tool)
  └── job_group(advocate, critic, analyst)
        └── DebateWorker (LLMContextTask, one per role)
```

- **Main task**: transport (STT, TTS) + LLM moderator with a `debate` direct function that fans out via `task.job_group(...)`.
- **Debate workers**: `LLMContextTask`s spawned on the runner. Each keeps its own `LLMContext` across rounds and ships its completed turn back as a job response via the assistant-aggregator's `on_assistant_turn_stopped` event.

## Voice code assistant

Talk to your codebase hands-free. Ask questions about code, project structure, or file contents and get spoken answers based on actual files. The Claude Agent SDK worker navigates the filesystem using `Read`, `Bash`, `Glob`, and `Grep` tools.

### Additional environment variables

| Variable            | Required by                    |
| ------------------- | ------------------------------ |
| `ANTHROPIC_API_KEY` | Code worker (Claude Agent SDK) |
| `PROJECT_PATH`      | Optional, defaults to cwd      |

### Running

```bash
# Default: explores the current directory
uv run code-assistant/code-assistant.py

# Specify a project path
PROJECT_PATH=/path/to/your/project uv run code-assistant/code-assistant.py
```

Open <http://localhost:7860/client> in your browser to talk to your bot.

To use Daily transport:

```bash
uv run code-assistant/code-assistant.py --transport daily
```

### Example questions

- "What does the main module do?"
- "Find all TODO comments in the project"
- "How is error handling implemented?"
- "What dependencies does this project use?"
- "Explain the test structure"

### Architecture

```
Main task (transport + LLM + `ask_code` tool)
  └── job → CodeWorker (Claude Agent SDK)
```

- **`code-assistant.py`** — Main task: STT, LLM (with system prompt + `ask_code` direct function), TTS, and transport. The `ask_code` tool dispatches a job to the worker via `task.job("code_worker", payload=...)`.
- **`code_worker.py`** — `CodeWorker`: a bus-only `BaseTask` spawned on the runner. It accepts `@job`-style requests through the bus and runs them sequentially through a persistent Claude SDK session so follow-up questions share context.

# Distributed

Examples where tasks run across separate processes or machines.

## Handoff via Redis

Same two-task handoff as the local example, but each task runs as a separate process connected via Redis pub/sub. Requires `pip install pipecat-ai[redis]`.

### Quick start (single machine, local Redis)

_Terminal 1_: start Redis

```bash
docker run --rm -p 6379:6379 redis:7
```

_Terminal 2_: start the greeter worker

```bash
uv run distributed-handoff/redis-handoff/llm.py greeter
```

_Terminal 3_: start the support worker

```bash
uv run distributed-handoff/redis-handoff/llm.py support
```

_Terminal 4_: start the main transport task

```bash
uv run distributed-handoff/redis-handoff/main.py
```

All processes connect to `redis://localhost:6379` by default.

### Running across machines

Point each process at the same Redis instance:

_Machine A_

```bash
uv run distributed-handoff/redis-handoff/main.py --redis-url redis://your-redis-host:6379
```

_Machine B_

```bash
uv run distributed-handoff/redis-handoff/llm.py greeter --redis-url redis://your-redis-host:6379
```

_Machine C_

```bash
uv run distributed-handoff/redis-handoff/llm.py support --redis-url redis://your-redis-host:6379
```

### Architecture

```
Machine A                    Redis                    Machine B
+------------+          +-------------+          +-------------+
| main.py    |  <---->  | pub/sub     |  <---->  |    llm.py   |
| (transport,|          | channel:    |          |  (greeter)  |
|  STT, TTS) |          | pipecat:acme|          +-------------+
+------------+          +-------------+          +-------------+
                               ^                 |    llm.py   |
                               +-------------->  |  (support)  |
                                                 +-------------+
```

- **[main.py](distributed-handoff/redis-handoff/main.py)** — Transport task: Daily/WebRTC, Deepgram STT, Cartesia TTS, and a `BusBridgeProcessor` over a `RedisBus`.
- **[llm.py](distributed-handoff/redis-handoff/llm.py)** — LLM worker: runs either `greeter` or `support` with OpenAI behind a bridged `LLMTask`.

## Handoff via PGMQ (Postgres)

Same shape as the Redis handoff, but the bus is backed by [PGMQ](https://github.com/tembo-io/pgmq) on a shared Postgres database (e.g. Supabase). Requires `pip install pipecat-ai[pgmq]`.

### Additional environment variables

| Variable       | Required by                                                          |
| -------------- | -------------------------------------------------------------------- |
| `DATABASE_URL` | PostgreSQL DSN (e.g. Supabase pooled connection string)              |
| `PGMQ_CHANNEL` | Optional, channel prefix for queue names. Defaults to `pipecat_acme` |

### Quick start

_Terminal 1_: start the greeter worker

```bash
uv run distributed-handoff/pgmq-handoff/llm.py greeter --database-url $DATABASE_URL
```

_Terminal 2_: start the support worker

```bash
uv run distributed-handoff/pgmq-handoff/llm.py support --database-url $DATABASE_URL
```

_Terminal 3_: start the main transport task

```bash
uv run distributed-handoff/pgmq-handoff/main.py --database-url $DATABASE_URL
```

You can also set `DATABASE_URL` in `.env` and omit the `--database-url` flag.

### Architecture

Same as the Redis handoff above; the `RedisBus` is replaced by a `PgmqBus`, and the "pub/sub channel" is a set of PGMQ queues on the shared Postgres instance.

## LLM task via WebSocket proxy

Runs an LLM task on a remote server, connected to the main transport task via a WebSocket proxy. No shared bus required — the proxy tasks forward bus messages point-to-point over the WebSocket.

### Quick start (single machine)

_Terminal 1_: start the remote assistant server

```bash
uv run remote-proxy-assistant/assistant.py
```

_Terminal 2_: start the main transport task

```bash
uv run remote-proxy-assistant/main.py --remote-url ws://localhost:8765/ws
```

Open <http://localhost:7860/client> in your browser to talk to the bot.

### Running across machines

_Server machine_: start the assistant

```bash
uv run remote-proxy-assistant/assistant.py --host 0.0.0.0 --port 8765
```

_Client machine_: point at the server

```bash
uv run remote-proxy-assistant/main.py --remote-url ws://server-host:8765/ws
```

### Architecture

```
    +-------------+    +-------------+           +-------------+     +-----------------+
    |             |    |             |           |             |     |                 |
    |  Main task  |    | Proxy task  |  <~~~~~>  | Proxy task  |     | Assistant task  |
    |             |    |  (client)   |           |  (server)   |     |                 |
    +-------------+    +-------------+           +-------------+     +-----------------+
        messages           messages                  messages             messages
            │                 │                         │                    │
  ══════════╧═════════════════╧════════         ════════╧════════════════════╧═══════════
                Task Bus                                       Task Bus
  ═════════════════════════════════════         ═════════════════════════════════════════
```

- **[main.py](remote-proxy-assistant/main.py)** — Transport task with STT, TTS, and a `BusBridge`. Spawns a `WebSocketProxyClientTask` that connects to the remote server and forwards `BusFrameMessage`s.
- **[assistant.py](remote-proxy-assistant/assistant.py)** — FastAPI server. Each WebSocket connection spawns a `WebSocketProxyServerTask` plus a bridged `AcmeAssistant` LLM task on a per-session `PipelineRunner`.

### Security

The proxy tasks filter messages by task name:

- Only messages targeted at the remote task cross the WebSocket
- Only messages targeted at the local task are accepted from the WebSocket
- Broadcast messages never cross the WebSocket

Pass HTTP headers for authentication:

```python
proxy = WebSocketProxyClientTask(
    "proxy",
    url="wss://server-host:8765/ws",
    remote_task_name="assistant",
    local_task_name="acme",
    headers={"Authorization": "Bearer <token>"},
)
```
