> ## 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.

# Core Concepts

> How agents, tasks, threads, skills, and memory map onto SDK classes

An *agent* is the runtime's central object: a configured LLM with instructions, skills, knowledge bases, memory, and a deployment target. In the SDK, skills are exposed as `tools`, the name agent loops use for them, so the class and attribute names on this page say `tools` while the prose says skills. This page is the SDK companion that tells you which Python class each of those concepts becomes when you `import xpander_sdk`.

<Card title="User guide → Agents and conversations" icon="book" href="/use">
  The same model from Xpander Chat side. Read that for the concepts; read this for the class names.
</Card>

## The two halves

xpander splits responsibilities between a **control plane** (cloud or self-hosted) and **your process**. Reading code, you'll move between three boundaries constantly:

```
Control plane              SDK objects in your process       Agent loop
(cloud / self-hosted)
                          ┌────────────────────┐
   Agent definition  ───▶ │  Backend, Agent    │ ─▶  Splatted into AgnoAgent(**args)
   Skills ─────────────▶ │  ToolsRepository   │ ─▶  agent.tools.functions
   Knowledge bases ────▶  │  KnowledgeBase     │ ─▶  retriever wired into args
   Postgres (sessions) ─▶ │  Agent.aget_db()   │ ─▶  args["db"]
                          │                    │
   Task created ────────▶ │  @on_task → Task   │ ─▶  Your handler runs
                          └────────────────────┘
```

The SDK objects are thin wrappers around xpander's HTTP API. They're how you read and write to it from Python.

## Backend

`Backend` is the class that fetches a fully resolved agent definition. Inside an `@on_task` handler, the typical use is one line:

```python theme={"dark"}
from xpander_sdk import Backend

backend = Backend(configuration=task.configuration)
args = await backend.aget_args(task=task)
```

The returned dict contains everything Agno needs:

1. Model client with credentials.
2. Instructions (system prompt, role, goal).
3. Skills (from the skills catalog + your `@register_tool` functions).
4. Session DB.
5. Memory settings.
6. Output schema.

Splatting it into `agno.agent.Agent(**args)` is the production pattern.

Pass the current `task` so the SDK can forward task-level overrides (instructions overrides, expected output, output schema) through to the agent loop. Both `get_args` (sync) and `aget_args` (async) accept the same arguments. The async form is what you'll use inside `@on_task` and any FastAPI service; the sync form is fine in scripts and notebooks. This async/sync pairing holds across the entire SDK.

## Agent

`Agent` is the loaded, in-memory representation of an agent. It carries everything the control plane knows about it:

1. Name and unique identifier.
2. Instructions (role, goal, general).
3. Agent loop selection (the `framework` field).
4. Model + provider.
5. Deployment type (`Serverless`).
6. Skills, knowledge bases, sub-agents.
7. Memory settings.

```python theme={"dark"}
from xpander_sdk import Agents

agent = Agents().get(agent_id="...")
print(agent.name, agent.model_provider, agent.model_name)
print(agent.deployment_type)              # AgentDeploymentType.Serverless
print(len(agent.tools.list))              # Skills available, including catalog skills
```

Two collection helpers:

* **`Agents().get(agent_id=...)`**: returns a fully loaded `Agent`. Heavyweight.
* **`Agents().list()`**: returns `AgentsListItem` summaries (just names + IDs). Faster when you're enumerating. Call `.aload()` on a list item to upgrade it to a full `Agent`.

## Task

A `Task` is one execution. It has an ID, a status, an input, and a result.

```python theme={"dark"}
task = agent.create_task(prompt="Summarize the attached PDF",
                         file_urls=["https://example.com/report.pdf"])

print(task.id, task.status)               # "executing", typically
```

Status values: `pending`, `executing`, `completed`, `failed`, plus a few transitional states.

Inside `@on_task`, xpander creates the Task for you and you receive it as a parameter. Your job is to set `task.result` and return the task. The decorator marks it `completed` if you return cleanly, or `failed` if you raise.

Helpers on `Task` for agent-loop integration:

* **`task.to_message()`**: joins input text, file URLs, and any embedded readable file content into a single string ready to feed into Agno.
* **`task.get_files()`**: returns PDFs as `agno.media.File` objects.
* **`task.get_images()`**: returns image URLs as `agno.media.Image` objects.

Load a task by ID later with `Tasks().get(task_id)`. That's how you implement retries, audit logging, or deferred result fetching.

## Threads (sessions)

The SDK doesn't have a class called `Thread`. What Xpander Chat shows as a thread is a `session_id` shared across multiple Tasks, with the conversation history persisted in the agent's Postgres schema.

If your agent has [Agno session storage enabled](/developers/memory/session-storage), you can list and inspect those sessions directly:

```python theme={"dark"}
sessions = agent.get_user_sessions(user_id="user_123")
for s in sessions:
    print(s.session_id, len(s.messages))

# Load a single session
session = agent.get_session(session_id="sess_abc")

# Wipe it
agent.delete_session(session_id="sess_abc")
```

Behind the scenes, `agent.get_db()` returns the underlying `agno.db.postgres.AsyncPostgresDb` instance scoped to this agent's schema. Reach for it directly only when you want to do something Agno doesn't expose, like writing custom Postgres queries against session metadata.

## Skills

Whatever you add to the agent in Xpander Chat under "Skills" becomes available in code as part of `agent.tools`. There are three flavors:

* **Catalog skills**: pre-built skills from the skills catalog (Slack, Gmail, GitHub, and more). Authenticated and configured in Xpander Chat.
* **Custom skills**: Python functions you've decorated with `@register_tool` in your handler.
* **Skills over MCP**: served over the Model Context Protocol, either remote (URL) or local (process), wired in through Xpander Chat.
* **`agent.tools`**: the unified `ToolsRepository` that flattens all three into one list. `agent.tools.list` enumerates `Tool` objects; `agent.tools.functions` returns normalized callables ready to bind to LangChain, OpenAI Agents SDK, or any agent loop that accepts plain Python functions.

