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

# Company Brain

> Ask your company's knowledge and manage collections, documents, brains and what each agent reads.

> [!NOTE]
>
> The Company Brain replaces knowledge bases. `KnowledgeBases` keeps working (see [Migrating from knowledge bases](https://docs.xpander.ai/developers/sdk-reference/brain.md#migrating-from-knowledge-bases)), but new code should use `Brain`. For the product side, see the [Company Brain](https://docs.xpander.ai/use/company-brain.md) guide.

The `Brain` module is the entry point to the [Company Brain](https://docs.xpander.ai/use/company-brain.md): ask a question and open what it cites, manage [collections](https://docs.xpander.ai/use/company-brain/admins.md#collections) and their documents, specific [brains](https://docs.xpander.ai/use/company-brain/admins.md#brains), what each agent reads, the [learned facts](https://docs.xpander.ai/use/company-brain/admins.md#learned-facts) agents propose, and the connected [sources](https://docs.xpander.ai/use/company-brain/admins.md#sources). Every method has an async form (`a` prefix) and a sync form.

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

brain = Brain()

# Ask
result = await brain.aask("What is our parental leave policy?")
for p in result.passages or []:
    print(p.citation.title, p.text[:80])

# Open a cited item in full
full = await brain.aread(result.citations[0].id)

# Add a document to a collection and wait for it
col = await brain.acreate_collection("HR policies")
ingest = await brain.aupload_document(col.id, file_path="handbook.pdf")
await brain.await_until_ready(ingest)
```

## Which key works

The brain answers for one person: the **owner** of the API key. Every ask, read and change runs with that person's access, so they see only the documents and sources they may read.

| Key | Result |
| - | - |
| A personal key, or a platform key with an owner who is an active member | Reads and writes as that owner. |
| The organization's default key, or a key with no owner | **403** |
| A key whose owner left the organization | **403** |
| A key limited to a project | `ask` and `read` only, inside its projects' collections (once projects are on for the organization). Everything else: **403**. |
| The key's owner could not be confirmed right now | **503**, retry. |

## Constructor

```python theme={"dark"}
Brain(
    configuration: Optional[Configuration] = None,
    base_url: Optional[str] = None,
)
```

| Parameter | Type | Default | Description |
| - | - | - | - |
| `configuration` | `Configuration` | `None` | SDK configuration. Falls back to env vars (`XPANDER_API_KEY`, `XPANDER_ORGANIZATION_ID`). |
| `base_url` | `str` | `None` | Where the brain's REST API lives, for example `https://api.xpander.ai/v1`. |

When `base_url` is not given, the SDK resolves it in this order:

1. The `XPANDER_BRAIN_BASE_URL` environment variable.
2. Derived from `configuration.base_url`:
   * `https://inbound.<domain>` (the SDK default) becomes `https://api.<domain>/v1`.
   * An agent-controller URL is kept, with the organization appended (inside a deployment).
   * Any other URL is kept as is.

The brain's routes are then `<base>/brain/...`.

## Module methods

| Method | Returns | What it does |
| - | - | - |
| [`aask` / `ask`](https://docs.xpander.ai/developers/sdk-reference/brain#ask) | `BrainAskResult` | Ask a question. |
| [`aread` / `read`](https://docs.xpander.ai/developers/sdk-reference/brain#read) | `BrainReadResult` | Open one cited item in full. |
| [`alist_collections` / `list_collections`](https://docs.xpander.ai/developers/sdk-reference/brain#collections) | `list[Collection]` | Collections the key's owner may see. |
| [`aget_collection` / `get_collection`](https://docs.xpander.ai/developers/sdk-reference/brain#collections) | `Collection` | One collection. |
| [`acreate_collection` / `create_collection`](https://docs.xpander.ai/developers/sdk-reference/brain#collections) | `Collection` | Create a collection. |
| [`aupdate_collection` / `update_collection`](https://docs.xpander.ai/developers/sdk-reference/brain#collections) | `Collection` | Change a collection. |
| [`adelete_collection` / `delete_collection`](https://docs.xpander.ai/developers/sdk-reference/brain#collections) | `None` | Delete a collection and its documents. |
| [`alist_documents` / `list_documents`](https://docs.xpander.ai/developers/sdk-reference/brain#documents) | `list[BrainDocument]` | Documents of a collection, a page at a time. |
| [`aget_document` / `get_document`](https://docs.xpander.ai/developers/sdk-reference/brain#documents) | `BrainDocument` | One document, with its access list. |
| [`aupload_document` / `upload_document`](https://docs.xpander.ai/developers/sdk-reference/brain#upload_document) | `DocumentIngestResult` | Add a file, bytes, text or a URL. |
| [`aupdate_document` / `update_document`](https://docs.xpander.ai/developers/sdk-reference/brain#documents) | `BrainDocument` | Change title, owner, review date or access. |
| [`adelete_document` / `delete_document`](https://docs.xpander.ai/developers/sdk-reference/brain#documents) | `None` | Delete a document. |
| [`await_until_ready` / `wait_until_ready`](https://docs.xpander.ai/developers/sdk-reference/brain#wait_until_ready) | `BrainDocument` | Wait for ingestion to finish. |
| [`alist_versions` / `list_versions`](https://docs.xpander.ai/developers/sdk-reference/brain#versions-and-chunks) | `list[DocumentVersion]` | A document's versions. |
| [`aadd_version` / `add_version`](https://docs.xpander.ai/developers/sdk-reference/brain#versions-and-chunks) | `DocumentIngestResult` | Add a new version. |
| [`aset_current_version` / `set_current_version`](https://docs.xpander.ai/developers/sdk-reference/brain#versions-and-chunks) | `CurrentVersionResult` | Roll to another ready version. |
| [`alist_chunks` / `list_chunks`](https://docs.xpander.ai/developers/sdk-reference/brain#versions-and-chunks) | `list[DocumentChunk]` | How the current version was split. |
| [`alist_brains` / `list_brains`](https://docs.xpander.ai/developers/sdk-reference/brain#brains) | `list[BrainItem]` | The Company Brain and every specific brain. |
| [`aget_brain` / `get_brain`](https://docs.xpander.ai/developers/sdk-reference/brain#brains) | `BrainItem` | One brain. |
| [`acreate_brain` / `create_brain`](https://docs.xpander.ai/developers/sdk-reference/brain#brains) | `BrainItem` | Create a specific brain. |
| [`aupdate_brain` / `update_brain`](https://docs.xpander.ai/developers/sdk-reference/brain#brains) | `BrainItem` | Change a brain. |
| [`adelete_brain` / `delete_brain`](https://docs.xpander.ai/developers/sdk-reference/brain#brains) | `None` | Delete a specific brain. |
| [`alist_brain_contents` / `list_brain_contents`](https://docs.xpander.ai/developers/sdk-reference/brain#brains) | `list[BrainContentItem]` | What a brain holds. |
| [`aadd_brain_content` / `add_brain_content`](https://docs.xpander.ai/developers/sdk-reference/brain#brains) | `BrainContentItem` | Add a collection or source slice. |
| [`aremove_brain_content` / `remove_brain_content`](https://docs.xpander.ai/developers/sdk-reference/brain#brains) | `None` | Remove one content item. |
| [`aget_agent_brains` / `get_agent_brains`](https://docs.xpander.ai/developers/sdk-reference/brain#what-an-agent-reads) | `AgentBrains` | What an agent reads. |
| [`aset_agent_brains` / `set_agent_brains`](https://docs.xpander.ai/developers/sdk-reference/brain#what-an-agent-reads) | `AgentBrains` | Set what an agent reads. |
| [`alist_facts` / `list_facts`](https://docs.xpander.ai/developers/sdk-reference/brain#learned-facts) | `list[BrainFact]` | Facts agents learned. |
| [`aget_fact` / `get_fact`](https://docs.xpander.ai/developers/sdk-reference/brain#learned-facts) | `BrainFact` | One fact. |
| [`aapprove_fact` / `approve_fact`](https://docs.xpander.ai/developers/sdk-reference/brain#learned-facts) | `BrainFact` | Approve a fact. |
| [`areject_fact` / `reject_fact`](https://docs.xpander.ai/developers/sdk-reference/brain#learned-facts) | `BrainFact` | Reject a fact. |
| [`alist_sources` / `list_sources`](https://docs.xpander.ai/developers/sdk-reference/brain#sources) | `list[BrainSource]` | Sources the brain reads. |
| [`aget_source` / `get_source`](https://docs.xpander.ai/developers/sdk-reference/brain#sources) | `BrainSource` | One source. |
| [`aget_source_connection` / `get_source_connection`](https://docs.xpander.ai/developers/sdk-reference/brain#sources) | `SourceConnection` | The key owner's own connection to a source. |
| [`avisibility` / `visibility`](https://docs.xpander.ai/developers/sdk-reference/brain#the-caller-and-settings) | `BrainVisibility` | Whether the brain is visible to the key's owner. |
| [`ame` / `me`](https://docs.xpander.ai/developers/sdk-reference/brain#the-caller-and-settings) | `BrainMe` | What the key's owner may do. |
| [`aget_settings` / `get_settings`](https://docs.xpander.ai/developers/sdk-reference/brain#the-caller-and-settings) | `BrainSettings` | The organization's knowledge settings. |
| [`aupdate_settings` / `update_settings`](https://docs.xpander.ai/developers/sdk-reference/brain#the-caller-and-settings) | `BrainSettings` | Change them (org admins only). |

## ask

`Brain.aask` asks the brain a question as the key's owner.

```python theme={"dark"}
result = await brain.aask(
    "What is the liability cap in the enterprise MSA?",
    brain="company",
    scope=["onsite"],
)
if result.ok:
    for p in result.passages:
        print(p.citation.title, p.citation.page, p.text[:100])
else:
    print(result.status, result.message)
```

#### Parameters

| Parameter | Type | Required | Default | Description |
| - | - | - | - | - |
| `question` | `str` | Yes | – | The question in plain words (1 to 4000 characters). |
| `brain` | `str` | No | `None` | `"company"` for the Company Brain, or a specific brain's id. `None` asks every brain the person has. |
| `scope` | `list[str]` | No | `None` | `"onsite"` (documents stored in xpander) and/or `"external"` (connected systems, read live as the person). `None` looks in both. |
| `mode` | `str` | No | `"passages"` | `"passages"` for ranked, cited passages; `"answer"` for a written, cited answer. |

#### Returns `BrainAskResult`

| Field | Type | Description |
| - | - | - |
| `status` | `str` | `ok` when answered. Otherwise (for example `not_configured`, `offline`, `not_available`, `invalid_input`) the answer is empty and `message` says why. Not raised. |
| `ok` | `bool` | `status == "ok"`. |
| `passages` | `list[Passage] \| None` | Set in `passages` mode. Each has `id`, `text` and a `citation`. |
| `answer` | `str \| None` | Set in `answer` mode. |
| `citations` | `list[Citation]` | Every cited item. |
| `sources_queried` | `list[SourceQueried]` | Each source searched: `source`, `count`, `cached`, `ms`, `dropped_reason`. |
| `timing` | `BrainTiming` | `total_ms` and `first_result_ms`. |
| `message` | `str \| None` | Why the answer is empty, when it is. |

`Citation` fields: `id` (pass it to `read`), `title`, `source_kind`, `source_label`, `url_or_ref`, `page`, `para`, `onsite`.

### Sync version

```python theme={"dark"}
result = Brain().ask("What is our travel policy?", mode="answer")
print(result.answer)
```

## read

`Brain.aread` opens one cited item in full: a citation id from an ask, or a source and its reference.

```python theme={"dark"}
full = await brain.aread(result.citations[0].id)
print(full.citation.title)
print(full.text)
```

#### Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `citation_id` | `str` | One of | A citation id from an earlier ask. |
| `source` | `str` | One of | The source the item lives in (with `ref`). |
| `ref` | `str` | With `source` | The item's reference in that source. |

Raises `ValueError` when neither a citation id nor both `source` and `ref` are given.

#### Returns `BrainReadResult`

`status`, `ok`, `citation`, `text`, `message`, as for `ask`.

### Sync version

```python theme={"dark"}
full = Brain().read(citation_id="onsite:...")
```

## Collections

A collection holds on-site documents. Its `audience` says who may read it, with access tokens `org:*`, `user:<id>`, `group:<id>` or `agent:<id>`.

```python theme={"dark"}
col = await brain.acreate_collection(
    "Legal docs",
    description="Contracts and playbooks",
    audience=["group:legal-team-id"],
)
cols = await brain.alist_collections(mine=True)
await brain.aupdate_collection(col.id, audience=["org:*"])
await brain.adelete_collection(col.id)
```

| Method | Parameters |
| - | - |
| `alist_collections` | `mine: bool = False` (only the ones the key's owner owns) |
| `aget_collection` | `collection_id: str` |
| `acreate_collection` | `name: str`, `description: str = ""`, `audience: list[str] \| None = None`, `owner_id: str \| None = None`, `project_id: str \| None = None`, `members_can_add_files: bool = False` |
| `aupdate_collection` | `collection_id: str`, then any of `name`, `description`, `audience`, `owner_id`, `project_id`, `members_can_add_files`. Only fields given (not `None`) change. |
| `adelete_collection` | `collection_id: str` |

Left out on create, `audience` makes a member's collection theirs alone and an admin's org wide. Only admins can give a collection to someone else or share it org wide (unless the [settings](https://docs.xpander.ai/use/company-brain/admins.md#settings) allow members to).

`Collection` fields: `id`, `name`, `description`, `owner_id`, `owner_name`, `project_id`, `audience`, `documents` (count), `brains` (ids of brains holding it), `members_can_add_files`, `can_manage`, `can_add_files`, `version`.

## Documents

```python theme={"dark"}
docs = await brain.alist_documents(col.id, limit=100, offset=0)
doc = await brain.aget_document(docs[0].id)
print(doc.title, doc.status, doc.acl)

await brain.aupdate_document(doc.id, title="Security policy 2026", review_by="2027-01-01")
await brain.adelete_document(doc.id)
```

| Method | Parameters |
| - | - |
| `alist_documents` | `collection_id: str`, `limit: int = 100` (1 to 500), `offset: int = 0` |
| `aget_document` | `document_id: str` |
| `aupdate_document` | `document_id: str`, then any of `title`, `owner_id`, `review_by` (`YYYY-MM-DD`), `acl` |
| `adelete_document` | `document_id: str` |

`BrainDocument` fields: `id`, `collection_id`, `title`, `owner_id`, `owner_name`, `access` (effective tokens), `version`, `current_version_id`, `status` (`queued`, `parsing`, `ready`, `failed`), `error`, `step`, `pages`, `sections`, `review_by`, `acl` (set by `get_document` and `update_document`), and the helpers `is_ready` / `is_failed`. A document the key's owner may not read answers 404.

### upload\_document

`Brain.aupload_document` adds a document to a collection. Give exactly one of `file_path`, `content` (with `filename`), `text` or `url`.

```python theme={"dark"}
# A local file
r = await brain.aupload_document(col.id, file_path="policies/security.pdf")

# Bytes you already have
r = await brain.aupload_document(col.id, content=pdf_bytes, filename="security.pdf")

# Pasted text
r = await brain.aupload_document(col.id, text="Office hours are 9 to 18.", title="Office hours")

# A public page or file
r = await brain.aupload_document(col.id, url="https://example.com/handbook.pdf")

doc = await brain.await_until_ready(r)
```

#### Parameters

| Parameter | Type | Required | Default | Description |
| - | - | - | - | - |
| `collection_id` | `str` | Yes | – | The collection to add to. |
| `file_path` | `str` | One of | `None` | A local file to upload. |
| `content` | `bytes` | One of | `None` | File bytes to upload (needs `filename`). |
| `filename` | `str` | With `content` | `None` | The file's name (defaults to the path's name). |
| `text` | `str` | One of | `None` | Text to add as a document. |
| `url` | `str` | One of | `None` | A public http(s) page or file to import. |
| `title` | `str` | No | `None` | Title (defaults to the filename or URL). |
| `mime_type` | `str` | No | `None` | File type (guessed from the name). |
| `owner_id` | `str` | No | `None` | The document's owner. |
| `review_by` | `str` | No | `None` | Review date, `YYYY-MM-DD`. |
| `acl` | `list` | No | `None` | The document's own access list: `{"principal_type": "user" \| "group", "principal_id": ...}` entries or `DocumentAclEntry` (the collection's owner or an admin only). |

Files are sent inline, 25 MB at most (`ValueError` above that, or when not exactly one content is given).

#### Returns `DocumentIngestResult`

`document_id`, `version_id`, `version` and `status` (`queued`). Parsing and embedding run in the background.

### wait\_until\_ready

`Brain.await_until_ready` polls a document until its newest version is ready.

```python theme={"dark"}
doc = await brain.await_until_ready(r, timeout=600, poll_interval=2)
```

| Parameter | Type | Default | Description |
| - | - | - | - |
| `document_id` | `str \| DocumentIngestResult` | – | A document id, or the result of `upload_document` / `add_version`. |
| `timeout` | `float` | `600.0` | Seconds to wait. |
| `poll_interval` | `float` | `2.0` | Seconds between checks. |

Returns the ready `BrainDocument`. Raises `ModuleException` **422** when ingestion failed (with the reason) and **408** when it is still running after `timeout`. A 502, 503 or 504 while polling is retried until the deadline.

### Versions and chunks

```python theme={"dark"}
r = await brain.aadd_version(doc.id, file_path="security-v2.pdf")
await brain.await_until_ready(r)

versions = await brain.alist_versions(doc.id)   # newest first, `current` marks the live one
await brain.aset_current_version(doc.id, versions[-1].id)   # roll back

chunks = await brain.alist_chunks(doc.id)
for c in chunks:
    print(" > ".join(c.heading_path), c.page, c.text[:60])
```

| Method | Parameters | Returns |
| - | - | - |
| `aadd_version` | `document_id`, and one of `file_path`, `content` + `filename`, `text`, `url`; `mime_type` | `DocumentIngestResult`. The current version keeps answering until the new one is ready. |
| `alist_versions` | `document_id` | `list[DocumentVersion]`: `id`, `version`, `status`, `pages`, `error`, `created_at`, `current`. |
| `aset_current_version` | `document_id`, `version_id` | `CurrentVersionResult`: `document_id`, `current_version_id`, `reindexing` (True when the version is rebuilt first). Only a ready version can become current (409). |
| `alist_chunks` | `document_id` | `list[DocumentChunk]`: `id`, `text`, `heading_path`, `page`. |

## Brains

The Company Brain holds everything the person asking can read; it keeps its name and cannot be deleted (409). A specific brain holds the collections and source slices you give it.

```python theme={"dark"}
legal = await brain.acreate_brain(
    "Legal",
    description="Contracts and settlements",
    reads_as="user",
    collections=[col.id],
)
contents = await brain.alist_brain_contents(legal.id)
await brain.aadd_brain_content(legal.id, collection_id=other_col.id)
await brain.aremove_brain_content(legal.id, contents[0].id)
```

| Method | Parameters |
| - | - |
| `alist_brains` | None |
| `aget_brain` | `brain_id: str` |
| `acreate_brain` | `name: str`, `description: str = ""`, `reads_as: "user" \| "agent" = "user"`, `owner_id`, `project_id`, `collections: list[str]`, `sources: list[SourceSlice \| dict]` (`{"source": <id>, "scope": <optional>}`) |
| `aupdate_brain` | `brain_id`, then any of `name`, `description`, `reads_as`, `owner_id`, `project_id`, `collections`, `sources`. `collections` and `sources`, when given, replace what the brain holds. |
| `adelete_brain` | `brain_id: str` |
| `alist_brain_contents` | `brain_id: str` |
| `aadd_brain_content` | `brain_id`, and exactly one of `collection_id` or `source_id` (with an optional `scope`) |
| `aremove_brain_content` | `brain_id`, `content_id` (from `list_brain_contents`) |

`reads_as="user"` reads as the person asking; `"agent"` reads as the agent it is attached to (see [Reads as the user, or as the agent](https://docs.xpander.ai/use/company-brain/admins.md#reads-as-the-user-or-as-the-agent)).

`BrainItem` fields: `id`, `name`, `description`, `is_company`, `reads_as`, `owner_id`, `owner_name`, `project_id`, `contents` (`BrainContentItem`: `id`, `kind`, `ref`, `name`, `scope`, `documents`, `sensitive`), `agent_count`, `agents_off` (Company Brain only), `version`, `can_edit`.

## What an agent reads

Each agent has a Company Brain toggle, specific brains and collections attached straight to it. These calls save right away: no `agent.save()` is needed (unlike `attach_knowledge_base`).

From the `Brain` client:

```python theme={"dark"}
current = await brain.aget_agent_brains("agent-123")
print(current.company, current.brains, current.collections)

await brain.aset_agent_brains("agent-123", company=False)               # keeps brains and collections
await brain.aset_agent_brains("agent-123", brains=[legal.id], collections=[col.id])
```

`aset_agent_brains(agent_id, *, company=None, brains=None, collections=None)`: `None` keeps the current value; a list replaces it. At most 50 brains and collections together.

From an `Agent`:

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

agent = await Agents().aget("agent-123")
print(agent.company_brain_enabled)

await agent.aset_company_brain(True)
await agent.aattach_collection(collection_id=col.id)
await agent.aattach_brain(brain=legal)
await agent.adetach_collection(collection_id=col.id)
await agent.adetach_brain(brain_id=legal.id)
await agent.aset_brains(company=True, brains=[legal.id], collections=[])
attached = await agent.aget_brains()
```

| `Agent` method | What it does |
| - | - |
| `aget_brains` / `get_brains` | `AgentBrains`; also refreshes `agent.company_brain_enabled`. |
| `aset_brains` / `set_brains` | `company`, `brains`, `collections` as above. |
| `aset_company_brain` / `set_company_brain` | `enabled: bool`. |
| `aattach_brain` / `attach_brain` | `brain: BrainItem` or `brain_id: str`; no change when already attached. |
| `adetach_brain` / `detach_brain` | Same arguments. |
| `aattach_collection` / `attach_collection` | `collection: Collection` or `collection_id: str`. |
| `adetach_collection` / `detach_collection` | Same arguments. |

`AgentBrains` fields: `company`, `brains`, `collections`, `public_agent`. The key's owner must be able to see the agent, and changing what it reads follows the agent's edit rights (403 otherwise).

## Learned facts

Agents propose facts they learned; their owner reviews them before anyone's answers use them. See [Learned facts](https://docs.xpander.ai/use/company-brain/admins.md#learned-facts).

```python theme={"dark"}
for fact in await brain.alist_facts(status="proposed", mine=True):
    print(fact.proposed_by_agent, fact.text)
    await brain.aapprove_fact(fact.id, text=fact.text.strip())
```

| Method | Parameters |
| - | - |
| `alist_facts` | `status: "proposed" \| "approved" \| "rejected" \| "all" = "proposed"`, `mine: bool = False`, `limit: int = 100` (1 to 200) |
| `aget_fact` | `fact_id: str` |
| `aapprove_fact` | `fact_id: str`, `text: str \| None = None` (an edited text) |
| `areject_fact` | `fact_id: str` |

`BrainFact` fields: `id`, `text`, `previous_text`, `kind` (`fact`, `decision`, `procedure`), `status`, `proposed_by_agent`, `proposed_by_agent_id`, `run_id`, `proposed_at`, `audience`, `target_collection`, `conflicts_with`, `conflicts`, `owner_id`, `decided_by`, `decided_at`.

## Sources

Read only: the on-site knowledge and the connected systems the brain reads live as each person.

```python theme={"dark"}
for s in await brain.alist_sources():
    print(s.name, s.kind, s.me_connected)

conn = await brain.aget_source_connection(source_id)
if not conn.connected:
    print("Sign in:", conn.connect_url)
```

| Method | Returns |
| - | - |
| `alist_sources` | `list[BrainSource]`: `id`, `kind`, `name`, `mode`, `enabled`, `connector_id`, `me_connected`, `auth`, `sensitive`, `cache_ttl_s`, `keep_copy`, `scope`, `speed`, and more. |
| `aget_source(source_id)` | `BrainSource` |
| `aget_source_connection(source_id)` | `SourceConnection`: `source_id`, `connected`, `connect_url` (the key owner's own sign-in link), `reason`, `account`, `required_scopes`. |

## The caller and settings

```python theme={"dark"}
print(await brain.avisibility())   # visible, resolved
print(await brain.ame())           # is_admin, can_create_collections, can_share_org_wide
print(await brain.aget_settings())
await brain.aupdate_settings(members_can_share_org_wide=True)   # org admins only
```

`aupdate_settings` takes `members_can_create_collections`, `members_can_share_org_wide` and `agents_can_propose_facts`; `None` keeps a setting.

## Migrating from knowledge bases

`KnowledgeBases` and `KnowledgeBase` keep their signatures and keep working. Constructing `KnowledgeBases` emits a `DeprecationWarning` once per process. Knowledge bases that moved into a brain are served by the brain behind the same calls; new content should go to collections.

| Knowledge bases (deprecated) | Company Brain |
| - | - |
| `KnowledgeBases().alist()` | `Brain().alist_collections()` |
| `KnowledgeBases().aget(knowledge_base_id)` | `Brain().aget_collection(collection_id)` |
| `KnowledgeBases().acreate(name, description)` | `Brain().acreate_collection(name, description=...)` |
| `kb.aadd_documents(document_urls)` | `Brain().aupload_document(collection_id, url=...)` per URL (or `file_path=`, `content=`, `text=`) |
| `kb.aadd_documents(urls, sync=True)` | `aupload_document(...)` then `await_until_ready(result)` |
| `kb.alist_documents()` | `Brain().alist_documents(collection_id)` |
| `kb.adelete_multiple_documents(document_ids)` | `Brain().adelete_document(document_id)` per id |
| `kb.asearch(search_query, top_k=...)` | `Brain().aask(question, brain=..., scope=["onsite"])`, then `result.passages` |
| `kb.adelete()` | `Brain().adelete_collection(collection_id)` |
| `agent.attach_knowledge_base(knowledge_base_id=...)` + save | `await agent.aattach_collection(collection_id=...)` (saved right away) |
| `agent.aget_knowledge_bases()` | `await agent.aget_brains()` |
| `agent.knowledge_bases` | `agent.company_brain_enabled` and `agent.aget_brains()` |

Differences to know:

* `ask` searches with the key owner's access (and, through `brain=`, one brain at a time); a knowledge base search read whatever the KB held.
* `ask` returns cited passages with titles, pages and source links instead of `{content, score}` rows; use `read` to open a cited item in full.
* Documents have versions, an owner, a review date and their own access list.

## Errors

Every method raises [`ModuleException`](https://docs.xpander.ai/developers/sdk-reference/overview#error-handling) on failure (`ask` and `read` do not raise for a non-`ok` `status`):

| Status | Cause |
| - | - |
| 403 | The key has no owner, its owner left the organization, a project key outside ask/read, or the change is not allowed for the key's owner. |
| 404 | The item does not exist, or the key's owner may not see it (also when the brain is hidden from them). |
| 408 | `wait_until_ready` timed out. |
| 409 | Conflict, for example deleting the Company Brain or a duplicate brain name. |
| 413 / 422 | Invalid input, for example a bad access token, an unknown collection id, or a failed ingestion (`wait_until_ready`). |
| 503 | The key's owner or the organization's brain could not be reached right now; retry. |
| 500 | Network or unexpected response. |

Invalid arguments caught before the request (no content, two contents, a file over 25 MB, no read target) raise `ValueError`.


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