> ## 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, tools, and memory map onto SDK classes

xpander.ai is a platform for building production AI agents. An *agent* is the platform's central object: a configured LLM with instructions, tools, knowledge bases, memory, and a deployment target. This page is the SDK companion that tells you which Python class each of those concepts becomes when you `import xpander_sdk`.

The job of this page: when you see `Backend`, `Task`, or `agent.tools.functions` in code, you should know exactly what they are without context-switching back to the conceptual docs.

<Card title="Platform Guides → Core Concepts" icon="book" href="/guides/core-concepts">
  The same model from the Agent Studio 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 runtime**. Reading code, you'll move between three boundaries constantly:

```
Control plane              SDK objects in your process       Framework
(cloud / self-hosted)
                          ┌────────────────────┐
   Agent definition  ───▶ │  Backend, Agent    │ ─▶  Splatted into AgnoAgent(**args)
   Tools / connectors ──▶ │  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 the platform's HTTP API. They're how you read and write to it from Python.

## Backend

`Backend` is the gateway. It's the only class that knows how to talk to the control plane to fetch 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. Tools (connectors + 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 framework. 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. Framework selection.
4. Model + provider.
5. Deployment type (`Serverless` or `Container`).
6. Tools, 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 or .Container
print(len(agent.tools.list))              # Tools available, including connectors
```

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`, the platform 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 framework 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 Agent Studio 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.

## Connectors and tools

Whatever you select in Agent Studio under "Tools" becomes available in code as part of `agent.tools`. There are three flavors and one container:

* **Connectors**: pre-built integrations from the catalog (Slack, Gmail, GitHub, 2,000+). Authenticated and configured in Agent Studio.
* **Custom tools**: Python functions you've decorated with `@register_tool` in your handler.
* **MCP servers**: model-context-protocol servers, either remote (URL) or local (process), wired in through Agent Studio.
* **`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 framework 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 connectors:
[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 framework-agnostic. You can import an xpander agent's tool 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 framework adapters fit your stack.
</Tip>

## Knowledge bases

`KnowledgeBase` represents one document collection. The platform-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 framework. 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 memory layers, and they're not the same thing. The Agno framework 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 layer 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 the platform 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 tool. SDK generates the JSON schema from your type hints.
* **`@on_tool_before` / `@on_tool_after` / `@on_tool_error`**: observe tool calls without modifying the tools themselves. Use for logging, metrics, alerting.
* **`@on_auth_event`**: fires when an MCP OAuth flow needs the user to log in.

You'll typically need `@on_task` plus `@register_tool`. 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/configuration) for the full setup.

## Cheat sheet

A one-line answer for the questions you'll have most often:

| Question                                   | Answer                                                                                                  |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| Where do I configure the agent's behavior? | Agent Studio. The SDK reads it.                                                                         |
| Where do my custom tools 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)))`. Framework-specific for others. |
| 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="Frameworks: Agno" icon="cube" href="/developers/frameworks/agno">
    What `Backend.aget_args()` actually wires up, with the AgnoSettings reference.
  </Card>

  <Card title="Custom Tools" 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>
