diff --git a/docs.json b/docs.json
index b8f20585..75c6dc2c 100644
--- a/docs.json
+++ b/docs.json
@@ -164,7 +164,8 @@
"guides/ai-agents/ai-writeback",
"guides/ai-agents/content-tools",
"guides/ai-agents/mcp-servers",
- "guides/ai-agents/autopilot"
+ "guides/ai-agents/autopilot",
+ "guides/ai-agents/agents-as-code"
]
},
"references/integrations/lightdash-mcp",
diff --git a/guides/ai-agents.mdx b/guides/ai-agents.mdx
index c538085f..a092e2bb 100644
--- a/guides/ai-agents.mdx
+++ b/guides/ai-agents.mdx
@@ -51,6 +51,9 @@ This guide covers everything you need to know about AI agents in Lightdash:
+
+
+
diff --git a/guides/ai-agents/agents-as-code.mdx b/guides/ai-agents/agents-as-code.mdx
new file mode 100644
index 00000000..ab061cd3
--- /dev/null
+++ b/guides/ai-agents/agents-as-code.mdx
@@ -0,0 +1,172 @@
+---
+title: "AI agents as code"
+sidebarTitle: "Agents as code"
+description: "Download AI agents to YAML, review them in Git, and promote them between projects using the same content-as-code workflow as charts and dashboards."
+---
+
+You can serialize a project's AI agents to YAML with the Lightdash CLI, keep them under version control, and promote them between environments — preview, staging, production — using the standard `lightdash download` / `lightdash upload` flow.
+
+Use agents as code when you want to:
+
+- Review agent instructions, tags, and model settings in pull requests before they reach production
+- Copy a working agent from one project to another (for example, from a preview into production) without recreating it in the UI
+- Roll agent config back to a known-good version using Git history
+- Bootstrap new projects with the same set of agents you use elsewhere
+
+Agent config as code is **opt-in**. A bare `lightdash download` does not include agents — you have to ask for them with `--include-agents` or `--agents`.
+
+
+This is part of the same content-as-code system as [dashboards as code](/guides/developer/dashboards-as-code). The same disposable-vs-Git-managed tradeoff and CI patterns apply.
+
+
+## On-disk layout
+
+Agent files live under `lightdash/ai-agents/` next to your `charts/` and `dashboards/` folders. Each agent is a single `.yml` file named after its slug:
+
+```text
+lightdash/
+├── ai-agents/
+│ ├── orders-support-agent.yml
+│ └── revenue-analyst.yml
+├── charts/
+└── dashboards/
+```
+
+`lightdash upload` automatically picks up every `.yml` (or `.yaml`) file in `lightdash/ai-agents/` and syncs it to the [selected project](/references/lightdash-cli#lightdash-config-set-project).
+
+## Downloading agents
+
+Use one of the two flags below to include agents in a download. Neither is set by default.
+
+Download every AI agent in the project (paginated automatically by the CLI):
+
+```bash
+lightdash download --include-agents
+```
+
+Download only specific agents by slug or UUID:
+
+```bash
+lightdash download --agents orders-support-agent revenue-analyst
+```
+
+You can combine agent flags with the usual `--charts`, `--dashboards`, `--project`, and `-p / --path` flags on [`lightdash download`](/references/lightdash-cli#lightdash-download).
+
+## Uploading agents
+
+`lightdash upload` uploads every agent file in `lightdash/ai-agents/` by default. Uploads are **idempotent**: the server creates missing agents, updates existing agents by slug, and leaves unchanged agents alone.
+
+Upload every agent along with charts and dashboards:
+
+```bash
+lightdash upload
+```
+
+Upload only specific agents by slug:
+
+```bash
+lightdash upload --agents orders-support-agent
+```
+
+Skip agent uploads entirely, even when files exist under `lightdash/ai-agents/`:
+
+```bash
+lightdash upload --skip-agents
+```
+
+
+Unlike charts and dashboards, you do **not** need `--force` to create a new agent. Uploading a file whose `slug` does not exist in the target project creates the agent; changing the `slug` in an existing file creates a new agent under the new slug (the old one is not deleted).
+
+
+### Exit codes and validation
+
+The CLI validates each agent file before uploading. Invalid YAML, missing required fields, wrong `contentType`, or duplicate slugs across files cause `lightdash upload` to exit with a non-zero status, which is what you want in CI so bad config fails the build.
+
+## YAML schema
+
+Every agent file uses the following top-level shape. The schema is versioned independently from the runtime engine — `version` describes the as-code file format, and `agentVersion` records which runtime version the agent uses.
+
+```yaml lightdash/ai-agents/orders-support-agent.yml
+contentType: ai_agent
+version: 1
+agentVersion: 2
+slug: orders-support-agent
+name: Orders Support Agent
+description: Answers questions about orders, fulfillment, and refunds.
+imageUrl: null
+instruction: |
+ You are a support analyst for the ecommerce team. Use the orders and
+ shipments explores to answer questions about order status, refunds, and
+ fulfillment SLAs. Always report totals in GBP.
+tags:
+ - ai
+ - orders
+enableDataAccess: true
+enableSelfImprovement: true
+enableContentTools: false
+enableUserContext: true
+modelConfig:
+ modelName: gpt-4o
+ modelProvider: openai
+ reasoning: false
+```
+
+### Field reference
+
+| Field | Type | Description |
+| --- | --- | --- |
+| `contentType` | string | Always `ai_agent`. The CLI refuses to upload files with any other value. |
+| `version` | number | Schema version of the file itself. Currently `1`. |
+| `agentVersion` | `1` \| `2` | Runtime version of the agent. Both v1 and v2 round-trip through download and upload. |
+| `slug` | string | Stable identifier used to match against agents in the target project. Changing the slug creates a new agent on upload. |
+| `name` | string | Human-readable name shown in the UI. |
+| `description` | string \| null | Short description. |
+| `imageUrl` | string \| null | URL for the agent's avatar. Uploaded-avatar provenance is preserved across environments. |
+| `instruction` | string \| null | The agent's system prompt / instructions. See [getting started](/guides/ai-agents/getting-started#instructions) for guidance on what to include. |
+| `tags` | string[] \| null | Semantic tags used for [data access control](/guides/ai-agents/data-access#limiting-access-to-specific-explores-and-fields). |
+| `enableDataAccess` | boolean | Whether the agent can execute queries and read actual data. See [data access](/guides/ai-agents/data-access). |
+| `enableSelfImprovement` | boolean | Whether the agent captures corrections into [agent memory](/guides/ai-agents/agent-memory). |
+| `enableContentTools` | boolean | Whether the agent can create and edit charts and dashboards. See [creating & editing content](/guides/ai-agents/content-tools). |
+| `enableUserContext` | boolean | Whether the agent receives the asking user's identity for [personalized answers](/guides/ai-agents/data-access#personalizing-answers-to-who-is-asking). |
+| `modelConfig` | object \| null | Default model settings: `modelName`, `modelProvider`, and optional `reasoning` flag. When `null`, the agent inherits the [organization default](/guides/ai-agents/getting-started#set-an-organization-default-ai-model). |
+
+## What is not versioned
+
+Some pieces of an agent are runtime state and are deliberately kept out of the YAML file — they stay in the target environment and are preserved across create/update/no-op uploads:
+
+- **Conversations and threads.** Chat history is runtime state and never written to YAML. Uploading a change to an agent does not touch existing threads.
+- **User and group access lists.** Access control is project-specific and stays managed in the target environment. See [user and group access](/guides/ai-agents/getting-started#user-and-group-access-optional).
+- **Slack integrations.** Slack channel bindings are per-environment and are not serialized.
+- **MCP server references.** [MCP server](/guides/ai-agents/mcp-servers) references stay in the target project.
+- **Uploaded knowledge documents.** Uploaded [documents](/guides/ai-agents/getting-started#knowledge-documents-optional) are preserved across upload but are not written into the YAML file itself.
+
+This split lets you promote agent behavior (instructions, tags, model config, capability flags) through Git while still letting each environment own the things that don't make sense to copy — production Slack channels, per-project MCP servers, per-environment access lists.
+
+## Lifecycle examples
+
+Assuming a file at `lightdash/ai-agents/orders-support-agent.yml`:
+
+- **Create.** The target project has no agent with slug `orders-support-agent`. `lightdash upload` creates it.
+- **Update.** The target project already has `orders-support-agent`. Editing `instruction` or `tags` in the file and running `lightdash upload` updates the existing agent in place.
+- **No-op.** Nothing has changed since the last upload. The CLI reports the agent as skipped and makes no API call.
+- **Rename by slug.** Changing `slug: orders-support-agent` to `slug: orders-support-agent-v2` and uploading creates a new agent under the new slug. The original agent is untouched.
+- **Per-project isolation.** Uploading with `--project ` only affects that project — the same slug in different projects refers to different agents.
+
+## API
+
+The CLI is a thin wrapper around two permission-gated project endpoints:
+
+- **`GET /api/v1/projects/{projectUuid}/aiAgents/code`** — paginated download of agents as code. Accepts repeated `ids` query params (slug or UUID) to filter, and an `offset` for pagination.
+- **`POST /api/v1/projects/{projectUuid}/aiAgents/code`** — idempotent create-or-update by slug for one or more agents. Accepts a `force` query flag. Returns a summary of `created`, `updated`, `unchanged`, and `deleted` slugs.
+
+Both endpoints require project-level permission to manage AI agents. See the [API reference](/api-reference/v1/introduction) for the full request and response schema.
+
+## CI/CD
+
+Because upload is idempotent and returns a non-zero exit code on invalid files, agents as code fits the same CI patterns as [Git-managed dashboards](/guides/developer/dashboards-as-code#git-managed-dashboards):
+
+1. Store `lightdash/ai-agents/` in Git.
+2. On PR, run `lightdash upload --skip-charts --skip-dashboards --agents ` against a [preview project](/guides/developer/preview-projects) to sanity-check the change.
+3. On merge to `main`, run `lightdash upload` against production.
+
+Lock down agent editing in the UI (via role permissions) if you want the YAML files to be the definitive source of truth for agent config.
diff --git a/guides/developer/dashboards-as-code.mdx b/guides/developer/dashboards-as-code.mdx
index c5fee1a3..00fbdda7 100644
--- a/guides/developer/dashboards-as-code.mdx
+++ b/guides/developer/dashboards-as-code.mdx
@@ -899,6 +899,11 @@ And the matching tile entry on a dashboard:
tileSlug: orders-by-status
```
+## AI agents as code
+
+AI agent configuration can also be managed as code and promoted between projects with the same `lightdash download` / `lightdash upload` workflow used for charts and dashboards. This is useful for keeping agent instructions and tags under review in Git, and for copying a working agent from a preview or staging project into production.
+
+Agent config as code is **opt-in**: a bare `lightdash download` does not include agents. See [AI agents as code](/guides/ai-agents/agents-as-code) for the full guide, including the YAML schema, CLI flags, and what runtime state stays in the target environment.
## Custom roles as code
[Custom roles](/references/workspace/custom-roles) are also managed as code. Unlike charts and dashboards, custom roles are **organization-scoped** rather than project-scoped, so downloading and uploading them uses the `--organization` mode of `lightdash download` and `lightdash upload` and requires an organization admin.
diff --git a/references/lightdash-cli.mdx b/references/lightdash-cli.mdx
index 061d1271..1457e935 100644
--- a/references/lightdash-cli.mdx
+++ b/references/lightdash-cli.mdx
@@ -709,6 +709,11 @@ You can make changes to the code and upload these changes back to your Lightdash
- `--skip-dashboards`
- (default: false)
- skip downloading dashboards
+- `--agents `
+ - opt-in flag to also download specific AI agents as code. Pass one or more agent slugs or UUIDs. Files are written to `lightdash/ai-agents/.yml`. See [AI agents as code](/guides/ai-agents/agents-as-code) for the YAML schema and lifecycle rules.
+- `--include-agents`
+ - (default: false)
+ - opt-in flag to download every AI agent in the project as code. The endpoint is paginated, so the CLI walks all pages automatically. Agent files are written to `lightdash/ai-agents/`. Bare `lightdash download` does not include agents.
- `--organization`
- (default: false)
- switch to organization mode and download organization-scoped content (currently [custom roles](/guides/developer/dashboards-as-code#custom-roles-as-code)) instead of project content. Files are written to `lightdash/custom-roles/.yml`. Requires organization admin permissions. Cannot be combined with project-content flags such as `--charts`, `--dashboards`, `--project`, or `--nested`.
@@ -777,6 +782,16 @@ Download just one data app and nothing else.
lightdash download --apps-only --apps 8f2b1c4d-1111-2222-3333-444455556666
```
+Download every AI agent in the project alongside charts and dashboards.
+
+```bash
+lightdash download --include-agents
+```
+
+Download only a specific AI agent by slug (or UUID).
+
+```bash
+lightdash download --agents orders-support-agent
Download all charts and dashboards, plus every virtual view in the project.
```bash
@@ -841,6 +856,11 @@ If there have been changes made to a chart or dashboard in the application that
- `--create-new`
- (default: false)
- always create a new data app from the uploaded source instead of appending a new version to the app referenced by `lightdash-app.yml`. Combine with `--apps` and `--project` when copying an app to a different project or instance in a non-interactive shell.
+- `--agents `
+ - upload only the AI agent files matching these slugs from `lightdash/ai-agents/`. Omit to upload every agent file in that folder. Upload is idempotent — the server creates missing agents, updates existing agents by slug, and leaves unchanged agents alone. See [AI agents as code](/guides/ai-agents/agents-as-code).
+- `--skip-agents`
+ - (default: false)
+ - skip uploading AI agents even when `lightdash/ai-agents/` contains files. Use this to keep an agents folder in your repo without pushing changes on every `lightdash upload`.
- `--organization`
- (default: false)
- switch to organization mode and upload organization-scoped content (currently [custom roles](/guides/developer/dashboards-as-code#custom-roles-as-code)) instead of project content. Reads YAML files from `lightdash/custom-roles/` and upserts each role by name — creating, updating, or reporting no change per file. Requires organization admin permissions. Cannot be combined with project-content flags such as `--charts`, `--dashboards`, `--project`, or `--force`. Uploads never delete roles.
@@ -921,6 +941,16 @@ Copy a data app to a different project as a brand-new app (non-interactive).
lightdash upload --apps --project 21eef0b9-5bae-40f3-851e-9554588e71a6 --create-new
```
+Upload every AI agent file under `lightdash/ai-agents/` (creates missing agents, updates existing ones by slug).
+
+```bash
+lightdash upload
+```
+
+Upload only a specific AI agent by slug.
+
+```bash
+lightdash upload --agents orders-support-agent
Upload every custom role in `lightdash/custom-roles/` back to the organization (requires org admin).
```bash