diff --git a/docs/web/basics/agentic-skills.mdx b/docs/web/basics/agentic-skills.mdx
index 6a6a37bdb..319e770b4 100644
--- a/docs/web/basics/agentic-skills.mdx
+++ b/docs/web/basics/agentic-skills.mdx
@@ -6,6 +6,12 @@ icon: robot
keywords: ["agents", "plugin", "claude code", "codex", "cursor", "copilot"]
---
+import {
+ PyCamelotSchema,
+ PyCamelotBatches,
+ PyCamelotOssIngestion,
+} from '/snippets/build_with_ai_agents.mdx';
+
A coding agent writing LanceDB code from memory writes whatever was in its
training data -- often an older API, sometimes one that never existed. The
LanceDB agent plugin fixes that by supplying the current reference at the point
@@ -13,11 +19,10 @@ the agent needs it.
## The plugin {#plugin}
-It ships as the `lancedb` plugin in the
-[lancedb-agent-plugins](https://github.com/lancedb/lancedb-agent-plugins)
-repository, which doubles as a plugin marketplace. It covers the Python and
-TypeScript APIs, which code is portable between a local database and a
-deployment, ingestion performance, and branch operations.
+It ships as the `lancedb` plugin in
+the [lancedb-agent-plugins](https://github.com/lancedb/lancedb-agent-plugins)
+repository, which is also a plugin marketplace. Install it with your agent's
+plugin manager:
```text Claude Code
@@ -35,27 +40,252 @@ npx plugins add lancedb/lancedb-agent-plugins
```
-The cross-tool installer detects which agent CLIs are on your `PATH` -- Cursor,
-GitHub Copilot CLI, VS Code, Grok Build, Kimi Code, Claude Code, Codex -- and
-installs through each one's native plugin system.
+The [`plugins`](https://www.npmjs.com/package/plugins) installer shown under
+**Other agents** is a cross-tool option. It detects which agent CLIs are on your
+`PATH` — Cursor, GitHub Copilot CLI, VS Code, Grok Build, and Kimi Code, as well
+as Claude Code and Codex — and installs through each one's native plugin system.
+
+The plugin supplements the agent's training data with current LanceDB
+instructions. To pick up later revisions, refresh the marketplace:
+
+
+```text Claude Code
+/plugin marketplace update lancedb
+```
+
+```bash Codex icon="terminal"
+codex plugin marketplace upgrade
+```
+
+```bash Other agents icon="terminal"
+npx plugins add lancedb/lancedb-agent-plugins
+```
+
+
+
+Each plugin manager clones the repository, reads its marketplace manifest to find
+the `lancedb` plugin, and hands the plugin to the agent's own plugin store rather
+than copying files into your project. Claude Code and Codex keep it under
+`~/.claude/plugins` and `~/.codex/plugins` respectively, so the plugin is available
+in every project on the machine.
+
+The `npx plugins` installer defaults to the same user-wide scope. Pass
+`--scope project` to record the plugin in the current repository instead, so that
+everyone working in it gets the same plugin, or `-t ` to install for a
+single agent rather than every one it detects:
+
+```bash
+npx plugins add lancedb/lancedb-agent-plugins --scope project -t cursor
+```
+
+Not every agent supports project scope; `npx plugins targets` lists what it
+found and what each target supports.
+
+
+### Get started with the LanceDB agent plugin {#get-started-with-the-lancedb-agent-plugin}
+
+This tutorial uses the Camelot dataset from the [quickstart](/quickstart), with
+a portrait added for each character. Each LanceDB row contains validated
+metadata and raw JPEG bytes. Text, images, and any embeddings you add later
+remain in the same table.
+
+### 1\. Download the multimodal dataset {#1-download-the-multimodal-dataset}
+
+From a new project directory, download the JSON file and portraits:
+
+```bash
+mkdir -p data/img
+
+BASE_URL="https://docs.lancedb.com/static/assets/tutorials/build-with-ai-agents/camelot/data"
+curl -fsSL "$BASE_URL/camelot.json" -o data/camelot.json
+
+for image in \
+ arthur.jpg \
+ guinevere.jpg \
+ merlin.jpg \
+ mordred.jpg \
+ sir_galahad.jpg \
+ sir_gawain.jpg \
+ sir_lancelot.jpg \
+ sir_percival.jpg
+do
+ curl -fsSL "$BASE_URL/img/$image" -o "data/img/$image"
+done
+```
+
+Each JSON record has this shape:
+
+```json
+{
+ "id": 2,
+ "name": "Merlin",
+ "role": "Wizard and Advisor",
+ "description": "A powerful wizard and prophet who mentors Arthur.",
+ "stats": {
+ "strength": 2,
+ "courage": 4,
+ "magic": 5,
+ "wisdom": 5
+ },
+ "img": "data/img/merlin.jpg"
+}
+```
+
+JSON input may have missing fields, unexpected fields, or values of the wrong
+type. The LanceDB plugin tells the agent to validate each record with strict
+Pydantic models before writing it.
+
+After the agent writes the pipeline, inspect the schema, batching, and write
+path rather than assuming it followed the plugin's guidance correctly.
+
+### 2\. Prompt your agent to build the pipeline {#2-prompt-your-agent-to-build-the-pipeline}
+
+Install the Python packages used by the example:
+
+```bash icon="terminal"
+uv init
+uv add lancedb pyarrow pydantic
+```
+
+If you're using LanceDB Enterprise, ask the agent to ingest into an Enterprise table,
+provide the relevant environment variables for connecting to your Enterprise deployment
+in a local `.env` file, and point the agent to it.
+
+```text .env
+LANCEDB_URI=db://your_project_name
+LANCEDB_API_KEY=your_api_key_here
+LANCEDB_REGION=us-east-1
+LANCEDB_HOST_OVERRIDE=https://your-enterprise-endpoint.com
+```
+
+If you're using LanceDB OSS, no connection settings are required, as it runs as an
+embedded retrieval library. A simple prompt like this should work:
+
+```text Agent prompt
+# If using OSS
+Use the lancedb plugin to ingest the dataset in `data/` into a LanceDB
+OSS table.
+
+# If using enterprise
+Use the lancedb plugin to ingest the dataset in `data/` into a LanceDB
+Enterprise table using the connection information in `.env`.
+```
+
+Because the plugin is registered with the agent's own plugin system, the agent
+should pick it up on its own once you restart the session. If it does not,
+simply ask it to use the `lancedb` plugin in the prompt, as shown above.
+
+That should be enough! The agent will create `ingest_multimodal.py`, or similar.
+The following sections inspect the script to verify that it follows the plugin's guidance.
+
+#### Data validation {#data-validation}
+
+The plugin encourages the agent to validate each record with Pydantic before writing it.
+The agent should ideally define a schema for the table and a nested schema for the
+`stats` field.
+
+
+ {PyCamelotSchema}
+
+
+In this case, our agent correctly defined `Character` and `Stats` Pydantic models
+and validated the JSON before adding it to the table.
+
+#### Batched ingestion {#batched-ingestion}
+
+Naively calling `table.add()` once per row is slow, and is considered an anti-pattern
+in LanceDB. The plugin encourages the agent to collect incoming rows into batches and
+write them with a single `table.add()` call. When you use the plugin, the agent should
+produce something like this:
+
+
+ {PyCamelotBatches}
+
+
+The script calls `Character.model_validate(...)` before adding a record to the
+batch. If validation fails, that batch is never written. The function yields up
+to `batch_size` rows at a time, providing an iterable of batches for the ingestion step,
+shown next.
+
+#### Table maintenance {#table-maintenance}
+For LanceDB OSS, the plugin instructs the agent to call
+`table.optimize()` after the ingestion loop. This compacts small fragments, cleans up
+old versions according to the retention policy, and incorporates new data into indexes.
+
+
+ {PyCamelotOssIngestion}
+
+
+If you're using LanceDB Enterprise, the plugin mentions that this step is not needed
+because LanceDB Enterprise handles maintenance automatically.
+
+
+
+This example dataset has only eight rows, so the default batch size writes it in
+one call. Larger inputs should still avoid single-row write commits.
+
+
+### 3\. Run the OSS pipeline {#3-run-the-oss-pipeline}
+
+```bash
+uv run python ingest_multimodal.py
+```
+
+The table now contains the validated character records and their JPEG bytes.
+Here are the first three rows:
+
+| Image | Character | Role |
+| --- | --- | --- |
+|
| King Arthur | King of Camelot |
+|
| Merlin | Wizard and Advisor |
+|
| Queen Guinevere | Queen of Camelot |
+
+Once your pipeline works, you can [run experiments on branches](/agent-branch-experiments)
+to try new embedding models, parsers, or search settings without touching `main`.
+
+### Takeaways {#takeaways}
+
+The example in this tutorial was small, but similar ideas apply to other workflows, too.
+Give the agent the data source, the constraints it must respect, and the
+artifacts it should return.
+
+The plugin supplies LanceDB-specific guidance, but it's the user's responsibility to
+ensure the output makes sense for the application.
+
+### Try the plugin with your own dataset {#try-the-plugin-with-your-own-dataset}
+
+The plugin shown in this tutorial should generalize reasonably well to other use cases.
+If you find any issues, open [an issue](https://github.com/lancedb/lancedb-agent-plugins/issues)
+on GitHub, clearly describing the intended behavior.
+
+You can choose OSS or Enterprise based on how the work will run:
+
+Start with [LanceDB OSS](/quickstart) during the early stages of a project
+when an agent is helping you prototype,
+explore a dataset, or run small workflows on a subset of the data. The application owns
+the storage and lifecycle work, so ask the agent to validate inputs, write in
+batches, and it will include table maintenance operations such as `optimize()`
+where appropriate.
+
+Choose [LanceDB Enterprise](/enterprise) when the resulting table becomes
+shared production infrastructure, and the workload needs distributed capacity,
+private deployment, or platform-managed operations. The underlying data format and table
+API stay the same, so the pipeline does not need to be redesigned. The agent
+instead connects to a remote `db://` table and lets the cluster handle
+maintenance and background work.
## What agents are good at here {#workflows}
-Two things about LanceDB make it unusually agent-friendly, and both have a
-worked tutorial.
+The walkthrough above is one pattern. The other has its own tutorial.
-
- Install the plugin and have an agent build a multimodal ingestion pipeline
- end to end.
-
Isolate agent-driven experiments on a branch, evaluate them on a fixed test
set, and promote only the winner.
-The second is the more interesting pattern. [Branches](/management/table#versioning)
+It is the more interesting pattern. [Branches](/management/table#versioning)
give an agent somewhere to be wrong safely: it can rewrite a schema, recompute a
column, or try three embedding models without touching `main`, and you keep only
what measured better.
diff --git a/docs/web/basics/apis.mdx b/docs/web/basics/apis.mdx
index 6446fb223..9eca49d41 100644
--- a/docs/web/basics/apis.mdx
+++ b/docs/web/basics/apis.mdx
@@ -73,7 +73,7 @@ table = reader.read_all()
`execute_query` returns a blocking Arrow reader once results start arriving; it
does not wait for the whole query. `execute_query_async` returns a handle
-instead -- see [Query and job](/basics/jobs#queries).
+instead -- see [Queries](/basics/query).
## Choosing between them {#choosing}
diff --git a/docs/web/basics/catalog.mdx b/docs/web/basics/catalog.mdx
index 0826ab673..580e99546 100644
--- a/docs/web/basics/catalog.mdx
+++ b/docs/web/basics/catalog.mdx
@@ -75,4 +75,4 @@ latency, or when the query is cheap.
performance decision -- see [Maintenance](/indexing).
**Jobs** are not stored data at all; they are handles to work the server is
-doing. See [Query and job](/basics/jobs).
+doing. See [Jobs](/basics/jobs).
diff --git a/docs/web/basics/guarantees.mdx b/docs/web/basics/guarantees.mdx
index 42710f5b5..59a89c32d 100644
--- a/docs/web/basics/guarantees.mdx
+++ b/docs/web/basics/guarantees.mdx
@@ -98,7 +98,7 @@ nothing.
**A job id is opaque.** It correlates with server logs and the jobs API.
Parsing it, or storing it to resume the job later, is not supported.
-See [Query and job](/basics/jobs).
+See [Jobs](/basics/jobs).
## Schema {#schema}
diff --git a/docs/web/basics/jobs.mdx b/docs/web/basics/jobs.mdx
index 925c21602..aa6c9cda5 100644
--- a/docs/web/basics/jobs.mdx
+++ b/docs/web/basics/jobs.mdx
@@ -1,127 +1,25 @@
---
-title: "Query and job"
-sidebarTitle: "Query & Job"
-description: "Sync and async in LanceDB: two different distinctions that use the same word."
+title: "Jobs"
+sidebarTitle: "Job"
+description: "Work that outlives the call that started it: submitting, waiting, inspecting, cancelling."
icon: hourglass-half
-keywords: ["job", "async", "sync", "query", "background", "await"]
+keywords: ["job", "async", "background", "events", "cancel", "refresh"]
---
-"Async" means two unrelated things in LanceDB, and conflating them is the most
-common source of confusion about how work gets done.
-
-| | What it is | What it changes |
-| --- | --- | --- |
-| **Async client** | `connect_async`, `await` | whether *your thread* blocks |
-| **Async execution** | `_async` methods returning a handle | whether *the work* outlives the call |
-
-They are independent. A synchronous Python call can start work that runs for an
-hour; an `await` can wait for something that finishes instantly.
-
-
-Jobs are listed in SQL with [`SHOW JOBS`](/sql/show#jobs), and their failures with [`SHOW ERRORS`](/sql/show#errors).
-
-
-## Every `_async` method returns a handle {#handles}
-
-An `_async` method submits work and hands back a **handle** to it. Which handle
-depends on what you submitted -- `execute_query_async` returns a query,
-everything else returns a [job](#jobs) -- but they are the same idea, and they
-answer the same four questions:
-
-| | Query handle | Job handle |
-| --- | --- | --- |
-| Which one is this? | `id` | `id` |
-| How is it going? | `describe()` | `status()`, `refresh()`, `events()` |
-| Give me the result | `reader()` — streams batches as they arrive | `wait()` — blocks for the terminal result |
-| Stop it | `cancel()` | `cancel()` |
-
-The difference is in the third row, and it follows from what the work produces.
-A query produces **rows**, so its handle streams them and you can start reading
-before the query finishes. A job produces **an outcome** -- an index built, a
-column filled -- so its handle has nothing to stream and gives you the result
-once, at the end.
-
-Everything else is shared: both identify the work on the server, both report on
-it without blocking, and both can be told to stop.
-
-## Async client: whether you block {#async-client}
-
-A concern of your language, not of LanceDB. The work is identical either way.
-
-
-```python Python icon="python"
-db = lancedb.connect("db://my-db", api_key="...") # blocking
-tbl = db.open_table("documents")
-
-db = await lancedb.connect_async("db://my-db", api_key="...") # coroutines
-tbl = await db.open_table("documents")
-```
-
-```typescript TypeScript icon="square-js"
-// TypeScript is async throughout; there is no blocking form.
-const db = await lancedb.connect("db://my-db", { apiKey: "..." });
-const tbl = await db.openTable("documents");
-```
-
-
-Python offers both. `AsyncConnection` exposes the same surface as coroutines, so
-choosing between them is about the program you are writing -- a service wants
-the async form, a script does not care.
-
-## Queries: reading before the query finishes {#queries}
-
-A query is not a job, but it has the same shape of choice: wait for everything,
-or take a handle and start reading.
-
-
-```python Python icon="python"
-reader = db.execute_query("SELECT id, text FROM my_docs LIMIT 1000")
-table = reader.read_all()
-
-query = db.execute_query_async("SELECT id, text FROM my_docs")
-```
-
-```sql SQL
-SELECT id, text FROM my_docs LIMIT 1000
-```
-
-
-`execute_query` returns a blocking Arrow reader **once results start arriving**
--- it does not wait for the whole query to finish. Results stream as record
-batches, so a large result set does not have to fit in memory before you see its
-first row.
-
-`execute_query_async` returns the query handle instead:
-
-```python
-query = db.execute_query_async("SELECT id, text FROM my_docs")
-
-query.id # identifies it on the server
-query.describe() # what it is doing, without consuming results
-reader = query.reader() # stream the rows when you want them
-query.cancel() # stop it
-```
-
-`describe_query(id)` reports on one from elsewhere, the way `open_job(id)` does
-for a job.
-
-
-SQL requires a deployment. See [SDKs and SQL](/basics/apis).
-
-
-Query-builder searches -- vector, full-text, hybrid -- are ordinary calls that
-return results. They are fast enough not to need a handle. See
-[Query](/search).
-
-## Jobs: work that outlives the call {#jobs}
-
Some operations take longer than a request should. Building an index over a
large table, refreshing a computed column, registering a function -- these run
on the server after the call that started them has returned.
Each is submitted the same way and returns the same handle.
-### The `_async` convention {#convention}
+
+A [query](/basics/query) also has an `_async` form, but its handle streams rows
+rather than waiting for an outcome. Both answer the same four questions --
+which one is this, how is it going, give me the result, stop it -- and differ
+only in the third.
+
+
+## The `_async` convention {#convention}
An operation that can run in the background has two forms. The plain name blocks
until the work is done. The `_async` / `Async` name submits the work and returns
@@ -140,7 +38,7 @@ waits. Reach for the `_async` form when you want to do something else meanwhile,
run several at once, or survive the client going away.
-### Submitting and waiting {#waiting}
+## Submitting and waiting {#waiting}
```python Python icon="python"
@@ -184,7 +82,7 @@ The TypeScript `wait()` resolves to `void`. Read the outcome from `result` after
it resolves.
-### Inspecting a job {#inspecting}
+## Inspecting a job {#inspecting}
Submitting an operation returns only an id, so the detail fields are empty until
the handle fetches the record. `refresh()` does that; `status()` does it as a
@@ -217,7 +115,7 @@ job.failure; // why it failed, when it did
Printing the handle renders every field it knows, which is usually faster than
reading them one at a time.
-### Progress, from events {#events}
+## Progress, from events {#events}
`result` only tells you anything once the job has finished. Events are written
as it runs, and they outlive the workers that produced them -- so a job that is
@@ -253,7 +151,7 @@ silently lose its tail unless you pass `limit`.
Events are recorded server-side, so this raises for an operation that ran
in-process.
-### Finding and cancelling jobs {#managing}
+## Finding and cancelling jobs {#managing}
The connection lists and reopens jobs, so a handle lost to a restarted process
is recoverable from its id.
diff --git a/docs/web/basics/limits.mdx b/docs/web/basics/limits.mdx
index 5ef4e5063..d615fade5 100644
--- a/docs/web/basics/limits.mdx
+++ b/docs/web/basics/limits.mdx
@@ -45,7 +45,7 @@ page most likely to mislead you, because nothing in the result says it was cut.
32 of them. Narrow with `FOR JOB` when you need a specific one rather than a
survey.
-See [Query and job](/basics/jobs).
+See [Jobs](/basics/jobs).
## Schema and types {#schema}
diff --git a/docs/web/basics/query.mdx b/docs/web/basics/query.mdx
new file mode 100644
index 000000000..b643d0f35
--- /dev/null
+++ b/docs/web/basics/query.mdx
@@ -0,0 +1,94 @@
+---
+title: "Queries"
+sidebarTitle: "Query"
+description: "Running a query, and reading its results before it finishes."
+icon: magnifying-glass
+keywords: ["query", "async", "streaming", "arrow", "reader", "execute_query"]
+---
+
+A query returns rows. How you get them depends on two choices that are easy to
+conflate: whether **your thread** blocks, and whether you take the results all
+at once or stream them as they arrive.
+
+## Blocking or not is a language concern {#async-client}
+
+A concern of your language, not of LanceDB. The work is identical either way.
+
+
+```python Python icon="python"
+db = lancedb.connect("db://my-db", api_key="...") # blocking
+tbl = db.open_table("documents")
+
+db = await lancedb.connect_async("db://my-db", api_key="...") # coroutines
+tbl = await db.open_table("documents")
+```
+
+```typescript TypeScript icon="square-js"
+// TypeScript is async throughout; there is no blocking form.
+const db = await lancedb.connect("db://my-db", { apiKey: "..." });
+const tbl = await db.openTable("documents");
+```
+
+
+Python offers both. `AsyncConnection` exposes the same surface as coroutines, so
+choosing between them is about the program you are writing -- a service wants
+the async form, a script does not care.
+
+## Reading before the query finishes {#queries}
+
+A query is not a job, but it has the same shape of choice: wait for everything,
+or take a handle and start reading.
+
+
+```python Python icon="python"
+reader = db.execute_query("SELECT id, text FROM my_docs LIMIT 1000")
+table = reader.read_all()
+
+query = db.execute_query_async("SELECT id, text FROM my_docs")
+```
+
+```sql SQL
+SELECT id, text FROM my_docs LIMIT 1000
+```
+
+
+`execute_query` returns a blocking Arrow reader **once results start arriving**
+-- it does not wait for the whole query to finish. Results stream as record
+batches, so a large result set does not have to fit in memory before you see its
+first row.
+
+`execute_query_async` returns the query handle instead:
+
+```python
+query = db.execute_query_async("SELECT id, text FROM my_docs")
+
+query.id # identifies it on the server
+query.describe() # what it is doing, without consuming results
+reader = query.reader() # stream the rows when you want them
+query.cancel() # stop it
+```
+
+`describe_query(id)` reports on one from elsewhere, the way `open_job(id)` does
+for a job.
+
+
+SQL requires a deployment. See [SDKs and SQL](/basics/apis).
+
+
+Query-builder searches -- vector, full-text, hybrid -- are ordinary calls that
+return results. They are fast enough not to need a handle. See
+[Query](/search).
+
+## Searches are ordinary calls {#searches}
+
+Query-builder searches -- [vector](/search/vector-search),
+[full-text](/search/full-text-search), [hybrid](/search/hybrid-search) -- return
+results directly. They are fast enough not to need a handle, and they have no
+SQL form. See [Semantic Search](/search).
+
+## When the work outlives the call {#jobs}
+
+Some operations take longer than a query should: building an index, refreshing a
+computed column, registering a function. Those return a **job** rather than
+rows, and a job is a different handle with a different shape. See
+[Jobs](/basics/jobs).
diff --git a/docs/web/build-with-ai-agents.mdx b/docs/web/build-with-ai-agents.mdx
deleted file mode 100644
index 4bfd6ab97..000000000
--- a/docs/web/build-with-ai-agents.mdx
+++ /dev/null
@@ -1,270 +0,0 @@
----
-title: "Tutorial: Use the LanceDB agent plugin"
-sidebarTitle: "Using LanceDB Agent Skill"
-description: "Install the LanceDB plugin and use an AI coding agent to quickly build a multimodal ingestion pipeline."
-icon: "robot"
-keywords: ["AI agents", "agent plugins", "multimodal", "Pydantic", "branches", "embeddings"]
----
-
-import {
- PyCamelotSchema,
- PyCamelotBatches,
- PyCamelotOssIngestion,
-} from '/snippets/build_with_ai_agents.mdx';
-
-The LanceDB agent plugin gives coding agents a maintained reference for the
-Python and TypeScript APIs. It also covers portable OSS and Enterprise code,
-ingestion performance, and branch operations. It ships as the `lancedb` plugin in
-the [lancedb-agent-plugins](https://github.com/lancedb/lancedb-agent-plugins)
-repository, which is also a plugin marketplace. Install it with your agent's
-plugin manager:
-
-
-```text Claude Code
-/plugin marketplace add lancedb/lancedb-agent-plugins
-/plugin install lancedb@lancedb
-```
-
-```bash Codex icon="terminal"
-codex plugin marketplace add lancedb/lancedb-agent-plugins
-codex plugin add lancedb@lancedb
-```
-
-```bash Other agents icon="terminal"
-npx plugins add lancedb/lancedb-agent-plugins
-```
-
-
-The [`plugins`](https://www.npmjs.com/package/plugins) installer shown under
-**Other agents** is a cross-tool option. It detects which agent CLIs are on your
-`PATH` — Cursor, GitHub Copilot CLI, VS Code, Grok Build, and Kimi Code, as well
-as Claude Code and Codex — and installs through each one's native plugin system.
-
-The plugin supplements the agent's training data with current LanceDB
-instructions. To pick up later revisions, refresh the marketplace:
-
-
-```text Claude Code
-/plugin marketplace update lancedb
-```
-
-```bash Codex icon="terminal"
-codex plugin marketplace upgrade
-```
-
-```bash Other agents icon="terminal"
-npx plugins add lancedb/lancedb-agent-plugins
-```
-
-
-
-Each plugin manager clones the repository, reads its marketplace manifest to find
-the `lancedb` plugin, and hands the plugin to the agent's own plugin store rather
-than copying files into your project. Claude Code and Codex keep it under
-`~/.claude/plugins` and `~/.codex/plugins` respectively, so the plugin is available
-in every project on the machine.
-
-The `npx plugins` installer defaults to the same user-wide scope. Pass
-`--scope project` to record the plugin in the current repository instead, so that
-everyone working in it gets the same plugin, or `-t ` to install for a
-single agent rather than every one it detects:
-
-```bash
-npx plugins add lancedb/lancedb-agent-plugins --scope project -t cursor
-```
-
-Not every agent supports project scope; `npx plugins targets` lists what it
-found and what each target supports.
-
-
-## Get started with the LanceDB agent plugin {#get-started-with-the-lancedb-agent-plugin}
-
-This tutorial uses the Camelot dataset from the [quickstart](/quickstart), with
-a portrait added for each character. Each LanceDB row contains validated
-metadata and raw JPEG bytes. Text, images, and any embeddings you add later
-remain in the same table.
-
-### 1\. Download the multimodal dataset {#1-download-the-multimodal-dataset}
-
-From a new project directory, download the JSON file and portraits:
-
-```bash
-mkdir -p data/img
-
-BASE_URL="https://docs.lancedb.com/static/assets/tutorials/build-with-ai-agents/camelot/data"
-curl -fsSL "$BASE_URL/camelot.json" -o data/camelot.json
-
-for image in \
- arthur.jpg \
- guinevere.jpg \
- merlin.jpg \
- mordred.jpg \
- sir_galahad.jpg \
- sir_gawain.jpg \
- sir_lancelot.jpg \
- sir_percival.jpg
-do
- curl -fsSL "$BASE_URL/img/$image" -o "data/img/$image"
-done
-```
-
-Each JSON record has this shape:
-
-```json
-{
- "id": 2,
- "name": "Merlin",
- "role": "Wizard and Advisor",
- "description": "A powerful wizard and prophet who mentors Arthur.",
- "stats": {
- "strength": 2,
- "courage": 4,
- "magic": 5,
- "wisdom": 5
- },
- "img": "data/img/merlin.jpg"
-}
-```
-
-JSON input may have missing fields, unexpected fields, or values of the wrong
-type. The LanceDB plugin tells the agent to validate each record with strict
-Pydantic models before writing it.
-
-After the agent writes the pipeline, inspect the schema, batching, and write
-path rather than assuming it followed the plugin's guidance correctly.
-
-### 2\. Prompt your agent to build the pipeline {#2-prompt-your-agent-to-build-the-pipeline}
-
-Install the Python packages used by the example:
-
-```bash icon="terminal"
-uv init
-uv add lancedb pyarrow pydantic
-```
-
-If you're using LanceDB Enterprise, ask the agent to ingest into an Enterprise table,
-provide the relevant environment variables for connecting to your Enterprise deployment
-in a local `.env` file, and point the agent to it.
-
-```text .env
-LANCEDB_URI=db://your_project_name
-LANCEDB_API_KEY=your_api_key_here
-LANCEDB_REGION=us-east-1
-LANCEDB_HOST_OVERRIDE=https://your-enterprise-endpoint.com
-```
-
-If you're using LanceDB OSS, no connection settings are required, as it runs as an
-embedded retrieval library. A simple prompt like this should work:
-
-```text Agent prompt
-# If using OSS
-Use the lancedb plugin to ingest the dataset in `data/` into a LanceDB
-OSS table.
-
-# If using enterprise
-Use the lancedb plugin to ingest the dataset in `data/` into a LanceDB
-Enterprise table using the connection information in `.env`.
-```
-
-Because the plugin is registered with the agent's own plugin system, the agent
-should pick it up on its own once you restart the session. If it does not,
-simply ask it to use the `lancedb` plugin in the prompt, as shown above.
-
-That should be enough! The agent will create `ingest_multimodal.py`, or similar.
-The following sections inspect the script to verify that it follows the plugin's guidance.
-
-#### Data validation {#data-validation}
-
-The plugin encourages the agent to validate each record with Pydantic before writing it.
-The agent should ideally define a schema for the table and a nested schema for the
-`stats` field.
-
-
- {PyCamelotSchema}
-
-
-In this case, our agent correctly defined `Character` and `Stats` Pydantic models
-and validated the JSON before adding it to the table.
-
-#### Batched ingestion {#batched-ingestion}
-
-Naively calling `table.add()` once per row is slow, and is considered an anti-pattern
-in LanceDB. The plugin encourages the agent to collect incoming rows into batches and
-write them with a single `table.add()` call. When you use the plugin, the agent should
-produce something like this:
-
-
- {PyCamelotBatches}
-
-
-The script calls `Character.model_validate(...)` before adding a record to the
-batch. If validation fails, that batch is never written. The function yields up
-to `batch_size` rows at a time, providing an iterable of batches for the ingestion step,
-shown next.
-
-#### Table maintenance {#table-maintenance}
-For LanceDB OSS, the plugin instructs the agent to call
-`table.optimize()` after the ingestion loop. This compacts small fragments, cleans up
-old versions according to the retention policy, and incorporates new data into indexes.
-
-
- {PyCamelotOssIngestion}
-
-
-If you're using LanceDB Enterprise, the plugin mentions that this step is not needed
-because LanceDB Enterprise handles maintenance automatically.
-
-
-
-This example dataset has only eight rows, so the default batch size writes it in
-one call. Larger inputs should still avoid single-row write commits.
-
-
-### 3\. Run the OSS pipeline {#3-run-the-oss-pipeline}
-
-```bash
-uv run python ingest_multimodal.py
-```
-
-The table now contains the validated character records and their JPEG bytes.
-Here are the first three rows:
-
-| Image | Character | Role |
-| --- | --- | --- |
-|
| King Arthur | King of Camelot |
-|
| Merlin | Wizard and Advisor |
-|
| Queen Guinevere | Queen of Camelot |
-
-Once your pipeline works, you can [run experiments on branches](/agent-branch-experiments)
-to try new embedding models, parsers, or search settings without touching `main`.
-
-## Takeaways {#takeaways}
-
-The example in this tutorial was small, but similar ideas apply to other workflows, too.
-Give the agent the data source, the constraints it must respect, and the
-artifacts it should return.
-
-The plugin supplies LanceDB-specific guidance, but it's the user's responsibility to
-ensure the output makes sense for the application.
-
-### Try the plugin with your own dataset {#try-the-plugin-with-your-own-dataset}
-
-The plugin shown in this tutorial should generalize reasonably well to other use cases.
-If you find any issues, open [an issue](https://github.com/lancedb/lancedb-agent-plugins/issues)
-on GitHub, clearly describing the intended behavior.
-
-You can choose OSS or Enterprise based on how the work will run:
-
-Start with [LanceDB OSS](/quickstart) during the early stages of a project
-when an agent is helping you prototype,
-explore a dataset, or run small workflows on a subset of the data. The application owns
-the storage and lifecycle work, so ask the agent to validate inputs, write in
-batches, and it will include table maintenance operations such as `optimize()`
-where appropriate.
-
-Choose [LanceDB Enterprise](/enterprise) when the resulting table becomes
-shared production infrastructure, and the workload needs distributed capacity,
-private deployment, or platform-managed operations. The underlying data format and table
-API stay the same, so the pipeline does not need to be redesigned. The agent
-instead connects to a remote `db://` table and lets the cluster handle
-maintenance and background work.
diff --git a/docs/web/docs.json b/docs/web/docs.json
index ef0635e85..e15887823 100644
--- a/docs/web/docs.json
+++ b/docs/web/docs.json
@@ -138,6 +138,10 @@
{
"source": "/training",
"destination": "/training/data-loading"
+ },
+ {
+ "source": "/build-with-ai-agents",
+ "destination": "/basics/agentic-skills"
}
],
"navigation": {
@@ -150,7 +154,8 @@
"pages": [
"index",
"quickstart",
- "architecture"
+ "architecture",
+ "basics/agentic-skills"
]
},
{
@@ -158,12 +163,12 @@
"pages": [
"basics/lance",
"basics/catalog",
+ "basics/query",
"basics/jobs",
"tables/consistency",
"basics/guarantees",
"basics/limits",
- "basics/apis",
- "basics/agentic-skills"
+ "basics/apis"
]
},
{
@@ -393,12 +398,6 @@
"tab": "Use Cases",
"pages": [
"tutorials/index",
- {
- "group": "General",
- "pages": [
- "build-with-ai-agents"
- ]
- },
{
"group": "Search",
"pages": [
diff --git a/docs/web/sql/index.mdx b/docs/web/sql/index.mdx
index 63f44116d..c8701c494 100644
--- a/docs/web/sql/index.mdx
+++ b/docs/web/sql/index.mdx
@@ -30,7 +30,7 @@ Queries execute on the server and results come back as Arrow record batches --
nothing is converted to pandas or Python objects unless you ask. `execute_query`
hands back a reader as soon as the first batch is ready rather than when the
query finishes, so a large result set does not have to fit in memory before you
-start reading it. See [Query and job](/basics/jobs#queries).
+start reading it. See [Queries](/basics/query).
Served over an [Arrow Flight SQL](https://arrow.apache.org/docs/format/FlightSql.html)
endpoint, so any Flight SQL client reaches it.
diff --git a/docs/web/sql/show.mdx b/docs/web/sql/show.mdx
index 4356581b3..14537d3da 100644
--- a/docs/web/sql/show.mdx
+++ b/docs/web/sql/show.mdx
@@ -49,7 +49,7 @@ SHOW JOBS
```
Every job the server knows about — id, type, state, creation time. The SQL
-counterpart of `list_jobs`; see [Query and job](/basics/jobs).
+counterpart of `list_jobs`; see [Jobs](/basics/jobs).
## SHOW ERRORS {#errors}