```python theme={"dark"}
@register_tool
def lookup_customer(customer_id: str) -> dict:
    """Fetch a customer record from the internal API."""
    ...

# After the agent loads, lookup_customer shows up alongside catalog skills:
[t.name for t in agent.tools.list]
# -> ['SlackPostMessage', 'GmailSend', 'lookup_customer', ...]
```

Each `Tool` has a `parameters` JSON schema that the agent's LLM uses to call it. The SDK auto-generates that schema from your function's type hints and docstring, which is why annotation matters more than usual here.

<Tip>
  `agent.tools.functions` is a plain list of function-calling schemas, so you can import an xpander agent's skill list into any LLM client that accepts plain Python callables, including raw `openai.OpenAI().chat.completions.create(tools=[...])` calls or a homegrown ReAct loop. This is the escape hatch when none of the supported SDK integrations fit your stack.
</Tip>

## Knowledge bases

`KnowledgeBase` represents one document collection. The xpander-managed type is the default; external KBs (your own vector store) are an advanced setup.

```python theme={"dark"}
from xpander_sdk import KnowledgeBases

kbs = KnowledgeBases()
kb = kbs.get(knowledge_base_id="...")

kb.add_documents([
    "https://example.com/handbook.pdf",
    "https://example.com/runbook.md",
])
results = kb.search(search_query="how do we handle refunds?", top_k=5)
```

Inside an agent, a KB is attached through `agent.knowledge_bases` and queried automatically by the agent loop. Call `kb.search` directly only when you're building something outside the agent loop, like a search box or a one-off enrichment job.

## Memory

There are three kinds of memory, and they're not the same thing. The Agno integration configures all of them through `agent.agno_settings`:

* **Session storage** (`session_storage=True`, default): keeps conversation history within a single thread. Postgres-backed, scoped per agent.
* **User memories** (`user_memories=True` or `agentic_memory=True`): facts the agent should remember about a specific user, across all their sessions. The two flags select between manual and agentic-managed mode.
* **Agent memories** (`agent_memories=True` or `agentic_culture=True`): organization-wide knowledge the agent should carry into every conversation. Same two-flag pattern.

Each kind is a separate switch because they each have a cost and a use case:

1. Session storage is essentially free.
2. User and agent memories cost LLM calls to maintain.

Pick what you need, leave the rest off. The deep dive is in [Memory & State](/developers/memory/session-storage).

## Decorators

The SDK exports a small set of decorators that handle the lifecycle for you:

```python theme={"dark"}
from xpander_sdk import on_task, on_boot, on_shutdown, register_tool
from xpander_sdk import on_tool_before, on_tool_after, on_tool_error
from xpander_sdk import on_auth_event
```

What each one does:

* **`@on_task`**: the entry point. Stands up the HTTP server and subscribes to xpander task stream.
* **`@on_boot` / `@on_shutdown`**: lifecycle hooks that run before and after the handler is registered.
* **`@register_tool`**: turns a Python function into an agent skill. SDK generates the JSON schema from your type hints.
* **`@on_tool_before` / `@on_tool_after` / `@on_tool_error`**: observe skill calls without modifying the skills themselves. Use for logging, metrics, alerting.
* **`@on_auth_event`**: fires when an MCP OAuth flow needs the user to log in.

Every agent needs `@on_task`; add `@register_tool` if you're defining local skills. The rest are situational and covered in their own pages.

## Configuration

`Configuration` is the explicit form of the credentials and base URL. Most code never instantiates it: the SDK reads from `XPANDER_API_KEY`, `XPANDER_ORGANIZATION_ID`, and `XPANDER_BASE_URL` automatically.

```python theme={"dark"}
from xpander_sdk import Configuration

config = Configuration(
    api_key="...",
    organization_id="...",
    base_url="https://agent-controller.acme.com",  # for self-hosted
)
backend = Backend(configuration=config)
```

Self-hosted deployments are the main reason to construct one explicitly. You need a custom `base_url`, and the agent controller's API key, not your cloud key. See [SDK configuration](/developers/sdk-reference/overview#configuration) for the full setup.

## Cheat sheet

| Question | Answer |
| - | - |
| Where do I configure the agent's behavior? | Xpander Chat. The SDK reads it. |
| Where do my custom skills live? | In your `xpander_handler.py`, decorated with `@register_tool`. |
| How do I run an agent in code? | Inside `@on_task`: `Agent(**(await Backend(...).aget_args(task=task)))`. Loop-specific for the other SDK integrations. |
| How do I receive incoming tasks? | A function decorated with `@on_task`. |
| How do I find session data? | `agent.get_user_sessions(user_id)` or `agent.get_session(session_id)`. |
| How do I add a document to a KB? | `KnowledgeBases().get(kb_id).add_documents([url])`. |
| Async or sync? | Every method has both. Async in production, sync in scripts. |

## Next steps

<CardGroup cols={2}>
  <Card title="SDK integrations: Agno" icon="cube" href="/developers/frameworks/agno">
    What `Backend.aget_args()` actually wires up, with the AgnoSettings reference.
  </Card>

  <Card title="Custom skills" icon="wrench" href="/developers/tools/custom-tools">
    The full `@register_tool` decorator surface.
  </Card>

  <Card title="Memory & State" icon="brain" href="/developers/memory/session-storage">
    Session storage, user memories, and agent memories explained.
  </Card>

  <Card title="SDK Reference" icon="book" href="/developers/sdk-reference/overview">
    Per-method reference for every class on this page.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.