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

# Quickstart

> Create an agent, run it locally, and deploy it. Entirely from the terminal. About 10 minutes.

This page builds an agent end-to-end from the command line. No Agent Studio, no clicking. By the end, you'll have:

1. Created the agent in xpander's control plane.
2. Run its handler locally against real prompts.
3. Deployed it as a container reachable through the REST API and any channel you wire up later.

<Note>
  If you'd rather start with a visual builder, the [Platform Guides Quickstart](/guides/quickstart) walks through the same flow inside [Agent Studio](https://app.xpander.ai).
</Note>

## Prerequisites

* **Node.js 20+** for the CLI.
* **Python 3.12+** for the local dev server.
* **An LLM provider key in your shell** (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or whichever provider you'll use). The agent runs against this key locally; in production, you can override it from the agent config.

## 1. Install and authenticate

All three commands are required:

1. The CLI ships via npm.
2. The SDK ships via pip.
3. `login` writes credentials that both the CLI and the SDK read.

```bash theme={"dark"}
# 1. CLI: creates agents, scaffolds projects, deploys, streams logs
npm install -g xpander-cli

# 2. SDK: the runtime library you import in Python
pip install "xpander-sdk[agno]"

# 3. Auth: opens a browser, writes ~/.xpander/credentials
xpander login

# Optional: on CI or a machine without a browser, use this instead.
#           Paste an API key from https://app.xpander.ai/settings.
xpander configure
```

## 2. Create the agent

`xpander agent new` does two things at once:

1. It creates the agent in xpander's control plane (giving it an ID, a default model, and an empty tool list).
2. It scaffolds the project files into the folder you point at.

```bash theme={"dark"}
# Create an agent named "my-first-agent" using the Agno framework
# and scaffold its files into the current directory.
xpander agent new \
    --name "my-first-agent" \
    --framework "agno" \
    --folder "."
```

Drop the flags to use the interactive wizard, which asks for the same three values one at a time.

When the command finishes, the current directory contains:

```
xpander_handler.py        # Your @on_task entry point.
xpander_config.json       # Agent ID, framework selection, deployment settings.
agent_instructions.json   # Agent system prompt (edit this to change behavior).
requirements.txt          # Python dependencies.
Dockerfile                # Used by xpander agent deploy.
.env                      # Prefilled with API key, org ID, and the new agent's ID.
```

Set up a virtual environment and install the dependencies:

```bash theme={"dark"}
# Use python3 explicitly so macOS doesn't fall back to system Python 2
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

## 3. Read the handler

The scaffolded `xpander_handler.py` is the canonical pattern for an xpander agent. Every example in the rest of these docs is a variation on this:

```python xpander_handler.py theme={"dark"}
from dotenv import load_dotenv
load_dotenv()

from xpander_sdk import on_task, Task, Backend, Tokens
from agno.agent import Agent

@on_task
async def handler(task: Task) -> Task:
    # 1. Fetch the agent's full configuration from xpander's control plane
    backend = Backend(configuration=task.configuration)
    agno_args = await backend.aget_args(task=task)

    # 2. Build the framework's own Agent class with that config
    agno_agent = Agent(**agno_args, debug_mode=True)

    # 3. Run the LLM loop with the task's input, files, and images
    result = await agno_agent.arun(
        input=task.to_message(),
        files=task.get_files(),
        images=task.get_images(),
    )

    # 4. Write the result back so xpander can store and display it
    task.result = result.content
    task.tokens = Tokens(
        prompt_tokens=result.metrics.input_tokens,
        completion_tokens=result.metrics.output_tokens,
    )
    task.used_tools = [t.tool_name for t in (result.tools or [])]
    return task
```

Here's what's happening:

1. `Backend(...).aget_args(task=task)` calls the xpander control plane and returns a dict containing the full agent configuration: instructions, tools, model with credentials, knowledge bases, session storage, memory settings.
2. That dict gets splatted into `agno.agent.Agent(...)`, which gives you a real Agno agent ready to run.
3. You run it with `arun()`, capture the output, and write it back to `task.result`.
4. The `@on_task` decorator stands up an HTTP server on port 59321 and subscribes to the platform's task event stream, so any task created for this agent (from the API, Slack, Agent Studio, anywhere) is routed to your handler.

## 4. Run it locally

The simplest "just boot it" command, copy-paste with zero typing:

```bash theme={"dark"}
# Boots the @on_task HTTP server and subscribes to the platform event stream
python3 xpander_handler.py
```

Tasks created for this agent (from any channel) will route to your handler. Hit Ctrl+C to stop.

For a one-shot test with a specific prompt, no server:

```bash theme={"dark"}
# Calls your handler exactly once with the prompt, prints the result, and exits
python3 xpander_handler.py --invoke --prompt "What can you do?"
```

Useful in CI and for quick debugging without a web client.

For an interactive dev session with extra CLI affordances (auto-reload prompts, log formatting):

```bash theme={"dark"}
# Same SSE subscription as `python3 xpander_handler.py`, plus dev tooling
xpander agent dev
```

What you get:

1. A local URL you can chat with.
2. **Once your dev process is running**, every task created for this agent (REST API, Slack, Agent Studio chat, MCP, any channel) is routed to your local handler, not just locally-initiated tests.
3. To iterate: edit `xpander_handler.py`, save, restart.

<Tip>
  **Routing cloud traffic to a local instance is a preview feature.**

  Inbound traffic goes to your deployed container by default. When a local instance is running via `xpander agent dev`, it takes over and all tasks route to your locally running agent instead. Only one can be active at a time.

  If a container is already deployed, run `xpander agent stop` first, then start dev. When you stop the local server, the cloud-based container automatically reclaims traffic.
</Tip>

## 5. Customize the agent (Optional)

The agent created by the CLI starts blank: a default system prompt, no tools. Open `agent_instructions.json` to rewrite the role, goal, and general description; the next `xpander agent dev` or `xpander agent deploy` syncs it to the control plane.

```json agent_instructions.json theme={"dark"}
{
  "role": [
    "You are a customer support assistant for Acme.",
    "Always confirm the customer's account ID before taking any action."
  ],
  "goal": [
    "Resolve the customer's issue in as few turns as possible.",
    "Escalate to a human if the request involves a refund over $500."
  ],
  "general": "Be concise, professional, and friendly. Never invent policy details; if you don't know something, say so and offer to escalate."
}
```

`role` and `goal` are arrays so you can add or remove individual statements without rewriting the prompt. `general` is a free-form description that wraps around them.

Beyond that, each customization has its own page:

1. **Custom tools** wrapped with `@register_tool`. See [Custom Tools](/developers/tools/custom-tools).
2. **Prebuilt connectors** from the catalog (Slack, Gmail, GitHub, 2,000+). See [Pre-built Tools](/developers/tools/pre-built).
3. **Knowledge bases** for RAG. See [Document Management](/developers/knowledge/document-management).
4. **Memory** (session storage, user memories, agent memories). See [Memory & State](/developers/memory/session-storage).
5. **Tool hooks** for logging, metrics, and guardrails around tool calls. See [Tool Hooks](/developers/tools/tool-hooks).
6. **A different framework** (OpenAI Agents SDK, LangChain, AWS Strands). See [Frameworks](/developers/frameworks).

## 6. Deploy

When the local version works, push it:

```bash theme={"dark"}
# Uploads code, builds a Docker image, replaces the running container
xpander agent deploy
```

What this gives you:

1. A new immutable version on every deploy (rolling back is one flag away).
2. Reachability through the REST API and every channel you enable.

Test the deployed version end to end:

```bash theme={"dark"}
# Hits the deployed agent and prints the response + round-trip time
xpander agent invoke "Run a quick health check"
```

Watch logs while it runs:

```bash theme={"dark"}
# Streams logs from the deployed container
xpander agent logs
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="KeyError: Missing required environment variable: XPANDER_API_KEY">
    The handler imports before `.env` is loaded. The scaffolded handler already includes `from dotenv import load_dotenv; load_dotenv()` at the top. If you wrote your own entry point, add it before any `xpander_sdk` import.
  </Accordion>

  <Accordion title="`pip install xpander-sdk[agno]` says 'no matches found'">
    zsh expands the brackets. Quote the package name: `pip install "xpander-sdk[agno]"`.
  </Accordion>

  <Accordion title="`Address already in use: 59321`">
    Another `@on_task` process is running. Either kill it, or set a different port for this one: `XPANDER_STREAMING_PORT=59322 python xpander_handler.py`.
  </Accordion>

  <Accordion title="`401 Unauthorized` when invoking via curl">
    The `/invoke` endpoint requires the `x-api-key` header on every request. The CLI sets it for you; if you're using curl or Postman, set it explicitly to your `XPANDER_API_KEY`.
  </Accordion>

  <Accordion title="Tasks don't reach my local handler">
    Inbound traffic goes to your deployed container by default. When a local dev instance is running, it takes over. If a container is already deployed, run `xpander agent stop` first to free the slot, then start `xpander agent dev` again.
  </Accordion>

  <Accordion title="`xpander agent invoke` returns instantly with no result">
    `xpander agent invoke` posts to the agent's webhook and waits for a sync response. If your handler is taking more than \~30 seconds, the webhook times out even though the task keeps running on the platform. Check the task's status in the Monitor tab, or invoke through the async REST endpoint (`/v1/agents/{id}/invoke-async`) for long-running tasks.
  </Accordion>
</AccordionGroup>

## Next steps

Things you can do now that you couldn't from Agent Studio alone:

<CardGroup cols={2}>
  <Card title="Custom tools" icon="wrench" href="/developers/tools/custom-tools">
    Wrap a private API as a tool with `@register_tool` instead of a REST connector.
  </Card>

  <Card title="Container deployment" icon="cube" href="/developers/deployment/containers">
    Ship custom dependencies the serverless runtime doesn't include (numerical libraries, system packages, internal SDKs).
  </Card>

  <Card title="Local MCP servers" icon="terminal" href="/developers/tools/pre-built">
    Run MCP servers that need filesystem or process access.
  </Card>

  <Card title="Lifecycle hooks" icon="bolt" href="/developers/tools/tool-hooks">
    Warmup, graceful shutdown, and observability around tool calls.
  </Card>

  <Card title="Core Concepts" icon="lightbulb" href="/developers/core-concepts">
    The SDK class names and how they map to agents, tasks, threads, and memory.
  </Card>

  <Card title="Frameworks: Agno" icon="cube" href="/developers/frameworks/agno">
    What `Backend.aget_args()` actually wires up.
  </Card>
</CardGroup>
