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

# Search index

> mob keeps a searchable mirror of the Discord messages its bot can see, and each Slack query goes to Slack's own search.

Discord has no API that searches on behalf of a member, so mob maintains
its own index of the messages the installed bot can see
(`api/src/mob/adapters/discord/events.py`). Slack has such an API, so mob
stores no Slack content at all; the [Slack](#slack) section covers that
path.

## What enters the index

The bot's channel permissions set the boundary.
[Set up your Discord app](/discord-setup) covers how a private channel
enters that coverage, and [Authorization](/authorization) covers the second
check at query time, where mob intersects the bot's channels with the ones
the requesting member can see.

Inside those channels mob indexes:

* message text, plus one line for each attachment filename, embed title and
  description, sticker name, and poll question
* public threads and forum posts, stored under their parent channel so they
  inherit its visibility
* messages from every author, apps and bots included

Private threads are never indexed. Their rows would sit under the parent
channel id and reach members who cannot open the thread.

## When messages become searchable

A live message is indexed as it arrives. History arrives behind it: each
channel keeps a cursor recording the newest and oldest message stored, and
a background task walks older pages one at a time, rotating across channels
so recent coverage lands everywhere before deep history lands anywhere
(`api/src/mob/adapters/discord/backfill.py`).

A server that was just installed answers searches over its recent messages
first, and older history keeps filling in for as long as the walk takes.
The walk paces itself against Discord's rate limits and resumes from the
stored cursors after a restart.

When the gateway reconnects, each channel re-indexes a short overlap behind
its stored frontier, which absorbs edits made while the connection was away
and removes messages deleted inside that window.

## Attachment contents

Every attachment is searchable by filename from the moment its message is
indexed. A second pass then downloads the file and adds its contents
(`api/src/mob/mirror/enrich.py`):

* text files, including source code and configuration formats, are decoded
* CSV and TSV files contribute their header, a sample of rows, and a row
  count
* PDFs contribute their text layer, so a scanned PDF carrying no text layer
  stays findable by filename
* PNG, JPEG, WebP, and GIF images go through one vision completion on
  `MOB_SMALL_MODEL`, which transcribes legible text and describes what the
  image shows

Images need a configured model endpoint and spend a per-installation daily
budget. The other formats need no key. Archives, video, audio, and office
documents are never downloaded. Download size, extracted characters, PDF
pages, and sampled rows all carry caps, and a file past a cap keeps its
filename in the index (`api/src/mob/mirror/extract.py`).

## How results are ranked

Every query runs Postgres full-text search over the indexed chunks. When
the deployer configured a model endpoint, mob also embeds the query, runs a
vector search, and fuses the two rankings
(`api/src/mob/mirror/search.py`). [Self-hosting](/self-hosting#model-endpoint)
covers the settings.

Both legs degrade instead of failing. A query whose embedding call fails
returns full-text results. Chunks whose embedding call failed during
ingest are stored without a vector and stay findable through full-text
search until a later edit or catch-up re-indexes them.

A long message is split into chunks that are ranked individually, and each
result carries the message it came from with a permalink.

## How messages leave the index

* An edit replaces the message's chunks, and an edit that shortens a
  message drops the chunks past its new end.
* A delete removes them.
* A member running `/mob delete` inside the server removes every message
  that member authored from that server's index.
* Losing bot access to a channel, or deleting the channel or thread,
  purges its rows.
* Disabling the installation from the console, removing the bot from the
  server, and uninstalling the app each drop the whole mirror for that
  installation.

A database loss is recovered by running the walk again rather than by
restoring rows. [Backups](/backups) covers what that costs.

## Slack

mob stores no Slack messages. Each query calls `assistant.search.context`
with the requesting member's own Slack token, Slack applies its own
visibility rules, and the response stays in request memory until the MCP
response completes. Slack indexes file contents itself, which is what
`search:read.files` adds to the same call.
