# Welcome to nao
Source: https://docs.getnao.io/index
The \#1 Open-Source Analytics Agent
nao is a framework to build and deploy analytics agents.
Create the context of your analytics agent with nao-core CLI: data, metadata, modeling, rules, etc.\
Deploy a UI for anyone to chat with your agent and run analytics on your data.
Star us on GitHub and contribute to the project
## Key Features
### For Data Teams
Create a file-system like context for your agent. Add anything you want: data, metadata, docs, tools, MCPs. No limit.
Works with any data warehouse, stack, type of context, LLM.
Unit test your agent performance before deploying. Version the context and track performance over time.
Self-host your analytics agent and use your own LLM keys to guarantee maximum security.
### For Business Users
Ask questions in plain English, get analytics straight away.
Create and customize visualizations directly in the chat interface.
See the agent reasoning and sources clearly.
Send feedback to the data team when an answer is right or wrong.
## Get Started
Deploy your first analytics agent in 1 minute
# Agent Settings
Source: https://docs.getnao.io/nao-agent/chat/admin/agent-settings
Choose the models for background tasks, tune per-model inference parameters, and cap Python execution
Admins can choose which models run nao's background work, tune how the agent calls each model, and cap how long it is allowed to run Python. All three live in the admin panel under **Settings** -> **Project**.
## Default models
Beyond answering chats, nao calls an LLM for a handful of background tasks that have no model picker of their own. The **Default models** card on **Settings** -> **Project** -> **Models** decides which model each of them uses.
Two modes:
* **One default for everything** - pick a single model for every background task.
* **A model per task** - pick a model per task, so you can send cheap work to a small model and keep a capable one where accuracy matters.
| Task | What it does |
| --------------------------- | ----------------------------------------------------------------------------------------------------- |
| **Live story refresh** | Rewrites [live story](/nao-agent/chat/capabilities/stories) narratives when their data refreshes |
| **Title generation** | Names chats and automations from their first prompt |
| **Conversation compaction** | Summarizes long conversations to keep them within the context window |
| **Context recommendations** | Analyzes past chats to suggest [context improvements](/nao-agent/context-engineering/recommendations) |
| **Other tasks** | Small background helpers such as natural-language schedule parsing and memory extraction |
Leave a task on **nao default** to use the built-in choice. Title generation and compaction run on almost every conversation, so they are the two worth pointing at a cheap model first.
If you later remove the provider or the model, the card flags the selection with a warning icon and nao falls back to another available model until you pick a new one.
## Model parameters
Each enabled model can carry its own inference settings, stored per project and per provider. Models without overrides use the provider's defaults.
1. Go to **Settings** -> **Project** -> **Models**.
2. Add or edit a provider.
3. In **Enabled Models**, click the **...** button on an enabled model to open the **Model parameters** dialog.
4. Set the parameters you want and click **Save**.
Custom models you added yourself have the same edit button. Clearing every field removes the overrides for that model.
### Available parameters
The dialog only shows the controls the selected model actually accepts, so the list changes from model to model:
| Parameter | Applies to |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Thinking effort** | Models with adaptive thinking (recent Claude, GPT-5.x, Gemini 3.x, OpenRouter, Bedrock Claude 4.6+). The available levels depend on the model. |
| **Thinking budget (tokens)** | Models with budget-based extended thinking (Claude 4.5 era, Gemini 2.5, legacy Bedrock Claude). |
| **Temperature**, **Top P**, **Top K** | Sampling models. Upper bounds are model-specific, and **Top K** only appears where the model supports it. |
| **Max output tokens** | Most models. |
| Provider extras | Provider-specific controls, for example **Parallel tool calls**, **Send reasoning back**, **Speed** and **Inference geography** on Anthropic, **Text verbosity**, **Reasoning summary**, **Max tool calls** and **Service tier** on OpenAI, **Include thoughts**, **Safety threshold** and **Media resolution** on Google, **Safe prompt** and document limits on Mistral. |
On Claude models (direct, or through Vertex or Bedrock), **Top P** and **Temperature** are mutually exclusive - when both are set, nao sends only the temperature. Values outside a model's accepted range are clamped before the call.
Thinking effort and max output tokens directly change token spend. See [Budgets](/nao-agent/chat/admin/budgets) to cap that spend per provider.
## Python execution duration
When the Python sandbox is enabled, you can cap how long a single Python execution may run.
1. Go to **Settings** -> **Project** -> **Agent**.
2. In the **Experimental** card, set **Python execution duration**.
The value is in seconds, defaults to **30**, and must be between **1** and **600**. Code that runs longer is stopped.
# Budgets
Source: https://docs.getnao.io/nao-agent/chat/admin/budgets
Cap LLM spend per provider and per user, with alerts and automatic cutoffs
Admins can set a dollar limit on how much each configured LLM provider can spend over a fixed period, and how much any single user can spend against it. When a limit is reached, nao blocks further LLM calls across every channel (web chat, Slack, Microsoft Teams, Telegram, WhatsApp, Mattermost).
Budgets are admin-only. Open the admin panel and go to **Settings** -> **Project** -> **Budgets**.
## Set a budget
1. Navigate to **Settings** -> **Project** -> **Budgets**.
2. Pick a provider (Anthropic, OpenAI, Azure OpenAI, Google, Mistral, AWS Bedrock, OpenRouter, Ollama). Providers declared in `nao_config.yaml` are listed here alongside the ones configured in the UI.
3. Enter a **project limit** in dollars, a **per-user limit**, or both.
4. Choose a reset period: **day**, **week**, or **month**.
5. Save.
Each provider can have its own budget. Providers without a budget are unlimited.
### Per-user limits
A project limit caps what the whole team can spend on a provider. A per-user limit caps what any one person can spend against that same provider, in the same period.
The two are independent, and both are enforced:
* A user who hits their own limit is blocked while everyone else keeps working.
* When the project limit is reached, everyone is blocked regardless of individual headroom.
Use a per-user limit to stop one heavy user (or a runaway automation) from consuming the whole team's budget before the period resets.
Per-user limits are gated by the `user-budget` feature on an Enterprise license. Without it, only the project limit applies. See [Enterprise overview](/nao-agent/enterprise/overview#installing-the-license).
### Setting budgets in `nao_config.yaml`
Budgets can also be declared next to the provider in your project config, so they are version-controlled with the rest of your context:
```yaml theme={null}
llm:
providers:
- provider: anthropic
api_key: ${{ env('ANTHROPIC_API_KEY') }}
budget:
limit: 500 # project-wide spend limit in USD for the period
per_user_limit: 50 # per-user spend limit in USD for the period
period: month # day, week, or month (defaults to month)
```
At least one of `limit` and `per_user_limit` is required. `period` defaults to `month`. These budgets show up on the Budgets page like any other.
## How spend is tracked
For every LLM call, nao records the cost computed from the provider's token pricing and attributes it to the provider's current period. The Budgets page shows, per provider:
* The configured limit and reset period
* Current-period spend
* Percentage of the limit used
Spend resets to zero at the start of the next period.
## Alerts and cutoff
Warnings and cutoffs are evaluated against whichever limit is closest to being reached - the project one or the user's own.
* **80% to 100% used**: a banner appears at the top of the chat, warning that the limit is close. It names which budget is running out ("your Anthropic budget" or "your personal Anthropic budget") and when it resets.
* **100% reached**: nao blocks new LLM calls for that provider until the next reset. Someone who hits only their personal limit is blocked on their own; when the project limit is reached, everyone is.
* **On project cutoff**: nao emails the organization admin(s) so they can raise the limit or wait for the reset.
The cutoff applies uniformly across channels: web chat, Slack, Microsoft Teams, Telegram, WhatsApp, and Mattermost all stop issuing calls to the blocked provider.
## Unblocking
To resume traffic before the period resets, either:
* Raise the limit (project or per-user) on the Budgets page, or
* Switch the default model to another provider that still has budget available.
To reduce spend rather than cap it, tune the per-model thinking effort and max output tokens under [Agent Settings](/nao-agent/chat/admin/agent-settings#model-parameters).
# File Explorer
Source: https://docs.getnao.io/nao-agent/chat/admin/file-explorer
Browse, search, and edit your project's context files from the admin panel
Open it from **Settings** -> **File Explorer**.
Available to **Admin** and **Context Admin** roles only. Editing also needs a [connected context repository](/nao-agent/chat/admin/git), either connected from the UI or supplied by the deployment through `NAO_CONTEXT_SOURCE=git` - until then, every file is read-only.
New to editing context files? The [Contributor Guide](/nao-agent/context-engineering/contributing) covers what belongs where, which files are safe to edit, and how the agent finds what you write.
## Browse
Search covers file contents as well as names, and multi-word searches match in any order. Results truncate after 5 seconds of loading or 200 files searched. When you open a result that matched on content, nao jumps to the match and highlights it.
You should toggle off the file contents search if your context repo is large.
The content of files over 1 MB, non-UTF-8 files, `.git`, `.env`, and `.naoignore` is not searched. `.git` and `.env` are not browsable or writable at all.
Markdown files open as the rendered page. Click **Source** to put the raw text beside it and edit with a live preview - nao remembers whether you keep it open. Each file also shows an estimated token count, so you can see what a context file actually costs the agent before deciding to split it.
## Edit
Saving (`Cmd+S`) writes to your own private copy of the repository, not the live context files that the agent sees. Your team won't see your changes either until your pull request is merged.
## Commit and open a pull request
Saved edits collect in the **Git** panel under the file tree, where you can see the changes and choose which to commit.
Discarding changes permanently throws away uncommitted edits.
Commits are authored as you, with nao as co-author. Committing from the main branch creates another branch so that pushing creates a PR that can then be reviewed.
A branch's first push opens a pull request. Later pushes update that same pull request rather than opening another.
Once it's merged, the files will reach the agent after a redeploy, a scheduled sync, or a pull.
## Why a file is read-only
When a file can't be edited, nao says why.
| What you see | What to do |
| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "This file is generated by nao sync. Add human notes in annotations.md." (or "Change its source instead.") | Put your notes in `annotations.md`, or change the source it's generated from |
| "This file is rendered from a Jinja template. Edit the template instead." | Edit the `.j2` template it's rendered from |
| "This path is replaced by nao sync. Change its source in nao\_config.yaml." | Change the source in `nao_config.yaml` |
| "This file can't be edited because it isn't in the connected repository. Add it there to make it editable." | Commit the file to the context repository |
| "No context repository is connected. Open repository setup to connect one." | [Connect a repository](/nao-agent/chat/admin/git) |
| "Connect your GitHub account before using Git actions in the context explorer." | [Connect your own account](/nao-agent/chat/admin/git) |
| "GitHub is not configured for this instance. Add the GitHub client credentials first." | Ask whoever deploys nao to set the [server keys](/nao-agent/chat/admin/git) |
| "Add an access token or SSH deploy key to edit context files." | On a `NAO_CONTEXT_SOURCE=git` deployment, set `NAO_CONTEXT_GIT_TOKEN` or `NAO_CONTEXT_GIT_SSH_KEY` - see [Option B](/nao-agent/chat/admin/git#option-b-edit-the-deployments-own-repository) |
| "Repository status is temporarily unavailable." | Retry - this is usually transient |
| "No tracked nao\_config.yaml was found in the connected repository." | Commit a `nao_config.yaml` to the repository |
| "Multiple nao projects were found in the connected repository." | Keep one `nao_config.yaml` per context repository |
# Git
Source: https://docs.getnao.io/nao-agent/chat/admin/git
Connect the repository that stores your context files, so admins can edit them in the browser
Connecting a context repository is what turns the [File Explorer](/nao-agent/chat/admin/file-explorer) into an editor.
There are two ways to get there. Pick the one that matches how your deployment gets its context.
## Option A: connect a repository from the UI
Use this when the deployment does not manage the context itself (`NAO_CONTEXT_SOURCE` is `local` or `api`). Set it up in **Settings** -> **Git**.
Register a **GitHub OAuth App** or a **GitLab application**, so nao can act on your team's behalf, then set the credentials on the server and restart nao. Until then, the other steps stay disabled.
* GitHub: `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`
* GitLab: `GITLAB_CLIENT_ID` and `GITLAB_CLIENT_SECRET` (plus `GITLAB_BASE_URL` for a self-hosted instance)
Point nao at the repository holding your context files. This doesn't overwrite your live project or push anything, and disconnecting it later deletes nothing. Only a project admin can disconnect it.
Authorize nao with your personal GitHub or GitLab account. This is what commits and opens pull requests (or merge requests) **as you**, so your name is on the change and your own permissions apply.
## Option B: edit the deployment's own repository
When the deployment already clones its context from Git (`NAO_CONTEXT_SOURCE=git`), nao can commit and open review requests against that same repository with **no OAuth app and no per-user authorization**. It reuses the credential the deployment already has: the access token in `NAO_CONTEXT_GIT_TOKEN`, or the SSH deploy key in `NAO_CONTEXT_GIT_SSH_KEY`.
Nothing extra is required beyond the credential itself. Two optional variables cover the awkward cases:
| Variable | When you need it |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `NAO_CONTEXT_GIT_PLATFORM` | Your host is a self-hosted GitHub, GitLab, or Bitbucket that nao cannot identify from the URL. Accepts `github`, `gitlab`, or `bitbucket`. |
| `NAO_CONTEXT_GIT_SUBPATH` | The nao project lives in a subdirectory of the repository (a monorepo). |
Commits are still authored as the nao user who made the change, with nao as co-author, even though the push uses the deployment's shared credential.
In this mode, **Settings -> Git** shows a read-only summary of the deployment-managed repository rather than the connection wizard, because the source is controlled by the environment and not the UI. Editing in the File Explorer still works. See [Deployment-managed context files](/nao-agent/self-hosting/context-source).
# Monitoring
Source: https://docs.getnao.io/nao-agent/chat/admin/monitoring
Monitor usage, costs, feedbacks
Access the "Usage & Costs" dashboard from the admin panel to view analytics, usage metrics, user feedback, and chats replay tools.
The Monitoring views are admin-only. Non-admin users are redirected away from protected routes.
# Usage analytics
In the Usage & Costs section, you can monitor:
* Number of message sent by users to the chat
* Total tokens consumed
* Total cost of tokens consumed
* Channel usage split across Web, Slack, and Microsoft Teams
You can filter on a specific model, and look at a day / week / month granularity.
## Feedbacks monitoring
In this section, you can see all postive and negative feedbacks sent by users in message threads.
The **Feedbacks** table shows:
* **Vote** - Thumbs up (👍) for positive feedback or thumbs down (👎) for negative feedback
* **User** - Who provided the feedback
* **Message** - The agent's response that was rated (truncated preview)
* **Reason** - User's explanation for their feedback (if provided)
* **Date** - When the feedback was given
## Chats replay
In this section, admins can inspect historical chats at the project level and replay messages for debugging and quality checks.
The **Chats Replay** view includes:
* **Project-wide chat table** - Browse chats in the current project
* **Filters** - Filter by column visibility, date, users, role, and tool state
* **Replay panel** - Navigate previous and next feedbacks or tool errors from a selected chat
* **Context window ring** - How much of the model's context window the selected chat is using, the same indicator users see in the chat input
### Tokens and cost per chat
The chat table breaks each conversation's token use into **uncached** and **cache read**, shown as two figures above a split bar. Hover it for the exact totals. A separate **Cost** column gives the dollar cost of the chat, and both columns are sortable, so you can sort by spend to find the conversations worth looking at.
A high cache-read share is the healthy case: it means the agent is re-reading a stable context instead of paying full price for it on every turn. A chat that is almost entirely uncached usually points at context that keeps changing between turns.
Chats Replay is also available to the [Context Admin role](/nao-agent/chat/admin/setup#user-roles).
If an admin or context admin opens the URL of a chat they do not own, nao redirects them to the Chats Replay view for that chat instead of showing a "Chat not found" page. Links to a colleague's chat therefore resolve straight to the replay panel, with the chat already selected.
## Asset analytics
Every chat and story keeps a record of how it has been used, so you can see whether a report is actually read and by whom.
Open it from:
* A chat: the **Analytics** icon in the chat header.
* A story: **Analytics** in the story header menu.
The dialog groups events by person, most recent first, and has a search box to filter by name. Tabs depend on the asset:
| Tab | Chats | Stories | What it shows |
| ------------- | ----- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Views** | Yes | Yes | Who opened the asset, when, and how long they spent on it. Sessions are listed per viewer with a total time. For stories, the version viewed is shown. |
| **Downloads** | Yes | Yes | Who exported the asset and in which format. Stories record PDF and HTML exports; chats record PNG chart images and CSV table exports, with the chart or table title. |
| **Forks** | Yes | Yes | Who branched off the asset, and whether the fork was made from the whole asset or from a text selection. |
| **Favorites** | No | Yes | Who favorited the story. The tab count reflects currently active favorites. |
| **Refreshes** | No | Yes | When the story data was refreshed, and whether the refresh was manual or scheduled. |
Repeat views by the same person on the same asset are collapsed inside a two-minute window, so refreshing a page does not inflate the count. Views coming from an embedded story, where there is no signed-in user, are grouped under **anonymous**.
Asset analytics are visible to the owner of the chat or story, and to admins for any asset in the project.
The same events are also queryable in natural language through the `v_analytics_event` view in admin mode (see below), which is the better route for project-wide questions such as "what are the most viewed stories this month?".
## Chat with your nao data (admin mode)
Admin mode lets you ask questions about how your nao project is being used, in natural language, instead of reading the usage dashboards manually. In this mode the agent answers by running read-only SQL over nao's own application data.
**Turn it on** from the chat, in two ways:
* The **Chat with nao data** entry in the settings sidebar.
* The **Admin mode** toggle in the chat input menu. When it's on, the chat input shows an amber border and an **Admin Mode** badge so it's clear you're querying internal data.
Admin mode is available to admins only.
**What it can read.** The agent queries a set of project-scoped, read-only views over nao's usage data. They are already filtered to the current project, so you never need to add a project filter:
* `v_messages` - full message history: user prompts, assistant answers, tool calls and errors, feedback votes and explanations, the model used, and the source (Web, Slack, Teams, etc.). The richest view for adoption, tool errors, and downvotes. It also carries token counts: `total_tokens` alongside the input split (`input_total_tokens`, `input_no_cache_tokens`, `input_cache_read_tokens`, `input_cache_write_tokens`) and the output split (`output_total_tokens`, `output_text_tokens`, `output_reasoning_tokens`). These values repeat on every part of a message, so deduplicate by `message_id` before summing them.
* `v_analytics_event` - asset engagement events (page views, downloads, forks, favorites, refreshes, view duration) for chats and stories.
* `v_llm_inference` - one row per LLM call, with type and token usage.
* `v_mcp_call_log` - MCP tool calls.
* `v_memories` - saved memories.
* `v_project` - project metadata.
Ask things like "how many messages were sent this week by channel?", "which questions got the most downvotes?", or "what are the most downloaded stories?" and the agent writes the SQL for you. Queries are read-only (`SELECT` / `WITH` only) and cannot touch anything outside the allowlisted views.
## Logs
Navigate to **Settings** -> **Logs** to view the agent's server-side logs in a terminal-style viewer.
Features:
* **Date range filter** - filter by preset ranges (last 15 minutes, 1 hour, 24 hours, 7 days) or a custom date and time range. Defaults to the last hour.
* **Load older logs** - scroll to the top of the log viewer or click the header to load older entries. Pagination is cursor-based.
* **Expandable rows** - click a log row to expand the full message text and see the `context` JSON payload when present. Truncated rows show a chevron indicator.
**What's Next?**
Configure users, authentication, and integrations
Learn how to systematically improve answer quality
# Setup
Source: https://docs.getnao.io/nao-agent/chat/admin/setup
Configure users, authentication, models, and prompts for nao chat
As an admin, you can configure how your team accesses nao chat, manage integrations, choose models, and customize agent behavior.
# Access the admin panel
The admin views are exposed from the same server as the chat UI.
1. Start nao chat locally (see the [chat overview](/nao-agent/chat/overview) for details).
2. Sign in with an admin-capable account.
3. Navigate to the admin section from the UI navigation.
The admin panel includes sections for:
* **Project** - General project settings and Google OAuth
* **Models** - LLM provider configuration
* **Agent** - Agent settings and template prompts
* **MCP Servers** - Model Context Protocol server configuration
* **File Explorer** - Browse, search, and edit your context folder files
* **Git** - Connect the repository that stores your context files (available to Admin and Context Admin roles)
* **Slack** - Slack bot integration
* **Telegram** - Telegram bot integration and user linking
* **WhatsApp** - WhatsApp bot integration and user linking
* **Mattermost** - Mattermost bot integration and user linking
* **Team** - User management
* **Usage & Costs** - Usage analytics, feedbacks, and chats replay (Chats Replay is also available to the Context Admin role)
* **Recommendations** - Suggested context improvements mined from usage (available to Admin and Context Admin roles)
# Add Users
User management is available for deployed instances. If you're running nao locally with `nao chat`, user management is not required. For deployment instructions, see the [Deployment Guide](/nao-agent/self-hosting/deployment-guide).
**To add a new user to a project:**
1. Navigate to **Team** in the left sidebar.
2. Click the **+** button in the top right of the Users panel.
3. Enter the user's email address
4. Pick a **role** (see below).
5. The user will then have to sign in with its email address and the generated password.
If you want to let users sign up autonomously, you can setup Google OAuth - see in next section.
The first user to sign up after deployment automatically becomes an admin. Additional users must be added through the admin interface. For more details on deploying nao, see the [Deployment Guide](/nao-agent/self-hosting/deployment-guide#step-5-customize-your-setup).
## User roles
Each user belongs to one project role:
* **Admin** - full access. Can manage users, integrations, models, prompts, budgets, and read every chat and story in the project.
* **Context Admin** - uses nao like a Member, plus ownership of the project's context: **Recommendations**, **Chats Replay**, the **File Explorer**, and the **Git** setup page. They can edit context files, commit, and open pull requests, but get no other admin capabilities (LLM config, team management, usage & costs, logs, project settings) and can't disconnect the context repository. Use it for context owners who shouldn't have full admin.
* **Member** - default role for new users. Can chat with the agent, create and share stories, edit memories scoped to their own conversations.
* **Viewer** - read-only. Can open shared stories and shared chats sent to them, but cannot start a new chat, edit context, or change settings. Use this role for stakeholders who only consume insights.
Change a user's role from **Team** -> click the user -> **Role**. Context Admin is a project-only role, so it isn't offered in the organization members list.
### Default role for new users
By default, new users created through any onboarding path (admin invite to org or project, or messaging-provider auto-creation from Slack/Telegram/WhatsApp) get the **Member** role. Set the `DEFAULT_USER_ROLE` environment variable to change this:
```bash theme={null}
DEFAULT_USER_ROLE=viewer # admin | user | viewer (defaults to "user" when unset)
```
Set `DEFAULT_USER_ROLE=viewer` for deployments where most people should consume insights read-only and only a few admins build context. Leaving it unset keeps the existing **Member** default.
# Reset user passwords with SMTP
If SMTP is configured, nao exposes a password reset flow for email and password users.
What users get:
* A **Forgot password** link on the login page
* A reset email with a secure link
* A dedicated **Reset password** page to choose a new password
What admins need:
1. [Configure SMTP](/nao-agent/self-hosting/deployment-guide#smtp-for-transactional-email) for the deployment
2. Make sure your public app URL is correct so reset links point back to your nao instance
3. Test the flow from the login page with a real user account
If SMTP is not configured, the forgot-password and reset-password routes stay unavailable.
**Reset a password from the CLI**
When SMTP is not configured, or a user is locked out, an admin with access to the deployment can reset a password directly against the local database with the `reset-password` command:
```bash theme={null}
nao reset-password user@example.com
```
The command generates a temporary password, stores it hashed, and prints it once so you can hand it to the user to sign in and change. This works without a running mail server or browser flow.
# Configure Google OAuth
Google OAuth configuration is for deployed instances. This allows users to sign in with their Google accounts instead of manually created accounts. For deployment instructions, see the [Deployment Guide](/nao-agent/self-hosting/deployment-guide#step-5-customize-your-setup).
Enable Google sign-in for your team and control which domains can self-register.
**Step 1: Create Google OAuth Credentials**
1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select an existing one
3. Navigate to **APIs & Services** → **Credentials**
4. Click **Create Credentials** → **OAuth client ID**
5. Configure the OAuth consent screen if prompted
6. Select **Web application** as the application type
7. Add authorized redirect URIs:
```
https://your-deployed-instance-url/api/auth/callback/google
```
8. Copy the **Client ID** and **Client Secret**
**Step 2: Configure in nao Admin Panel**
1. Navigate to **Project** in the admin sidebar
2. Scroll to the **Google Credentials** section
3. Under **Google OAuth (Override Env)**, enter your **Client ID**
4. Click the **ENV** button to manage environment variables if needed
5. Set the following environment variables in your deployment:
```bash theme={null}
GOOGLE_CLIENT_ID=your-client-id-here
GOOGLE_CLIENT_SECRET=your-client-secret-here
```
**Step 3: Configure Allowed Domains**
To restrict sign-up to specific email domains:
1. Set the `GOOGLE_AUTH_DOMAINS` environment variable:
```bash theme={null}
GOOGLE_AUTH_DOMAINS=getnao.io,example.com
```
2. Only users with email addresses from these domains will be able to sign up
If you configure Google OAuth, users can sign in with their Google accounts. Make sure to set allowed domains if you want to restrict access to your organization.
## Configure GitHub SSO
Enable GitHub sign-in so your team can log in with their GitHub accounts.
1. Create a GitHub OAuth App at [github.com/settings/developers](https://github.com/settings/developers).
2. Set the following environment variables:
```bash theme={null}
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GITHUB_SSO=true
```
3. Optionally restrict access to specific GitHub usernames:
```bash theme={null}
GITHUB_ALLOWED_USERS=username1,username2
```
The same GitHub OAuth app is also used by automations that need GitHub repository access. If `GITHUB_SSO` is not set to `true`, the OAuth app is available for automations but not as a login provider.
## Configure GitLab SSO
Enable GitLab as a login provider and let users connect a GitLab account to import repositories and open merge requests from [context recommendations](/nao-agent/context-engineering/recommendations).
1. Create an application in GitLab under **User Settings -> Access -> Applications**.
2. Add the callback URLs (replace `localhost:5005` with your deployment host):
```
https:///api/auth/callback/gitlab # "Sign in with GitLab" (SSO)
https:///api/gitlab/callback # "Connect GitLab" in settings
```
3. Grant the scopes `api`, `read_user`, `openid`, and `email`.
4. Set the following environment variables:
```bash theme={null}
GITLAB_CLIENT_ID=your-gitlab-client-id
GITLAB_CLIENT_SECRET=your-gitlab-client-secret
GITLAB_REDIRECT_URI=https:///api/gitlab/callback
```
5. Optional settings:
```bash theme={null}
GITLAB_SSO=true # expose GitLab as a login provider
GITLAB_BASE_URL=https://gitlab.example.com # for a self-hosted GitLab instance
GITLAB_ALLOWED_USERS=username1,username2 # restrict sign-in to specific users
```
As with GitHub, if `GITLAB_SSO` is not set to `true`, the OAuth app is still available for connecting an account and opening merge requests, but not as a login provider.
## Set the project date format
Choose how dates are displayed across the project from **Settings -> Project**. The selected format applies everywhere dates render: chat tables and charts, stories, story exports (PDF/HTML), and messaging-channel replies (Slack, Teams, Telegram, WhatsApp, Mattermost).
Set it once at the project level so everyone on the team sees dates in a consistent format.
## Configure Slack integration
Connect your nao agent to Slack so your team can ask questions directly in Slack channels.
Configure Slack integration from the **Slack** section in the admin sidebar, or see the [Slack Bot guide](/nao-agent/connectors/slack) for detailed setup instructions.
## Choose LLM models
Configure which LLM providers and models are available to your agent.
**Configure API Keys**
In the **Models** section of the admin panel:
* **If the provider isn't configured anywhere yet**: Enter your LLM API key directly in the UI
* **If a key already comes from `nao_config.yaml` or your environment variables**: the provider is listed with a `nao_config.yaml` or `ENV` badge, and the form tells you where its key comes from. Leave the key field empty to keep using it, or enter a different key to override it
Providers declared in the `llm` block of `nao_config.yaml` show up here with the models, display names and prices you gave them, so you don't have to re-enter anything in the UI. Editing one saves an override in the database, prefilled with the file's values.
For each provider, nao resolves credentials and models from the first source that declares it: the **admin UI**, then `nao_config.yaml`, then environment variables. Overrides saved in the UI persist across restarts and keep winning until you delete them. See [LLM providers and models](/nao-agent/context-builder/configuration#llm-providers-and-models).
**Azure OpenAI setup**
Use the **Azure OpenAI** provider when your Azure endpoint serves OpenAI-compatible models such as GPT.
1. Navigate to **Models** in the admin sidebar
2. Add or edit the **Azure OpenAI** provider
3. Enter your Azure API key
4. Configure either a **Resource Name** or a **Base URL**
Use **Resource Name** when your endpoint follows Azure OpenAI's standard format:
* Resource name: `my-resource`
* nao builds the URL as `https://my-resource.openai.azure.com/openai/v1`
Use **Base URL** when Azure gives you a full endpoint or you are going through a proxy:
* If Azure shows `https://my-resource.openai.azure.com/openai/v1/`, enter `https://my-resource.openai.azure.com/openai`
* Do not include the trailing `/v1` or a request path, because nao adds those automatically
Optional Azure OpenAI fields:
* **API Version**: Set this only if your Azure setup requires a specific version. Otherwise leave the default `v1`
* **Use Deployment-Based URLs**: Enable this only if your Azure deployment uses the legacy `/deployments/{deploymentId}` route style
For Azure OpenAI, the model ID in nao should match the deployment name you created in Azure.
You can also configure Azure OpenAI entirely via environment variables instead of the UI:
```bash theme={null}
AZURE_API_KEY=...
AZURE_RESOURCE_NAME=my-resource # or AZURE_OPENAI_BASE_URL=https://my-resource.openai.azure.com/openai
AZURE_API_VERSION=v1 # optional, defaults to v1
AZURE_USE_DEPLOYMENT_BASED_URLS=false # optional, set to true only for legacy /deployments/{id} endpoints
```
The Azure OpenAI provider ships with an empty default model list, so you must add the deployment names you want to expose before users can pick them.
**Azure-hosted Anthropic setup**
If you deploy Claude through **Azure AI Foundry**, use the **Anthropic** provider in nao, not **Azure OpenAI**.
1. Navigate to **Models** in the admin sidebar
2. Add or edit the **Anthropic** provider
3. Enter your Azure AI Foundry API key
4. Set the **Base URL** to your Anthropic endpoint prefix
Example:
* If Azure shows a target URI such as `https://my-resource.services.ai.azure.com/anthropic/v1/messages`
* Enter `https://my-resource.services.ai.azure.com/anthropic/v1` in nao
* Do not include the trailing `/messages`, because the Anthropic API client adds it automatically
For Azure-hosted Claude models, the model ID in nao should match your Azure deployment name, for example `claude-sonnet-4-5`.
**OpenAI-compatible proxy (LiteLLM)**
To send OpenAI (or Anthropic / OpenRouter) traffic through a proxy such as [LiteLLM](https://docs.litellm.ai/), set a custom endpoint on the provider:
1. Navigate to **Models** in the admin sidebar
2. Add or edit the **OpenAI** provider
3. Enter the proxy API key, or leave it empty to use the environment variable
4. Under **Enabled Models**, add the model IDs your proxy exposes
5. Expand **Advanced settings** and set **Custom Base URL** to your proxy endpoint, for example `http://0.0.0.0:4000`
Settings changed in the UI are stored in the database and keep overriding `nao_config.yaml` until you delete the provider from the **Models** page. Declaring the provider, its `base_url` and its models in `nao_config.yaml` keeps the setup in version control and reproducible across deployments. See [OpenAI-compatible proxy](/nao-agent/context-builder/configuration#openai-compatible-proxy-litellm).
**AWS Bedrock Setup (Step-by-Step)**
Use one of these two authentication paths:
1. **Bearer token**
* Set `AWS_BEARER_TOKEN_BEDROCK`
* Optional: set `AWS_REGION` (defaults to `us-east-1`)
* In **Models**, add provider **AWS Bedrock**, then configure model IDs
2. **IAM credentials**
* Set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
* Optional: set `AWS_SESSION_TOKEN` and `AWS_REGION` (defaults to `us-east-1`)
* In **Models**, add provider **AWS Bedrock**, then configure model IDs
For IAM credentials, you can leave the Bedrock API key field empty.
**Custom models**
When adding a model that isn't in a provider's default list (self-hosted endpoints, fine-tuned variants, preview models), click **Add custom model** to open the custom model dialog. You can set:
* **Display name** - a friendly label shown to users in the model picker (e.g. "GPT-4 Turbo (fine-tuned)")
* **Model ID** - the identifier your provider expects in API calls
* **Input token price** and **Output token price** - per-million-token costs used by the budget tracker
Custom pricing is optional. If left blank, nao falls back to its built-in price table, and a model it doesn't know won't appear in cost breakdowns.
The same models and prices can be declared in version control instead, under `llm.providers[].models` in `nao_config.yaml`. See [Model costs](/nao-agent/context-builder/configuration#model-costs).
**Select Available Models**
Control which models your users can access by providing the model ID from your LLM provider:
1. Navigate to **Models** in the admin sidebar
2. Add or edit a provider (Anthropic, Azure OpenAI, OpenAI, Google, Mistral, AWS Bedrock, or OpenRouter)
3. Enter the **model ID** for each model you want to make available
4. Users will only be able to select from the models you've configured
Model IDs are provider-specific. For example:
* Anthropic: `claude-sonnet-4-6`, `claude-opus-4-7`, `claude-haiku-4`
* Azure OpenAI: use your Azure deployment name
* OpenAI: `gpt-4`, `gpt-4-turbo`, `gpt-3.5-turbo`
* Google: `gemini-3.1-pro-preview`, `gemini-2.0-flash-exp`, `gemini-1.5-pro`
* Mistral: `mistral-large-latest`, `mistral-medium-latest`
* AWS Bedrock: `us.anthropic.claude-sonnet-4-6`, `eu.anthropic.claude-opus-4-6-v1`
* OpenRouter: `vendor/model` slugs from the [OpenRouter model list](https://openrouter.ai/models), for example `deepseek/deepseek-v4-flash` or `openai/gpt-4.1-mini`
**Google Vertex AI (Claude models)**
If you access Claude through Google Vertex AI, use the **Google** provider and set the model ID to a `claude-*` model. nao automatically enables prompt caching for Claude models served through Vertex, using the same cache control mechanism as the direct Anthropic provider. No extra configuration is needed.
**Supported LLM Providers**
nao supports the following LLM providers:
* **Anthropic** - Claude models (prompt caching enabled automatically)
* **Azure OpenAI** - Azure-hosted OpenAI-compatible models
* **OpenAI** - GPT models
* **Google** - Gemini models and Claude models via Vertex AI (prompt caching enabled for Claude)
* **Mistral** - Mistral AI models
* **AWS Bedrock** - Claude and other models through Amazon Bedrock (supports bearer token or IAM credentials)
* **OpenRouter** - Access to multiple models from various providers through a single API (see [OpenRouter](/nao-agent/context-builder/configuration#openrouter) to declare it in `nao_config.yaml`)
## Configure MCP Servers
Use MCP Servers to expose external tools (for example BI tools, internal APIs, or other systems) to the nao agent.
The actual server configuration lives in your nao-core project, in the `agent/mcps/mcp.json` file created by `nao init`. Once that file is present in the project linked to your deployment, you can manage it from the admin panel:
1. Navigate to **MCP Servers** in the admin sidebar.
2. See the list of MCP servers defined in your project.
3. Enable or disable individual MCP servers for this deployment.
4. Optionally restrict which tools from each server are exposed to users.
When a server is enabled, its tools become available to the agent as part of its toolset (see [Chat capabilities](/nao-agent/chat/capabilities/tools-mcps-skills#agent-tools)) and can be called automatically or via tool shortcuts in the chat UI.
## Configure nao MCP endpoint
Expose nao as an MCP server so external AI clients (Cursor, Codex, Claude Code, Claude Desktop, or any custom MCP client) can call the agent, run SQL, browse context, and manage stories.
For an overview of modes and tools, see [MCP Endpoint](/nao-agent/connectors/mcp).
**Step 1: Enable the endpoint**
1. Navigate to **Settings** -> **MCP Endpoint**.
2. Toggle **Enable MCP endpoint** on.
3. Enable the modes you want exposed: **Sub-agent mode**, **Context-layer mode**, or both. Story and chart tools are exposed automatically whenever either mode is on.
4. Copy the **Endpoint URL** and **Bearer token** from the **Connection guide** section.
**Step 2: Connect a client**
The connection guide on the settings page shows ready-to-paste snippets for each client. Replace `` and `` with the values from your settings page.
1. Open **Settings** -> **Tools & MCP**.
2. Click **New MCP Server** and paste the JSON below, or edit `.cursor/mcp.json` manually.
3. Authenticate in your browser when prompted.
```json theme={null}
{
"mcpServers": {
"nao": {
"type": "http",
"url": ""
}
}
}
```
1. Open **Settings** -> **MCP servers** -> **+ Add server** -> **Streamable HTTP**.
2. Paste your endpoint URL into the URL field, then save.
3. Authenticate in your browser when prompted.
1. Open `/.mcp.json` (project root).
2. Paste the config below.
```json theme={null}
{
"mcpServers": {
"nao": {
"type": "http",
"url": "",
"headers": {
"Authorization": "Bearer "
}
}
}
}
```
**Method 1 - via Settings UI**
1. Open **Settings** -> **Connectors** -> **Add custom connector**.
2. Set **Name** to anything, and **Remote MCP Server URL** to your endpoint URL.
3. Enable the connector and authenticate in your browser.
**Method 2 - via config file**
1. Open `claude_desktop_config.json` (typically in `~/Library/Application Support/Claude/`).
2. Add the server using the JSON below.
3. Restart Claude Desktop and authenticate when prompted.
```json theme={null}
{
"mcpServers": {
"nao": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
""
]
}
}
}
```
Use the config below in any MCP client that supports HTTP transport.
```json theme={null}
{
"mcpServers": {
"nao": {
"type": "http",
"url": "",
"headers": {
"Authorization": "Bearer "
}
}
}
}
```
Treat the Bearer token like a password. Do not commit it to source control and rotate it from the settings page if exposed.
## Configure Telegram
Use Telegram when you want users to message nao from the Telegram app.
1. Navigate to **Project** -> **Telegram**
2. Create a bot with `@BotFather`
3. Paste the **Bot Token** into nao
4. Save the form so nao registers the webhook automatically
5. Choose the model used for Telegram replies
Each user also gets a **Linking Code** on the Telegram page. They send that code to the bot once so nao can map their Telegram identity to the correct account.
For the full walkthrough, see [Telegram Bot](/nao-agent/connectors/telegram).
## Configure WhatsApp
Use WhatsApp when you want users to interact with nao from a WhatsApp number backed by a Meta app.
1. Navigate to **Project** -> **WhatsApp**
2. Paste the **Access Token**, **App Secret**, **Phone Number ID**, and **Verify Token**
3. Copy the **Webhook URL** from nao into your Meta webhook configuration
4. Save the form
5. Choose the model used for WhatsApp replies
Users link their identity by copying the **Linking Code** from the WhatsApp page and sending it to the bot.
For the full walkthrough, see [WhatsApp Bot](/nao-agent/connectors/whatsapp).
## Configure Mattermost
Use Mattermost when your team already chats on a self-hosted Mattermost server.
1. Navigate to **Project** -> **Mattermost**
2. Create a bot account in the Mattermost System Console and copy its **token**
3. Paste the **Server URL** and the **Bot Token** into nao
4. Save the form so nao opens the WebSocket connection
5. Choose the model used for Mattermost replies
Users are linked automatically when their Mattermost email matches their nao account. If the emails differ, they send the bot a direct message with `login `, using the code from the Mattermost page.
For the full walkthrough, see [Mattermost Bot](/nao-agent/connectors/mattermost).
## Browse and edit the context folder
**Settings** -> **File Explorer** browses the context folder and searches it by filename or contents. Once a context repository is connected, the same screen becomes an editor: change a file, commit it to a branch, and open a pull request without leaving nao. See [File Explorer](/nao-agent/chat/admin/file-explorer).
It respects `.naoignore`, excluded directories, and a 1 MB read limit per file. `.git` and `.env` are never accessible.
## Configure Template Prompts
Customize prompt suggestions for your users in the chat.
These prompts will be shown to your users in a new chat. These can be used to give your users examples of questions they can send to the chat.
1. Navigate to **Agent** in the admin sidebar
2. Look for **Template Prompts** section
3. Add example prompts that users can click to get started, such as:
* "What were our top 5 products by revenue last month?"
* "Show me user signups by country for the past quarter"
* "Compare this month's sales to last month"
* "What's the average order value by customer segment?"
These template prompts help users understand what types of questions they can ask and provide quick-start examples for common analytics queries.
## Context recommendations
nao can audit its own usage and recommend where your context needs work - which file to edit and what is missing. Review and configure them as an admin under **Settings -> Recommendations**:
* Recommendations are ordered by impact, each naming a target file and citing the originating chat and the model that produced it.
* **Acknowledge**, **snooze**, or **dismiss** each one as you work through them.
* Configure the **analysis model**, the **run frequency** (daily / weekly / monthly, or **Run now**), optional **custom audit instructions**, and the **GitHub repository** where fixes are opened as pull requests. Turn on **YOLO mode** to open those PRs automatically without review.
Enable the audit on your deployment by setting `BETA_CONTEXT_RECOMMENDATIONS_ENABLED=true`.
For the full workflow - what it scans, how it fits the evaluation feedback loop, and the GitHub pull-request flow - see [Recommendations](/nao-agent/context-engineering/recommendations).
**What's Next?**
Learn how to track usage, feedback, and costs
Deploy nao chat to production with step-by-step instructions
# Automations
Source: https://docs.getnao.io/nao-agent/chat/capabilities/automations
Schedule recurring agent tasks with email, Slack, and GitHub integrations
Automations let you run headless agent tasks on a cron schedule or from an external webhook. Each automation stores a natural-language prompt, one or more triggers, and optional integrations so the agent can deliver results to email, Slack, or GitHub without anyone opening the chat.
## Activity feed
The **Feed** page in the left sidebar shows a chronological log of all automation activity across your project. Each entry shows the automation name, run status, timestamp, and links to the resulting chat or story. Use the feed to monitor scheduled runs at a glance without opening individual automations.
## Creating an automation
1. Click **Automations** in the left sidebar.
2. Click **New automation**.
3. Enter a title and the prompt you want the agent to run. In the prompt field, `Enter` inserts a newline instead of submitting, so you can write multi-line prompts. Save with the save button or `Cmd`/`Ctrl` + `S`.
4. Pick a schedule and configure integrations (see below).
5. Save.
After creation, each automation gets its own detail page where you can edit settings, review previous runs, and trigger a manual run.
## Triggers
An automation can be started **on a schedule**, **via a webhook**, or both. Pick the trigger(s) in the automation form. An automation needs at least one trigger to be active.
## Schedule
The schedule trigger runs the automation on a cron cadence. The UI offers presets:
* Hourly
* Daily (9 am)
* Weekdays (9 am)
* Weekly (Monday 9 am)
* Monthly (1st at 9 am)
You can also enter a custom cron expression for any other cadence.
Use the toggle on the automation detail page to pause or resume the schedule at any time.
## Webhook trigger
The webhook trigger lets an external system start an automation with an HTTP request instead of (or in addition to) a schedule. Enable **Via webhook** in the trigger picker; the detail page then shows the endpoint URL and a copyable example `curl`.
Send a `POST` to the automation's run endpoint, authenticated with an organization API key (the same `nao_...` bearer keys used by `nao deploy`). Create or manage keys under **Settings -> Organization**:
```bash theme={null}
curl -X POST https:///api/automations//run \
-H "Authorization: Bearer nao_..."
```
On success the endpoint starts a run and responds `202` with `{ runId, automationId, status }`. It returns `401` for a missing or invalid key, `404` for an unknown automation or a key from another organization, `403` if the webhook trigger is disabled, and `409` if the automation is paused.
Pausing gates every trigger: a paused automation rejects its webhook too. Webhook-only automations (no schedule) stay active until you disable the webhook.
## Run now
Click **Run now** on the automation detail page to trigger an immediate run outside any schedule or webhook. The run follows the same flow as a scheduled execution.
## Integrations
Each automation can optionally deliver results through one or more integrations. Configure these in the right sidebar of the automation form.
### Email
When enabled, the agent sends its output by email after the run completes.
* The automation owner's email is included by default.
* Add extra recipients as a comma-separated list.
* Optionally override the email subject line.
### Slack
When enabled, the agent posts a message to a Slack channel with its results.
* Enter the Slack channel ID (e.g. `C0123456789`).
* Requires a Slack integration configured in your project settings.
* The agent resolves `@handle` mentions to Slack user IDs before posting.
To keep the channel uncluttered, the agent posts a short headline to start a thread, then delivers the full report inside that thread. Charts and stories are uploaded into the thread automatically.
### GitHub
When enabled, the agent gets a set of dedicated GitHub tools:
* **github\_read** - fetch repository data (issues, PRs, files, branches) for context
* **github\_create\_issue** - open new issues
* **github\_create\_pull\_request** - open new pull requests
* **github\_add\_comment** - comment on issues or PRs
Requirements:
* A GitHub OAuth connection on the automation owner's account.
* Optionally filter to specific repos (comma-separated) to scope which repositories the agent can access.
### MCP servers
When enabled, the agent can call tools from your connected MCP servers during the run. Toggle individual servers on or off per automation.
## Model selection
By default, automations use your project's default model. You can override this per automation from the model selector in the form sidebar.
## Previous runs
The automation detail page shows a list of previous runs with:
* Timestamp and duration
* Status badge (running, completed, or failed)
* Integration result icons showing which deliveries succeeded or failed
* A link to open the full agent chat for any run
A run is marked failed if the agent did not call an expected integration tool, or if the agent loop itself errored.
# Files and Storage
Source: https://docs.getnao.io/nao-agent/chat/capabilities/files-and-storage
Attach documents to a chat, query them with SQL, and save results to permanent storage
nao gives every user a durable folder the agent can read from and write to. It shows up to the agent as `/home`, part of the same file tree as the project context, and it is private to that user in that project.
Two things use it:
* **Attachments** - files you add to a message land in `/home/uploads`.
* **Saved results** - exports and intermediate results the agent writes, so they outlive the chat.
Permanent storage is configured once for the whole deployment with `NAO_STORAGE_BACKEND` (see [Configuring storage](#configuring-storage)). It defaults to `local`. When it is set to `none`, the agent has no write tool and file attachments are off - only [images](/nao-agent/chat/capabilities/overview#image-input) still work.
## Attaching files
Add files the same way you add images: **Attach file** in the `+` menu of the chat input, drag and drop, or paste.
| | |
| --------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **Documents** | `csv`, `tsv`, `json`, `jsonl`, `parquet`, `xls`, `xlsx`, `pdf`, `docx`, `md`, `txt`, `html`, `xml`, `sql`, `yaml`, `yml` |
| **Images** | `png`, `jpeg`, `gif`, `webp` |
| **Per message** | Up to 5 attachments, images and documents combined |
| **Size** | 5 MB per image; documents are capped by `NAO_STORAGE_MAX_FILE_SIZE_MB` (10 MB by default) |
Documents and images reach the model very differently. An image is inlined into the request so the model can look at it. A document is only ever handed over as a **path** in `/home/uploads` - never its contents - so a large spreadsheet costs nothing until the agent actually opens it.
## What the agent does with a file
* **`read`** extracts the text of a PDF page by page. On an `.xlsx` it returns the workbook outline instead of the cells - every sheet in tab order with its row and column counts - so the agent knows which sheet it wants before querying. Parquet and Word documents are not text, so `read` refuses them.
* **`search`** finds files by name, and **`grep`** looks inside them (on S3 backends, where files are not on a local disk, `grep` is unavailable and the agent falls back to `search` plus `read`).
* **`execute_sql`** queries tabular files in place - see below.
* **`execute_sandboxed_code`** mounts a `/home` path into the micro-VM through its `storage_files` argument, for anything the other tools cannot parse.
## Querying files with SQL
nao ships its own DuckDB engine alongside your warehouses, addressed as the database id **`duckdb_local`**. It is always available, and it is the only way to query a file or to join one against an earlier query result.
It can:
* read CSV, JSON, Parquet, and Excel files by their `/home` path,
* expose every earlier `execute_sql` result as a table named after its query id,
* join the two together - for example a spreadsheet someone uploaded against last quarter's revenue from the warehouse.
Just ask in plain English: *"join this CSV against our customers table and show me who is missing."*
`duckdb_local` is a reserved database id. A warehouse configured under that name in `nao_config.yaml` would be unreachable.
## Saving results
The agent writes to `/home` when you ask it to keep, export, or update something.
* **`save_to` on `execute_sql`** writes a `duckdb_local` result straight to a file, as `csv` (for a file someone opens) or `parquet` (which keeps column types, so the result can be queried again later). The rows still come back in the chat as usual.
* **The `write` tool** saves anything else. `/home` is the only writable place in the tree; everything else is read-only.
When the agent mentions a stored file in its answer, clicking it opens the side panel viewer: spreadsheets render sheet by sheet, PDFs and markdown render inline, and text files are shown as-is. The **Download** button in the panel header saves the file to your machine.
## Your own storage
Open **Settings** -> **Storage** to see how much space your files take in the current project. You get your own space in every project you belong to, and nobody else can read it.
## Configuring storage
Storage is set through environment variables, not the UI. Admins can review the resolved configuration, a reachability check, and per-project usage under **Settings** -> **Storage**.
```bash theme={null}
NAO_STORAGE_BACKEND=local
NAO_STORAGE_LOCAL_PATH=./storage # optional, defaults to ./storage
```
The default. Point it at a directory on disk.
The path must be **durable**. In a container, mount a volume (or a PVC) at it - otherwise every restart wipes what users saved. Running more than one replica, every replica must mount the **same** read-write-many volume (NFS, EFS, Filestore, RWX PVC), or each replica sees a different set of files. Above one replica, prefer S3.
```bash theme={null}
NAO_STORAGE_BACKEND=s3
NAO_STORAGE_S3_BUCKET=my-nao-storage
NAO_STORAGE_S3_REGION=eu-west-1
NAO_STORAGE_S3_PREFIX=nao # optional, share a bucket with other workloads
NAO_STORAGE_S3_ENDPOINT=https://minio.internal:9000 # optional, for S3-compatible providers
NAO_STORAGE_S3_FORCE_PATH_STYLE=true # needed by MinIO
NAO_STORAGE_S3_ACCESS_KEY_ID= # optional
NAO_STORAGE_S3_SECRET_ACCESS_KEY= # optional
```
Works with AWS S3 and S3-compatible providers (MinIO, Cloudflare R2, ...). Recommended for any deployment above one replica.
Credentials are optional: when omitted, the default AWS credential chain is used, so IAM roles, ECS task roles, and EKS/IRSA work with no configuration.
```bash theme={null}
NAO_STORAGE_BACKEND=none
```
The agent has no write tool and cannot reach stored files, and file attachments are disabled. Nothing already saved is deleted.
`NAO_STORAGE_MAX_FILE_SIZE_MB` (default `10`) caps the largest single file that can be written, on every backend. A bigger file is rejected before anything is stored.
## Next steps
The full list of tools the agent can call
Mount a durable volume when self-hosting
# Memory
Source: https://docs.getnao.io/nao-agent/chat/capabilities/memory
How user memory works in nao chat and how to control it
Memory helps nao personalize answers across conversations.
## What Memory Stores
Memories are user-level, not project-level. They are not shared between users.
When memory is enabled, the agent stores only instructions and preferences you explicitly provide, such as:
* Preferred metrics and dashboards.
* Definitions you repeatedly use (for example, how you define an active customer).
* Repeated user preferences mentioned across chats.
## What Memory Does Not Store
The agent does not store:
* Query results.
* Generated answer content.
* Internal reasoning traces.
It stores only your expressed instructions and preferences.
## User Controls
You can manage memory directly from the chat interface:
* Open the **Memory** panel.
* Review stored entries.
* Edit or delete entries.
* Disable memory for your account.
Project admins can also enable or disable memory availability for a deployment.
# Overview
Source: https://docs.getnao.io/nao-agent/chat/capabilities/overview
Key features, chat workflow, speak to prompt, and context management
The nao chat combines context-aware analytics with transparent execution.
## Key Features
Ask questions in plain English and get actionable answers.
Automatically generate charts and visual explanations from query results.
Inspect the SQL and logic behind each answer.
Continue previous analyses with full chat history.
Dictate a question with your microphone and send the transcribed prompt.
Preserve user-level preferences across conversations.
## How It Works
When you ask a question, the agent follows this flow:
The agent searches and reads your context files to understand your data model, definitions, and rules.
It composes SQL based on the available context and your question.
The query runs against connected data sources and returns results.
If useful, the agent renders charts from the result set.
The agent summarizes findings in plain language with supporting evidence.
## Speak To Prompt
From the chat input:
1. Click the microphone icon.
2. Grant microphone access when prompted.
3. Speak your request.
4. Review or edit the transcription, then send it.
Only transcribed text is stored in chat history.
Speak to Prompt requires an admin-configured OpenAI API key, and is currently supported with OpenAI models.
## Context Management
The agent grounds answers in project context, including:
* Context files and business definitions synced into your project.
* Rules and instructions that shape generation behavior.
* Data source metadata and available schema information.
For details on configuring context, see [Context Builder](/nao-agent/context-builder/overview).
## Visualizations and Image Input
The chat UI can render agent query results as charts (bar, line, area, pie, donut, KPI card, scatter, radar, and stacked variants) or as tables with conditional formatting. Both can be edited after the fact, and every chart exposes the SQL query and rows behind it.
For the full list of chart types, the chart edit dialog, table formatting rules, and the project date format setting, see [Visualizations](/nao-agent/chat/capabilities/visualizations).
### Image input
You can attach images directly in the chat input when you want the agent to inspect a screenshot, dashboard, chart, or UI state. The agent treats images as part of the conversation - it can describe them, transcribe text out of them, compare them to data, or use them to disambiguate a question ("the spike in *this* chart").
**How to attach an image**
There are three ways to add images to a message:
* Click the `+` menu in the chat input and pick **Attach file**.
* Drag an image file from your desktop into the chat input - the input shows a highlighted drop zone while you drag.
* Paste an image directly from your clipboard (e.g. after `cmd+shift+4` on macOS or a snipping tool on Windows). The paste handler is scoped to the chat input so pasting elsewhere on the page still works normally.
Selected images appear as thumbnails above the input. Click the small `X` on any thumbnail to remove it before sending.
**Supported formats and limits**
* Formats: `image/png`, `image/jpeg`, `image/gif`, `image/webp`.
* Up to **5 attachments** per message, images and documents combined.
* Up to **5 MB** per image.
Files outside these formats or above the size cap are rejected client-side before upload.
### Document input
The same input takes documents - PDFs, spreadsheets, CSVs, Parquet files, and more. Unlike images, a document is handed to the agent as a path in permanent storage rather than inlined into the request, so a large file costs nothing until the agent opens it. nao can also query tabular files directly with SQL and join them against your warehouse.
See [Files and Storage](/nao-agent/chat/capabilities/files-and-storage) for the full list of formats, what the agent can do with each, and how an admin configures storage.
**Viewing images in the conversation**
Images you've sent stay attached to the message and re-display when the chat is reloaded or shared. Click any image in the conversation to open it in a full-size lightbox - close it with `Esc`, the X button, or by clicking outside.
Images are stored against your project and served from a stable URL, so they keep rendering inside the chat history and any story or shared link the conversation feeds into.
## Editing, resending, and message versions
Hover over any of your sent messages to reveal a toolbar with actions and a relative timestamp (e.g. `8m ago`, with the full date and time in a tooltip).
* **Resend** - the resend button (circular-arrow icon) re-runs the same prompt as-is, superseding the conversation from that turn onward.
* **Edit** - change the prompt text and resend it to branch the conversation from that point.
When a message turn has been edited or resent, a `‹ i/n ›` control appears next to the action icons. Use it to navigate the history of superseded versions and restore any of them as the active branch - the message and the answer below it update together.
## Sharing and forking chats
Every chat can be shared with your project or with specific people from the share icon in the chat header. Recipients can:
* **Continue chat** - open a new forked chat that contains the full shared history. Each fork is independent, so the original is never modified.
* **Ask about a selection** - highlight any text in the chat and click the **Ask** button that appears above the selection. The quoted text becomes a pending citation in the input, and once sent, the answer keeps a **Go to original selection** link back to the source passage.
Selection-based asks also work on your own chats and stories, not just shared ones, and in the admin **Chats replay** view. For the story-specific flow (**Discuss story** and version handling), see [Stories](/nao-agent/chat/capabilities/stories#sharing-forking-and-text-selection).
## Managing Your Chat History
Your chat list lives in the left sidebar, sorted by most recent activity (last updated first). The list shows the 10 most recent chats by default; click **Show more** to expand the full list and **Show less** to collapse it again.
**Group and filter**
Use the filter menu at the top of the chat list to organize conversations:
* **Group by** stars, date, project, ownership, or none.
* **Filter** the visible chats with the search box to narrow the list to matching titles.
When a chat starts from a text selection or **Ask about a selection** action, nao automatically regenerates its title using the quoted context, so the sidebar entry reflects the question rather than a generic placeholder.
**Pruning the list**
To keep the sidebar tidy, nao gives you two ways to remove conversations:
* **Star** the chats you want to keep so they're always preserved.
* Open **Settings -> General -> Danger Zone** and click **Delete all non-starred chats** to bulk-clear everything you haven't starred.
The action is destructive but recoverable internally: chats are soft-deleted (hidden from your sidebar, search, and history), not erased. Starred chats are never affected.
## Tool call density
Control how much detail the chat shows for the agent's tool calls (SQL queries, sandbox runs, and similar steps). Set it from **Settings -> Account**:
* **Detailed** - tool calls are expanded inline so you can read the SQL and intermediate steps as the agent works.
* **Compact** - tool calls are collapsed into a compact group, keeping the conversation focused on the answer. Expand any step when you want the detail.
The preference is stored against your account, so it follows you across devices and sessions.
## Query Guardrails
For BigQuery connections, nao can enforce a maximum query size in GB before the agent runs SQL.
When configured, nao performs a dry run first:
* Queries under the limit execute normally
* Queries over the limit are blocked before BigQuery scans the data
# Stories
Source: https://docs.getnao.io/nao-agent/chat/capabilities/stories
Create, edit, and share long-form analytics reports directly from your chats
Stories turn a chat analysis into a persistent, shareable document with live charts, tables, and narrative text. You can revisit it, refine it, and distribute it to your team.
## Creating a Story
Stories are created directly from a chat conversation. Simply ask the agent to write one:
> *"Write a story about our new user growth since the last release."*
> *"Create a story summarizing MRR trends over the past quarter."*
The agent will:
1. Run the relevant queries against your data.
2. Compose a structured document with narrative text, charts, and tables embedded inline.
3. Open the story in a side panel next to the chat thread.
## Editing a Story
Once a story is open in the side panel, click the **pencil icon** to switch to edit mode.
The editor is **Notion-style**: every block (text, chart, table) can be dragged and reordered using the grip handle on the left. You can edit text in rich markdown, rearrange sections, and remove blocks you don't need.
When you're done, click **Save**: this creates a new version of the story.
To discard your changes, click **Cancel**.
### Renaming a story
The story owner can rename a story from its title in the header, without opening the editor. Click the title, type a new one, and the change is saved in place. The new title propagates everywhere the story is referenced - other story pages and the chat cards that link to it - and the story's link stays the same, so shared URLs keep working.
### Arranging charts into a grid
Charts and tables don't have to stack one per row. In edit mode, each chart and table has a **drag handle** next to its edit pencil:
* **Move** a block by dragging its handle anywhere in the story.
* **Put blocks side by side** by dropping one beside another - they form a row. Drop a third into an existing row to add it.
* **Resize** by dragging the divider between two charts in a row. Widths snap to clean fractions (halves, thirds, quarters), and charts in a row line up at the same height with aligned baselines. Labels and legends adapt as columns get narrower.
The agent can also build multi-chart grids with uneven widths directly when it composes a story.
### Selecting multiple blocks
To move several blocks at once, select them first:
* **Cmd/Ctrl-click** a block to toggle it in the selection.
* **Shift-click** to select a range.
* **Esc**, or a plain click, clears the selection.
Selected blocks are highlighted. Drag the handle on any selected block to move the whole selection together, keeping their order. This works with non-contiguous selections and with charts, tables, and whole grids.
Press **Backspace** to delete exactly what you have selected - the highlighted blocks and grid columns, and nothing else. Removing one chart from a row leaves the rest of the row intact rather than wiping the whole row.
## Tabs
A story can be split into tabs so one report can hold several views without becoming a long scroll. Tabs are written with `` markup:
```html theme={null}
...charts, tables, and text...
...
```
In edit mode, use the pencil button to **add, rename, reorder, and delete** tabs. Editing is tab-aware: you edit one tab at a time, and saving writes all tabs as a single new version. Stories without tabs are unchanged.
You can ask the agent for tabs in plain English (*"split this into an overview tab and a per-region tab"*), and the [Add to story](#adding-a-chart-from-the-thread) action drops a chart into the tab you're currently viewing.
When a tabbed story is [exported](#exporting-stories) to PDF or HTML, the tabs are flattened into sequential titled sections so nothing is hidden in the static file.
## Filters
Interactive filters are a **beta** feature and are **off by default**. Enable them on your deployment by setting `BETA_STORY_FILTERS_ENABLED=true`. Until then, the filter controls and the agent's filter tooling are hidden.
Filters let a reader change what a story shows without editing it - pick a country, a date range, or a search term from a **filter bar** at the top of the story, and the charts and tables re-run their queries against the new selection. It turns one story into a self-serve view over your data.
You usually don't write the markup by hand: ask the agent (*"add a country filter and a date-range filter to this story"*) and it declares the filters and rewires the queries for you. The mechanics below are useful for understanding and reviewing what it produces.
### Declaring a filter
A filter is declared with a `` tag. Four types are available:
| Type | Control | Notes |
| -------------- | ---------------------- | ----------------------------------------- |
| `select` | Single-choice dropdown | Needs an options source |
| `multi_select` | Multi-choice dropdown | Needs an options source |
| `search` | Free-text input | Matches a value you template into the SQL |
| `date_range` | Date range picker | Expands to a start and end bound |
For `select` and `multi_select`, give the options either from your data or as a fixed list:
```html theme={null}
```
When options come from `table`+`column` and your project has more than one database, add `database_id` so nao loads the options from the right connection.
### Wiring a filter into SQL
A chart or table query opts into a filter with a `{% filter %} ... {% endfilter %}` block, and drops the selected value in with `{{ filters..sql }}`:
```sql theme={null}
SELECT country, sum(amount) AS revenue
FROM orders
WHERE 1 = 1
{% filter country %} AND country IN ({{ filters.country.sql }}) {% endfilter %}
{% filter period %} AND order_date BETWEEN {{ filters.period.sql }} {% endfilter %}
GROUP BY country
```
* `{{ filters..sql }}` expands to the selected value(s), safely quoted.
* For `date_range`, the single `{{ filters..sql }}` already expands to `'start' AND 'end'`, so pair it with `BETWEEN` - don't reach for `.start`, `.end`, or `.value`.
* When a filter has no selection, its whole `{% filter %}` block is stripped before the query runs, so the query still works with nothing selected. Changing a selection re-renders and re-executes the affected charts and tables, and the same applies on [live-story](#live-stories) refreshes.
If the agent writes an invalid filter template, it comes back as a template warning to fix before the story is saved.
## Versions
Every time you save a story, nao creates a new version. Use the **version navigator** in the story header (`v1 / v2 / ...`) to browse previous versions.
* **← / →** arrows step through versions.
* When viewing an older version, a **Restore** button lets you roll back to that version (creating a new version from it).
Recipients who receive a shared link always see the latest version automatically.
## Editing Charts
After a chart is generated, you can tweak its configuration without re-running the query.
**In a chat thread:** click the **pencil icon** next to the chart's download button. This opens an edit dialog where you can change:
* Chart title
* Chart type (bar, line, area, pie, etc.)
* X-axis key and type
* Per-series settings: data key, label, and color
Click **Save** to persist the changes. The updated chart config is written back to the database, and any cached chart image (used for Slack/Teams/WhatsApp previews) is regenerated on next request.
**In a story:** hover over any chart block and click the **pencil icon** in the top-right corner. The same edit dialog opens. Saving creates a new story version with the updated chart tag, following the same versioning flow as text edits.
Chart editing is only available to the chat or story owner. The edit button is hidden while the agent is running, on archived stories, in shared/read-only views, and when viewing older story versions.
## Editing the Story Code
The story side panel has a **Code** view that shows the raw markdown behind the story. This view is now editable: you can modify text, chart tags, table tags, and grid blocks directly.
The editor validates your changes in real-time:
* Missing required chart attributes (`query_id`, `chart_type`, `x_axis_key`)
* Invalid chart type or axis type values
* Malformed or empty `series` definitions
* Missing `query_id` on table blocks
* Unterminated or unclosed tags
* Out-of-range `cols` on grid blocks
Validation errors appear as red markers in the editor and a banner above it. The **Save** button is disabled until all errors are resolved. Press **Cmd+S** (or **Ctrl+S**) as a shortcut to save.
Saving creates a new story version, just like editing in the visual editor. The code view is read-only on shared stories.
## Adding a Chart from the Thread
When the agent produces a chart in the chat thread, you can embed it directly into a story:
1. Open the story side panel.
2. Ask the agent to add the chart: *"Add this chart to the story."*
The chart is embedded as a live block: it renders with the same query data and can be repositioned in the editor like any other block.
## Sharing a Story
Click the **share icon** in the story header to open sharing settings.
You can share with:
All members of your nao project can view the story via the shared link.
Search by name or email and select individual team members.
Clicking **Share & copy link** copies the URL to your clipboard. Recipients open the story in their browser: they always see the **latest version**.
**Notify people**
The share dialog has a **Notify people** toggle that sends recipients an email letting them know about the story. It is **off by default**, so sharing is silent unless you turn it on. The same toggle is available when sharing a chat.
The toggle only appears when [SMTP is configured](/nao-agent/self-hosting/deployment-guide#smtp-for-transactional-email) on your deployment. Without SMTP, no notification email is sent.
**Managing an existing share**
Once a story is shared, the share icon turns green. Click it again to:
* **Copy link** - grab the URL again.
* **Update access** - add or remove specific people (for specific-person shares).
* **Unshare** - revoke access and disable the link entirely.
## Sharing, forking, and text selection
Once a chat or story is shared, recipients can branch from it or ask targeted follow-ups without breaking the original. The same selection-based "Ask" flow also works on your own (non-shared) chats and stories.
**Continue chat**
On a shared chat, **Continue chat** opens a new forked chat that contains the full shared history. Each click creates a fresh fork, so the original conversation is never modified. The fork copies the messages only: stories that belong to the origin chat are not duplicated into your Stories library.
**Discuss story**
On a shared story, **Discuss story** opens a new chat with the story embedded at the top in editable form, ready to be questioned or refined.
**Ask about a selection**
Select any text inside a chat or a story and an **Ask** button appears above the selection. Clicking it opens an inline frame where you can quote the selection and ask a follow-up about that specific passage:
* The quoted text shows up as a **pending citation** in the input until you send the question, so you can see exactly what the agent will see.
* Once sent, the answer keeps a **Go to original selection** link that jumps back to the exact passage in the source chat or story.
* The frame leaves a small persistent indicator when collapsed so you can reopen it later.
Selection-based asks work in four places:
* Your own chats
* Your own stories
* Shared chats and stories you have received
* The admin **Chats replay** view (asking about a past chat for debugging or follow-up)
All "Continue chat", "Discuss story", and "Ask" actions create new conversations under the current user's account, so the original content stays untouched.
## Exporting stories
Stories can be exported to PDF or HTML from the download button in the story header. Export works on your own stories and on shared stories, and you can pick any existing version from the version navigator before exporting.
**PDF**
* A4 page size, formatted for printing.
* Charts are rendered as vector SVG so they stay sharp at any zoom level.
* Tables are included up to 10 rows per table to keep the document readable; longer tables are truncated in the PDF (the full dataset is still available in the HTML export and in the live story).
**HTML**
* Self-contained single file: no external assets, works offline.
* Interactive chart tooltips are preserved, so recipients can hover data points in their browser.
* Styled for print, so **File -> Print** in the browser produces a clean printed copy without separate theming.
Use PDF when you need a fixed-layout report to attach to an email or a doc. Use HTML when you want the story to stay interactive and portable.
## Live Stories
By default, a story is a snapshot: charts and tables freeze the data that was returned when the story was created. **Live stories** keep the story connected to the underlying queries so the numbers refresh on a schedule (or on demand) without anyone having to rebuild the report.
### Enabling live mode
1. Open the story in the side panel.
2. Click the **settings** icon in the story header.
3. Toggle **Live mode** on.
4. Pick a refresh strategy (see below).
5. Save.
A green **Live** badge appears in the story header once it's enabled, along with a "last refreshed" timestamp. Anyone who opens the shared link sees the most recently refreshed version.
### Refresh strategies
When live mode is on, choose how freshness should work:
* **Manual refresh only** - the story keeps its last refreshed values until someone clicks the refresh button in the header.
* **No cache (always fresh)** - queries re-run every time the story is opened. Best for low-volume stories where you always want live numbers; avoid on expensive queries.
* **Scheduled refresh presets** - every 5 minutes, hourly, daily, weekly, or monthly.
* **Custom schedule** - type a schedule in plain English (e.g. *"every weekday at 8am"*, *"first Monday of the month"*) and nao converts it to cron, or paste a raw cron expression directly.
Each refresh re-runs every query block in the story against your connected warehouse, then updates the cached results that recipients see.
### Refreshing the narrative
For live stories, you can also enable **Regenerate the narrative** in the same settings panel.
When turned on, nao rewrites the text blocks with the updated numbers while keeping the layout intact:
* Section titles stay in place.
* Charts and tables stay embedded in the same positions.
* Only the prose around them is rewritten so the commentary matches the new data.
This is what turns a live story into a real recurring report - the agent doesn't just refresh the numbers, it explains them.
### Access and refresh on shared stories
Live refresh respects the same access rules as the share itself:
* For project-wide shares, any project member can trigger a manual refresh.
* For specific-people shares, only the people on the access list can refresh.
* Scheduled refreshes always run with the story owner's permissions, so the data the recipients see is the data the owner is allowed to query.
If a query underlying a live story starts failing (schema change, permissions, etc.), the story keeps showing the last successful refresh and surfaces an error indicator in the header so you can investigate.
## Stories Library
The Stories Library is your central hub for all stories. Access it by clicking **Stories** in the left sidebar.
The library is a folder explorer: it lists the folders in the current location, then the stories inside it. Stories you created and stories shared with you appear together, each card showing its author and last update.
From the toolbar you can switch between a **grid view** (thumbnail cards) and a **list view**, search by title, and jump to archived items with **See archives** (**Back to stories** returns to the main view).
Sort pills above the list order the current folder by **Name**, **Owner**, or **Updated**. Click the active pill again to reverse the direction. Your view and sort choices are remembered.
### Folders
Organize stories into folders to keep the library tidy:
* Create one with **New folder** in the explorer.
* Each folder card has a menu with **Modify** (rename), **Move to...**, **Archive**, and **Delete**. Archived folders offer **Restore folder**.
* Folders can be nested. Navigate down with a click and back up with the **breadcrumb** at the top of the explorer.
* **Drag and drop** stories and folders onto another folder, or onto a breadcrumb level, to move them.
### Pinned and Favorites
Two sections at the top of the library promote the items you reach for most. Each one is collapsible and only appears when it has content:
* **Favorites** - favorite any story or folder for yourself. Favorites are per-user, so only you see yours.
* **Pinned** - pinned stories surface at the top for everyone with access. Pinning is admin-only and applies to shared stories.
### Bulk actions
Click **Select** in the toolbar to enter selection mode, then tick multiple stories and folders. A bar appears at the bottom with the number selected and the action available in the current view: **Archive** in the main library, **Restore** when browsing archives. Up to 100 stories and 100 folders can be handled in one action.
Leave selection mode with **Cancel**.
### Who can change what
Renaming, moving, archiving, and deleting a story or a folder is restricted to its owner and to project admins. Other members can open, favorite, and search items shared with them, but not reorganize them.
### Usage analytics
Open **Analytics** from the story header menu to see who viewed, downloaded, forked, or favorited a story, and when it was last refreshed. See [Asset analytics](/nao-agent/chat/admin/monitoring#asset-analytics) for the full breakdown.
# Tools, MCPs, Skills
Source: https://docs.getnao.io/nao-agent/chat/capabilities/tools-mcps-skills
Built-in tools and extensibility model for the nao chat agent
## Agent Tools
The nao agent uses built-in tools autonomously to answer user requests.
Execute SQL against connected databases and return structured results.
* Supports multiple database connections.
* Returns typed columns and row counts.
* Outputs can be reused by other tools.
* Pass `duckdb_local` as the database to use nao's own DuckDB engine instead of a warehouse: it queries CSV, JSON, Parquet, and Excel files by path, and joins them against earlier query results. See [Files and Storage](/nao-agent/chat/capabilities/files-and-storage#querying-files-with-sql).
* `save_to` writes the result to a CSV or Parquet file in permanent storage, on top of returning the rows.
Create charts from SQL results.
**Supported chart types:**
* Bar charts
* Stacked bar charts
* Line charts
* Pie charts
* KPI cards
Charts are rendered in chat and can also be sent in Slack conversations.
Execute code in an isolated sandbox (micro-VM) for advanced analysis.
* Supports Python and shell execution.
* Can install Python packages for a run.
* Can reuse prior SQL outputs as CSV inputs.
* Images uploaded to the chat are mounted into the sandbox, so the agent can read or manipulate them directly from Python (e.g. OCR, cropping, chart comparison).
* Files in permanent storage are mounted the same way through `storage_files`, for formats the other tools cannot parse.
* Requires enabling **Sandboxes** in Admin -> Agent -> Experimental.
Save a file to permanent storage, under the user's `/home` folder.
* `/home` is the only writable place in the file tree; project context is read-only.
* Used for exports, spreadsheets the agent builds, and intermediate results worth reusing.
* Unavailable when the deployment sets `NAO_STORAGE_BACKEND=none`. See [Files and Storage](/nao-agent/chat/capabilities/files-and-storage).
Search files with glob patterns in your context.
List files and directories so the agent can navigate project structure.
Read context files such as SQL models, docs, and rule files.
Search text patterns across context files with regex.
Search the public web with your model provider tools and fetch cited pages when needed.
* Uses provider-native capabilities (OpenAI, Anthropic, Google) when enabled.
* Lets the agent answer questions that need fresh external information.
* Sources are shown in tool call output for traceability.
Ask the user a focused question when their request is genuinely ambiguous (multiple plausible tables, unclear metric, missing time range, etc.).
* Renders a "Quick question" card with the question and up to 5 clickable answer chips.
* Clicking a chip sends the answer directly - no extra Enter press required.
* Free-form answers via the normal chat input are also supported.
* The agent pauses and waits for the user's reply before continuing.
* Multi-turn clarification works naturally: previous cards switch to an "Answered" state with a checkmark on the selected chip so the full decision trail stays visible.
You do not need to select tools manually. The agent chooses and orchestrates tools based on each question.
Web search is optional and can be enabled per project from Settings -> Project -> Agent -> Web search.
## MCPs
MCP (Model Context Protocol) servers expose external tools that the agent can call next to built-in tools.
Configure MCP servers in `agent/mcps/mcp.json`:
```json theme={null}
{
"mcpServers": {
"metabase": {
"command": "npx",
"args": ["-y", "@getnao/metabase-mcp-server@latest"],
"env": {
"METABASE_URL": "http://localhost:3008/",
"METABASE_API_KEY": "your-metabase-api-key"
},
"disabled": false,
"autoApprove": [],
"disabledTools": []
}
}
}
```
Do not commit secrets. Store credentials in runtime environment variables or a secrets manager.
### Remote HTTP servers
Alongside local `command` servers, you can connect to remote MCP servers over HTTP. Set `transport` to `streamable-http` and point `url` at the server endpoint:
```json theme={null}
{
"mcpServers": {
"notion": {
"transport": "streamable-http",
"url": "https://mcp.notion.com/mcp"
},
"linear": {
"transport": "streamable-http",
"url": "https://mcp.linear.app/mcp"
}
}
}
```
Accepted transport values are `streamable-http`, `sse`, and `http`. Servers declared with a `command` run over stdio.
### How nao loads MCP tools
nao does not hold a live client connection to each server, and it never loads every tool definition into the context window. Instead:
1. nao connects to the server once and reads its tool list.
2. It writes one OpenAPI JSON file per enabled tool into the context filesystem, at `agent/mcps//.json`. The file name is the tool name.
3. The agent discovers what it needs on demand with the normal `list`, `read`, and `grep` tools, then invokes it through a single `mcp_call` tool.
`mcp_call` takes the `server` name, the `tool` to run (the operation's `operationId`), and an `arguments` object matching that operation's request body schema. Arguments are validated against the schema before the call runs, so a malformed call comes back as a validation error listing the specific issues rather than failing at the server.
The generated spec directories are gitignored - they are discovered at runtime and do not need to be committed.
### Inline authentication
When a remote server requires OAuth, nao prompts the user to sign in inline the first time its tools are needed, so each user authenticates with their own account instead of sharing one organization-wide login. The connection is authorized per user and reused on later runs.
If the agent calls a tool on a server the user has not connected yet, the call returns an auth-required result and a **Connect** button is shown below the conversation. The agent stops and waits instead of retrying.
### Managing servers and tools
Admins manage MCP servers from **Settings** -> **MCP Servers**. The table lists each server declared in `agent/mcps/mcp.json` with its transport, connection status, the number of enabled tools out of the total, and a toggle to enable or disable the whole server.
**Connect all MCP servers** re-runs discovery across every server, and each row has its own refresh action to reconnect a single server. Connection status shows as Connected, Cached (discovered previously, not re-tested), Waiting for connection (OAuth pending), Error, or Not tested.
Expanding a row shows the path where that server's specs were written, plus its tools grouped by category - **Read-only**, **Write**, **Delete**, or **Unknown** - inferred from the tool name. Each category has a toggle to enable or disable the whole group at once, and each tool has its own toggle. Disabled tools are not written to the context filesystem, so the agent cannot discover or call them.
See [Admin Setup](/nao-agent/chat/admin/setup#configure-mcp-servers) for the full configuration walkthrough.
In chat, MCP tool calls and their outputs are rendered with a dedicated block: each call shows the server name, the tool used, and the returned payload formatted for readability (tables, JSON, and text are laid out distinctly rather than dumped as raw strings).
## Skills
Skills are reusable workflows defined as markdown files in `agent/skills/`.
A file is recognized as a skill only if:
1. It is stored in `agent/skills/`.
2. It starts with YAML frontmatter including `name` and `description`.
```yaml theme={null}
---
name: top-customers
description: Analyze database to find top 5 customers by total payments.
---
```
In chat, users can trigger skills through `/` shortcuts or natural prompts that match skill descriptions.
# Visualizations
Source: https://docs.getnao.io/nao-agent/chat/capabilities/visualizations
Chart types, chart editing, table formatting, and display settings
When the agent answers a question with data, it can render the result set as a chart or as a table. Both are produced by the `display_chart` tool from a previous `execute_sql` result, and both can be adjusted afterwards without re-running the query.
## Chart types
The agent picks a chart type from the result set, and you can change it later from the edit dialog.
| Type | Use it for |
| -------------- | ----------------------------------------------------------------------------- |
| Bar | Comparing values across categories |
| Stacked bar | Composition of a total across categories |
| Horizontal bar | Ranking one or more metrics across categories, drawn as sideways tracked bars |
| Line | Trends over time |
| Area | Time-series trends with filled emphasis |
| Stacked area | How multiple series compose a total over time |
| Pie | How one measure splits across categories |
| Donut | Same as pie, with a hollow centre |
| KPI card | A single headline number |
| Scatter | Correlation between two numeric variables |
| Radar | Several metrics compared across a fixed set of categories |
| Mixed | Bars, lines, and areas combined in one chart, optionally on two Y-axes |
Stacked bar, stacked area, and horizontal bar charts also have a normalized variant that plots each series as a share of its category total on a 0-100% axis. See [Normalize to 100 percent](#normalize-to-100-percent) below.
For large numeric values, nao shortens Y-axis labels to values such as `100K`, `1.5M`, or `2B` to keep charts readable.
### Pie and donut charts
Pie and donut charts take exactly one series. Categories are shown in a legend rather than as labels drawn on the slices, and slices are separated by a small gap so adjacent categories stay distinguishable.
When a result set has more than 10 categories, nao keeps the 10 largest slices by value and sums the remainder into a single **Other** slice. If the data already contains a category called "Other", the remainder is merged into it rather than creating a duplicate.
### Horizontal bar charts
A horizontal bar chart draws one row per category, each with a sideways bar in a grey track. The category sits on the left and the value in a column on the right, and every bar is sized against the largest value in the data. Values are shown on the bars by default and can be turned off with the **Show data labels** toggle in the edit dialog.
A single value column gives one bar per row. With two or more series the bars stack within each row, and a legend appears. Because the rows are ranked visually, the agent is instructed to sort and limit the rows in SQL so the chart stays readable.
With two or more series you can also [normalize to 100%](#normalize-to-100-percent): each row then fills the same width and its bars show each series' share of that row's total.
## Editing a chart
Click the **pencil icon** next to a chart's download button to open the edit dialog. Changes apply to the existing chart config, so the query is not re-run.
The dialog lets you set:
* **Title**
* **Chart type**
* **X-axis type**: `Auto`, `Category`, `Date`, or `Number`
* **X-axis column**
* **X-axis label**: an optional custom label drawn under the axis
* **Series**: for each series, the data column, an optional label, a color, a [value format](#value-formatting) (number format, unit, and placement), and - on [mixed charts](#combo-charts-and-a-second-y-axis) - a series type and Y-axis side. Use **Add series** to plot another column, or the bin icon to remove one (at least one series is required).
* **Y-axis range**
* **Show data labels**
Click **Save** to persist the changes. Any cached chart image used for Slack, Teams, and WhatsApp previews is regenerated on the next request.
Chart editing is available on charts in a chat thread and on chart blocks inside a story. It is restricted to the chat or story owner, and hidden while the agent is running, on archived stories, in shared or read-only views, and when viewing older story versions.
### Normalize to 100 percent
When the selected chart type is a stacked bar or stacked area chart, the dialog shows a **Normalize to 100%** toggle. Turning it on switches the chart to its 100% stacked variant: each series is drawn as a share of the category total and the axis runs from 0 to 100%. Tooltips show percentages instead of absolute values.
Horizontal bar charts have the same toggle, but only once they carry two or more series to split each row between - a single-series horizontal bar has nothing to normalize.
Use it when the composition matters more than the absolute totals. Turning the toggle off returns the chart to absolute stacking.
### Y-axis range
By default the Y axis adapts to the values in the result set. Line and scatter charts auto-scale to a readable range rather than forcing a zero baseline.
To pin the scale, enter a **Min**, a **Max**, or both in the **Y-axis range** fields. Leaving a field on its `Auto` placeholder keeps that bound automatic. The minimum must be lower than the maximum, otherwise the dialog rejects the change.
The Y-axis range fields are not shown for pie, KPI card, and radar charts, which have no Y axis.
### Data labels
The **Show data labels** toggle prints the numeric value of each data point directly on the chart, instead of requiring a hover. You can also ask the agent for it in plain English (for example: *"show the values on the chart"*).
On line and area charts, labels are limited to the most significant points so a dense series stays legible.
### Combo charts and a second Y-axis
Pick the **Mixed** chart type to draw several series with different shapes in one chart - for example bars for revenue and a line for a conversion rate. Each series then gets two extra controls in its row of the edit dialog:
* **Series type** - draw that series as a `bar`, `line`, or `area`, independent of the others.
* **Y-axis side** - plot the series against the `left` or `right` axis. A right axis appears as soon as any series uses it, which is what lets you compare metrics on very different scales or units in a single chart.
The right axis has its own **range** and **label** fields, mirroring the left axis. You can also just ask the agent: *"plot revenue as bars and the conversion rate as a line on a second axis."*
### KPI comparison pills
A **KPI card** can show a small change pill under its number, comparing the latest value to the previous period. Turn it on with the **Comparison pill** control in the edit dialog (it appears only for KPI cards), or ask the agent for it. There are three styles:
* **Percentage** - the signed percentage change, colored green or red with an arrow.
* **Variation** - the signed absolute change, colored green or red with an arrow.
* **Absolute** - just the magnitude of the change, with no color or arrow.
The pill names the period it compares against (*"vs. last month / last week / last quarter / last year"*), inferred from the data's dates, and falls back to *"vs. previous period"* when the spacing is unclear. Comparison pills need the query to return **two or more time-ordered rows** (oldest to newest). Set the style to **none** to hide the pill.
### Hiding the tooltip total
When a chart stacks several series, the tooltip shows a **Total** row by default. That total is meaningless when the series can't be meaningfully added up - unrelated metrics, or different units or currencies. The agent can set a chart-wide `hide_total` option to drop the Total row in those cases; additive charts still show it, and percentage charts still show 100%.
## Value formatting
By default chart values render as plain numbers. Value formatting lets you show a currency symbol, a `%`, or any unit (V, kg, GB, …) and control how numbers are rounded and abbreviated. Formatting is set **per series**, so a chart can mix, for example, a dollar series and a percentage series.
A format applies everywhere the series' value appears: the value axis, tooltips, data labels, and KPI cards. It also carries through into [stories](/nao-agent/chat/capabilities/stories), including their HTML and PDF exports. The value axis automatically widens to fit longer labels (such as `$1,000,000`) so they don't clip.
A value format has three parts:
* A **number format** - a [d3-format](https://d3js.org/d3-format) specifier that controls rounding, thousands separators, and abbreviation of the raw number.
* A **unit** - free text such as a currency symbol (`$`, `€`, `¥`, `£`), a `%`, or a unit like `V`, `kg`, `GB`.
* A **placement** - whether the unit sits *before* the number (**Prefix**, for currencies) or *after* it (**Suffix**, for `%` and most units).
### Setting it in the editor
In the [chart edit dialog](#editing-a-chart), each series row has a **Number format**, a **Unit**, and a **Placement** (Prefix or Suffix). Any part can be left blank.
### Asking the agent
You usually don't need to open the editor - ask in plain English and the agent sets the format for you:
> *"Format revenue as US dollars."*
> *"Show the conversion rate as a percentage with one decimal."*
> *"Label the storage column in GB."*
## d3-format number cheat sheet
The **number format** field takes a raw [d3-format](https://d3js.org/d3-format) specifier and applies it to the value **as-is** (it does not multiply or divide). Copy-paste one of these:
| Format | `1234.567` renders as | Use for |
| ------- | --------------------- | --------------------------------------- |
| `,.0f` | `1,235` | Whole numbers with thousands separators |
| `,.1f` | `1,234.6` | 1 decimal place |
| `,.2f` | `1,234.57` | 2 decimals (money) |
| `.0f` | `1235` | Whole number, no separator |
| `.2f` | `1234.57` | 2 decimals, no separator |
| `,` | `1,234.567` | Separators, keep all decimals |
| `.2s` | `1.2K` | Abbreviate to 2 significant digits |
| `.3s` | `1.23K` | Abbreviate to 3 significant digits |
| `+,.0f` | `+1,235` | Always show the sign |
The pattern is `[,][.precision][type]`: a leading `,` adds thousands separators, `.N` sets precision, and the trailing letter is the type - `f` for fixed decimals or `s` for SI-prefix abbreviation. See the [full d3-format docs](https://d3js.org/d3-format) for more.
### Copy-paste recipes (format + unit)
| Goal | Number format | Unit | Placement |
| ---------------------------------- | ------------- | ---- | --------- |
| US dollars | `,.2f` | `$` | Prefix |
| US dollars, no cents | `,.0f` | `$` | Prefix |
| Euros, no decimals | `,.0f` | `€` | Prefix |
| Abbreviated dollars (`$1.2M`) | `.2s` | `$` | Prefix |
| Percentage (data stored as `42.5`) | `.1f` | `%` | Suffix |
| Volts | `,.0f` | ` V` | Suffix |
| Bytes, abbreviated (`1.2M`) | `.2s` | `B` | Suffix |
### Percentages
Do **not** use d3's `%` format type - it multiplies the value by 100. If your data is already stored as a percentage (for example `42.5` meaning 42.5%), use a plain number format (`.1f`) with a `%` unit set to **Suffix** placement. That renders `42.5` as `42.5%`.
### Abbreviating large numbers
The `.2s` (SI-prefix) number format abbreviates large numbers. How the abbreviation letters display depends on the series' compact mode, which the agent picks based on the kind of value:
* **Financial** (the default) is meant for money and counts: `k` becomes `K` and `G` becomes `B`, while `M` and `T` are unchanged - so you get `1.2K`, `3.4M`, `5.6B`.
* **SI** keeps d3's scientific letters unchanged, which is what you want for scientific units such as bytes (`1.2k`, `3.4M`, `5.6G`).
The editor accepts number formats that render identically in the interactive chart and in static story exports - the `f` and `s` types, a bare `,`, and a precision (for example `,.2f`, `.2f`, `,`, and `.2s`). If you enter a format that would export differently, the dialog shows a warning and blocks saving until you adjust it. The agent is not limited to these when it sets a format directly.
## Viewing the SQL and data behind a chart
Every chart is backed by a SQL query and its result set. Click the **View SQL query and data** action (the `<>` icon) on the chart toolbar to open a side panel with the `execute_sql` query on top and the result rows below, so you can verify exactly how a chart was built.
## Tables
Ask for a table instead of a chart and the agent renders the result set with `display_chart` using `chart_type: "table"`.
Tables include:
* **Right-aligned numeric columns** with tabular figures, so digits line up.
* **Sortable columns** - click a column header to sort by it. Each click cycles through ascending, descending, and back to the original order (the chevron shows the current direction). Column widths stay stable across sorting and paging. This works on tables in chat, in stories, and on `execute_sql` results.
### Exporting table data
Any table - in a chat thread, in a story, or from an `execute_sql` result - can be downloaded from its toolbar. The export menu offers two formats:
* **CSV** - a plain comma-separated file.
* **Excel** (`.xlsx`) - a native spreadsheet file.
Both export the full result set, not just the rows currently on screen.
### Conditional formatting
Numeric, boolean, and text columns can be colored by rule. Open the **Edit table formatting** dialog from the table toolbar and pick a rule per column - columns default to **None**.
Available rules depend on the column type:
**Color scale** shades each cell on a gradient between the column minimum and maximum. **Threshold** colors cells matching a comparison (`≥`, `>`, `≤`, `<`, `=`) against a value.
**Boolean** sets a background color for true cells, false cells, or both. Leave a color unset to skip that case.
**String** colors cells that match an operator: `equals` for an exact match, `in list` for one of several values, or `contains` for a case-insensitive substring.
For color scales, nao derives the gradient from a single color by default, running from a light tint at the low end to the full color at the high end. The bounds default to the column minimum and maximum.
**Asking the agent to format a table**
You do not have to open the dialog. Ask for the formatting in plain English and the agent applies the rules itself:
> *"Show revenue by region as a table and highlight anything under 10k in red."*
> *"Put a color scale on the conversion rate column."*
The agent is instructed to use real conditional formatting for these requests rather than faking it with emoji or by adding an extra status column to the data.
## Date format
Dates rendered by nao follow a project-level setting rather than each viewer's locale, so everyone on the project reads the same format.
Set it in **Settings -> Project** on the **Date format** card. This is an admin setting.
| Preset | Pattern |
| -------- | ---------------- |
| European | `DD/MM/YYYY` |
| American | `MM/DD/YYYY` |
| ISO 8601 | `YYYY-MM-DD` |
| Custom | Your own pattern |
European is the default. Choosing **Custom** lets you compose a pattern from the supported tokens: `YYYY`, `YY`, `MMMM`, `MMM`, `MM`, `M`, `DD`, `D`, `dddd`, and `ddd`. The card previews the result on a sample date as you type.
The setting applies to chart axes, tooltips, legends, and KPI cards, to SQL result tables, and to the charts and tables inside stories and their PDF and HTML exports.
## Maps
When a query returns geographic data, the agent can plot it on an interactive map instead of a chart, using the `display_map` tool. Maps render over an OpenStreetMap-based basemap, and you can pan and zoom them like any web map. Ask in plain English: *"map our customers by city."*
Maps are off by default. An admin turns them on under **Settings -> Project -> Agent**, on the **Maps** card. Until then the agent won't offer the map tool.
### Map types
| Type | What it draws | Needs |
| ------------------ | --------------------------------- | -------------------------------------------------------- |
| **Points** | A marker per row | Latitude and longitude columns, in WGS84 decimal degrees |
| **Scatter bubble** | Markers sized by a numeric column | Latitude, longitude, and a size column |
| **Choropleth** | Regions shaded on a color scale | A numeric value column, plus boundaries to join against |
A choropleth gets its shapes from one of three sources: a **built-in boundary set** (`world_countries`, `france_regions`) matched on a region column, a **public GeoJSON URL**, or a **geometry column** in the result set (for example `ST_AsGeoJSON` output from PostGIS). Prefer a URL over a geometry column when a public one exists - it avoids pulling large polygons through SQL.
### The map toolbar
Every map carries the same toolbar as a chart:
* **View map**, **View data table**, and **View SQL query** switch between the map, the rows behind it, and the `execute_sql` query that produced them.
* **Add to story** drops the map into a [story](/nao-agent/chat/capabilities/stories).
* **Download** saves the map as a PNG, or exports the underlying rows as CSV or Excel when you are on the data view.
* **Edit map** (the pencil icon) opens the edit dialog.
Maps also expand to full width, and the basemap style can be changed from the style selector.
### Editing a map
The edit dialog covers presentation, so the query is never re-run. You can set:
* **Title**
* **GeoJSON boundaries** - on a choropleth built from a URL, the URL itself; on one built from a boundary set, a read-only summary of which set it uses
* **Color** - the marker color, or the base color of the choropleth scale
* **Marker size** - the radius for points, or the largest bubble for a scatter bubble map (not shown on choropleths)
For anything structural - a different map type, other columns, different boundaries - ask the agent instead: *"size the bubbles by revenue instead of headcount."*
### Maps in stories and exports
Maps behave like charts everywhere else in nao:
* Map blocks render in [stories](/nao-agent/chat/capabilities/stories) and in story thumbnails.
* PNG and PDF exports include the map as rendered.
* Slack, Teams, Telegram, WhatsApp, and Mattermost previews render the map as an image that mirrors what you see in the web chat.
### Custom GeoJSON boundaries
Built-in boundary sets only cover countries and French regions. To shade anything else - postal codes, sales territories, store catchment areas - an admin can add your own boundaries to the **GeoJSON Boundary Library** under **Settings -> Project -> Agent**.
Paste an HTTPS URL to a GeoJSON `FeatureCollection`. nao loads it, validates it, and previews it on a map so you can confirm you have the right shapes.
Give it a **Label** (what admins see, e.g. `French postal codes`) and a **Key** (what the agent addresses it by, e.g. `french_postal_codes`).
Pick the **Join property** - the GeoJSON feature property whose value must match the region column in your SQL. nao lists the properties it found in the file. Add a **Region key hint** describing what those values look like, so the agent knows what to select.
Saved sets become available to the agent as boundary options alongside the built-in ones. Removing a set from the library breaks any map that was using it.
## Custom charts
Beyond the built-in chart types, a project can ship its own **custom charts** - browser-rendered visualizations authored as small JavaScript modules in `agent/charts/`. Use them when a question needs a visual that nao's built-in types don't cover.
Each custom chart is a file `agent/charts/.js` that exports a `render` function:
```js theme={null}
export function render(element, context) {
const { data, config, colors, theme, libs } = context;
// draw into `element` using the query's `data`
return () => element.replaceChildren(); // optional cleanup
}
```
* `` must start with a lowercase letter and must not collide with a built-in type (`bar`, `line`, `pie`, `table`, …); names that break the rules are ignored.
* `context` carries the `data` rows from the referenced `execute_sql` result, the chart `config`, the nao `colors` and `theme`, and a set of charting `libs`.
* Charts render in the browser only. Stories, exports, automations, MCP embeds, and messaging previews keep using the built-in chart types.
Active charts hot-reload as you edit the file, so you can iterate without restarting. See the `build-custom-charts` skill in your project for the full authoring reference.
# Overview
Source: https://docs.getnao.io/nao-agent/chat/overview
Interact with your analytics agent through natural language
The nao chat interface provides an intuitive way for your team to interact with data using natural language.
## Start Chat
You can access the chat interface in two ways:
### Option 1: Using nao chat command
```bash theme={null}
nao chat
```
This will start a local server at `http://localhost:5005`.
### Option 2: Using Docker
Run the Docker container to access the UI:
**With built-in example:**
```bash theme={null}
docker run -d \
--name nao \
-p 5005:5005 \
-e BETTER_AUTH_URL=http://localhost:5005 \
getnao/nao:latest
```
**With your project:**
```bash theme={null}
docker run -d \
--name nao \
-p 5005:5005 \
-e BETTER_AUTH_URL=http://localhost:5005 \
-v /path/to/your/project:/app/project \
-e NAO_DEFAULT_PROJECT_PATH=/app/project \
getnao/nao:latest
```
Access the UI at `http://localhost:5005`.
The chat interface runs locally on your machine. No data is sent to external servers unless you configure an external LLM provider.
## Ask Questions
**Question Input**
* Type your question in natural language
* Reference previous results in follow-up questions
**Response Display**
The agent's response includes:
* **Answer**: Plain language summary of findings
* **SQL Query**: The generated query (expandable)
* **Data Table**: Results in table format
* **Visualizations**: Automatic charts when appropriate
* **Sources**: Which context files were used
## Tool call density
By default the agent shows its work in detail: SQL queries, Python, and sandbox executions each render as their own block in the thread, alongside charts and stories.
If that is more than you want to read, change it from **Settings** -> **Account** -> **General Settings** -> **Tool Call Density**. The slider has two positions:
* **Detailed** (default) - SQL, Python, and sandboxed code executions are shown as individual expandable blocks.
* **Compact** - those steps are folded into a single collapsed group you can expand on demand. Charts, stories, clarifications, and follow-up suggestions stay visible either way.
The setting is per user and follows you across devices.
## Keyboard shortcuts
Press `Cmd+/` (or `Cmd+:`) to open the shortcut list from anywhere in the chat. On Windows and Linux, `Ctrl` replaces `Cmd`.
| Shortcut | Action |
| ------------------ | --------------------------------- |
| `Cmd+B` | Toggle sidebar |
| `Cmd+K` | Command menu |
| `Cmd+Shift+L` | Toggle light/dark mode |
| `Cmd+/` or `Cmd+:` | Keyboard shortcuts |
| `Cmd+Shift+O` | New chat |
| `Cmd+Shift+S` | Go to Stories |
| `Ctrl+C` | Cancel messages |
| `Shift+Tab` | Cycle model (from the chat input) |
Shortcuts match the **character** on your keyboard, not its physical position, so they work the same on AZERTY, QWERTZ, and QWERTY. `Cmd+:` exists because `Cmd+/` is taken by a system shortcut on AZERTY Macs; it is deliberately inactive on QWERTY.
## Give Feedbacks
Each response has:
* 👍 Thumbs up - Mark good responses
* 👎 Thumbs down - Flag issues
These responses given by your users are stored and accessible for you to audit.
**What's Next?**
Explore all chat features and agent tools
Set up Slack integration for your team
# Deploy project context
Source: https://docs.getnao.io/nao-agent/cloud/deploy
Push a local nao project to a remote nao instance with the nao deploy CLI command
The `nao deploy` command packages a local nao project and uploads it to a remote nao instance. It is the bridge between a project built locally with `nao init` / `nao sync` and a Cloud (or self-hosted) instance that will run chats, scheduled jobs, and integrations against that project.
Use it to push the same context folder you work on locally to the instance your team chats with.
## Prerequisites
* A remote nao instance URL (Cloud workspace or your self-hosted deployment).
* An organization-level API key. Create one from **Settings** -> **Organization** -> **API Keys**. See [Per-organization API keys](/nao-agent/cloud/overview#per-organization-api-keys).
* A local project that contains a valid `nao_config.yaml` with a `project_name` field.
## Command
Run from the project directory (the folder that contains `nao_config.yaml`):
```bash theme={null}
nao deploy --api-key
```
Or from anywhere, pointing at the project folder explicitly:
```bash theme={null}
nao deploy --api-key --path /path/to/project
```
### Flags
| Flag | Description |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `` | URL of the remote nao instance (positional). For nao Cloud, use `https://app.getnao.io`. |
| `-k`, `--api-key` | Organization API key used to authenticate the upload. |
| `-p`, `--path` | Project directory. Defaults to the current working directory. |
## What gets uploaded
`nao deploy` builds a gzipped tarball of the project folder and uploads it to `/api/deploy`. The server extracts the archive, reads `project_name` from `nao_config.yaml`, and either creates a new project in the organization or updates the existing one with the same name.
### Default exclusions
These paths are always skipped when building the archive:
* `.git`
* `.venv`
* `.env`
* `node_modules`
* `__pycache__`
* `repos`
* `*.pyc`
### `.naoignore`
Add a `.naoignore` file at the project root to exclude extra files. One pattern per line, `#` for comments. Patterns are matched against path parts (directory or file names). Example:
```
# .naoignore
dbt_packages
logs
secrets.yaml
*.parquet
```
## Create vs update
The server looks up a project by `(organization, project_name)`:
* If no project with that name exists in the organization, a new one is created and the response reports `status: "created"`.
* If a project with that name already exists, its context folder is replaced with the uploaded content and the response reports `status: "updated"`.
There is no merge. Every deploy is a full replacement of the project's context folder on the remote instance.
## Example
```bash theme={null}
$ nao deploy https://app.getnao.io --api-key nao_sk_live_...
Deploying my_project to https://app.getnao.io
Packaging project files...
Package size: 2.3 MB
Uploading...
Project my_project created
Project ID: 4f2d...
```
## Errors
* `Authentication failed. Check your API key.` - the API key is missing, revoked, or does not belong to the target organization.
* `No nao_config.yaml found in current directory` - run the command from a project root or pass `--path`.
* `nao_config.yaml is missing a 'project_name' field` - add `project_name: my_project` at the top of `nao_config.yaml`.
* `Could not connect to ` - check the URL and that the instance is reachable from your network.
# Get Started with nao Cloud
Source: https://docs.getnao.io/nao-agent/cloud/get-started
Sign up at app.getnao.io, ship your first project, and invite your team
nao Cloud is the hosted version of nao, running at [**app.getnao.io**](https://app.getnao.io). We run the chat app, database, and your projects - you don't deploy or manage any infrastructure. Build your context locally (or in Git) and push it to your Cloud workspace.
Prefer to run nao on your own infrastructure? See the [Deployment Guide](/nao-agent/self-hosting/deployment-guide). The organization and project model below is the same in both.
## Step 1: Create your account
Go to [app.getnao.io/signup](https://app.getnao.io/signup) and sign up with email and password, or with **Google**.
On your first sign-in, nao automatically creates a **workspace** (your organization) with you as its admin - there is no separate setup wizard. If you sign up with a Google address whose domain already has a nao Cloud workspace, you can be added to that existing workspace instead.
Already have an account? Sign in at [app.getnao.io/login](https://app.getnao.io/login).
## Step 2: Add your first project
A fresh workspace has no project yet. A **project** holds one agent's context, data connections, chats, and members. There are two ways to create one.
### Option A: Import from GitHub
If your nao project already lives in a GitHub repository (a folder containing `nao_config.yaml`), import it straight from the browser:
1. From the empty-project screen, choose **Import from GitHub**.
2. Authorize the nao GitHub app on the account or organization that owns the repo.
3. Pick the repository and branch that holds your `nao_config.yaml`.
nao clones the repo and creates the project. You can re-pull the latest commit anytime from the project's **Git sync** settings.
The repository picker only lists repos the nao GitHub app can access. If yours isn't listed, add it to the app's installation on GitHub.
### Option B: Deploy from the CLI
If you build context locally with the CLI, push it to your workspace with `nao deploy`.
1. Build your project locally - see the [Quickstart](/nao-agent/quickstart):
```bash theme={null}
pip install nao-core
nao init # creates nao_config.yaml and the context folder
nao sync # pulls schemas, repos, and docs into the context
```
2. Create an organization API key in the Cloud UI: **Settings -> Organization -> API Keys -> Create key**. Copy it - it's shown only once.
3. From the project folder, deploy to your Cloud workspace:
```bash theme={null}
nao deploy https://app.getnao.io --api-key nao_sk_...
```
The project name comes from `project_name` in `nao_config.yaml`. Deploying a new name creates a project; deploying an existing name replaces its context in place.
Full command flags, exclusions, `.naoignore`, and create-vs-update behavior
## Step 3: Configure the project
Your data connections, rules, and semantics are defined in `nao_config.yaml` and your context folder - edit them locally and re-deploy (or push to the connected Git branch). A few things are set directly in the Cloud UI, under **Settings -> Project**:
* **Environment variables** - provide values for the secrets your config references with `{{ env('MY_VAR') }}` (warehouse credentials, API tokens), so you never ship them in Git.
* **Models** - add your LLM provider keys and pick the models the agent can use.
* **Agent settings** - template prompts, MCP servers, budgets, and connectors (Slack, Teams, Telegram, WhatsApp, Mattermost).
Structure the databases, repos, rules, and semantics your agent reads from
## Step 4: Invite your team
Add teammates from **Settings -> Organization -> Members**: invite by email and pick a role. Each new member signs in with their email and the generated password.
Members join projects to get access to that project's chats, context, and data - manage each project's members under **Settings -> Project -> Team**. For roles and permissions, see [Admin Setup](/nao-agent/chat/admin/setup#user-roles).
On Cloud, users who sign in with a Google address on your workspace's claimed domain can join automatically - no manual invite needed.
## Step 5: Start chatting
Open your project and ask a question in plain English. The agent uses your project's context to write SQL, run it against your connected warehouse, and return results and charts.
## What's next
Organizations, projects, members, API keys, and managed env vars
Run several projects in one workspace with a project switcher
Automate context deploys with GitHub Actions
Stories, memory, automations, and admin tools
# nao Cloud
Source: https://docs.getnao.io/nao-agent/cloud/overview
Managed hosting with organizations, teams, and git-synced context
nao Cloud is the hosted version of nao. It runs your analytics agent in our infrastructure and adds organization-level features that are not available in self-hosted deployments: multi-project organizations, team management, per-organization API keys, GitHub-connected context sync, and managed environment variables.
The Cloud UI ships alongside the self-hosted chat app. The features described here appear under **Settings** -> **Organization** when your account belongs to a Cloud organization.
## Organizations and projects
A Cloud account belongs to one or more **organizations**. Each organization owns one or more **projects**, and each project is the unit that hosts an agent, its context, its connected data sources, and its chats.
* Organization-level settings (billing, members, API keys) apply across every project in the organization.
* Project-level settings (data connections, context, chat behavior) are scoped to a single project.
* A user can belong to several organizations and switch between them from the workspace switcher.
To manage organization-wide settings, open **Settings** -> **Organization**.
## Managing team members
Organizations and projects both have their own member lists.
**Organization members**
Open **Settings** -> **Organization** -> **Members** to:
* Invite a new member by email. They receive an invite link and become a member once they sign in.
* Change a member's role (admin or member).
* Remove a member from the organization.
Admins can manage billing, API keys, members, and organization-wide settings. Members can use the projects they are added to.
**Project members**
Each project has its own member list under **Settings** -> **Project** -> **Team**. Add organization members to a project to give them access to that project's chats, context, and data. A user must be in the organization before they can be added to one of its projects.
## Per-organization API keys
API keys live at the organization level and are used to call the nao API from your own services, CI, or other integrations.
1. Open **Settings** -> **Organization** -> **API Keys**.
2. Click **Create key**, give it a name, and copy the key. It is shown only once.
3. Revoke a key from the same page when it is no longer needed.
Each key is scoped to its organization and can access every project the organization owns.
## Connecting a GitHub repository for context
Cloud projects can sync their context folder from a GitHub repository instead of uploading files manually. Once connected, nao pulls the latest state of the repo on a schedule so the agent always sees the current context.
1. Open **Settings** -> **Project** -> **Context** -> **Git sync**.
2. Click **Connect GitHub** and authorize the nao GitHub app on the organizations you want to expose.
3. Pick the repository and branch that holds your `nao_config.yaml` and context folder.
4. Save. nao performs the first sync immediately and then refreshes on every push to the selected branch.
The repo picker only lists repositories the nao GitHub app has access to. If you do not see the repo you want, go back to GitHub and add it to the app's installation.
## Environment variables
Cloud projects expose an **Environment variables** section under **Settings** -> **Project** -> **Environment**. Use it to set credentials and other values referenced from `nao_config.yaml` without shipping them in your git repo.
* Add, edit, or remove variables from the UI.
* Variables are injected into the runtime for sync jobs and chat requests.
* Reference them in config the same way as on self-hosted: `{{ env('MY_VAR') }}`.
Secrets are encrypted at rest and are not displayed back in the UI after they have been saved.
## What's next
Configure models, integrations, and template prompts
Structure the context your agent reads from
# Mattermost Bot
Source: https://docs.getnao.io/nao-agent/connectors/mattermost
Enable your team to ask questions directly in Mattermost
The nao Mattermost bot brings your analytics agent directly into Mattermost, allowing your team to ask questions and get insights without leaving their workspace.
When configured, the nao Mattermost bot:
* Runs against your own **self-hosted Mattermost** server
* Connects as a **bot account** with a personal access token, over a persistent WebSocket
* Answers mentions in channels, and every message in direct messages
* Returns answers with SQL queries, data tables, and visualizations
* Streams the answer into a single message that is edited as the answer grows
The Mattermost bot uses the same agent context, rules, models, and capabilities
as your web chat interface. Any improvements to your agent automatically
benefit Mattermost users.
Each nao project connects its own bot, so several projects can live on the same Mattermost server.
## Setup
### Step 1: Create a Bot Account in Mattermost
1. In Mattermost, go to the **System Console** → **Integrations** → **Bot Accounts** and set **Enable Bot Account Creation** to **true**. It is off by default.
2. Go to **Integrations** → **Bot Accounts** → **Add Bot Account**, create the bot, and copy the **token**. It is shown only once, at creation.
3. Add the bot to the **team** first, then to each **channel** where it should answer.
Adding the bot straight to a channel does not work until it is a member of the
team. Add it to the team first, then to the channels.
See the [Mattermost bot accounts guide](https://docs.mattermost.com/integrations-guide/bot-accounts.html) for more detail.
### Step 2: Connect your Mattermost Bot to nao
1. In nao, click on your profile → **"Project"** → **"Mattermost"**
2. Fill in the **Server URL** (for example `https://mattermost.example.com`) and the **Bot Token**
3. Click **"Save"**
4. Choose the model used for Mattermost replies
### Step 3: Turn on the Stop button
On the same settings page, turn on **Enable interactive buttons**. This adds a **Stop** button to the answer while nao is generating, so people can cut a long answer short. The button disappears on its own once the answer finishes.
Buttons work by Mattermost calling back into nao over HTTP, so **your Mattermost server must be able to reach nao**. Two things commonly block that:
1. **The address is wrong.** The address your Mattermost server uses to reach nao can differ from the one you use in your browser. That is what the optional **Callback URL** field is for — leave it blank unless buttons fail. For example, with Mattermost running in Docker on a laptop, the callback URL would be `http://host.docker.internal:5005`.
2. **The host is not allow-listed.** Mattermost refuses calls to internal hosts unless you allow them.
Go to **System Console** → **Environment** → **Developer** → **Allow
untrusted internal connections to** and add the host from your Callback URL.
This is the most common reason buttons fail silently.
This step is optional. Everything except the Stop button works with the toggle off.
### Step 4: Account Linking
People are linked **automatically by their Mattermost email**. If the email on someone's Mattermost account matches their nao account, they are recognised on their first message and there is nothing to type.
If the two emails do not match, they can link manually instead: send the bot a direct message with `login `. The linking code is on the Mattermost settings page, tucked behind a collapsible section.
Write `login ` without a leading slash. Mattermost intercepts
slash commands, so `/login` never reaches the bot.
## Usage
**Ask Questions**
In a channel, mention the bot:
```
@nao What were our top 5 products by revenue last month?
```
The bot will:
1. Process your question using the same agent as the web interface
2. Generate SQL queries based on your context
3. Execute queries against your connected databases
4. Stream the answer into a single Mattermost message, editing it as the answer grows
5. End the answer with a bold **Open in nao** link to the full conversation
**Thread Conversations**
Once a thread exists, follow-ups inside it need no mention:
```
@nao What were our top 5 products by revenue last month?
# ... bot responds in the channel ...
# Reply in the thread under the bot's message:
Can you break that down by region?
# ... bot responds with regional breakdown ...
```
The first answer lands in the channel itself, and follow-up housekeeping
messages go into a thread under it, so channels stay quiet. Direct messages
are never threaded.
**Direct Messages**
In a direct message the bot answers every message. No mention needed.
**Tables and formatting**
Mattermost supports full markdown, so answers come through with real tables, code blocks, bold text, and links. Charts are posted as images. Large tables are capped at about 20 rows, with a note pointing to the full result in nao.
**Feedback**
React to an answer with 👍 or 👎 and the vote is recorded in nao, the same as the thumbs in the web UI. Remove the reaction to remove the vote.
**What's Next?**
Configure authentication, models, and messaging integrations
Learn how the main chat interface works
# MCP Endpoint
Source: https://docs.getnao.io/nao-agent/connectors/mcp
Expose nao as an MCP server so external AI clients can query your data
nao can act as an MCP (Model Context Protocol) server, letting external AI clients (Claude, Cursor, Codex, and others) connect directly to your analytics agent.
Once enabled, those clients can ask analytics questions, run SQL, browse your context files, and manage stories, all without leaving their own chat interface.
Enable and configure the MCP endpoint from **Settings** -> **MCP Endpoint**. For step-by-step client setup, see [Configure nao MCP endpoint](/nao-agent/chat/admin/setup#configure-nao-mcp-endpoint) in the admin guide.
On a self-hosted deployment you can have the endpoint enabled out of the box with `MCP_ENDPOINT_ENABLED=true`. It only sets the first-boot default: once an admin saves anything on the **MCP Endpoint** settings page, the stored value wins and the env var is ignored. This keeps external MCP clients connected across reinstalls without clicking the toggle again.
## Modes
Two modes control which capabilities are exposed. Enable either or both, depending on how much of the workflow you want the external client to drive.
| Mode | What it exposes |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sub-agent mode** | `ask_nao` and `get_nao_answer`: sends a question to the nao agent, which runs the full agentic loop and streams the answer back. The reasoning trace is saved as a chat visible in the nao UI. Use this when you want the external client to delegate analytics questions to nao. |
| **Context-layer mode** | `ls_nao_context`, `grep_nao_context`, `read_nao_context`, `execute_sql`, `create_story`, `update_story`: lets the external client browse your context files, run queries, and build stories step by step. Use this for tighter integration where the external client drives the tool calls itself. |
You can enable either mode or both.
### Story and chart tools
Whenever **either** mode is enabled, a shared set of story and chart tools is also exposed: `display_chart`, `list_stories`, `get_story`, `archive_story`, and `delete_story`. These let a connected client render charts and manage existing nao stories regardless of which mode drives the workflow.
### Long-running analyses
`ask_nao` waits up to 45 seconds for the agent to finish. If the run completes within that window, the answer comes back directly with `status: "complete"`.
If it does not, `ask_nao` returns `status: "running"` with the `chatId` of the run, which keeps going in the background. The client then calls `get_nao_answer` with that `chatId` until `status` becomes `complete` or `error`. This avoids the client request timing out on multi-minute analyses.
The endpoint streams responses over SSE, so progress notifications emitted during a run reach the client while it waits.
### Clarifying questions
When the agent needs more information, `ask_nao` and `get_nao_answer` return `status: "needs_clarification"` and a `clarification` object holding the `question` and optional `options` to choose from. Relay it to the user, then call `ask_nao` again with the same `chatId` and their answer.
## Authentication
nao uses Bearer token authentication. Your token is shown on the **MCP Endpoint** settings page. Include it in requests:
```
Authorization: Bearer
```
Keep this token secret. Rotate it from the settings page if it is compromised.
Calls run as the user the token belongs to, and that user needs a role on the project other than viewer. Users with the viewer role get a `403` response. The endpoint also advertises OAuth protected-resource metadata, so clients that support it can authenticate through the standard MCP OAuth flow instead of a static token.
### Serving MCP on a separate public host
By default nao validates and advertises the MCP endpoint on the same host as the UI (`BETTER_AUTH_URL`). If you serve the MCP endpoint on an internet-facing host while keeping the UI on a private or VPN-only host, set `MCP_PUBLIC_URL` to the public host:
```bash theme={null}
MCP_PUBLIC_URL=https://mcp.your-company.com
```
Without it, an external client connecting to the public host is rejected during the OAuth token exchange with `requested resource invalid`, because the public host is not among the endpoint's valid audiences. Setting `MCP_PUBLIC_URL` adds the public host to the accepted audiences and makes each host advertise itself in its protected-resource metadata. Single-host deployments do not need it.
## Available tools
The tools available to a connected client depend on the modes you have enabled.
Sends a natural language question to the nao agent and streams the response.
* Requires **Sub-agent mode**.
* Streams progress as the agent reasons and calls tools.
* Returns `status`, `chatId`, `chatUrl`, `text`, the `queries` the agent ran, and the `story_ids` it created or updated.
* Pass an optional `chatId` to continue an existing chat instead of starting a new one.
Polls the result of an `ask_nao` run that is still in progress.
* Requires **Sub-agent mode**.
* Takes the `chatId` returned by an `ask_nao` call that responded with `status: "running"`.
* Returns the same payload as `ask_nao`, plus an `error` field when `status` is `error`.
Runs a SQL query against your connected data warehouse and returns results.
* Requires **Context-layer mode**.
Browse your nao context: list files and directories, search text patterns across them, and read individual files.
* Requires **Context-layer mode**.
Create a new nao story or update an existing one from query results.
* Requires **Context-layer mode**.
Renders a chart from an `execute_sql` result, returned as an interactive embed.
* Available whenever **either** mode is enabled.
List, read, archive, and delete existing nao stories.
* Available whenever **either** mode is enabled.
## nao MCP app
The nao MCP app brings the nao UI directly into your AI agent. When a connected client calls `display_chart` or the story tools (`create_story`, `update_story`, `get_story`), nao returns an interactive embed that renders inline in the conversation - the same charts and stories you see in the nao web UI, without leaving your agent.
Clients that don't support iframes get a sandboxed HTML fallback.
### What gets embedded
| Tool | Embed |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `display_chart` | Interactive chart with hover values, zoom, and axis controls - same design as the nao UI |
| `create_story` / `update_story` / `get_story` | Full story view with sections, charts, and a download button (PDF or HTML). Click the link to open the story in the nao UI for further editing and sharing. |
### How it works
1. Ask a question (e.g. "analyze number of PRs merged every week"). The agent runs SQL via nao MCP.
2. nao stores the query results in a temporary table (retained for 7 days) and returns an embed URL.
3. The MCP client renders the chart or story inline as an interactive iframe - you can hover over data points to see values, just like in the nao web UI.
4. To keep editing, click the link to open the story in the nao UI where you can share it with your team.
### Why this matters
The MCP app gives you the best of both worlds: the power of your AI agent for analysis, and the nao UI for visualization. Everyone at the company gets the same data reliability, the same chart design, and the data team keeps full observability - regardless of which agent the end user is working in.
### Supported clients
The nao MCP app works in any MCP client that supports iframes. Tested clients:
* Cursor
* GitHub Copilot
* ChatGPT
* Claude Desktop
### Agent-rendered charts (data mode)
Some MCP clients cannot render the interactive embeds above (Dust is the common case). For those, turn on **data mode** by appending `?chart_output=data` to the endpoint URL you give the client:
```
https://your-nao-host/mcp?chart_output=data
```
In data mode a `display_chart` result also carries the chart config, the resolved data rows, and rendering instructions, so the client's own agent draws the chart as an interactive component instead of only showing an "Open in nao" link. The deep link back to nao is always kept.
nao falls back to the plain link when a chart cannot be handed over this way: custom chart types, empty result sets, or large results (more than 500 rows or 80k characters).
Clients that render embeds, such as Claude, do not need this. Leave the parameter off and they get the MCP App embeds by default.
### Story downloads
From the embed header, viewers can export a story as PDF or HTML. The download endpoint validates the same signed token as the view, so anyone with the embed link can download the story within the 7-day window.
## What's next
Step-by-step config for Cursor, Codex, Claude Code, Claude Desktop, and CLI
Explore what the nao agent can do natively in chat
# Microsoft Teams Bot
Source: https://docs.getnao.io/nao-agent/connectors/microsoft_teams
Enable your team to ask questions directly in Microsoft Teams
The nao Teams bot brings your analytics agent directly into Microsoft Teams, allowing your team to ask questions and get insights without leaving their workspace.
When configured, the nao Teams bot:
* Responds to messages in Teams channels and group chats
* Can also answer from direct messages in Teams
* Processes questions using the same agent as the web chat interface
* Returns answers with SQL queries, data tables, and visualizations
* Maintains conversation context within threads
The Teams bot uses the same agent context, rules, and capabilities as your web
chat interface. Any improvements to your agent automatically benefit Teams
users.
## Setup
### Step 1: Create an Azure Bot
1. Go to [https://portal.azure.com](https://portal.azure.com)
2. Click **"Create a resource"**
3. Search for **"Azure Bot"** and select it
4. Click **"Create"** and fill in:
* **Bot handle**: Unique identifier for your bot
* **Subscription**: Your Azure subscription
* **Resource group**: Create new or use existing
* **Pricing tier**: F0 (free) for testing
* **Type of App**: Single Tenant (recommended for enterprise)
* **Creation type**: Create new Microsoft App ID
5. Click **"Review + create"** then **"Create"**
### Step 2: Open the Teams Integration in nao
1. In nao, click on your profile → **"Project"** → **"Microsoft Teams"**
2. Keep this page open — you will paste your credentials here in the next step
### Step 3: Get App Credentials
1. Go to your Bot resource, then **"Configuration"**
2. Copy the **Microsoft App ID** and paste it as **App ID** in nao
3. Click **"Manage Password"** (next to Microsoft App ID)
4. In the App Registration page, go to **"Certificates & secrets"**
5. Click **"New client secret"**, add a description, select expiry, click **"Add"**
6. Copy the **Value** and paste it as **App Password** in nao
7. Go to **"Overview"**, copy **Directory (tenant) ID** and paste it as **Tenant ID** in nao
8. Click **"Save"** in nao. Automatically, a file **"app.zip"** will be downloaded
### Step 4: Configure the Bot Endpoint
1. In nao, Copy the **Messaging Endpoint URL**
2. Go back to your Azure Bot resource → **"Configuration"**
3. Paste the URL into the **Messaging endpoint** field
4. Click **"Apply"**
### Step 5: Enable the Microsoft Teams Channel
1. In your Azure Bot resource, go to **"Channels"**
2. Click on **"Microsoft Teams"**
3. Accept the Terms of Service and click **"Agree"**
4. Click **"Apply"**
### Step 6: Configure App Registration
1. In the Azure portal, go to **"App registrations"** and open your bot's app
2. Go to **"API permissions"**
3. Click **"Add a permission"** → **"Microsoft Graph"** → **"Application permissions"**
4. Search for and add both **`User.Read.All`** and **`Directory.Read.All`**
5. Click **"Grant admin consent"** to activate the permission
```
If you are not an admin, ask your Azure admin to grant consent by following
those steps:
1. In the Azure portal, search for "Enterprise Applications"
2. Search for your app name
3. Click on the app, then go to "Permissions" in the left sidebar
4. Click "Grant admin consent for Default Directory"
```
6. Go to **"Authentication"**
7. Under **"Supported accounts"**, select **"Multiple Entra Id tenants"**
8. Check **"Allow all tenants"**
9. Click **"Save"**
### Step 7: Install the Bot in Teams
1. In Microsoft Teams, click **"Apps"** in the left sidebar
2. Click **"Manage your apps"** → **"Upload an app"** → **"Upload a custom app"**
3. Select **"app.zip"**
4. Start talking to your bot
## Usage
**Ask Questions**
Mention the bot in any channel:
```
@nao What were our top 5 products by revenue last month?
```
The bot will:
1. Process your question using the same agent as the web interface
2. Generate SQL queries based on your context
3. Execute queries against your connected databases
4. Return results in a formatted Teams message
5. Send you the link to the full conversation to open it in the browser UI
**Thread Conversations**
Follow-up questions in the thread maintain context:
```
@nao What were our top 5 products by revenue last month?
# ... bot responds ...
@nao Can you break that down by region?
# ... bot responds with regional breakdown ...
```
The bot replies in threads to keep conversations organized. Use thread replies
for follow-ups.
**Direct Messages**
You can also ask the bot via direct message in Teams (no channel mention required).
**What's Next?**
Learn about the web chat interface
Deploy nao with Docker or on your own Cloud Run instance
# Slack Bot
Source: https://docs.getnao.io/nao-agent/connectors/slack
Enable your team to ask questions directly in Slack
The nao Slack bot brings your analytics agent directly into Slack, allowing your team to ask questions and get insights without leaving their workspace.
When configured, the nao Slack bot:
* Listens for mentions in Slack channels
* Can also answer from direct messages in Slack
* Processes questions using the same agent as the web chat interface
* Returns answers with SQL queries, data tables, and visualizations
* Maintains conversation context within Slack threads
* Streams progress updates and can upload CSV outputs for query results
The Slack bot uses the same agent context, rules, and capabilities as your web
chat interface. Any improvements to your agent automatically benefit Slack
users.
## Setup
### Step 1: Create your Slack App
1. In nao, click on your profile → **"Project"** → **"Slack"**
2. Verify your deployment URL
3. Click **"Create Slack App"**
This opens Slack with a pre-filled app manifest: scopes, event subscriptions, and interactivity are configured automatically.
### Step 2: Install the App to your Workspace
1. In your Slack app settings, go to **"Install App"**
2. Click **"Install to Workspace"** and allow the permissions
### Step 3: Connect your Slack Bot to nao
1. In **"OAuth & Permissions"**, copy the **Bot User OAuth Token** (starts with `xoxb-`) and paste it as **Bot Token** in nao
2. In **"Basic Information"**, copy the **Signing Secret** and paste it as **Signing Secret** in nao
3. Click **"Save"**
### Step 4: Add Bot to Channels
1. In Slack, go to any channel where you want the bot
2. Type `/invite @nao` (or your bot's name)
3. The bot will now listen for mentions in that channel
### Optional: Socket Mode for private VPC deployments
Use **Socket Mode** when nao runs inside a private VPC or air-gapped environment without a public webhook URL.
1. In your Slack app under **Settings -> Socket Mode**, enable Socket Mode and generate an **App-Level Token** with `connections:write` scope (starts with `xapp-`).
2. In nao Slack settings, switch the **Transport mode** toggle from **Webhook** (default) to **Socket Mode**.
3. Paste the App-Level Token. The Bot Token and Signing Secret from Step 3 are still used.
4. Save. nao opens an outbound WebSocket and forwards every event into the same handler the webhook uses, so all Slack features behave identically.
Webhook mode stays the default for public deployments. Switch to Socket Mode only when you cannot expose an inbound URL.
### Optional: Auto-create users from Slack
When a teammate mentions the bot from a workspace email that matches an **allow list** you configure, nao auto-creates a user account on first use - no manual invite needed.
1. In nao Slack settings, open the **User auto-creation** section.
2. Add the email domains or specific addresses allowed to self-onboard (e.g. `@your-company.com`).
3. Save. The next time someone in the allow list mentions `@nao`, an account is provisioned with the default role (see [Add Users](/nao-agent/chat/admin/setup#add-users)).
Leave the allow list empty to keep manual user provisioning only.
### Optional: Reply mode
By default, once you `@nao` in a thread the bot stays engaged and replies to every following message in that active thread. Admins can change this so the bot only answers messages that explicitly mention it.
1. In nao Slack settings, open the **Settings** card.
2. Toggle **Reply only when mentioned**:
* **Off** (default) - after the first mention, the bot keeps answering follow-ups in that thread without needing another `@nao`. The Connection card shows `Replies: Every message in active threads`.
* **On** - nao still reads thread context but only answers messages that tag the bot. The Connection card shows `Replies: Only when mentioned`.
The setting saves immediately and applies to the whole project. Only admins can change it; other members see the current mode on the Connection card.
Use **Reply only when mentioned** in busy shared channels where you want tighter control over when the bot speaks.
## Usage
**Ask Questions**
Mention the bot in any channel where it's been added:
```
@nao What were our top 5 products by revenue last month?
```
The bot will:
1. Process your question using the same agent as the web interface
2. Generate SQL queries based on your context
3. Execute queries against your connected databases
4. Return results in a formatted Slack message
5. Send you the link to the full conversation to open it in browser UI
**Thread Conversations**
Follow-up questions in the thread maintain context:
```
@nao What were our top 5 products by revenue last month?
# ... bot responds ...
@nao Can you break that down by region?
# ... bot responds with regional breakdown ...
```
The bot replies in threads to keep conversations organized. Use thread replies
for follow-ups.
**Direct Messages**
You can also ask the bot in Slack direct messages (no channel mention required).
**Starting a fresh chat with `/new`**
In a direct message or a private channel, a conversation with nao keeps its context indefinitely. Use `/new` to draw a line under it:
* **`/new`** on its own ends the current chat. Whatever you send next starts a fresh one.
* **`/new `** starts a fresh chat *and* opens a thread for it, headed by your question, with the agent answering inside that thread. The main conversation stays free, so you can run several independent chats side by side - one per thread.
`/new ` is only available in direct messages and private channels. In a public channel, send `/new` on its own.
**Reading the thread when mentioned mid-conversation**
When you `@nao` inside an existing Slack thread, the bot reads the previous messages in that thread before answering. This means you can have a human discussion first and then loop nao in with a question like `@nao can you pull the numbers for this?` - it will pick up the earlier context without you having to restate the question.
**Tables and formatting**
Markdown tables in the agent's answer are rendered as native Slack tables instead of raw pipe characters, so query results stay readable in the channel. Text around a table is split into separate blocks and converted to Slack formatting: headings and bold become bold text, and code spans and fenced code blocks are left untouched. This applies to streamed replies as well as messages posted by automations.
**Compact view**
A **compact view** mode renders nao's replies as shorter Slack messages: SQL and large tables are collapsed behind links so the channel stays readable, and full results are available in the linked web conversation. Toggle it from the Slack admin page under **Settings** -> **Project** -> **Slack**. Use compact view in busy channels and the default (expanded) view when you want the full SQL and data inline.
**Stop Conversations**
While generating, you will see a "stop generation" button appear in the slack conversation.
Click on that button if you want to stop the chat from generating more text.
**What's Next?**
Learn about the web chat interface
Deploy nao with Docker or on your own Cloud Run instance
# Telegram Bot
Source: https://docs.getnao.io/nao-agent/connectors/telegram
Enable your team to ask questions directly in Telegram
The nao Telegram bot brings your analytics agent directly into Telegram, allowing your team to ask questions and get insights without leaving their workspace.
When configured, the nao Telegram bot:
* Responds to messages in Telegram groups and direct messages
* Processes questions using the same agent as the web chat interface
* Returns answers with SQL queries, data tables, and visualizations
* Maintains conversation context within reply threads
The Telegram bot uses the same agent context, rules, and capabilities as your
web chat interface. Any improvements to your agent automatically benefit
Telegram users.
## Setup
### Step 1: Create a Telegram Bot
1. Open Telegram and search for **@BotFather**
2. Send `/newbot`
3. Follow the prompts to choose a **name** and **username** for your bot
4. BotFather will send you a **Bot Token** — copy it
### Step 2: Connect your Telegram Bot to nao
1. In nao, click on your profile → **"Project"** → **"Telegram"**
2. Paste the **Bot Token** from BotFather
3. Click **"Save"**
4. Open a chat with your bot in Telegram and send `/login ` — you can find your **Linking Code** in the Telegram settings page
### Step 3: Configure Bot for Groups
If you want the bot to respond in group chats, you need to enable group access and disable privacy mode in BotFather:
1. Open **@BotFather** in Telegram
2. Send `/mybots` and select your bot
3. Go to **"Bot Settings"**
4. Click **"Allow Groups"** — make sure it is **turned on**
5. Click **"Group Privacy"** — set it to **disabled** so the bot can read all messages in the group
### Step 4: Add Bot to Groups
1. Open the Telegram group where you want the bot
2. Click **"Add Members"**
3. Search for your bot's username and add it
4. The bot will now respond to messages in that group
## Usage
**Ask Questions**
Mention the bot in any group where it's been added:
```
@nao What were our top 5 products by revenue last month?
```
The bot will:
1. Process your question using the same agent as the web interface
2. Generate SQL queries based on your context
3. Execute queries against your connected databases
4. Return results in a formatted Telegram message
5. Send you the link to the full conversation to open it in the browser UI
**Thread Conversations**
Follow-up questions using replies maintain context:
```
@nao What were our top 5 products by revenue last month?
# ... bot responds ...
# Reply to the bot's message:
Can you break that down by region?
# ... bot responds with regional breakdown ...
```
The bot replies in threads to keep conversations organized. Use replies for
follow-ups.
**Direct Messages**
You can also message the bot directly in a private chat (no mention required).
**What's Next?**
Configure authentication, models, and messaging integrations
Learn how the main chat interface works
# WhatsApp Bot
Source: https://docs.getnao.io/nao-agent/connectors/whatsapp
Enable your team to ask questions directly in WhatsApp
The nao WhatsApp bot brings your analytics agent directly into WhatsApp, allowing your team to ask questions and get insights without leaving their workspace.
When configured, the nao WhatsApp bot:
* Responds to messages in WhatsApp conversations and group chats
* Processes questions using the same agent as the web chat interface
* Returns answers with SQL queries, data tables, and visualizations
* Maintains conversation context within threads
The WhatsApp bot uses the same agent context, rules, and capabilities as your
web chat interface. Any improvements to your agent automatically benefit
WhatsApp users.
## Setup
### Step 1: Create a Meta App
1. Go to [developers.facebook.com/apps](https://developers.facebook.com/apps)
2. Click **"Create App"**
3. Fill in your app name and contact email
4. Select **"WhatsApp"** as the use case
5. Link a **business portfolio** to the app
6. Click **"Review and Create"**
### Step 2: Open the WhatsApp Integration in nao
1. In nao, click on your profile → **"Project"** → **"WhatsApp"**
2. Keep this page open — you will paste your credentials here in the next steps
### Step 3: Get App Credentials
1. From your app dashboard, click **"Personalize WhatsApp"**
2. Go to **"API Configuration"**
3. Click **"Generate access token"** — copy it and paste it as **Access Token** in nao
4. Copy the **Phone Number ID** and paste it in nao
The default access token expires in 24 hours. For production, generate a
permanent **System User Token** via Meta Business Settings.
5. Go to **App Settings** → **"General"**
6. Copy the **Secret Key** and paste it as **App Secret** in nao
7. In nao, fill in a **Verify Token** of your choice, then click **"Save"**
### Step 4: Configure the Webhook
1. Go back to **"Personalize WhatsApp"** → **"Configuration"**
2. Paste the **Webhook URL** and **Verify Token** from nao, then click **"Verify and Save"**
3. Subscribe to the **`messages`** webhook field
### Step 5: Link WhatsApp accounts to project users
Before a user can message the bot, their WhatsApp identity must be mapped to a nao project user. nao uses a per-user linking code for this.
**Admin side**
1. In nao, go to **Settings** -> **Project** -> **WhatsApp** -> **Users**.
2. For any project user, click **Generate linking code**. Each code is single-use and scoped to that user.
3. Share the code with the user through a secure channel.
Admins can regenerate or revoke codes from the same screen.
**User side**
1. Open WhatsApp and message the bot's phone number.
2. Send `/login ` using the code their admin generated.
3. The bot confirms that their WhatsApp account is now linked to the matching nao project user.
Once linked, messages from that WhatsApp account are attributed to the correct user in monitoring and audit views.
### Persistent memory and Redis
WhatsApp conversations share the same [user memory](/nao-agent/chat/capabilities/memory) as the web chat: preferences, definitions, and notes the user has asked nao to remember are preserved across WhatsApp sessions and across channels.
On self-hosted deployments, enabling Redis improves reliability under load and is recommended when you expect several WhatsApp users in parallel. See the [deployment guide](/nao-agent/self-hosting/deployment-guide) for Redis configuration.
## Usage
**Ask Questions**
Message the bot directly:
```
What were our top 5 products by revenue last month?
```
The bot will:
1. Process your question using the same agent as the web interface
2. Generate SQL queries based on your context
3. Execute queries against your connected databases
4. Return results in a formatted WhatsApp message
5. Send you the link to the full conversation to open it in the browser UI
**Thread Conversations**
Follow-up questions maintain context within the same conversation:
```
What were our top 5 products by revenue last month?
# ... bot responds ...
Can you break that down by region?
# ... bot responds with regional breakdown ...
```
**What's Next?**
Configure authentication, models, and messaging integrations
Learn how the main chat interface works
# Configuration
Source: https://docs.getnao.io/nao-agent/context-builder/configuration
Initialize and configure your nao project
# nao init
The `nao init` command sets up your context repository with all necessary files and structure.
**Run nao init**
```bash theme={null}
nao init
```
If `nao_config.yaml` already exists, `nao init` runs an update flow and lets you adjust existing configuration values instead of starting from scratch.
`nao init` keeps the interactive setup to the essentials - project name, one database, one LLM, and one git repository. Everything else (extra databases and repos, Slack, Notion, MCP servers, skills, profiling) is configured afterwards by editing `nao_config.yaml`.
**1. Project Name**
```
What is the name of your project?
> my-analytics-agent
```
The project name defaults to the current folder name - press Enter to accept it, or type a different name.
**2. Database Connection (Optional)**
```
Set up database connections? [Y/n]
> y
Select database type:
1. Snowflake
2. BigQuery
3. Databricks
4. PostgreSQL
...
```
If you say yes, you'll be prompted for the connection details specific to your database type. The database is scaffolded with the `columns` and `preview` templates (plus `ai_summary` when you configure an LLM); `profiling` and `query_history` are written as commented-out options you can enable later. See [Databases](/nao-agent/context-builder/databases) for the full template and connection reference.
**3. LLM API Key (Optional)**
```
Select your LLM provider:
1. OpenAI
2. Anthropic
3. Mistral
4. Gemini
5. OpenRouter
6. Ollama
7. AWS Bedrock
8. Google Vertex AI
```
If you use the Ollama provider you can skip the API key, as Ollama does not require one. To offer several providers or models, add them to the `llm.providers` list in `nao_config.yaml` afterwards - see [LLM providers and models](#llm-providers-and-models).
**4. Git Repository (Optional)**
```
Set up git repositories? [Y/n]
> y
Repository URL:
> https://github.com/your-org/dbt-project
Path within repo (optional):
> models/
```
`nao init` sets up a single database, LLM, and repo. To add more, or to configure Slack, Notion, MCP servers, or skills, edit `nao_config.yaml` directly - re-running `nao init` on an existing project opens an update flow instead of starting over.
**What Gets Created**
After running `nao init`, you'll have a folder with the architecture of your context:
```
my-analytics-agent/
├── nao_config.yaml # Main configuration file
├── RULES.md # Agent behavior rules
├── agent/ # Agent tools and integrations
│ ├── mcps/ # Model Context Protocols (MCP servers)
│ ├── skills/ # Reusable skills workflows
│ └── tools/ # Custom tools
├── databases/ # Database schemas (populated after sync)
└── docs/ # Documentation files (including synced Notion pages)
```
MCP servers are configured via the `agent/mcps/mcp.json` file, while skills are defined as markdown files in the `agent/skills/` folder. Both are part of your project context and are discovered automatically by the agent.
# nao sync
Once initialized, populate your context with actual content:
```bash theme={null}
nao sync
```
This will:
* Connect to configured databases and pull schemas
* Clone configured repositories
* Sync configured Notion pages into markdown files under `docs/notion/`
* Generate structured context files
* Index content for your agent
# nao debug
Verify your configuration:
```bash theme={null}
nao debug
```
This checks:
* Configuration file syntax
* Database connectivity
* LLM API access
* Environment variables
* File permissions
## nao\_config.yaml
The `nao_config.yaml` file is the central configuration for your analytics agent.
You can always edit it and re-launch a sync with this configuration.
**Basic Structure**
```yaml theme={null}
project_name: my-analytics-agent
# Database Connections
databases:
- name: bigquery-prod
type: bigquery
project_id: my-project
dataset_id: analytics
# Option 1: Use credentials_path for local files
credentials_path: /path/to/credentials.json
# Option 2: Use credentials_json for environment variables (recommended for cloud deployments)
# credentials_json: {{ env('GCP_SERVICE_ACCOUNT_KEY_JSON') }}
templates:
- columns
- preview
- query_history
# Optional (requires llm config below):
# - ai_summary
- profiling
profiling:
refresh_policy: always # always (default), interval, or once
interval_days: 7 # used only when refresh_policy: interval
# ai_summary takes the same refresh config (requires llm config below):
# ai_summary:
# refresh_policy: always
# interval_days: 7
include: []
exclude: []
sso: false
location: US
# Repository Integrations
repos:
- name: dbt
url: https://github.com/your-org/dbt-project.git
branch: main
# LLM Configuration
llm:
# Optional model used by database ai_summary templates (when ai_summary template is enabled)
annotation_model: claude-3-7-sonnet-latest
providers:
- provider: anthropic
api_key: {{ env('ANTHROPIC_API_KEY') }}
# Optional: restrict the models offered in the chat model picker
models:
- id: claude-sonnet-4-5
default: true
- id: claude-3-7-sonnet-latest
# Local Ollama example (no API key required)
- provider: ollama
# Slack Integration (optional)
slack:
bot_token: {{ env('SLACK_BOT_TOKEN') }}
signing_secret: {{ env('SLACK_SIGNING_SECRET') }}
post_message_url: https://slack.com/api/chat.postMessage
# Notion Integration (optional)
notion:
api_key: {{ env('NOTION_API_KEY') }}
pages:
- 0123456789abcdef0123456789abcdef
- fedcba9876543210fedcba9876543210
# Defaults for nao test (optional)
test:
models:
- anthropic:claude-sonnet-4-5
threads: 4
```
Every key is documented in the [configuration reference](/pages/configuration-reference). The `llm` block is detailed in [LLM providers and models](#llm-providers-and-models) below, and the `test` block in [Evaluation](/nao-agent/context-engineering/evaluation#defaults-in-nao_config-yaml).
**Environment Variables**
**Never commit sensitive credentials to Git!** Always use environment variables for secrets.
Store sensitive values in environment variables (examples for different providers and warehouses):
```bash theme={null}
# .env file (add to .gitignore)
# OpenAI / Anthropic / Azure OpenAI
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=...
# AWS Bedrock (choose one auth method)
# Option 1: bearer token
AWS_BEARER_TOKEN_BEDROCK=...
# Option 2: IAM credentials
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-east-1
# Warehouse credentials
SNOWFLAKE_USER=my_user
SNOWFLAKE_PASSWORD=my_password
# Slack
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...
AWS_REGION=us-east-1
```
Reference them in your config:
```yaml theme={null}
api_key: {{ env('OPENAI_API_KEY') }}
user: {{ env('SNOWFLAKE_USER') }}
password: {{ env('SNOWFLAKE_PASSWORD') }}
```
**Warehouse Credentials**
For Warehouse credentials, you can use either method:
**Method 1: credentials\_path (local development)**
```yaml theme={null}
databases:
- name: bigquery-prod
type: bigquery
project_id: my-project
dataset_id: analytics
credentials_path: /path/to/service-account.json
```
**Method 2: credentials\_json (cloud deployments)**
```yaml theme={null}
databases:
- name: bigquery-prod
type: bigquery
project_id: my-project
dataset_id: analytics
credentials_json: {{ env('GCP_SERVICE_ACCOUNT_KEY_JSON') }}
```
Use `credentials_json` for cloud deployments (Cloud Run, GitHub Actions, etc.) where you store the full JSON content in an environment variable or secret manager. Use `credentials_path` for local development with credential files.
When using `credentials_json`, the environment variable must contain the **entire JSON content** of your service account key file, not just the path.
## LLM providers and models
The `llm` block holds a list of providers under `providers`, and each provider lists the models it exposes. Use it to offer your users a choice of models, to expose models nao doesn't know about (a proxy alias, a fine-tune, a model released after your nao version), and to price each of them.
```yaml theme={null}
llm:
# Model used by database ai_summary templates and background tasks
annotation_model: gpt-4.1-mini
providers:
- provider: openai
api_key: {{ env('OPENAI_API_KEY') }}
models:
- id: gpt-4.1
default: true # preselected for new chats
- id: gpt-4.1-mini
name: GPT-4.1 mini # label shown in the model picker
costs: # US dollars per million tokens
input_no_cache: 0.4
input_cache_read: 0.1
output: 1.6
- provider: anthropic
api_key: {{ env('ANTHROPIC_API_KEY') }}
models:
- id: claude-sonnet-4-5
settings:
temperature: 0 # inference parameters for this model
```
A provider can appear only once, and only one model per provider can be the `default`. Leave `models` out to expose the provider's built-in model list, which is what `nao init` writes.
Every provider you declare is picked up by `nao chat`, `nao debug` and `nao test`. The chat server reads the block directly, so models, display names, inference settings and prices reach the app without any extra environment variable.
Providers declared in `nao_config.yaml` show up in the admin UI under **Settings** -> **Project** -> **Models**, badged `nao_config.yaml`. Editing one there saves an override in the database that keeps winning over the file until you delete it, so the file remains the durable source of truth. Resolution order per provider is **admin UI** -> `nao_config.yaml` -> environment variables. See [Choose LLM models](/nao-agent/chat/admin/setup#choose-llm-models).
### Provider credentials
AWS Bedrock with a bearer token:
```yaml theme={null}
llm:
providers:
- provider: bedrock
api_key: {{ env('AWS_BEARER_TOKEN_BEDROCK') }}
aws_region: us-east-1
```
AWS Bedrock with IAM credentials:
```yaml theme={null}
llm:
providers:
- provider: bedrock
access_key: {{ env('AWS_ACCESS_KEY_ID') }}
secret_key: {{ env('AWS_SECRET_ACCESS_KEY') }}
aws_region: {{ env('AWS_REGION') }}
```
Bedrock also accepts `aws_profile` to use a named AWS CLI profile, such as an SSO one, instead of inline keys.
Google Vertex AI, authenticating with Application Default Credentials, an inline service account or a key file:
```yaml theme={null}
llm:
providers:
- provider: vertex
gcp_project: my-gcp-project
gcp_location: us-east5
# service_account_json: {{ env('GCP_SERVICE_ACCOUNT_KEY_JSON') }}
# key_file: /path/to/service-account.json
```
### OpenRouter
[OpenRouter](https://openrouter.ai/) routes a single API key to models from many vendors, which is the quickest way to expose a model nao doesn't ship with:
```yaml theme={null}
llm:
providers:
- provider: openrouter
api_key: "{{ env('OPENROUTER_API_KEY') }}"
models:
- id: deepseek/deepseek-v4-flash
default: true
```
Model IDs are OpenRouter slugs in `vendor/model` form - copy them from the [model list](https://openrouter.ai/models) exactly as shown, including the vendor prefix. Leave `models` out to expose OpenRouter's built-in list instead.
If you omit `api_key`, nao falls back to the `OPENROUTER_API_KEY` environment variable. `base_url` (or `OPENROUTER_BASE_URL`) overrides the endpoint, for example to reach OpenRouter through a proxy.
Two things are worth adding once the provider works:
```yaml theme={null}
llm:
# Runs database ai_summary templates; defaults to openai/gpt-4.1-mini on OpenRouter
annotation_model: openai/gpt-4.1-mini
providers:
- provider: openrouter
api_key: "{{ env('OPENROUTER_API_KEY') }}"
models:
- id: deepseek/deepseek-v4-flash
name: DeepSeek V4 Flash # label shown in the model picker
default: true
costs: # US dollars per million tokens
input_no_cache: 0.27
input_cache_read: 0.03
output: 1.1
```
nao's built-in price table doesn't cover every OpenRouter slug, so declare `costs` on models you route through it - otherwise their calls are missing from the [usage dashboard](/nao-agent/chat/admin/monitoring), [budgets](/nao-agent/chat/admin/budgets) and `nao test` cost column. OpenRouter publishes the per-token prices on each model page; the values above are an example, not a quote.
Run `nao debug` after editing the file to check the key and endpoint before starting a chat.
### OpenAI-compatible proxy (LiteLLM)
To route the agent through an LLM proxy such as [LiteLLM](https://docs.litellm.ai/), keep the underlying provider and add `base_url` pointing at the proxy endpoint:
```yaml theme={null}
llm:
# Model used by database ai_summary templates, prefixed with the provider
annotation_model: openai/gpt-5.2
providers:
- provider: openai
api_key: {{ env('LITELLM_API_KEY') }}
base_url: http://0.0.0.0:4000
models:
- id: gpt-5.2
- id: claude-sonnet-4-5
```
`base_url` is supported on the `openai`, `anthropic`, and `openrouter` providers. `nao debug` runs its connectivity check against this base URL, so the test reflects the endpoint your agent actually calls.
Because a proxy exposes model IDs nao has no price for, list them under `models` with their `costs` to keep cost tracking accurate.
### Model costs
nao ships a price table for the models it knows and computes the cost of every call from it. A `costs` block on a model overrides that table token type by token type:
```yaml theme={null}
llm:
providers:
- provider: openai
api_key: {{ env('LITELLM_API_KEY') }}
base_url: http://0.0.0.0:4000
models:
- id: my-proxy-alias
name: Internal GPT
costs:
input_no_cache: 2.5
input_cache_read: 0.25
input_cache_write: 3.13
output: 10
```
All four fields are optional and expressed in US dollars per million tokens; the ones you leave out fall back to nao's table. These prices feed the [usage dashboard](/nao-agent/chat/admin/monitoring), the [budgets](/nao-agent/chat/admin/budgets), and the cost column of [`nao test`](/nao-agent/context-engineering/evaluation).
### Migrating an older config
Before multiple providers were supported, the `llm` block declared a single provider inline:
```yaml theme={null}
llm:
provider: anthropic
api_key: {{ env('ANTHROPIC_API_KEY') }}
annotation_model: claude-3-7-sonnet-latest
```
That shape still loads — nao reads it as a one-entry `providers` list — but it prints a deprecation warning and cannot express several providers or per-model costs. Rewrite it with:
```bash theme={null}
nao migrate
# Print the result without touching the file
nao migrate --dry-run
```
The command only touches the `llm` block and rewrites it as text, so comments and `{{ env('...') }}` references survive untouched.
`llm.meta.costs` is deprecated. Prices declared there apply to **every** model, which gets misleading as soon as you run more than one. Move them to the `costs` of the matching model.
**Next Steps**
Learn how to sync and update your agent's context
Learn how to optimize your context for reliability, speed, and cost
# Confluence
Source: https://docs.getnao.io/nao-agent/context-builder/confluence
Sync Confluence pages, spaces, and labels into your agent context
Add Confluence as a context provider to give your agent access to the documentation, wikis, and runbooks your team keeps in Confluence.
Works with both **Confluence Cloud** and **Confluence Data Center/Server**.
## Why add Confluence?
Syncing Confluence lets your agent:
* Answer questions using your team's internal documentation
* Reference business definitions, metric glossaries, and data dictionaries
* Use onboarding docs, runbooks, and process guides as context
## Adding Confluence
**During initialization**
When you run `nao init`, the wizard prompts for Confluence interactively:
```bash theme={null}
nao init
```
It asks for your base URL, the deployment flavour, credentials, and what to sync.
**Manual configuration**
Add a `confluence` block to your `nao_config.yaml`:
```yaml theme={null}
confluence:
base_url: https://acme.atlassian.net/wiki
deployment: cloud
email: {{ env('CONFLUENCE_EMAIL') }}
api_token: {{ env('CONFLUENCE_API_TOKEN') }}
spaces:
- DATA
```
Confluence needs the optional `confluence` extra:
```bash theme={null}
pip install 'nao-core[confluence]'
```
Never commit Confluence credentials to Git. Use `{{ env('VAR') }}` for every secret.
### Connection
| Field | Description |
| ------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `base_url` | Absolute HTTP(S) URL. `https://acme.atlassian.net/wiki` on Cloud, `https://confluence.acme.com` on Data Center/Server. |
| `deployment` | `cloud` (default) or `server`. |
### Authentication
Credentials differ by deployment.
Cloud uses Basic auth with an Atlassian API token. Both fields are required:
```yaml theme={null}
confluence:
base_url: https://acme.atlassian.net/wiki
deployment: cloud
email: {{ env('CONFLUENCE_EMAIL') }}
api_token: {{ env('CONFLUENCE_API_TOKEN') }}
spaces:
- DATA
```
Self-hosted takes either a personal access token (Bearer auth) or a username and password (Basic auth). Supply one or the other:
```yaml theme={null}
confluence:
base_url: https://confluence.acme.com
deployment: server
personal_access_token: {{ env('CONFLUENCE_PAT') }}
spaces:
- DATA
```
```yaml theme={null}
confluence:
base_url: https://confluence.acme.com
deployment: server
username: {{ env('CONFLUENCE_USERNAME') }}
password: {{ env('CONFLUENCE_PASSWORD') }}
spaces:
- DATA
```
### Choosing what to sync
Four selectors decide which pages are pulled. They combine, and a page selected twice is only synced once. **At least one of them must be set** - a `confluence` block with none of them fails validation.
| Selector | What it pulls |
| ------------ | ---------------------------------------------------------------------------------------------------- |
| `pages` | Individual pages, given as numeric page IDs or as URLs that carry one. |
| `page_trees` | A page **and all of its descendants**. Use this to take a whole section without listing every child. |
| `labels` | Every page carrying the label. Scope it to one space with `SPACE:label`. |
| `spaces` | A space in full, by its key (`ENG`, `DATA`). |
```yaml theme={null}
confluence:
base_url: https://acme.atlassian.net/wiki
deployment: cloud
email: {{ env('CONFLUENCE_EMAIL') }}
api_token: {{ env('CONFLUENCE_API_TOKEN') }}
pages:
- https://acme.atlassian.net/wiki/spaces/DATA/pages/123456/Metric+Glossary
page_trees:
- 789012
labels:
- DATA:certified
spaces:
- ENG
```
Only **pages** and **blog posts** are synced. Whiteboards, databases, and attachments expose no text through the API and are skipped.
## What happens at `nao sync`
When you run `nao sync`, nao:
1. Resolves every selector into a single set of pages
2. Fetches each page's body and converts the rendered HTML to markdown
3. Writes the markdown under `docs/confluence/` in your nao project
Each file carries YAML frontmatter with the page's `title`, `id`, `space`, `version`, and `url`, so the agent can cite the source page.
### Folder layout
Files mirror the Confluence page tree. Pages live under their space, then under a directory per ancestor. A page that has synced children keeps its own body in a file beside the directory holding them. Blog posts have no tree, so they collect under `blog/` in their space.
```
your-project/
└── docs/
└── confluence/
├── space=DATA/
│ ├── metric-glossary-123456.md
│ ├── runbooks-789012.md
│ └── runbooks-789012/
│ └── nightly-refresh-789013.md
└── space=ENG/
└── blog/
└── q3-platform-review-345678.md
```
### Incremental syncs
nao records each page's Confluence version in its frontmatter. On the next run, a page whose version is unchanged is left alone instead of being re-fetched, so repeat syncs only pay for what actually changed.
Pages that disappear from your selectors are cleaned up - but **only after a run that succeeded in full**. If any page failed to fetch or write, stale files are kept in place rather than risk deleting content over a transient error.
### Images
Confluence images point at attachment URLs that need the same auth as the API, so a bare reference is of no use to the agent. Every image is replaced with an `[image]` placeholder in the exported markdown.
## Editing synced files
Files under `docs/confluence/` are **read-only** in the [File Explorer](/nao-agent/chat/admin/file-explorer), the same as `docs/notion/` and `repos/`. They are replaced on every `nao sync`, so edits belong in Confluence itself, or in the `confluence` block of `nao_config.yaml`.
**Next Steps**
Initialize and configure your nao project
Learn how to sync and update your agent's context
# Custom Context
Source: https://docs.getnao.io/nao-agent/context-builder/custom-context
Your context repository is just a file system — customize it freely
Your context repository is **just a file system**. You have complete freedom to organize it however works best for your team.
## Customize your context
You can customize your context by:
* **Creating new folders** — Organize by team, domain, project, or any structure that makes sense
* **Adding new types of context** — There are no restrictions on what you can include
* **Using any file format** — Markdown, YAML, SQL, JSON, CSV, and more
## Examples of Custom Context
Here are some common types of context teams add to their repository:
* **Business Definitions** — How your organization defines key metrics (e.g., active customer, churn, revenue)
* **Query Examples** — Vetted SQL queries your team can reference
* **Dashboard References** — Links to trusted dashboards and reports
* **Glossary & FAQs** — Common questions, acronyms, and terminology
* **Data Quality Notes** — Known data gaps, timezone info, caveats
Your teammates can add to this structure too - the [Contributor Guide](/nao-agent/context-engineering/contributing) explains what belongs where and how the agent finds it.
Keep your custom context organized and modular. Ensure each metric has only one canonical definition, and organize context by domain to keep the agent focused and reduce token costs.
## Request New Context Types
Want a new type of context to be automatically synced? We'd love to hear from you.
Request new context types or suggest improvements on GitHub
**Next Steps**
Step-by-step guide to building effective context
Learn how to optimize your context structure
Use your fully configured agent
Deploy for your team
# Databases
Source: https://docs.getnao.io/nao-agent/context-builder/databases
Connect your data warehouses and databases
Connect your databases to give your agent access to table schemas, descriptions, and sample rows.
## Supported Databases
The current `nao` CLI supports these database types in `nao_config.yaml`:
* **Athena**
* **BigQuery**
* **ClickHouse**
* **Databricks**
* **DuckDB**
* **Fabric**
* **MotherDuck**
* **MSSQL**
* **MySQL**
* **Postgres**
* **Redshift**
* **Snowflake**
* **StarRocks**
* **Trino**
## Common Parameters
Every database entry supports these shared fields:
```yaml theme={null}
databases:
- name: warehouse_prod
type: snowflake
include:
- analytics.*
exclude:
- analytics.tmp_*
exclude_columns:
- "*._peerdb_*"
- "*.updated_at"
templates:
- columns
- query_history
- preview
```
* `name`: Friendly connection name
* `type`: One of `athena`, `bigquery`, `clickhouse`, `databricks`, `duckdb`, `fabric`, `motherduck`, `mssql`, `mysql`, `postgres`, `redshift`, `snowflake`, `starrocks`, `trino`
* `include`: Optional glob patterns for `schema.table` values to include
* `exclude`: Optional glob patterns for `schema.table` values to exclude
* `exclude_columns`: Optional glob patterns for `schema.table.column` values to hide (see below)
* `templates`: Optional list of rendered context files
### Excluding columns
`include` and `exclude` filter at the table level. To drop individual **columns** from everything the agent sees (column lists, previews, and profiling), use `exclude_columns`. Patterns are glob-matched against the fully-qualified `schema.table.column` name:
```yaml theme={null}
databases:
- name: warehouse_prod
type: snowflake
exclude_columns:
- "*._peerdb_*" # replication metadata on every table
- "analytics.orders.internal_notes" # one specific column
- "*.pii_*" # any column prefixed pii_
```
Use it to keep sensitive or noisy columns (PII, internal bookkeeping, replication metadata) out of the agent's context entirely. An empty or omitted list hides nothing.
The `templates` field used to be called `accessors`. The old key still works (nao will read it and migrate automatically), but new configs should use `templates`.
## Templates
These are the built-in templates nao can render per table:
* `columns` (default; schema plus the table description, row count, and partitioning metadata)
* `preview` (default; sample rows)
* `query_history` (default; AI-friendly per-table usage context built from your warehouse's query history)
* `profiling` (optional; column-level statistics)
* `ai_summary` (optional; AI-generated table summary)
If you omit `templates`, nao renders `columns`, `query_history`, and `preview` by default.
The table description, row count, partitioning, and clustering metadata all live in `columns.md`. `profiling.md` repeats the clustering columns on a short `Clustering:` line alongside its statistics.
`nao init` scaffolds a smaller set than the config-file default: `columns` and `preview`, plus `ai_summary` when you configure an LLM. It writes `profiling` and `query_history` as commented-out options you can uncomment later.
**Migrating from `how_to_use` / `description`.** The `how_to_use` template was renamed to `query_history`, and the standalone `description` template was removed (its content moved into `columns.md`). Both old names still load: nao migrates them automatically and prints a `FutureWarning` asking you to rename `how_to_use` to `query_history` (and drop `description`) in `nao_config.yaml`. Existing `how_to_use.md` and `description.md` files already written to disk are not removed automatically yet (tracked in getnao/nao#1193), so delete them by hand for now.
### `query_history`
`query_history` produces a single markdown file per table built from your warehouse's query history:
* How often the table is queried
* The tables it is most often joined with
* The most frequently run queries against it
Table metadata (description, row count, and partitioning) is not in this file: it now lives in `columns.md`.
To enable query-history-based context, set `query_history_days` on the database (defaults to 0, meaning no history is fetched):
```yaml theme={null}
databases:
- name: warehouse_prod
type: snowflake
query_history_days: 30
templates:
- columns
- query_history
- preview
```
Query history is supported on BigQuery, Snowflake, Databricks, Postgres, and Redshift. On warehouses without history support, `query_history` reports that no history was found; the table metadata is always available in `columns.md`.
#### Customizing query history
Two optional fields let you control which queries feed into the `query_history` analysis:
**`query_history_sql`** overrides the built-in history query for a database. The SQL must return a `query_text` column. Use the `{days}` placeholder to inject the configured `query_history_days` value:
```yaml theme={null}
databases:
- name: warehouse_prod
type: snowflake
query_history_days: 30
query_history_sql: |
SELECT regexp_replace(query_text, '-- Looker.*$', '', 1, 0, 'm') AS query_text
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD(day, -{days}, CURRENT_TIMESTAMP())
AND execution_status = 'SUCCESS'
AND query_type = 'SELECT'
LIMIT 10000
```
This is useful when you need to strip BI tool comments (e.g. Looker slugs) before grouping, or to query a custom history table.
**`query_history_exclude_patterns`** filters out noise after fetching. Each entry is a case-insensitive regex. Any query whose text matches at least one pattern is dropped before analysis:
```yaml theme={null}
databases:
- name: warehouse_prod
type: snowflake
query_history_days: 30
query_history_exclude_patterns:
- 'SYSTEM\$'
- '^SELECT CURRENT_SESSION\(\)'
- '^SHOW '
```
When patterns are active, the console output reports how many queries were excluded so you can iterate on the list.
### `profiling`
Use the optional `profiling` config block to control profiling refresh behavior:
```yaml theme={null}
databases:
- name: warehouse_prod
type: snowflake
templates:
- columns
- preview
- profiling
profiling:
refresh_policy: interval # always (default), interval, or once
interval_days: 7 # used only when refresh_policy: interval
```
* `refresh_policy`: When to recompute profiling. One of `always` (default, every sync), `interval` (every `interval_days` days), or `once` (only when the file is missing).
* `interval_days`: Refresh interval in days when `refresh_policy: interval` (default `7`, minimum `1`).
Profiling works across all supported warehouses for primitive columns and for complex column types (`array`, `struct`, `map`, `json`, `row`, `tuple`, `variant`, `object`, `super`). For array columns, nao unpacks the values before computing distinct counts and top values; other complex types are stringified before profiling.
### `ai_summary`
`ai_summary` is opt-in. To use it, add `ai_summary` to `templates` and configure `llm.annotation_model` in `nao_config.yaml`.
When enabled, nao renders `databases/ai_summary.md.j2` and calls `prompt("...")` inside that template to generate LLM-based summaries during `nao sync`.
The summary bases its data-quality and distribution notes on the full-table profiling statistics (empty counts, unique counts, min/max, most common values), which nao computes once per table and shares with the summary. The preview rows are treated as a small, non-representative sample, so the summary never infers data quality from them; when no profiling statistics are available it stays silent about data quality.
`ai_summary` takes the same refresh config as `profiling`:
```yaml theme={null}
databases:
- name: warehouse_prod
type: snowflake
templates:
- columns
- preview
- ai_summary
ai_summary:
refresh_policy: interval # always (default), interval, or once
interval_days: 7 # used only when refresh_policy: interval
```
* `refresh_policy`: When to regenerate the summary. Defaults to `always`, so existing setups keep regenerating it on every sync.
* `interval_days`: Refresh interval in days when `refresh_policy: interval` (default `7`, minimum `1`).
## Database Parameters
### Athena
```yaml theme={null}
databases:
- name: athena_prod
type: athena
s3_staging_dir: s3://my-query-results/athena/
region_name: us-east-1
schema_name: analytics
work_group: primary
profile_name: default
```
Optional auth fields:
* `profile_name`
* `aws_access_key_id`
* `aws_secret_access_key`
* `aws_session_token`
### BigQuery
```yaml theme={null}
databases:
- name: bigquery_prod
type: bigquery
project_id: my-gcp-project
dataset_id: analytics
credentials_path: /path/to/service-account.json
sso: false
location: US
max_query_size: 5
partition_filters:
events: "event_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)"
```
You can authenticate with either:
* `credentials_path`
* `credentials_json`
* `sso: true` for ADC / browser auth
Optional for partitioned tables:
* `partition_filters`: map of `table_name: SQL filter` used for preview queries on tables that enforce `require_partition_filter = TRUE`
#### Limit query size
Use `max_query_size` (in GB) to cap how much data a single query can scan. When set, nao runs a BigQuery dry run before every SQL execution and rejects the query if the estimated bytes processed exceed the limit.
```yaml theme={null}
databases:
- name: bigquery_prod
type: bigquery
project_id: my-gcp-project
dataset_id: analytics
max_query_size: 5 # GB
```
* The check applies to **every query** the agent runs - chat, stories, evaluations, anything going through `nao chat` or `nao test`.
* The limit is enforced before BigQuery scans the data, so blocked queries cost nothing.
* Errors include the estimated bytes and the configured limit so the agent can suggest a tighter filter and retry.
* Leave the field unset (or set it to `0`) to disable the check.
When you create a BigQuery connection through `nao init`, the CLI prompts for a maximum query size as part of the interactive setup. The same field is available in the IDE under **Settings -> Warehouse Connections** for the cloud and IDE flows.
#### Partition vs clustering columns
For BigQuery tables, `nao sync` exposes **partition columns** and **clustering columns** as separate sections in each table's context. The agent uses partition columns to enforce `WHERE` filters that prune scanned bytes, and clustering columns to recommend the right join keys and predicate ordering for performance. Both are detected automatically - no config required.
Both partition and clustering columns are written to `columns.md`; `profiling.md` also repeats the clustering columns on a short `Clustering:` line. Columns hidden via `exclude_columns` are filtered out of the clustering list too.
#### Native column types
For BigQuery, generated context reports the **native** BigQuery type rather than the normalized internal type. This matters because `DATETIME` and `TIMESTAMP` are distinct in BigQuery, and documenting both as a generic timestamp led the agent to write invalid SQL (for example `TIMESTAMP_SUB` on a `DATETIME` column). `columns.md`, `ai_summary.md`, and `profiling.md` now show `INT64`, `DATETIME`, `TIMESTAMP`, and so on. If the `INFORMATION_SCHEMA` lookup fails, nao falls back to the normalized types.
Separately, 64-bit integer columns are no longer reported as `int32` in profiling on any warehouse. BigQuery `INT64`, Snowflake `INTEGER` / `NUMBER(38,0)`, and `BIGINT` columns elsewhere are now profiled with their true width.
### ClickHouse
```yaml theme={null}
databases:
- name: clickhouse_prod
type: clickhouse
host: your-instance.clickhouse.cloud
port: 8443
database: analytics
user: default
password: "{{ env('CLICKHOUSE_PASSWORD') }}"
secure: true
templates:
- columns
- preview
```
ClickHouse table and index metadata (ORDER BY, PARTITION BY, projections, primary key) is rendered inside `columns.md`, so no separate template is needed for it.
**Connection protocol**
nao connects to ClickHouse over the HTTP interface by default. For deployments that only expose the **native TCP protocol** (ports 9000 / 9440), set `protocol: native`:
```yaml theme={null}
databases:
- name: clickhouse_native
type: clickhouse
host: your-instance.example.com
port: 9440
database: analytics
user: default
password: "{{ env('CLICKHOUSE_PASSWORD') }}"
secure: true
protocol: native # "http" (default) or "native"
```
`protocol: http` (the default) keeps the existing behavior, no change needed for current configs. When prompting with `nao init`, the default port flips automatically based on the protocol you pick (8123/8443 for HTTP, 9000/9440 for native).
`secure: true` means HTTPS on `protocol: http` and TLS over TCP on `protocol: native`. On the native protocol you can add `verify: false` to skip TLS certificate verification (for example against a self-signed internal certificate):
```yaml theme={null}
databases:
- name: clickhouse_native
type: clickhouse
host: clickhouse.internal
port: 9440
database: analytics
user: default
password: "{{ env('CLICKHOUSE_PASSWORD') }}"
protocol: native
secure: true
verify: false
```
Two optional timeouts are passed through to the underlying client on both protocols: `connect_timeout` and `send_receive_timeout`, both in seconds.
### Databricks
```yaml theme={null}
databases:
- name: databricks_prod
type: databricks
server_hostname: adb-1234567890123456.7.azuredatabricks.net
http_path: /sql/1.0/warehouses/abc123
access_token: "{{ env('DATABRICKS_TOKEN') }}"
catalog: main
schema_name: analytics
```
**Read-only default schema**
Some queries need to stage temporary data in Unity Catalog. If the schema nao connects to is read-only, those queries fail. Set `temp_schema` to a schema you have write access to:
```yaml theme={null}
databases:
- name: databricks_prod
type: databricks
server_hostname: adb-1234567890123456.7.azuredatabricks.net
http_path: /sql/1.0/warehouses/abc123
access_token: "{{ env('DATABRICKS_TOKEN') }}"
catalog: main
schema_name: analytics # read-only, still used for table discovery
temp_schema: scratch # writable, used for temporary storage
```
When `temp_schema` is set it becomes the connection schema in place of `schema_name`; `schema_name` still drives which schema is synced. `nao init` prompts for it during interactive setup. Leave it unset if your default schema is already writable.
### DuckDB
```yaml theme={null}
databases:
- name: warehouse_local
type: duckdb
path: ./jaffle_shop.duckdb
```
`duckdb_local` is a reserved connection name - it addresses [nao's own DuckDB engine](/nao-agent/chat/capabilities/files-and-storage#querying-files-with-sql). A warehouse configured under that name would be unreachable.
Local DuckDB connections are locked down right after connect: `enable_external_access` is set to `false` and the configuration is locked. Agent and user SQL on that connection therefore cannot read local files, fetch remote URLs, or `ATTACH` another database - it only sees the tables in the file you pointed at. To let the agent query files, use the built-in `duckdb_local` engine instead.
MotherDuck paths keep external access on, since the service itself is remote. See [MotherDuck](#motherduck).
### Fabric
```yaml theme={null}
databases:
- name: fabric_prod
type: fabric
server: myworkspace.datawarehouse.fabric.microsoft.com
database: analytics
schema_name: dbo
auth_method: azure_cli
```
Fabric supports these authentication modes:
* `sql_password` (SQL username/password)
* `azure_cli` (`az login` token)
* `azure_interactive` (browser login)
* `azure_service_principal` (client ID and secret)
### MotherDuck
[MotherDuck](https://motherduck.com) is DuckDB's managed cloud, and it has its own `motherduck` type. `nao init` offers it as a choice, so you no longer have to know DuckDB's connection-string convention:
```yaml theme={null}
databases:
- name: motherduck
type: motherduck
database: my_database # optional, defaults to your account's default database
token: "{{ env('MOTHERDUCK_TOKEN') }}" # optional, see below
```
nao builds the `md:` connection path for you and connects through the same DuckDB engine underneath.
**Authentication.** Prefer `{{ env('MOTHERDUCK_TOKEN') }}` in `nao_config.yaml` so the token stays out of the file. Leave `token` out entirely and DuckDB reads the `MOTHERDUCK_TOKEN` (or `motherduck_token`) environment variable itself:
```bash theme={null}
export MOTHERDUCK_TOKEN=
```
Existing configs that use `type: duckdb` with an `md:` path keep working - nao detects the MotherDuck path and connects as before. New configs should use `type: motherduck`.
### MSSQL
```yaml theme={null}
databases:
- name: mssql_prod
type: mssql
host: sqlserver.example.com
port: 1433
database: analytics
user: "{{ env('MSSQL_USER') }}"
password: "{{ env('MSSQL_PASSWORD') }}"
driver: FreeTDS
schema_name: dbo
```
### MySQL
```yaml theme={null}
databases:
- name: mysql_prod
type: mysql
host: mysql.example.com
port: 3306
database: analytics
user: "{{ env('MYSQL_USER') }}"
password: "{{ env('MYSQL_PASSWORD') }}"
```
### Postgres
```yaml theme={null}
databases:
- name: postgres_prod
type: postgres
host: postgres.example.com
port: 5432
database: analytics
user: "{{ env('POSTGRES_USER') }}"
password: "{{ env('POSTGRES_PASSWORD') }}"
schema_name: public
```
### Redshift
```yaml theme={null}
databases:
- name: redshift_prod
type: redshift
host: cluster.region.redshift.amazonaws.com
port: 5439
database: analytics
user: "{{ env('REDSHIFT_USER') }}"
password: "{{ env('REDSHIFT_PASSWORD') }}"
schema_name: public
sslmode: require
```
Optional SSH tunnel:
```yaml theme={null}
databases:
- name: redshift_prod
type: redshift
host: cluster.region.redshift.amazonaws.com
port: 5439
database: analytics
user: "{{ env('REDSHIFT_USER') }}"
password: "{{ env('REDSHIFT_PASSWORD') }}"
ssh_tunnel:
ssh_host: bastion.example.com
ssh_port: 22
ssh_username: ec2-user
ssh_private_key_path: ~/.ssh/id_rsa
ssh_private_key_passphrase: "{{ env('SSH_KEY_PASSPHRASE') }}"
```
### Snowflake
```yaml theme={null}
databases:
- name: snowflake_prod
type: snowflake
username: "{{ env('SNOWFLAKE_USER') }}"
account_id: xy12345.us-east-1
password: "{{ env('SNOWFLAKE_PASSWORD') }}"
database: ANALYTICS
warehouse: COMPUTE_WH
schema_name: PUBLIC
```
Snowflake also supports:
* `private_key_path`
* `passphrase`
* `authenticator`
* `token`
For SSO, use:
```yaml theme={null}
databases:
- name: snowflake_prod
type: snowflake
username: "{{ env('SNOWFLAKE_USER') }}"
account_id: xy12345.us-east-1
database: ANALYTICS
warehouse: COMPUTE_WH
authenticator: externalbrowser
```
For Programmatic Access Token (PAT) authentication:
```yaml theme={null}
databases:
- name: snowflake_prod
type: snowflake
username: "{{ env('SNOWFLAKE_USER') }}"
account_id: xy12345.us-east-1
database: ANALYTICS
warehouse: COMPUTE_WH
authenticator: programmatic_access_token
token: "{{ env('SNOWFLAKE_PAT') }}"
```
#### Snowflake semantic views
When the agent runs `nao sync` against Snowflake, it also imports any **semantic views** declared in your account. Each view (metrics, dimensions, relationships pulled from `INFORMATION_SCHEMA.SEMANTIC_VIEWS`) is written to:
```text theme={null}
databases/type=snowflake/database=/schema=/semantic_view=/definition.md
```
The agent reads these alongside table metadata, so business definitions you maintain in Snowflake flow into the context layer with no extra config. If your account has no semantic views (or the view feature isn't available on your edition), `nao sync` skips the step silently. No new fields in `nao_config.yaml`.
### StarRocks (`type: starrocks`)
```yaml theme={null}
databases:
- name: starrocks_prod
type: starrocks
host: starrocks.example.com
port: 9030
database: analytics
user: "{{ env('STARROCKS_USER') }}"
password: "{{ env('STARROCKS_PASSWORD') }}"
```
StarRocks supports multi-catalog environments. To sync tables from multiple catalogs, add a `catalogs` list:
```yaml theme={null}
databases:
- name: starrocks_prod
type: starrocks
host: starrocks.example.com
port: 9030
database: analytics
user: "{{ env('STARROCKS_USER') }}"
password: "{{ env('STARROCKS_PASSWORD') }}"
catalogs:
- default_catalog
- iceberg_catalog
```
StarRocks uses a dedicated connector (not the MySQL connector) to avoid transaction-related errors with `SHOW` statements.
### Trino
```yaml theme={null}
databases:
- name: trino_prod
type: trino
host: trino.example.com
port: 8080
catalog: iceberg
user: "{{ env('TRINO_USER') }}"
password: "{{ env('TRINO_PASSWORD') }}"
schema_name: analytics
```
**HTTPS and TLS**
nao talks to Trino over plain HTTP by default. Set `http_scheme: https` for TLS-only coordinators (Starburst, Stackable, OPA-authorized OSS Trino). Password authentication requires it:
```yaml theme={null}
databases:
- name: trino_prod
type: trino
host: trino.example.com
port: 8443
catalog: iceberg
user: "{{ env('TRINO_USER') }}"
password: "{{ env('TRINO_PASSWORD') }}"
schema_name: analytics
http_scheme: https
verify: /etc/ssl/certs/internal-ca.pem
```
`verify` controls certificate validation and only applies when `http_scheme: https`:
* `true` (default): verify against the system CA bundle
* `false`: disable verification
* a path: verify against a custom CA bundle, for an internal CA
**JWT bearer authentication**
For coordinators behind Trino's OAuth2/JWT authenticator, pass a bearer token instead of a password:
```yaml theme={null}
databases:
- name: trino_prod
type: trino
host: trino.example.com
port: 8443
catalog: iceberg
user: "{{ env('TRINO_USER') }}"
schema_name: analytics
jwt_token: "{{ env('TRINO_JWT') }}"
```
If your tokens are short-lived, point `jwt_token_file` at a file that an external refresher rewrites. nao re-reads the file on every connection, so rotation needs no config change:
```yaml theme={null}
databases:
- name: trino_prod
type: trino
host: trino.example.com
port: 8443
catalog: iceberg
user: "{{ env('TRINO_USER') }}"
jwt_token_file: /var/run/trino/token
```
Precedence when several are set: `jwt_token_file`, then `jwt_token`, then `password`. A JWT always forces `http_scheme: https`, even if the config says `http`, so the bearer token is never sent in cleartext. If the token file is missing or empty, nao falls back to `jwt_token` and then to password auth.
When syncing Trino tables, nao automatically imports table-level comments from `system.metadata.table_comments` and column-level comments from `DESCRIBE`. These comments appear in the generated context files alongside schema metadata, giving the agent richer descriptions without any extra configuration.
## SQL dialect handling
When the agent generates SQL, nao auto-detects the warehouse dialect from the target database's `type` and injects extra rules into the system prompt so the query uses the right syntax:
* **T-SQL (MSSQL, Fabric)**: use `TOP N` instead of `LIMIT`.
* **BigQuery**: quote identifiers with backticks and use `SAFE_DIVIDE(a, b)` instead of `a / b` to avoid divide-by-zero errors.
* **MySQL**: quote identifiers with backticks and use `IFNULL` instead of `COALESCE` for null handling.
PostgreSQL, Snowflake, Redshift, Databricks, and other standard SQL warehouses do not get extra dialect rules - the agent falls back to standard SQL. If a chat uses several databases of different types, the rules for each are scoped to queries targeting that database.
## Synchronization
Once configured, sync your database schemas:
```bash theme={null}
nao sync
```
This will:
1. Connect to each database
2. Extract schema information
3. Render the configured templates
4. Save the output in `databases/`
## Context Files
After syncing, you'll see a structure like:
```text theme={null}
databases/
└── type=bigquery/
└── database=my-gcp-project/
└── schema=analytics/
└── table=dim_users/
├── annotations.md
├── columns.md
├── preview.md
├── query_history.md
└── profiling.md
```
### Generated vs manual files
Every file `nao sync` writes carries a YAML frontmatter header saying who owns it, so it is clear at a glance what will be overwritten on the next sync.
Generated files (`columns.md`, `preview.md`, `query_history.md`, `profiling.md`, `ai_summary.md`, and Snowflake `definition.md`) start with:
```yaml theme={null}
---
type: generated
comment: This file is managed and generated by the agent, do not modify it!
---
```
Alongside them, `nao sync` creates one `annotations.md` per table folder for your own notes:
```yaml theme={null}
---
type: manual
comment: These are manual notes you want the agent to keep in mind, safe to edit.
---
```
`annotations.md` is created empty below the header, and **never overwritten**: subsequent syncs leave an existing file untouched. Use it for table-specific rules the agent should honor that your warehouse metadata does not carry, for example "this table double-counts refunds, join to `fct_refunds` to net them out". For rules that apply across tables, use [RULES.md](/nao-agent/context-builder/rules-context) instead.
Example generated files:
**`columns.md`**
```markdown theme={null}
---
type: generated
comment: This file is managed and generated by the agent, do not modify it!
---
# dim_users
**Dataset:** `prod_silver`
## Description
Registry of all users, one row per user.
## Table Metadata
| Property | Value |
|----------|-------|
| **Row Count** | 2,198 |
## Columns (5)
- user_id (INT64)
- email (STRING)
- username (STRING)
- created_at (TIMESTAMP)
- is_paying (BOOL)
```
The table description, row count, and partitioning metadata all live in `columns.md`. There is no separate `description.md` file anymore.
**`preview.md`**
```markdown theme={null}
# dim_users - Preview
**Dataset:** `prod_silver`
## Rows (10)
- {"user_id": 101, "email": "user_101@example.com", "username": "user_101", "created_at": "2026-03-01 10:15:00+00:00", "is_paying": true}
- {"user_id": 102, "email": "user_102@example.com", "username": "user_102", "created_at": "2026-03-02 08:05:42+00:00", "is_paying": false}
```
**`profiling.md`**
```markdown theme={null}
# dim_users - Profiling
**Dataset:** `prod_silver`
**Computed at:** `2026-03-14T18:57:58.672988+00:00`
**Columns:** 12
**Clustering:** `country`, `created_at`
## Column Profiles (JSONL)
- {"column": "user_id", "type": "INT64", "total_count": 2198, "null_count": 0, "null_percentage": 0.0, "distinct_count": 2198}
- {"column": "is_paying", "type": "boolean", "total_count": 2198, "null_count": 0, "null_percentage": 0.0, "distinct_count": 2, "top_values": [{"value": false, "count": 698}, {"value": true, "count": 1500}]}
- {"column": "country", "type": "STRING", "total_count": 1598, "null_count": 600, "null_percentage": 27.29, "distinct_count": 56, "top_values": [{"value": "US", "count": 500}, {"value": "FR", "count": 300}, {"value": "ES", "count": 200}]}
```
The `**Clustering:**` line only appears when the warehouse reports clustering, sort key, or liquid clustering columns for the table.
## Table Selection
Control which tables are synced with `include` and `exclude`.
Use glob patterns on `schema.table`:
* `analytics.orders`
* `analytics.*`
* `*.orders`
* `*_staging`
* `test_*`
* `*`
If both are set, nao applies `include` first and then removes matches from `exclude`.
```yaml theme={null}
databases:
- name: warehouse_prod
type: snowflake
include:
- analytics.*
- marts.fct_*
exclude:
- analytics.tmp_*
- marts.fct_*_backup
```
## Best Practices
* Start with your core schemas only
* Keep `templates` small if token usage matters
* Use `include` and `exclude` to avoid temp, backup, and test tables
Learn how to find the optimal balance between comprehensiveness and efficiency
# Git Repository Setup
Source: https://docs.getnao.io/nao-agent/context-builder/git-repository
Turn your local context folder into a GitHub repository
Convert your local nao project into a Git repository for version control, collaboration, and automated deployments.
Once your context lives in a GitHub repository, you can also connect it to nao chat so context admins can edit context files and open pull requests from the browser. See [Git](/nao-agent/chat/admin/git).
## Why Use Git?
Using Git for your nao project provides:
* **Version Control**: Track changes to your context files
* **Collaboration**: Multiple team members can contribute
* **Automated Sync**: Use GitHub Actions to automatically sync context
* **Deployment**: Easy integration with cloud deployments (Cloud Run, etc.)
* **Backup**: Your context is safely stored in the cloud
## Initial Setup
### Step 1: Initialize Git Repository
Navigate to your nao project directory:
```bash theme={null}
cd your-nao-project
```
Initialize a Git repository:
```bash theme={null}
git init
```
### Step 2: Create .gitignore
Create a `.gitignore` file to exclude sensitive files:
```gitignore theme={null}
# Environment variables
.env
# Credentials (never commit these!)
credentials/
# Python
venv/
```
**Never commit credentials or API keys to Git!** Always use environment variables or secret management systems.
### Step 3: Configure for Git
Update your `nao_config.yaml` to use environment variables instead of file paths:
**Before (local files):**
```yaml theme={null}
databases:
- name: bigquery-prod
type: bigquery
project_id: my-project
dataset_id: analytics
credentials_path: /path/to/credentials/key-file.json
```
**After (environment variables):**
```yaml theme={null}
databases:
- name: bigquery-prod
type: bigquery
project_id: my-project
dataset_id: analytics
credentials_json: {{ env('GCP_SERVICE_ACCOUNT_KEY_JSON') }}
```
### Step 4: Add and Commit Files
Add your project files:
```bash theme={null}
git add .
git commit -m "Initial nao project"
```
### Step 5: Create GitHub Repository
1. Go to [GitHub](https://github.com) and create a new repository
2. **Don't** initialize with README, .gitignore, or license (you already have these)
3. Copy the repository URL
### Step 6: Connect to GitHub
Add the remote and push:
```bash theme={null}
git remote add origin https://github.com/your-username/your-repo.git
git branch -M main
git push -u origin main
```
## Best Practices
**1. Use Environment Variables**
Always use environment variables for secrets:
```yaml theme={null}
# ✅ Good
credentials_json: {{ env('GCP_SERVICE_ACCOUNT_KEY_JSON') }}
api_key: ${OPENAI_API_KEY}
# ❌ Bad - Never do this
credentials_path: ./credentials/key.json
api_key: sk-abc123...
```
**2. Store Secrets Securely**
**For Local Development:**
* Use `.env` file (in `.gitignore`)
* Load with `source .env` or a tool like `direnv`
**For Cloud Deployments:**
* Use secret management (Google Secret Manager, AWS Secrets Manager, etc.)
* Store in GitHub Secrets for Actions
**For GitHub Actions:**
* Go to repository Settings → Secrets and variables → Actions
* Add secrets for all credentials used in `nao_config.yaml`
**3. Commit Synced Context**
After running `nao sync`, commit the generated context files:
```bash theme={null}
nao sync
git add databases/ docs/ repos/
git commit -m "Update context from nao sync"
git push
```
**4. Use Automated Sync**
Set up [GitHub Actions](/nao-agent/context-builder/synchronization#github-actions) to automatically sync and commit context updates.
## Deployment Integration
**Cloud Run Deployment**
When deploying to Cloud Run:
1. Connect your GitHub repository to Cloud Build
2. Cloud Build automatically builds and deploys on push
3. Use Secret Manager for credentials
4. Environment variables are set in Cloud Run configuration
See the [Self-Hosting Guide](/nao-agent/self-hosting/deployment-guide#3-deploy-nao-to-google-cloud-run) for Cloud Run deployment details.
**Dockerfile**
Create a `Dockerfile` in your repository:
```dockerfile theme={null}
FROM getnao/nao:latest
# Copy your project
COPY . /app/project/
# Set working directory
WORKDIR /app/project
```
Create a `.dockerignore`:
```
.env
venv/
.git/
.gitignore
credentials/
```
**What's Next?**
Set up automated syncing with GitHub Actions
Deploy your repository to production (local Docker & Cloud Run)
# Notion
Source: https://docs.getnao.io/nao-agent/context-builder/notion
Sync Notion pages into your agent context
Add Notion as a context provider to give your agent access to documentation, wikis, and knowledge bases stored in Notion.
## Why Add Notion?
Syncing Notion pages allows your agent to:
* Answer questions using your team's documentation
* Reference business definitions and glossaries
* Use onboarding docs, runbooks, and process guides as context
## Adding Notion
**During initialization**
When you run `nao init`, you can add Notion interactively:
```bash theme={null}
nao init
```
The wizard will prompt for your Notion API key and the page IDs you want to sync.
**Manual configuration**
Add Notion to your `nao_config.yaml`:
```yaml theme={null}
notion:
api_key: {{ env('NOTION_API_KEY') }}
pages:
- 0123456789abcdef0123456789abcdef
- fedcba9876543210fedcba9876543210
```
* **`api_key`** — Your Notion integration API key. Use `{{ env('NOTION_API_KEY') }}` to reference an environment variable.
* **`pages`** — List of Notion page IDs to sync. You can find a page ID in the page URL: `https://notion.so/workspace/Page-ID-here`.
Never commit your Notion API key to Git. Use environment variables for secrets.
## What Happens at nao sync
When you run `nao sync`, nao:
1. Connects to Notion using your API key
2. Fetches each configured page and its content
3. Exports pages as markdown files
4. Writes them under `docs/notion/` in your nao project
```
your-project/
└── docs/
└── notion/
├── page-1.md
├── page-2.md
└── ...
```
The exported markdown becomes part of your agent's context. Users can ask questions like "What does our onboarding doc say about X?" and the agent will read from the synced Notion content.
## Databases and embedded views
`nao sync` exports Notion **databases** as markdown tables, both when a database is listed directly in `notion.pages` and when it is embedded inside a synced page. Previously an embedded database was replaced with a `child_database is not supported` note, leaving the file looking complete but missing the data - now the table content comes through.
A database referenced by its plain URL is exported in full, without any view-specific filtering. To export a specific **view** instead, include its `?v=` in the URL - the export then applies that view's filters, sorts, row order, and visible columns, so the agent sees the same slice you see in Notion.
```yaml theme={null}
notion:
api_key: {{ env('NOTION_API_KEY') }}
pages:
- https://notion.so/workspace/Metrics-Glossary-0123456789abcdef # full database
- https://notion.so/workspace/Roadmap-abcdef0123456789?v=fedcba987654 # one view
```
**Next Steps**
Initialize and configure your nao project
Learn how to sync and update your agent's context
# Overview
Source: https://docs.getnao.io/nao-agent/context-builder/overview
Create the perfect context for your analytics agent
## Why a Context Builder?
The Context Builder helps you build a file-system for your agent. This way you can have a full transparent view on the context your agent has access to, and engineer the right way to:
1. **Make it reliable for users** - Ensure consistent, accurate responses
2. **Make it optimized** - Reduce costs, tokens usage, and improve speed
Context is built with nao using a CLI. The CLI will initialize your context repository and sync your context within this repo.
## How It Works
nao uses a file-system approach to organize context:
```
your-project/
├── nao_config.yaml # Main configuration
├── RULES.md # Agent behavior rules
├── agent/ # Agent tools and integrations
│ ├── mcps/ # Model Context Protocols (MCP servers)
│ ├── skills/ # Reusable skills workflows
│ └── tools/ # Custom tools
├── databases/ # Database context
├── docs/ # Documentation files (including synced Notion pages)
├── repos/ # Cloned repositories
└── semantics/ # Semantic context
```
## Build Your Context
Learn how to add different types of context:
Connect databases and sync schemas
Add dbt projects and documentation
Define how your agent should behave
## Next Steps
Learn how to initialize and configure your nao project
Core principles for optimizing your agent's performance
Step-by-step guide to building effective context for your analytics agent
Test and evaluate your agent with unit tests
# Repositories
Source: https://docs.getnao.io/nao-agent/context-builder/repos
Add Git repositories to your agent context
Add repositories to your agent context to give it access to dbt repo, semantic layers (Cube, LookML, etc.), documentation, or anything else.
## Why Add Repositories?
Adding repositories to your context allows your agent to:
* Access dbt model definitions and documentation
* Access your semantic layer repository
* Reference internal documentation
## Adding a Repository
When you run `nao init`, you can add a repository interactively:
```bash theme={null}
nao init
```
Or add repositories in your `nao_config.yaml`:
```yaml theme={null}
repos:
- name: dbt_project
url: https://github.com/your-org/dbt-project
```
You can also use a local path instead of a Git URL:
```yaml theme={null}
repos:
- name: dbt_project
local_path: ../dbt
```
`url` and `local_path` are mutually exclusive. You must set exactly one.
Optional file filters:
```yaml theme={null}
repos:
- name: dbt_project
local_path: ../dbt
include:
- "models/**/*.sql"
- "models/**/*.yml"
exclude:
- "**/__pycache__/**"
- "*.pyc"
```
### Syncing only a subdirectory
If you only need a specific folder from a large repo (e.g. the `models/` directory from a monorepo), use `include` patterns to scope the sync:
```yaml theme={null}
repos:
- name: dbt_models
url: https://github.com/your-org/monorepo
include:
- "dbt/models/**"
```
This clones the full repository but only indexes files matching the pattern. Combine multiple patterns to pull from several subdirectories:
```yaml theme={null}
repos:
- name: data_platform
url: https://github.com/your-org/data-platform
include:
- "dbt/models/**/*.sql"
- "dbt/models/**/*.yml"
- "docs/data-dictionary/**/*.md"
exclude:
- "**/test_*"
```
For local paths, you can also point `local_path` directly at the subdirectory instead of using filters:
```yaml theme={null}
repos:
- name: dbt_models
local_path: ../monorepo/dbt/models
```
## Context Files
**Synchronisation**
Sync your repositories to pull the latest content:
```bash theme={null}
nao sync
```
This will:
1. Clone or copy the repository
2. Extract relevant files from the specified path
3. Index the content for the agent
4. Make it searchable
**Context Output**
The output will be the synced repository content in the `repos/` folder.
After syncing, repositories are stored in:
```
context/
└── repos/
└── dbt_project/
```
# Rules
Source: https://docs.getnao.io/nao-agent/context-builder/rules-context
Define how your agent should behave and respond
Rules guide your agent's behavior, ensuring it responds appropriately and follows your organization's standards.
## RULES.md File
When you initialize a nao project with `nao init`, a `RULES.md` file is automatically created in your project root. It is loaded with **every** message to the agent, so keep it lean.
`RULES.md` has two jobs:
1. **Orchestrator**: point the agent to the right context fast - which metric maps to which definition, which topic maps to which file, which question type maps to which table or skill.
2. **Broad rules**: how to query and how to answer (tone, SQL conventions, data access).
Everything else - per-table schemas, full metric semantics, domain-specific rules - belongs in a referenced file. **`RULES.md` should never duplicate context that already lives elsewhere in your repo.** It points to it.
Since `RULES.md` is included with every message, keeping it concise optimizes performance and cost. When `nao sync` has already populated per-table docs, a semantic layer, or `docs/`, point to those instead of restating them.
## Recommended structure
`nao init` scaffolds `RULES.md` with the sections below. Keep the ones that fit your project and drop any already covered by synced context.
| Section | Purpose |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `## Business overview` | What the company does and how it makes money. |
| `## Data architecture` | Warehouse, data stack, layers, and data sources. |
| `## Context map` | The index of where context lives: per-table files, key repo files, `docs/`, and the semantic layer. The orchestrator's routing table. |
| `## Core data models` | `Most Used Tables` (one-line pointers, always) plus `Tables detail` (only when no richer table docs exist elsewhere). |
| `## Key Metrics Reference` | Canonical metric definitions, **only if** no semantic layer or metric docs already define them. Otherwise a one-line pointer to where they live. |
| `## Date filtering` | A few example SQL formulas (last X weeks, last X days, current month) the agent extrapolates from. |
| `## Analysis Process` | The steps the agent follows: resolve the metric definition, read relevant docs, select tables, check column values before filtering, query, validate, add context. |
| `## Chart & Visualization Guidelines` | How the agent's charts should look, so the whole project stays on-brand: the brand color palette for chart series and the semantic colors for good vs bad values. |
### Point to context, don't copy it
The `## Context map` section is what keeps `RULES.md` lean. Instead of restating column lists or metric formulas, route the agent to where they already live:
```markdown theme={null}
## Context map
**Per-table context** - each `databases/.../table=/` folder contains:
- `columns.md` - column names, types, descriptions
- `profiling.md` - distinct counts, min/max, and `top_values`. Read before filtering on any column value.
**Repos:**
- `repos/dbt/` - dbt project. Column docs: `models/silver.yml`. Domain decisions: `models/silver/*_ANALYTICS_DECISIONS.md`. Semantic layer (metrics): `models/silver_semantic_layer.yml`.
**Docs:**
- `docs/crm.md` - CRM funnel statuses and opportunity stages. Read before any sales question.
**Semantic layer:** metrics are defined in `repos/dbt/models/silver_semantic_layer.yml`. Query through it, don't recompute from raw tables.
```
### Conditional sections
Two sections are conditional. Include them only when that context does not already exist elsewhere:
* **`Tables detail`**: skip it when per-table docs (`query_history.md`, `ai_summary.md`) or dbt `schema.yml` column docs already describe your tables - the `Most Used Tables` pointers and the Context map route there already. Reserve it for cross-table pitfalls documented nowhere else.
* **`Key Metrics Reference`**: skip it when a semantic layer or metric docs define your metrics. Replace it with a one-line pointer so each metric keeps a single canonical definition.
Each metric should have exactly one canonical definition across all your context. Duplicating it in `RULES.md` and a semantic layer is the most common cause of inconsistent answers.
### Chart and visualization guidelines
`RULES.md` can also tell the agent how charts should look, so every visualization stays on-brand and consistent. Add a `## Chart & Visualization Guidelines` section with two parts:
* **Brand color palette**: the colors to use for chart series. Single-series charts use the primary brand color; multi-series charts spread across the palette, then use tints and shades of those colors beyond about six series rather than introducing off-brand hues. Tell the agent to never fall back to the charting library's default palette.
* **Semantic colors**: fixed colors for when a chart encodes good vs bad values (deltas, variances, positive or negative KPIs). Keep these separate from the brand palette, which stays reserved for purely categorical series.
Fill the palette from your company's design system or brand guidelines. `nao init` scaffolds this section with placeholder colors for you to replace.
```markdown theme={null}
## Chart & Visualization Guidelines
### Color palette (brand)
Always use these colors for every chart. Never use the charting library's defaults.
| Color | Use |
| --- | --- |
| `#2B2BFF` | Primary - default single-series color |
| `#12B981` | Second series |
| `#F59E0B` | Third series |
- Single-series charts: use the primary brand color.
- Multi-series charts: assign colors across the palette; beyond ~6 series, use tints and shades rather than off-brand hues.
### Semantic colors (good vs bad)
When a chart encodes good or bad values, use fixed semantic colors, not the brand palette:
- Good: `#12B981`
- Bad: `#EF4444`
```
## Broad rules example
Beyond routing, `RULES.md` holds the broad rules for how the agent queries and answers. Here's an example of that portion:
```markdown theme={null}
# Agent Rules
## Tone of Voice
- Be professional, concise, and friendly
- Always explain your reasoning
- If you make assumptions, state them clearly
- Suggest follow-up questions when relevant
## Interacting with Business Users
### Clarify Before Analyzing
Before running analysis, ask clarifying questions if any of these are unclear:
- **Time scope**: "What time period?" (last 7 days, MTD, YTD, specific dates)
- **Geographical scope**: "Which regions/countries?" (all, specific markets)
- **Segmentation**: "Which customer segments?" (all, B2B, B2C, enterprise)
- **Metric definition**: "Which definition of [metric]?" (if multiple exist)
- **Granularity**: "Daily, weekly, or monthly view?"
### Response Structure
For every analysis, follow this structure:
1. **Answer first**: State the key finding upfront
2. **Supporting data**: Show the numbers/visualization
3. **Context**: Add relevant comparisons (vs last period, vs target)
4. **Sanity checks**: Validate against known benchmarks
5. **Caveats**: Mention any data quality issues or limitations
### Sanity Check Guidelines
Before presenting results, verify:
- Numbers align with order-of-magnitude expectations
- Totals match when aggregated different ways
- Check against reference tables (e.g., monthly_kpi_summary)
- Flag if results differ significantly from historical trends
### Follow-Up Suggestions
After answering, suggest relevant next questions:
- Drill-downs (e.g., "Want to see this by region?")
- Related metrics (e.g., "Should we look at retention too?")
- Trend analysis (e.g., "Want to see how this changed over time?")
## SQL Code Style
- Use explicit JOIN syntax
- Add meaningful table aliases (e.g., u for users, o for orders)
- Format SQL for readability (indentation, line breaks)
- Always use LIMIT clauses for queries
- Prefer CTEs over nested subqueries
## Data Access
- Only query production tables (exclude *_test, *_staging)
- Default to last 30 days of data unless specified
- Maximum 10,000 rows per query
## Privacy & Security
- Never display full email addresses or phone numbers
- Flag queries accessing PII
- Aggregate personal data when possible
## Orchestration - Domain-Specific Context
For detailed information on specific topics, read the appropriate file:
- **Marketing questions**: Read `agent/semantics/marketing.md`
- **Finance questions**: Read `agent/semantics/finance.md`
- **Product questions**: Read `agent/semantics/product.md`
These files contain:
- Detailed business definitions
- Metric calculations
- Data quality considerations
- Domain-specific rules
```
## Sub Rules Files
For detailed, domain-specific information, create additional `.md` files in your context. The agent will read these files only when needed.
**File Structure Example**
```
context/
├── RULES.md # Core rules (always sent)
└── agent/
└── semantics/
├── marketing.md # Marketing-specific context
├── finance.md # Finance-specific context
└── product.md # Product-specific context
```
You can use sub-rules files for this kind of context:
* **Business Context**: Context on your company and products.
* **Business Glossary**: Glossary on your business key concepts and terms.
* **Domain-Specific Context**: Specific knowledge on a domain (Business definitions, key metrics, key tables)
* **Data Quality Information**: Context on specific data issues or specificity to add explainability to the agent.
* **Events Information**: Context on specific events that happened and can help explain data.
Organize your rules modularly to keep token costs low and maintain agent focus. Each metric should have only one canonical definition across all your rules files.
Learn how to organize rules effectively using MECE principles and modular structure
**Example: Specialized File (marketing.md)**
Here's an example of a specialized context file:
````markdown theme={null}
# Marketing Context
## Business Definitions
### Customer Lifecycle
- **Lead**: Contact with valid email, no account created
- **Prospect**: Account created, no purchase
- **Customer**: At least one purchase completed
- **Active Customer**: Purchase within last 90 days
- **Churned Customer**: No activity for 180+ days AND no active subscription
### Campaign Types
- **Acquisition**: Targeting new leads/prospects
- **Retention**: Targeting existing customers
- **Winback**: Targeting churned customers
- **Upsell**: Targeting customers with expansion opportunities
## SQL Metrics Definitions
### Customer Acquisition Cost (CAC)
```sql
-- Definition
SELECT
SUM(marketing_spend) / COUNT(DISTINCT customer_id) AS cac
FROM campaigns c
LEFT JOIN customers cu ON c.campaign_id = cu.acquisition_campaign_id
WHERE c.campaign_type = 'acquisition'
AND cu.created_at BETWEEN c.start_date AND c.end_date + INTERVAL '30 days'
````
# Synchronization
Source: https://docs.getnao.io/nao-agent/context-builder/synchronization
Sync and update your agent's context
## nao sync
The `nao sync` command populates your context folder with content from configured sources.
```bash theme={null}
nao sync
```
You can limit a sync to specific providers with `--provider` (or `-p`).
```bash theme={null}
nao sync --provider databases
```
Supported provider values are:
* `databases` (aliases: `db`, `dbs`, `database`)
* `repositories` (aliases: `repo`, `repos`, `repository`)
* `notion`
You can also target one configured connection by using `provider:connection-name`:
```bash theme={null}
nao sync --provider databases:my-db
nao sync -p repositories:dbt_project
nao sync -p databases -p notion
```
Use this when you want to refresh only part of your context instead of running every configured sync provider.
**Sync specific schemas or tables with `--select`**
By default, if you narrow a database sync to a subset of tables, `nao sync` removes any previously-synced table or schema that was not part of the run. Use `--select` (or `-s`) to refresh only a selection **without deleting the rest** of your synced context.
```bash theme={null}
# Sync a single table
nao sync --select analytics.orders
# Sync a whole schema
nao sync --select analytics
# Multiple selections and glob patterns
nao sync -s analytics.orders -s staging.dim_*
# Combine with a specific connection
nao sync -p databases:my-warehouse -s analytics.orders
```
Behavior:
* A pattern with **no dot** (`analytics`) selects every table in that schema (`analytics.*`).
* A pattern with a **dot** (`analytics.orders`) selects a specific table; glob wildcards are supported (`staging.dim_*`).
* Selection is applied **on top of** the existing `include` / `exclude` config in `nao_config.yaml`: it narrows further, never widens.
* When `--select` is passed, stale-path cleanup is skipped, so tables and schemas outside the selection are preserved.
* Tables and Snowflake semantic views both respect the selection.
Use this to refresh one table on a large warehouse without re-syncing everything.
**Run the sync in parallel**
By default `nao sync` uses a single worker thread. Speed up large syncs with `-t` / `--threads`:
```bash theme={null}
nao sync --threads 4
```
This overrides the `threads` value in `nao_config.yaml` (which itself defaults to `1`). The value must be `1` or greater.
**What Gets Synced**
When you run `nao sync`, the following happens:
**1. Database Schemas**
For each database in your `nao_config.yaml`:
* **Connect** to the database
* **Extract schema information** (tables, columns, data types)
* **Generate context files** in `databases/` folder
* **Create structured files** for each table:
* `columns.md` - Column definitions and types, table description, row count, and partitioning
* `preview.md` - Sample data preview
* `profiling.md` - Data profiling information
* `annotations.md` - Your own notes for the agent, created once and never overwritten
Example structure after sync:
```
databases/
└── type=snowflake/database=analytics/
├── table=customers/
│ ├── annotations.md
│ ├── columns.md
│ ├── preview.md
│ └── profiling.md
└── table=orders/
├── annotations.md
├── columns.md
├── preview.md
└── profiling.md
```
**Who owns each file**
Every file written during a sync starts with a frontmatter header declaring its origin, so you can tell what a re-sync will overwrite:
* `type: generated` - written by the agent on every sync. Edits are lost on the next run.
* `type: manual` - written once and left alone afterwards. Safe to edit.
`annotations.md` is the manual file: `nao sync` creates it per table folder with only the header, and skips it on later runs if it already exists. Everything else under `databases/` is regenerated. See [Databases](/nao-agent/context-builder/databases#generated-vs-manual-files) for details.
**2. Repositories**
For each repository in your configuration:
* **Clone or pull** the latest code
* **Extract relevant files** from specified paths
* **Index content** for the agent
* **Store** in `docs/` folder
**3. Indexing**
After syncing:
* **Content is indexed** for fast semantic search
* **Embeddings are created** for relevant context retrieval
* **Agent can access** all synced information
## Scheduling
### GitHub Actions
Set up automated syncing with GitHub Actions to keep your context up to date.
**1. Create Workflow File**
This workflow will regularly run `nao sync` on GitHub's servers, commit any changes to your context files (like updated database schemas or docs), and push them back to your repository so your context stays in sync without manual commands.
Create `.github/workflows/nao-sync.yml` in your repository:
```yaml theme={null}
name: Nao Sync
on:
schedule:
- cron: "0 0 */2 * *" # every 2 days at 00:00 UTC
workflow_dispatch: # allows manual triggering
jobs:
nao-sync:
runs-on: ubuntu-latest
permissions:
contents: write # needed to commit changes
env:
NAO_CONFIG_PATH: ./nao_config.yaml
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Nao CLI
run: |
pip install --upgrade pip
pip install nao-core
pip install "ibis-framework[bigquery]" # warehouse driver for your DB
- name: Run nao sync
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GOOGLE_APPLICATION_CREDENTIALS_JSON: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY_JSON }}
# Add other secrets as needed (e.g., NOTION_API_KEY, etc.)
run: nao sync
- name: Configure Git
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- name: Commit and push changes
run: |
git add -A
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "nao sync update"
git push origin HEAD:${{ github.ref_name }}
fi
```
**2. Configure Repository Secrets**
In your GitHub repository:
1. Go to **Settings** → **Secrets and variables** → **Actions**
2. Click **"New repository secret"**
3. Add secrets for all environment variables used in your `nao_config.yaml`:
* `ANTHROPIC_API_KEY` - Required for `nao sync` to run the agent
* `GCP_SERVICE_ACCOUNT_KEY_JSON` - Full JSON content of your BigQuery service account
* `NOTION_API_KEY` - If using Notion integration
* Any other credentials referenced in your config
Never commit secrets directly in your workflow file. Always use GitHub Secrets.
**3. Customize Schedule**
Adjust the cron schedule to match your needs:
```yaml theme={null}
on:
schedule:
- cron: "0 0 * * *" # Daily at midnight UTC
- cron: "0 0 */2 * *" # Every 2 days
- cron: "0 0 * * 1" # Weekly on Monday
- cron: "0 3 * * *" # Daily at 3 AM UTC
```
**4. Manual Trigger**
You can manually trigger the sync:
* Go to **Actions** tab in your repository
* Select **"Nao Sync"** workflow
* Click **"Run workflow"**
**5. Monitor Sync Results**
* Check the **Actions** tab to see sync history
* Review logs if sync fails
* Verify changes are committed to your repository
The workflow automatically commits and pushes any changes from `nao sync` back to your repository, keeping your context files up to date.
**Next Steps**
Learn about configuring databases and repos
Learn how to measure, iterate, and optimize your context
Use your synced context with the agent
Understand core principles for effective context engineering
# System Prompts
Source: https://docs.getnao.io/nao-agent/context-builder/system-prompts
Override or extend the agent's system prompt per surface from your context repo
Each bot surface (nao web Bot, Slack, Teams, Telegram, WhatsApp, Mattermost, and automations) ships with a built-in system prompt. You can customize or fully replace that prompt per surface by adding markdown files to an `agent/prompts/` folder in your context repo - no code changes or forking required.
Because the files live in your context repo, prompt changes are versioned with the rest of your context and stay reviewable in pull requests. `nao init` scaffolds the `agent/prompts/` folder for you with a `README.md` and an example `slack.md`.
## Per-surface files
Place any of these files in `agent/prompts/`:
| File | Applies to |
| --------------- | -------------------------------------------------------------------------------------- |
| `system.md` | Every surface (nao web Bot, Slack, Teams, Telegram, WhatsApp, Mattermost, automations) |
| `slack.md` | Slack Bot only |
| `teams.md` | Teams Bot only |
| `telegram.md` | Telegram Bot only |
| `whatsapp.md` | WhatsApp Bot only |
| `mattermost.md` | Mattermost Bot only |
| `automation.md` | Automation runs only |
Resolution order for a given surface:
1. The surface-specific file (e.g. `slack.md`), if present.
2. Otherwise `system.md`, if present.
3. Otherwise nao's built-in default prompt.
The nao web Bot has no surface-specific file - it uses `system.md` when present, otherwise the built-in default.
Prompt files are read fresh on every run, just like `RULES.md`. You can edit a prompt and see the change on the next message without restarting nao.
## Replace vs. extend: `{{ nao_prompt }}`
By default, a prompt file **fully replaces** the built-in prompt for that surface.
To **keep** the default and only add to it, include the `{{ nao_prompt }}` placeholder. At runtime it expands to nao's default prompt for that surface (in `slack.md` it expands to the default Slack prompt, in `mattermost.md` to the default Mattermost prompt, in `system.md` to the web prompt, and so on). The placeholder tolerates whitespace variations (`{{nao_prompt}}` or `{{ nao_prompt }}`) and can appear more than once.
Extend the default Slack prompt - `agent/prompts/slack.md`:
```markdown theme={null}
{{ nao_prompt }}
## Extra Slack guidance
- Always start the answer with a one-line TL;DR.
- Link the source dashboard when one exists.
```
Fully replace the default for every surface - `agent/prompts/system.md`:
```markdown theme={null}
You are the ACME analytics assistant. Answer only with figures sourced
from the `prod_finance` schema, and never speculate beyond the data.
```
Without `{{ nao_prompt }}`, the file content becomes the entire prompt.
Internal runs that pass an explicit system prompt (for example, context recommendations) are not affected by these overrides.
## Warehouse dialect rules
nao also adds SQL rules to the built-in prompt based on the warehouses your project is connected to. This happens automatically: the rules for a dialect are injected whenever a connection of that type exists, so you do not have to restate them in `RULES.md`.
The exact rules for each dialect live in one file in the nao repo. Rather than restate them here (they evolve as we add coverage), each row links to the source:
| Connection type | Dialect rules |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| BigQuery | Identifier quoting, safe division, window `GROUP BY`, aggregate-function differences: [view rules](https://github.com/getnao/nao/blob/65c988553861ba94773f9ba8278b07d89e0097ee/apps/backend/src/components/ai/dialect-rules.tsx#L61-L92) |
| Redshift | `PERCENTILE_CONT`, `LISTAGG`, string concat, date and filtered-count differences: [view rules](https://github.com/getnao/nao/blob/65c988553861ba94773f9ba8278b07d89e0097ee/apps/backend/src/components/ai/dialect-rules.tsx#L103-L134) |
| MSSQL and Fabric | `TOP N`, no `GROUP BY ALL`, T-SQL date functions, `ISNULL()`: [view rules](https://github.com/getnao/nao/blob/65c988553861ba94773f9ba8278b07d89e0097ee/apps/backend/src/components/ai/dialect-rules.tsx#L41-L60) |
| MySQL | Identifier quoting, `IFNULL()`: [view rules](https://github.com/getnao/nao/blob/65c988553861ba94773f9ba8278b07d89e0097ee/apps/backend/src/components/ai/dialect-rules.tsx#L93-L102) |
| ClickHouse | Read `indexes.md` for ordering and partitioning before querying: [view rules](https://github.com/getnao/nao/blob/65c988553861ba94773f9ba8278b07d89e0097ee/apps/backend/src/components/ai/dialect-rules.tsx#L30-L40) |
Warehouses without a dialect entry (for example PostgreSQL, Snowflake, Databricks, DuckDB) use the generic SQL rules in the built-in prompt.
Dialect rules are part of nao's built-in prompt. A prompt file that fully replaces the default drops them; keep `{{ nao_prompt }}` in the file if you want to preserve them.
**Next Steps**
Shape per-message behavior with RULES.md
Organize your context repo however works best
# Contributor Guide
Source: https://docs.getnao.io/nao-agent/context-engineering/contributing
How anyone on your team can add or fix context in your organization's nao context repository
This guide is for people who use the nao agent every day and want to make it smarter: analytics engineers, analysts, data leads, and the business users who know what a metric really means.
This is about **your organization's context repository** - the repo holding `nao_config.yaml`, `RULES.md`, and your synced schemas. It is not about contributing to the [nao open-source project](https://github.com/getnao/nao) itself.
You do not need to be a nao admin, and you do not need to understand how nao is deployed. If you can write Markdown and open a pull request, you can improve the agent.
## How nao works
The agent knows nothing about your business on its own. Everything it understands - what a customer is, which table holds revenue, why last July looks strange - comes from your **context**: a set of Markdown and YAML files describing your data and your rules.
When someone asks a question, the agent goes through roughly the same motions an analyst would:
It looks for the files that describe the concepts in the question - definitions, table documentation, business rules.
Based on what it found, not on guesses about your schema.
Then it explains the result, showing the SQL it used.
That first step is where your contribution lands. **The quality of the answers is the quality of the context** - a wrong answer is almost always a missing or ambiguous file, not a broken model.
The important part for you: the context is a plain file system. Nothing is hidden in a database or in a black box, which is exactly what makes it contributable by the people who know the business.
## How a nao context is structured
```text theme={null}
your-context/
├── nao_config.yaml
├── RULES.md
├── semantics/
├── docs/
├── databases/
├── repos/
├── agent/
└── tests/
```
| Path | Type | What it holds |
| ----------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `nao_config.yaml` | Manual | Database connections, LLM models, and sync settings. Usually maintained by an admin. |
| `RULES.md` | Manual | The agent's core instructions, plus its map of where everything else lives. Read on every message. |
| `semantics/` | Manual | Domain knowledge: metric definitions, funnel stages, business terminology. |
| `docs/` | Manual, except `docs/notion/` | Free-form documentation. Pages synced from Notion land in `docs/notion/`. |
| `databases/` | Auto, except `annotations.md` | One folder per table with its schema, sample rows, and statistics. Your own notes on a table go in its `annotations.md`. |
| `repos/` | Auto | Copies of your dbt, BI, or ETL repositories, refreshed from the source repo. |
| `agent/` | Manual | Skills, system prompt overrides, MCP servers, and custom tools. |
| `tests/` | Manual | Question and expected-SQL pairs used by `nao test`. |
**Manual** files are written by you and your teammates. **Auto** files are produced by `nao sync`, which pulls them from your warehouse and your repositories - and rewrites them on every run. More on what that means for your edits [below](#generated-vs-manual-files).
The structure is not fixed. It is your file system - you can add folders and organize by team, domain, or project. See [Custom Context](/nao-agent/context-builder/custom-context).
## How the agent finds context
Writing a good file is only half the job. If the agent never opens it, it may as well not exist.
### What the agent always sees
On every single message, before it does anything:
* **The system prompt** - nao's built-in prompt, plus any override in `agent/prompts/`, plus the [SQL dialect rules](/nao-agent/context-builder/system-prompts#warehouse-dialect-rules) for your warehouses, injected automatically.
* **`RULES.md`** - in full, every time.
That is it. Everything else in the repository is invisible until the agent decides to go looking.
### What the agent has to go find
To reach anything else, the agent uses its file tools:
| Tool | What it does | What makes your file findable |
| -------- | --------------------------------- | ------------------------------------------------------------------------- |
| `list` | Lists files and directories | A predictable path and an obvious folder |
| `search` | Finds files by glob pattern | A descriptive **file name** (`marketing_attribution.md`, not `notes2.md`) |
| `grep` | Regex search across file contents | The **words your users actually type** appearing in the text |
| `read` | Reads one file | A pointer telling it this file is worth opening |
So a contribution is discoverable when at least one of these is true:
1. **`RULES.md` points at it.** This is the reliable one. The `## Context map` section exists to route the agent: "CRM funnel statuses -> `docs/crm.md`, read before any sales question."
2. **Its name says what it holds.** `semantics/finance.md` gets found by a glob search for a finance question. `misc.md` does not.
3. **It contains the vocabulary of the question.** If your team says "churn" but your file only ever says "attrition", `grep` will miss it. Write both.
4. **It sits where the agent is already looking.** A note filed next to `table=orders/` gets read when the agent inspects that table, with no pointer needed.
A perfect file in `docs/`, referenced by nothing and named nothing memorable, is dead weight: it costs review time and gets read only by accident. **Adding a new file usually means also adding one line to `RULES.md`.**
## Generated vs manual files
Some of your context is written by hand. The rest is produced by `nao sync`, which connects to your warehouse and your repositories and regenerates files from them.
This is the single most common way a contribution gets lost: an edit to a generated file disappears on the next sync.
You never have to guess which is which. Every file written by `nao sync` carries a frontmatter header declaring who owns it.
**Generated** - overwritten on the next sync. Your edit will disappear.
```yaml theme={null}
---
type: generated
comment: This file is managed and generated by the agent, do not modify it!
---
```
**Manual** - written once, never touched again. Safe to edit.
```yaml theme={null}
---
type: manual
comment: These are manual notes you want the agent to keep in mind, safe to edit.
---
```
If the thing you want to fix lives in a generated file, fix it **upstream** instead:
| You want to change | Don't edit | Do this instead |
| ----------------------------- | ------------------------- | ------------------------------------------------------------------------------ |
| A column description | `databases/**/columns.md` | Fix the description in the warehouse or in your dbt `schema.yml`, then re-sync |
| A caveat about one table | `databases/**/columns.md` | Write it in that table's `annotations.md` |
| Anything in a synced dbt repo | `repos/dbt/...` | Open a PR on the dbt repo itself; nao pulls it on the next sync |
| A Notion page | `docs/notion/...` | Edit the page in Notion |
| Which tables are in scope | `databases/` | Change `include` / `exclude` in `nao_config.yaml` (ask an admin) |
`annotations.md` exists in every table folder for exactly this reason. It is created empty and never overwritten, so it is the right home for "this table double-counts refunds, join to `fct_refunds` to net them out".
## Where does my contribution belong?
| What you want to add | Where it goes |
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| A rule that applies to *every* answer (tone, SQL style, row limits, PII handling) | `RULES.md`, broad rules section |
| A pointer to where some context lives | `RULES.md`, `## Context map` |
| The canonical definition of a metric | Your semantic layer if you have one, otherwise `semantics/.md` - **once, in one place only** |
| Domain knowledge (funnel stages, lifecycle definitions, campaign types) | `semantics/.md` |
| A quirk, trap, or caveat about one specific table | that table's `annotations.md` |
| A column description | Upstream: the warehouse or dbt `schema.yml` |
| A repeatable multi-step analysis the agent should always run the same way | A [skill](/nao-agent/context-engineering/skills) in `agent/skills/` |
| A one-off explanation of a data incident ("July numbers are low, tracking broke") | `docs/`, dated, with a pointer from `RULES.md` |
| A question the agent must get right forever | A test in `tests/` |
When in doubt between `RULES.md` and a sub-file: `RULES.md` costs tokens on every message ever sent. A sub-file costs tokens only when it is read. Put it in a sub-file and point to it.
## Rules for writing context
### Size
| Rule | Why |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **Hard limit: keep every file under 32,000 characters** | That is the `read` tool's cap. Past it, the tail of your file is silently never seen. |
| **Practical target: one screen to a few pages per file** | Cheap to read, easy to review, easy for the agent to keep straight. |
| **Split by domain, not by size** | `semantics/marketing.md` + `semantics/finance.md` beats one `semantics.md` cut in half. |
| **Keep `RULES.md` lean** | It is billed on every message. It should be an index and a set of broad rules, never an encyclopedia. |
The File Explorer shows an estimated token count per file, and [Recommendations](/nao-agent/context-engineering/recommendations) flags files that are `truncated_on_read`, `frequent_and_expensive`, or `rare_but_outlier`. If your file is flagged, split it.
### Content
* **Open with a scope line.** One sentence saying what the file covers and when to read it, in the words a user would use. It is what tells the agent whether this is the file it needs.
* **One canonical definition per concept.** Two definitions of "active customer" in two files is worse than none - the agent will pick one at random and be inconsistent. If it is already defined somewhere, link to it instead of restating it.
* **Never duplicate what `nao sync` generates.** Column lists and row counts are already in `databases/`. Restating them means they go stale the day the schema changes.
* **Be explicit, not elegant.** Name the exact table, the exact column, the exact filter. `WHERE status = 'won'` beats "filter on won opportunities".
* **Show the SQL.** A formula the agent can copy is worth three paragraphs describing it.
* **Write the failure modes.** "Do not use `raw_orders`, it includes test orders; use `fct_orders`" prevents a whole class of wrong answers.
* **Use your users' words.** Include synonyms and internal jargon so `grep` finds the file.
* **Date and timezone conventions matter.** Week start, fiscal calendar, timezone of `created_at`. These cause silent, plausible-looking errors.
* **No secrets, no PII, no credentials.** Context files are read by the agent and visible to reviewers.
## When to contribute
Contribute whenever you catch the agent being wrong, vague, or slow in a way you know how to fix:
| What you noticed | What that usually means |
| ------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| The agent used the wrong table for a metric you own | No canonical definition, or two conflicting ones |
| It gave a number that "looks right but isn't" | A join, filter, or deduplication caveat isn't written down anywhere |
| It asked a clarifying question everybody on your team knows the answer to | A convention (fiscal year, week start, default currency) is missing |
| It answered inconsistently to the same question asked twice | The same concept is defined in two places |
| It took several exploratory queries to get there | The agent had to discover by querying what your context could have told it |
Before you write anything, check **Settings -> Recommendations**. nao audits its own production usage and may already have found and drafted the exact fix you were about to write. See [Recommendations](/nao-agent/context-engineering/recommendations).
## How to contribute
| | From the browser | From your machine |
| ------------ | ------------------------------------------------- | -------------------------------------------------------------- |
| **Who** | Admins and Context Admins | Anyone with access to the context repo |
| **Where** | **Settings -> File Explorer** in nao | Your own editor, on a git clone |
| **Good for** | A quick wording fix, adding a rule, an annotation | Larger edits, running `nao sync` or `nao test`, editing config |
| **Output** | A commit on a branch, then a pull request | A commit on a branch, then a pull request |
Both paths end the same way: a pull request that someone reviews. Context is a shared source of truth, so it goes through review like code.
### From the browser
Open **Settings -> File Explorer**. Search covers file contents as well as names, so you can find the file by the term the agent got wrong.
Markdown opens as a rendered page. Click **Source** to edit with a live preview. Each file shows an estimated token count, so you can see what it costs the agent before you make it longer.
`Cmd+S` saves to **your own private copy** of the repository. Neither the live agent nor your teammates see the change yet.
Saved edits collect in the **Git** panel under the file tree. Pick what to commit. Commits are authored as you, with nao as co-author. Committing from the main branch creates a branch automatically.
The first push on a branch opens a pull request. Later pushes update that same one.
Editing requires a [connected context repository](/nao-agent/chat/admin/git). Without one, every file is read-only. If a specific file refuses to be edited, nao tells you why - see [Why a file is read-only](/nao-agent/chat/admin/file-explorer#why-a-file-is-read-only).
### From your machine
Clone the context repo, edit, and open a PR as you would for any other repository.
```bash theme={null}
git clone https://github.com/your-org/your-nao-context.git
cd your-nao-context
git checkout -b add-marketing-semantics
```
You do not need database credentials to edit Markdown. You only need them if you want to run `nao sync` (to regenerate schema files) or `nao test` (to check your change against the test suite).
Never commit credentials. Secrets belong in environment variables referenced from `nao_config.yaml`, never in a context file. See [Git Repository Setup](/nao-agent/context-builder/git-repository).
### What reviewers look for
If you review context pull requests, check these:
* Does this contradict a definition that already exists elsewhere?
* Is it in the right file, or was `RULES.md` used as a dumping ground?
* Will it survive the next `nao sync`?
* Is it findable - named well, pointed to, using the vocabulary of real questions?
* Is it explicit enough that two different people would read it the same way?
* Does it carry a test?
## Next steps
The MECE and token-cost rules behind these guidelines
The full reference for RULES.md and sub-rules files
Write tests that protect your contribution
Let nao tell you what to contribute next
# Evaluation
Source: https://docs.getnao.io/nao-agent/context-engineering/evaluation
Test and evaluate your analytics agent with unit tests
## Overview
The `nao test` command allows you to measure your agent's performance on a set of unit tests created by you. It's meant to help you monitor and improve your context's quality over time.
`nao test` is your **offline eval**: a fixed suite you run before shipping and in CI to catch regressions against a benchmark you control. Its online counterpart is [Recommendations](/nao-agent/context-engineering/recommendations), which mines real production usage to surface the gaps you didn't think to test. Use both to close the feedback loop between shipping context and improving it.
## nao test
The `nao test` command runs unit tests from your `tests/` folder, executes them against your agent, and compares results to verify correctness.
### Create unit tests
Create a `tests/` folder in your project root:
```
your-project/
├── nao_config.yaml
├── RULES.md
├── tests/ # Test folder
│ ├── total_revenue.yml # Test file 1
│ ├── customer_metrics.yml # Test file 2
│ └── outputs/ # Test results (auto-generated)
│ └── results_20250209_143022.json
```
Then create your test files. Each test is a YAML file in the `tests/` folder. Test files should have a `.yml` or `.yaml` extension.
Test files follow this template:
```yaml theme={null}
name: total_revenue
prompt: What is the total revenue from all orders?
sql: |
SELECT SUM(amount) as total_revenue
FROM orders
```
**Required fields:**
* `name`: A descriptive name for the test
* `prompt`: The question or prompt to test
* `sql`: SQL query which produces the right data
### Launch nao test command
Before running `nao test`:
* **Start the nao chat server** (for example with `nao chat` or your usual local setup) so that the backend API is available.
* On the **first `nao test` run**, the CLI will prompt you to log in in your browser — **use the same account you use in the local nao chat interface**, so tests run under the same project and permissions.
Run all tests:
```bash theme={null}
nao test
```
This will:
* Discover all `.yml` and `.yaml` files in the `tests/` folder
* Run each test against the configured models, `openai:gpt-4.1` by default
* Display results in a summary table
* Save detailed results to `tests/outputs/results_TIMESTAMP.json`
**Specify LLM model to test:**
```bash theme={null}
# Test with GPT 4.1
nao test -m openai:gpt-4.1
```
Model format: `provider:model_id`. The provider must be one you configured in the `llm` block of `nao_config.yaml` or in the admin UI - see [LLM providers and models](/nao-agent/context-builder/configuration#llm-providers-and-models).
Repeat the flag to compare several models on the same suite. Every test then runs once per model, and the summary table has one row per test and model:
```bash theme={null}
nao test -m openai:gpt-4.1 -m anthropic:claude-sonnet-4-5
```
A multi-model run also prints two comparison tables: **Performance by Model**, one row per model ranked by pass rate then cost, and **Pass / Fail by Test and Model**, a grid showing which model passes which test. Both are described in [Test Outputs](#test-outputs).
**Run tests in parallel:**
```bash theme={null}
# Run with 4 parallel threads
nao test -t 4
```
This speeds up execution when running many tests, but output may be interleaved.
**Run specific tests:**
```bash theme={null}
# Run one test by test name or YAML filename stem
nao test -s total_revenue
```
You can also use the long flag:
```bash theme={null}
nao test --select total_revenue
```
Pass a comma-separated list to run several named tests in one go. Selection order is preserved, and surrounding whitespace and duplicates are tolerated:
```bash theme={null}
nao test -s total_revenue,customer_metrics
```
A selection also matches a subfolder of `tests/`, which runs every test it contains:
```bash theme={null}
# Runs all tests under tests/contracts/
nao test -s contracts
```
An unknown name errors and prints the available tests.
**Run non-interactively (CI):**
By default, the first `nao test` run opens a browser to log in. For CI pipelines, pass credentials directly so the run never blocks on a prompt:
```bash theme={null}
# Inline flags
nao test -u user@example.com --password ''
# Or via environment variables (recommended in CI -
# the password never appears in the command line or in shell history)
export NAO_USERNAME=user@example.com
export NAO_PASSWORD=''
nao test
```
The credentials are reused on session refresh if the auth token expires mid-run. The password is never echoed to stdout or stderr.
### Defaults in nao\_config.yaml
Rather than repeating the same flags on every run, declare your defaults in the `test` block of `nao_config.yaml` and commit them with the suite:
```yaml theme={null}
test:
models:
- openai:gpt-4.1
- anthropic:claude-sonnet-4-5
threads: 4
comparison:
rtol: 0.00001
atol: 0.00000001
decimals: 2
```
Every key is optional, and the matching command line flag overrides it for a single run:
| Key | Default | Flag | Description |
| ------------ | -------------------- | ----------------- | ----------------------------------------------------------- |
| `models` | `["openai:gpt-4.1"]` | `-m`, `--model` | Models to run the suite against, as `provider:model_id` |
| `threads` | `1` | `-t`, `--threads` | Test runs executed in parallel |
| `comparison` | see below | — | Tolerances used when comparing results to the expected data |
Which tests to run stays a per-run decision with `-s`/`--select`, and credentials stay out of the config: pass them with `-u`/`--password` or the `NAO_USERNAME` / `NAO_PASSWORD` environment variables.
### Comparison tolerances
`test.comparison` controls how the agent's data is compared to the result of your `sql` query:
| Key | Default | Description |
| ---------- | ------------ | --------------------------------------------------------- |
| `rtol` | `0.00001` | Relative tolerance for numeric values |
| `atol` | `0.00000001` | Absolute tolerance for numeric values |
| `decimals` | `2` | Decimals kept when rounding float values before comparing |
Raise `decimals` when a test legitimately hinges on more precision than two decimals, and lower it when rounding differences between the agent's SQL and your reference SQL cause false failures.
### Test costs
The cost of each run comes from nao's built-in price table for the model under test. When you declare a model under `llm.providers[].models` with a `costs` block, those prices win — which is how you get accurate costs for a model nao doesn't know, such as an alias exposed by a LiteLLM proxy:
```yaml theme={null}
llm:
providers:
- provider: openai
api_key: {{ env('LITELLM_API_KEY') }}
base_url: http://0.0.0.0:4000
models:
- id: my-proxy-alias
costs:
input_no_cache: 2.5
output: 10
```
Then run the suite against it with `nao test -m openai:my-proxy-alias`. Models with no price anywhere report an empty cost, while tokens, duration and pass/fail are still recorded. See [Model costs](/nao-agent/context-builder/configuration#model-costs).
The deprecated `llm.meta.costs` block is still read as a last-resort price for every model. Move those prices onto the models they belong to so a multi-model run reports real numbers.
### Test mode behavior
When `nao test` runs a prompt, the agent operates in **test mode**. In this mode the clarification tool is removed from the agent's toolset, so the agent cannot ask follow-up questions. Instead, it makes reasonable assumptions or states that it cannot answer. This prevents tests from hanging on a clarification prompt and ensures every test produces a deterministic result.
Interactive chat is unaffected: the clarification tool remains available in normal conversations.
### How It Works
1. **Test Discovery**: Scans the `tests/` folder for `.yml` and `.yaml` files
2. **Test Execution**: For each test:
* Sends the prompt to your agent in test mode (clarification disabled, see above)
* Captures the agent's full conversation history, tool calls, and response text
3. **Data Verification**:
* **Extract actual data**: rather than asking the model to retype its answer as JSON (which loses precision on large or wide results), nao loads **every `execute_sql` result from the run into an in-memory DuckDB**, one table named after each query id, and asks the model for a **DuckDB query over those rows** that returns the final answer. The query runs locally against the data the agent already fetched.
Rows are loaded as newline-delimited JSON so DuckDB infers the column types instead of nao guessing them. If the model's query fails, **one repair attempt** is allowed: the error is sent back and the model gets a second try.
* The verification query produces **structured data** matching the expected columns.
* **Execute expected SQL**: the `sql` query from your test file is executed against your database to get the expected results
* **Compare data**: the agent answer's data and expected data (from SQL execution) are compared
4. **Data Comparison Process**:
* **Normalize datasets**: Both datasets are converted to DataFrames and normalized (resets index, infers types, and sorts columns alphabetically)
* **Ignore row order**: Rows are sorted by all columns before comparison so equivalent results with different row order still pass
* **Row count match**:If row count doesn't match, they are not compared
* **Round floats**: Float columns are rounded to `comparison.decimals` (2 by default) so display-level precision differences don't fail a test
* **Exact match**: First attempts exact equality comparison
* **Approximate match**: For numeric columns, uses numpy's `allclose` with the `comparison.rtol` and `comparison.atol` tolerances (1e-5 and 1e-8 by default) to handle floating-point differences
* **Diff generation**: If both comparisons fail, generates a detailed diff showing where values differ
5. **Result Collection**: Collects metrics including:
* Pass/fail status of the data diff
* Token usage and costs (inputs and outputs of the LLM), priced as described in [Test costs](#test-costs)
* Execution duration
* Tool call count
### Test Outputs
**Console Output**
The command displays a summary table with:
* Test name
* Model used
* **Pass/fail** status
* Message (e.g., "match", "values differ")
* **Token usage**
* **Cost**
* **Execution time**
* **Tool call count**
* A final summary with total passed/failed counts
**Example output:**
When the run covers more than one model, two extra tables follow it.
**Performance by Model** aggregates every run of a model, ranked by pass rate and then by cost, so the cheapest model wins a tie. It reports pass rate, passed runs out of total, tokens, cost, average duration and average tool calls:
```
Performance by Model
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┓
┃ ┃ Pass ┃ ┃ ┃ ┃ Avg ┃ Avg ┃
┃ Model ┃ Rate ┃ Passed ┃ Tokens ┃ Cost ┃ Time (s)┃ Tools ┃
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
│ anthropic │ 100.0% │ 2/2 │ 46000 │ 0.089 │ 8.8 │ 5.5 │
│ claude-sonnet-4-5 │ │ │ │ │ │ │
│ openai │ 50.0% │ 1/2 │ 73390 │ 0.138 │ 13.6 │ 8.0 │
│ gpt-4.1 │ │ │ │ │ │ │
└───────────────────┴─────────┴────────┴────────┴─────────┴─────────┴──────────┘
```
**Pass / Fail by Test and Model** puts one row per test and one column per model, which is how you spot a test that only one model gets right:
```
Pass / Fail by Test and Model
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ ┃ openai ┃ anthropic ┃
┃ Test ┃ gpt-4.1 ┃ claude-sonnet-4-5 ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
│ total_revenue │ ✓ │ ✓ │
│ revenue_per_customer │ ✗ │ ✓ │
└──────────────────────┴─────────┴───────────────────┘
```
With `-t`/`--threads`, live output is still interleaved but these tables always list runs grouped by model, in the order the models were configured.
**JSON Results File**
Detailed results are saved to `tests/outputs/results_TIMESTAMP.json`. Each result carries a `details.reference_sql` field holding the `sql` query from the test YAML file, including for tests that errored out:
```json theme={null}
{
"timestamp": "2025-02-09T14:30:22.123456",
"results": [
{
"name": "total_revenue",
"model": "openai:gpt-4.1",
"passed": true,
"message": "match",
"tokens": 1250,
"cost": 0.0125,
"duration_ms": 234,
"tool_call_count": 1,
"details": {
"response_text": "...",
"actual_data": [...],
"expected_data": [...],
"reference_sql": "SELECT SUM(amount) as total_revenue\nFROM orders",
"tool_calls": [...]
}
}
],
"summary": {
"total": 3,
"passed": 3,
"failed": 0,
"total_tokens": 3750,
"total_cost": 0.0375,
"total_duration_ms": 702,
"total_duration_s": 0.7,
"total_tool_calls": 3,
"avg_duration_ms": 234,
"avg_tool_calls": 1.0
},
"by_model": [
{
"model": "openai:gpt-4.1",
"total": 3,
"passed": 3,
"failed": 0,
"pass_rate": 100.0,
"total_tokens": 3750,
"total_cost": 0.0375,
"total_duration_ms": 702,
"avg_duration_ms": 234,
"total_tool_calls": 3,
"avg_tool_calls": 1.0
}
]
}
```
`summary` aggregates every run of the file, while `by_model` holds the same metrics per model, ranked exactly like the **Performance by Model** table. Use it to assert a per-model pass rate in CI instead of recomputing it from `results`.
## nao test server
The `nao test server` command starts a web server to explore test results in a visual interface.
The test server provides:
* **Summary Dashboard**: Overview cards showing pass rate, total tests, tokens, costs, and duration
* **Performance by Model**: for multi-model runs, one row per model with its pass rate, passed runs, tokens, cost, average duration and average tool calls. Click a row to filter the run table down to that model
* **Pass / Fail by Test and Model**: for multi-model runs, a grid of tests against models. Each cell shows the status and duration, and opens the detailed view of that run
* **Results Table**: Interactive table of all test runs with status, metrics, and details, filterable by model
* **Detailed View**: Click any test to see:
* Full response text
* **Reference SQL**: the `sql` query defined in the test YAML file, so you can compare it side by side with what the agent ran. It is shown even when the test errored out.
* Tool calls with arguments and results. `execute_sql` calls render their `sql_query` as formatted multi-line SQL instead of a JSON blob, with any remaining arguments listed separately.
* Data comparison (actual vs expected)
* Diff view for failed tests
* Performance metrics
**Test server UI**
**Zoom on one test**
Start the Server with:
```bash theme={null}
nao test server
```
This will start the test server on `http://localhost:8765`
The test server reads from `tests/outputs/`. Make sure you've run `nao test` at least once to generate result files.
Result files written before per-model summaries existed still get the model views: the server computes `by_model` on the fly when a file doesn't carry it.
## Best Practices
### Creating Effective Tests
1. **Start with critical queries**: Test the most important questions your users ask
2. **Cover edge cases**: Include tests for boundary conditions and complex scenarios
3. **Keep tests focused**: Each test should verify one specific behavior
4. **Avoid overfitting and leakage**: Avoid including exact answers or overly specific details in your context that would allow the agent to "cheat" by pattern matching rather than actually understanding the context.
### Integrating into Workflow
1. **Version control**: Commit your `tests/` folder to git
2. **CI/CD integration**: Run tests automatically on context changes
3. **Regular evaluation**: Run tests weekly or after major context updates
4. **Track trends**: Monitor pass rates and costs over time
Learn how to integrate testing into your context engineering workflow
# Playbook
Source: https://docs.getnao.io/nao-agent/context-engineering/playbook
Step-by-step guide to start context engineering for your analytics agent
Read the full Agentic Analytics Playbook
## Getting Started with Context Engineering
This playbook provides a systematic approach to building and maintaining effective context for your analytics agent. Follow these steps in order to ensure a solid foundation and scalable growth.
## First POC on small, reliable context
**Step 1: Add Your Data Context**
Start with a **restricted perimeter** of your data warehouse:
* **Maximum 20 tables** to begin with
* Focus on **clean, gold, or mart layer** tables (avoid raw staging tables)
* Choose tables that represent core business domains
Starting small helps you validate your approach before scaling. You can always add more tables later.
**Step 2: Add Your Documentation Repository**
Include your documentation sources in context:
* **dbt documentation** (schema.yml, docs blocks)
* **Semantic layer** definitions
* Any other relevant documentation repositories
This helps the agent understand business logic, relationships, and data lineage.
**Step 3: Add Company and Domain Rules**
Create rules that provide context on:
* **Your company** - business context, terminology, conventions
* **Different domains** covered by your 20 tables - e.g., sales, marketing, finance, operations
These high-level rules set the foundation for domain-specific understanding.
**Step 4: Add Sub-Rules for Each Sub-Domain**
For each sub-domain covered, create detailed sub-rules that include:
* **Business definitions** - what key terms mean in your organization
* **Metrics definitions** - how metrics are calculated and used
* **List of tables** - which tables belong to this domain
* **Relevant docs yaml** - specific documentation for this domain
This modular approach makes your context easier to maintain and scale.
## Measure, test and iterate
**Step 5: Create a Set of 20 Key Questions**
Develop a **test suite** of 20 key questions that represent:
* Common user queries
* Critical business questions
* Edge cases
* Different complexity levels
These questions will serve as your quality benchmark throughout the process.
**Step 6: Test and Iterate**
Test the chat on your 20 questions:
* Run all questions through the agent
* Verify answers are correct and complete
* Identify gaps in context or understanding
* **Iterate on context** - add missing information, clarify ambiguities, refine rules
Repeat until all 20 questions are answered correctly.
Learn how to build comprehensive test suites and integrate testing into your workflow
**Step 7: Roll Out to Users**
Once your test suite passes:
* **Roll out** to a small group of users initially
* **Track usage** - monitor what questions users are asking
* **Monitor real-life performance** using logs of questions and feedback
* Collect user feedback to identify improvement areas
**Step 8: Version Control and Quality Assurance**
Maintain context quality over time:
* **Version your context** using git repositories
* **Run `nao test` frequently** (e.g., weekly or after major changes)
* Ensure context quality doesn't drift as you make updates
* Set up automated tests in CI/CD pipelines
* Track test results over time to monitor performance trends
## Close the feedback loop
**Step 9: Let Recommendations Surface the Next Fix**
Your 20 test questions catch regressions, but they can't predict every question real users will ask. Once the agent is live, turn production usage into your next backlog:
* Enable [Recommendations](/nao-agent/context-engineering/recommendations) so nao audits its own usage on a schedule and ranks where your context is missing, wrong, or unclear.
* Work the impact-ordered list: tool errors, downvotes, and recurring questions each point at a specific file to fix.
* Open the fix as a pull request straight from a recommendation when a GitHub repo is connected, then re-run `nao test` to confirm it holds.
This is the online half of evaluation: `nao test` guards the benchmark you wrote, Recommendations finds the gaps you didn't. Feeding production friction back into context (and into new test cases) is what keeps the agent improving instead of drifting.
## Scale
**Step 10: Scale Gradually**
As adoption grows:
* **Extend the number of datasets** available in the agent
* **Make documentation and rules modular** to support scalability
* Add new domains incrementally, following the same process
* Maintain the same quality standards as you expand
Review the core principles that guide effective context engineering.
# Principles
Source: https://docs.getnao.io/nao-agent/context-engineering/principles
Core principles for optimizing your analytics agent's performance
## The Goal of Context Engineering
Context engineering optimizes the performance of your analytics agent. Well-engineered context leads to more accurate, reliable, and cost-effective responses.
## Core Performance Metrics
The performance of your analytics agent is measured across three key dimensions:
### Reliability
* **Percentage of questions answered** - How often the agent can provide a response
* **Percentage of correct answers** - How accurate those responses are
### Speed
* **Response time** - How quickly the agent can process and respond to queries
### Costs
* **Token costs** - The computational cost of processing context and generating responses
* **Query execution costs** - The cost of running SQL queries against your data warehouse
These metrics are interconnected. Improving one often requires balancing trade-offs with others. The goal is to optimize all three simultaneously.
## Core Principles
Context engineering follows the same principles as data engineering:
### Measure
Track your agent's performance across all three metrics:
* Monitor query accuracy and answer rates
* Measure response times
* Track token usage and query execution costs
### Iterate
Continuously improve your context based on real-world usage:
* Identify patterns in failures and gaps
* Add missing context or clarify ambiguities
* Test improvements with sample queries
* Refine based on user feedback
### Optimize
Find the optimal balance for your specific use case:
* **Too little context**: Agent can't answer questions, writes incorrect queries, or needs multiple exploratory queries (increasing costs)
* **Too much context**: Higher token costs, slower responses, and confused answers from processing irrelevant information
* **Optimal balance**: Include all necessary information without exploratory queries, exclude irrelevant schemas, and organize context modularly
## Concrete Rules
### 1. Be Exhaustive and MECE
Your context needs to be **Mutually Exclusive, Collectively Exhaustive** (MECE) so that the agent is reliable:
* **Collectively Exhaustive**: All metrics and data points your users might ask about should be defined in your context
* **Mutually Exclusive**: Each metric should have only one canonical definition - no conflicting definitions across tables or documentation
* **Consistent across tables**: The same metric or data point should mean the same thing wherever it appears, ensuring consistency across your schema
**Why it matters:** Missing metric definitions lead to incomplete or incorrect answers. Conflicting definitions across tables cause inconsistent responses. The agent needs a single source of truth for each metric.
### 2. Balance Token Costs
But not too exhaustive so that it's not too costly in terms of tokens:
* Include only relevant schemas, tables, and documentation
* Avoid redundant or unnecessary information
* Focus on what your users actually need
**Why it matters:** Every token costs money. Including irrelevant context increases costs without improving quality.
### 3. Minimize Query Execution
Provide enough context upfront so that it's not too costly in terms of query execution:
* Provide enough context upfront so the agent doesn't need to explore the schema through multiple queries
* Document relationships and join patterns explicitly
* Include example queries that demonstrate efficient patterns
**Why it matters:** Each exploratory query costs money and time. Well-documented context reduces the need for trial-and-error queries.
### 4. Keep It Modular
Your context should be modular so that it's not too costly in tokens and keeps the agent focused:
* Organize context into logical, domain-based modules
* Structure documentation and rules hierarchically
* Enable the agent to load only relevant pieces of context at a time
**Why it matters:** Modular context allows the agent to read small, focused pieces of information rather than processing everything at once. This reduces token costs and improves focus.
## Built-in reliability guardrails
Some reliability behavior is handled by nao itself and needs no context work on your side.
* **Truncated results are flagged** - when a query the agent wrote returns exactly as many rows as its own `LIMIT` or `TOP` clause allows, the tool output warns the agent that the result is almost certainly truncated and that the row count reflects the limit, not the total. The agent is told to run a separate `COUNT(*)` query without a limit rather than report the capped number as a total.
* **Warehouse dialect rules** - SQL rules for the warehouses you are connected to are injected into the system prompt automatically. See [System Prompts](/nao-agent/context-builder/system-prompts#warehouse-dialect-rules).
## Next Steps
Apply these principles step-by-step with our context engineering playbook.
Measure and test your agent's performance with unit tests
# Recommendations
Source: https://docs.getnao.io/nao-agent/context-engineering/recommendations
Close the feedback loop - nao audits its own usage to recommend context fixes (online evals)
Context engineering is a feedback loop: ship context, watch how the agent performs on real questions, fix what breaks, repeat. nao gives you both halves of that loop.
| | Offline evals | Online evals |
| ----------- | ------------------------------------------------------- | ------------------------------------------------------------ |
| **Tool** | [`nao test`](/nao-agent/context-engineering/evaluation) | Recommendations |
| **When** | Before you ship, in CI | Continuously, on production usage |
| **Signal** | A fixed suite of questions you wrote | What real users actually asked and where the agent struggled |
| **Answers** | "Did I regress against my benchmark?" | "Where is my context missing, wrong, or unclear?" |
`nao test` catches regressions against a benchmark you control. Recommendations catch the gaps you didn't think to test, by mining what users do once the agent is live. Together they close the loop between shipping context and improving it.
## How it works
On a schedule you choose (daily, weekly, or monthly), or on demand with **Run now**, nao runs an analysis agent that audits your own project context against real usage. The agent reads your usage data only through a read-only, project-scoped sandbox (auth and PII columns are excluded, and it can only see the current project). It produces a ranked list of context improvements, each pointing at the file to change and what is missing.
## What it scans
The agent mines nao's own usage to find friction, then reads your context files to locate where each fix belongs.
**Usage signal** (read-only SQL over nao's usage views):
* **Tool errors** - queries or tools that fail repeatedly
* **Negative feedback** - messages users downvoted
* **Regenerations and corrections** - answers users had to redo or correct
* **Coverage gaps** - questions the agent couldn't answer, or recurring analysis patterns it handles inconsistently
**Context files** it audits to place the fix: `RULES.md`, `semantics/*.md`, `databases/**`, `docs/`, and synced repositories under `repos//`.
**Read cost** - nao also tracks what the `read` tool spends on each context file and flags three patterns, so the audit can propose a cheaper shape rather than only a more correct one:
| Flag | What it means | What the agent tends to suggest |
| ------------------------ | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `truncated_on_read` | The file is longer than the `read` tool's 32,000-character limit, so its tail is never seen | Split it into several readable files |
| `frequent_and_expensive` | The agent reads it often and it costs a lot of tokens every time | Split it, move a recurring process into a [skill](/nao-agent/context-engineering/skills), or cut the parts nobody uses |
| `rare_but_outlier` | Rarely read, but very expensive when it is | Split it, or summarize it |
## What it outputs
Review recommendations as an admin under **Settings -> Recommendations**:
* **Impact-ordered cards** - the highest-friction issues first.
* **A category** - `Tool errors`, `Hallucinations`, `Semantic missing`, `Context bloat`, `Skills`, or `Other`.
* **A concrete target** - each recommendation names the file to edit and what is missing, wrong, or unclear.
* **Provenance** - the chat the friction came from (with the user and the chat title, so you can jump to it), and the model that produced the recommendation.
* **Lifecycle actions** - **acknowledge**, **snooze**, or **dismiss** each one as you work through them.
* **A drafted patch** - when nao can write the fix itself, the card says how many files it drafted. **Show diff** opens a side panel with the proposed edit rendered file by file, with added and removed line counts and collapsed unchanged context you can expand.
* **Manual fix guidance** - when the fix belongs in an auto-generated file, the card shows **How to fix** instead, with written guidance and a ready-to-use prompt.
### Filtering and sorting
The toolbar above the list narrows a long backlog:
* **Filter** by category, by the user whose chat triggered the recommendation, or by whether it came with explicit feedback. Each filter shows its count.
* **Sort** by date, by number of triggering chats, or by pull request state, ascending or descending.
## Running an audit
The Recommendations page shows the state of the latest run.
* **Run now** starts an audit immediately, outside the schedule. The button shows **Running...** while an audit is in progress.
* If a run fails, the error is shown on the page rather than failing silently.
## What you can configure
From the Recommendations settings page you control:
* **Analysis model** - which LLM provider and model run the audit.
* **Run frequency** - daily, weekly, or monthly, at a fixed time of day. You can also trigger a run any time with **Run now**.
* **Custom system prompt instructions** - extra guidance appended to the built-in audit instructions on every run. For example: *"Spot recurring analysis patterns and propose new skills to structure them"* or *"Scan the conversations of the last 3 weeks"*.
* **GitHub repository** - the `owner/name` repo where fixes are opened as pull requests (see below). Project files are not synced from this repo; it is only the target for PRs.
* **YOLO mode** - when on, nao opens pull requests automatically after each run, without human review, and marks those recommendations as applied. Leave it off to review each recommendation before opening a PR.
Enable the audit on your deployment by setting `BETA_CONTEXT_RECOMMENDATIONS_ENABLED=true`.
## Ways to use it
* **Catch tools that fail regularly** - a metric the agent keeps querying wrong usually means a definition is missing or ambiguous in `semantics/`.
* **Act on new user feedback** - downvotes and corrections surface as recommendations instead of sitting unread in the feedback log.
* **Encode recurring analysis patterns** - when many users ask the same kind of question, the agent suggests documenting it once in your context so every answer is consistent.
## Turn a recommendation into a pull request
When a GitHub or GitLab repository is connected, you can open the fix as a pull request (GitHub) or merge request (GitLab) directly from a recommendation - nao writes the edit to the right context file and opens it for your review.
1. **Connect GitHub or GitLab to your nao account.** nao uses the same OAuth app as [GitHub SSO](/nao-agent/chat/admin/setup#configure-github-sso) or [GitLab SSO](/nao-agent/chat/admin/setup#configure-gitlab-sso) for repository access. Connect it once at the account level.
2. **Choose the target repository** on the Recommendations settings page, in `owner/name` format. nao suggests the repositories already declared in your `nao_config.yaml`.
3. **Open the PR or MR.** Review the drafted edit with **Show diff**, then click **Create PR**. nao proposes the concrete edit (for human-written files like `RULES.md` or `semantics/**`, or for upstream files under `repos//` when that repo maps to a GitHub or GitLab URL) and opens a pull request or merge request. You review and merge it like any other change, keeping your context under version control.
Once the pull or merge request exists, the card replaces **Create PR** with **View PR** and shows its current state.
For fixes that belong in generated files (`databases/**`, which `nao sync` rewrites) or in unconnected sources, nao instead hands you clear guidance to apply yourself rather than opening a PR.
### Batching several fixes into one pull request
Opening one PR per recommendation gets noisy once you have a backlog. Select several cards instead and ship them together.
Turn on selection mode, tick the recommendations you want, and an action bar appears at the bottom of the page:
* **Open 1 PR** commits each selected fix separately and opens a **single** pull request containing all of them, so your reviewer sees one change with one commit per recommendation. Only recommendations with a drafted patch and no existing PR are eligible - the bar shows how many of your selection qualify.
* **Copy prompts** copies the fix prompts for everything selected, so you can paste them into your own coding agent instead.
* **Select all** / **Deselect all** work on the currently filtered list.
Up to 20 recommendations can be actioned at once.
When the nao project lives in a subdirectory of the connected repository (a monorepo, for example), nao writes the fixes at the right path inside it rather than at the repository root.
To skip the manual step entirely, turn on **YOLO mode** (see [What you can configure](#what-you-can-configure)) and nao opens these PRs automatically after each run for you to review and merge.
## Next steps
Build the offline test suite that complements recommendations
See where recommendations fit in the end-to-end workflow
# Skills
Source: https://docs.getnao.io/nao-agent/context-engineering/skills
Six published nao skills that automate the context-engineering lifecycle inside Claude Code, Codex, Cursor, or any agent that loads SKILL.md files
## What these skills are
nao publishes six **context-engineering skills** — standard `SKILL.md` files (YAML frontmatter + markdown) that any agentic CLI (Claude Code, Codex, Cursor, the Claude Agent SDK, …) can load and invoke.
Each skill is a self-contained workflow that automates one stage of the [Context Engineering lifecycle](/nao-agent/context-engineering/playbook): scoping a project, writing rules, building a test suite, auditing what's there, adding a semantic layer once tests show metric gaps, and deploying context to production via CI.
These skills are **for the human/agent driving nao** (in their IDE or terminal). They are different from the runtime skills the chat agent calls at query time — see [Tools, MCPs, Skills](/nao-agent/chat/capabilities/tools-mcps-skills) for those.
## Demo
## Install
The published source of truth lives at [github.com/getnao/nao/tree/main/skills](https://github.com/getnao/nao/tree/main/skills). Install all five into the current project's `.claude/skills/` with:
```bash theme={null}
nao skills add getnao/nao
```
`nao skills` is a thin wrapper around the [open-source `skills` CLI from Vercel Labs](https://github.com/vercel-labs/skills), so the equivalent direct call also works:
```bash theme={null}
npx skills add getnao/nao
```
Run from the root of the project where `nao_config.yaml` lives. Re-run any time to pick up updates; pass through `--force` to overwrite local edits.
Once installed, the skills are auto-discovered by Claude Code, Codex, and other agentic CLIs that load `.claude/skills/`. Trigger one by name (e.g. "use the setup-context skill") or let the agent route on the skill description.
## The six skills
### setup-context
Takes the user from `pip install nao-core` to a synced project with a starter `RULES.md`. **First-time install only** — for editing rules, generating tests, or reviewing an existing context, use the other skills below.
**Steps**
1. **Ask everything in one round** — warehouse + auth, scope (which tables, ≤100 with 20 as the target), extra context (dbt / ETL / BI repos, Notion, internal docs), LLM provider.
2. **Look up the warehouse-specific config** from [docs.getnao.io/nao-agent/context-builder/databases](/nao-agent/context-builder/databases), write `nao_config.yaml`, run `nao init`, then print a summary for the user to confirm before continuing.
3. **`nao sync`** — populate `databases/`, `repos/`, `docs/`, `semantics/`. Don't move on until sync is clean.
4. **Generate `RULES.md`** by handing off to `write-context-rules`.
5. **Wire up the LLM key** via `${ANTHROPIC_API_KEY}` (or equivalent) — never paste keys into chat.
6. **Recommend next steps** — smoke test with `nao chat`, review `RULES.md`, then `create-context-tests`.
### write-context-rules
Owns `RULES.md`. Generates the six standard sections, section by section, showing the user each block before moving on. If `RULES.md` already has content, runs an audit-and-fill flow that fills only what's missing.
**Steps**
1. **`## Business overview`** — Product + Business model (sourced from web search + `databases/` + dbt repo).
2. **`## Data architecture`** — Warehouse, data stack, layers, sources.
3. **`## Core data models`** — `### Most Used Tables` (one-line pointers) + `### Tables detail` (Purpose, Granularity, Key Columns ≤10, Use For).
4. **`## Key Metrics Reference`** — grouped by category; `**metric** → table, column, formula`.
5. **`## Date filtering`** — three example formulas (last X weeks / last X days / current month) keyed off the user's week-boundary and current-period-inclusion conventions.
6. **`## Analysis Process`** — five subsections: Understand → Select Table → Write Query → Validate → Context.
7. **Validate metrics with the user** — confirm every source-of-truth pointer in the metrics reference.
8. **Date filtering, with the user** — pick week boundary (Sunday vs Monday) and current-period inclusion.
**Template** — [`templates/RULES.md`](https://github.com/getnao/nao/blob/main/skills/write-context-rules/templates/RULES.md), the six-section scaffold:
````markdown theme={null}
# RULES.md
> Included with every message sent to the nao agent. Keep it lean.
> Per-table detail belongs in `databases/.md`, not here.
## Business overview
**Product**: …
**Business model**: …
## Data architecture
**Warehouse:** …
**Data stack:** …
**Data layers:** …
**Data sources:** …
## Core data models
### Most Used Tables
- `` — one-line purpose. See `databases/.../table=/` folder.
### Tables detail
#### ``
**Purpose**: …
**Granularity**: One row per …
**Key Columns**: (≤10)
**Use For**: …
## Key Metrics Reference
###
- **** → `.`, ``
## Date filtering
> Convention: e.g. "Week starts Monday; 'last X weeks' excludes the current incomplete week."
### Last X weeks
```sql
…
```
### Last X days
### Current month
## Analysis Process
### 1. Understand the Question
### 2. Select the Right Table(s)
### 3. Write Efficient Queries
### 4. Validate Results
### 5. Provide Context
````
### create-context-tests
Generates a test suite of natural-language → SQL pairs that becomes the reliability benchmark. `nao test` runs each prompt through the agent, executes both the agent's SQL and the test's expected SQL, and **diffs the result data row-by-row**. See [Evaluation](/nao-agent/context-engineering/evaluation) for the scoring model.
**Two authoring rules**
* **Prompts read like real chat.** Short, vague, no table / column / method hints. `"How's churn looking this quarter?"`, not `"What was the churn rate from fct_subscriptions in Q1?"`.
* **Output column names encode format / unit, not source.** `churn_rate_float_0_1`, not `churn_rate_from_fct_subscriptions`.
**Steps**
1. **Ask once** — does the user have trusted source-of-truth queries (Looker, dashboards, prior benchmarks)? Transform each into a test; for metrics without a trusted query, draft new ones.
2. **Save flat under `tests/`** (no subfolders), one YAML file per test.
3. **Have the user validate** — prompts match their team's phrasing, SQL matches their definition of truth.
4. **Run `nao test -m -t 10`** — recap pass rate, token cost, wall-clock time as the baseline.
5. **Diagnose failures** — read `tests/outputs/`, identify the rule gap, route to `write-context-rules` for the smallest fix. Re-run between fixes so impact is attributable.
**Template** — [`templates/test.yaml`](https://github.com/getnao/nao/blob/main/skills/create-context-tests/templates/test.yaml):
```yaml theme={null}
name: churn_rate_last_quarter
prompt: How's churn looking this quarter?
sql: |
SELECT
SAFE_DIVIDE(churned, total) AS churn_rate_float_0_1
FROM (
SELECT
COUNTIF(churned_at IS NOT NULL) AS churned,
COUNT(*) AS total
FROM ..fct_subscriptions
WHERE started_at < DATE_TRUNC(CURRENT_DATE, QUARTER)
);
# Optional:
# category: revenue | activity | conversion | churn | retention | …
# difficulty: easy | medium | hard
# notes: why this test matters / what failure mode it catches
```
### audit-context
Diagnoses a nao context. Finds gaps, MECE violations, failure root causes, and bloat. Output is a short in-conversation report ending in a prioritized plan. **Diagnose only — never fixes.** Routes fixes to `write-context-rules` / `add-semantic-layer` / `create-context-tests`. Run any time: right after `setup-context`, mid-build, before a release, or when behavior gets surprising.
**Steps — six checks in order**
1. **Synced context** — what's wired in (warehouse, repos, Notion, semantic layer, MCPs) vs missing. Has `nao sync` run? Scope check: ≤100 tables hard ceiling, ≤20 ideal. Oversized scope is the biggest predictor of reliability failure — flag it explicitly.
2. **`RULES.md` vs target structure** — six sections from `write-context-rules`. Per section, mark **present / missing / thin**. Flag placeholders, `TODO:` markers, and metric entries with no source-of-truth pointer.
3. **Per-table coverage** — every table in `databases/`: is it in `## Most Used Tables`? Has a `## Tables detail` block? dbt context (`schema.yml`)? Per-table gaps: undocumented columns, calculated fields with no explanation, foreign keys with no relation.
4. **Data model consistency (MECE)** — two tables computing the same metric differently? Asked metrics no in-scope table can answer? Duplicated columns under different names? Ambiguous columns (`amount` without unit, `status` without enum values)?
5. **Test coverage** — if `tests/` is empty, recommend `create-context-tests`. Otherwise read `tests/outputs/` and categorize each failure (data model / date selection / test issue / interpretation / metric definition) with the smallest rule change per failure.
6. **Token optimization** — files >40KB, `## Tables detail` blocks past the 10-column cap, duplication between `RULES.md` and `databases/.md`, in-scope tables with no mention in any test.
**Output**
Lead with a one-paragraph summary (`sync state | scope wideness | rules quality (N/6 sections substantive) | test coverage`), deep-dive only sections with findings, end with a prioritized plan that names the skill that does each fix:
```
## Plan
1. (easy / 5 min) … → write-context-rules
2. (small / 30 min) … → create-context-tests
3. (medium / 1-2 hr) … → audit-context (rerun after)
4. (large / multi-session) … → add-semantic-layer
```
### add-semantic-layer
Wires a semantic layer into the agent so metric queries go through a single canonical definition. **Only after `nao test` shows metric-reliability failures** — not before. A semantic layer reduces the scope of answerable questions; the trade-off only pays off when reliability is the bottleneck. Schema gaps or date logic failures are rule-fixes, not semantic-layer fixes.
**Steps**
1. **Pick the tool**
| Option | Type | When |
| ------------------------------ | -------------- | ---------------------------------------------------------- |
| **dbt MetricFlow** | Metric store | Already running dbt Cloud with the Semantic Layer enabled. |
| **Snowflake views / semantic** | Semantic layer | Snowflake; using curated views or native semantic views. |
| **nao semantic files** | Semantic layer | No existing layer. Want a lightweight in-repo YAML. |
| **Other** (Looker, Cube, …) | Varies | Search the MCP registry; otherwise fall back to nao YAML. |
2. **Install the matching MCP** under `.claude/mcp.json` (dbt-mcp / mcp-server-snowflake / Cortex MCP, etc.). Credentials via `${ENV_VAR}` only — never inline.
3. **Hand off to `write-context-rules`** to route every metric in `## Key Metrics Reference` through the new layer (e.g. `MRR → query via dbt MCP query_metric (semantic layer)`).
4. **Validate** — `nao chat` one of the user's top questions, confirm the agent uses the semantic layer, then `nao test` and **compare to the pre-semantic-layer baseline pass rate**. Reliability is the only reason to do this — measure it.
**Template — nao YAML option** ([`templates/semantic.yaml`](https://github.com/getnao/nao/blob/main/skills/add-semantic-layer/templates/semantic.yaml)):
```yaml theme={null}
dimensions:
- name: date
type: date
description: Calendar date. Use this for any time-based slicing.
- name: plan
type: categorical
description: Subscription plan tier.
values: [free, pro, enterprise]
metrics:
- name: mrr
definition: Monthly Recurring Revenue from active paying subscriptions, in USD.
source:
table: fct_stripe_mrr
column: mrr_amount
aggregation: SUM # SUM | COUNT | COUNT_DISTINCT | AVG | MIN | MAX
grain: month # day | week | month | quarter | year
dimensions: [date, plan, country]
filters:
- "status = 'active'"
```
For the dbt MetricFlow / Snowflake / Cortex paths, the metric definitions live upstream — this skill installs the MCP and routes `RULES.md` to it instead of writing local YAML.
### deploy-context
Wires a GitHub Actions workflow that runs `nao deploy` on every push to `main`, so context changes go live automatically. Handles API key creation, GitHub Secrets setup, `.naoignore` hardening, and the workflow file itself.
**Steps**
1. **Prerequisites check** — remote URL, repo, `project_name`, confirm who can mint an API key.
2. **API key creation** — walk through Settings -> Organization -> API Keys. The key is shown once; store it as a GitHub Secret, never in the workflow or `nao_config.yaml`.
3. **GitHub Secrets** — `NAO_URL` and `NAO_API_KEY`, plus optional warehouse/Notion secrets if the user also wants `nao sync` in CI. For multi-environment teams, use GitHub Environments.
4. **`.naoignore` hardening** — review the always-excluded set (`.git`, `.venv`, `.env`, `node_modules`, `__pycache__`, `repos`, `*.pyc`) and add project-specific patterns.
5. **Workflow file** — `.github/workflows/nao-deploy.yml` with `on: push: branches: [main]`, `workflow_dispatch`, concurrency serialization, and the API key passed via `env:` (never a CLI literal).
6. **Optional `nao sync` step** — most teams keep sync on a cron and let its commit trigger the deploy workflow.
7. **End-to-end verification** — trigger a push, watch for the success message, confirm the remote project matches `main`.
**Guardrails**: never commit secrets; one API key per repo/environment; no `pull_request` trigger (forks could exfiltrate the key); every deploy is a full replacement.
## When to use which
```
setup-context → write-context-rules → create-context-tests → audit-context (anytime)
(first time only) (any rules change) (benchmark + extend) (diagnose, never fix)
│
▼
tests reveal metric
reliability gaps?
│
▼
add-semantic-layer
(then back to write-context-rules)
Ready to ship?
│
▼
deploy-context
(CI/CD to production)
```
| If you want to… | Use |
| -------------------------------------------------------------- | ------------------------------------------------------------- |
| Set up nao on a brand-new project | `setup-context` |
| Generate or rewrite `RULES.md` | `write-context-rules` |
| Add tests for a new metric, or build the first benchmark | `create-context-tests` |
| Find out what's missing, broken, or bloated | `audit-context` |
| Make metric calculations consistent across questions | `add-semantic-layer` (only after tests show the gap) |
| Refine `RULES.md` because the agent keeps making the same miss | `write-context-rules` (preceded by `audit-context` if unsure) |
| Auto-deploy context to production on every push | `deploy-context` |
## Source material
The skills are distilled from the public Context Engineering content:
* [Playbook](/nao-agent/context-engineering/playbook)
* [Principles](/nao-agent/context-engineering/principles)
* [RULES.md](/nao-agent/context-builder/rules-context)
* [Evaluation](/nao-agent/context-engineering/evaluation)
Read each skill's source `SKILL.md` in the repo: [github.com/getnao/nao/tree/main/skills](https://github.com/getnao/nao/tree/main/skills).
# Watch Demo 🎬
Source: https://docs.getnao.io/nao-agent/demo
Watch nao in action
Watch a quick demo to see how nao works and what you can build with it.
## Ready to Get Started?
Deploy your first analytics agent in 1 minute
Learn how to build context for your agent
# Contributing to nao
Source: https://docs.getnao.io/nao-agent/developers/overview
Join the nao open source community and help build the \#1 analytics agent
nao is an open source project, and we welcome contributions from the community!
## Get Involved
Show your support by starring the repository.
Found a bug? Open an issue and help us improve.
Contribute code by submitting a pull request.
Share ideas and connect with other contributors.
## Development Setup
To contribute to nao, clone the repository and follow the setup instructions:
```bash theme={null}
# Clone the repository
git clone https://github.com/getnao/nao.git
cd nao
# Install dependencies
npm install
# Follow the development setup in CONTRIBUTING.md
```
For detailed development guidelines, commands, and contribution standards, see our [CONTRIBUTING.md](https://github.com/getnao/nao/blob/main/CONTRIBUTING.md) file.
## Tech Stack
nao is built with modern technologies:
### Backend
| Technology | Purpose |
| -------------------------------------------------------- | ---------------------------------- |
| [Fastify](https://fastify.dev/docs/latest/) | High-performance web framework |
| [Drizzle ORM](https://orm.drizzle.team/docs/get-started) | TypeScript ORM for database access |
| [tRPC](https://trpc.io/docs/server/routers) | End-to-end typesafe APIs |
### Frontend
| Technology | Purpose |
| --------------------------------------------------------------------------------- | ------------------------- |
| [React](https://react.dev/) | UI framework |
| [Tanstack Query](https://tanstack.com/query/latest/docs/framework/react/overview) | Data fetching and caching |
| [tRPC Client](https://trpc.io/docs/client/tanstack-react-query/usage) | Typesafe API client |
| [Shadcn UI](https://ui.shadcn.com/docs/components) | Component library |
### CLI
| Technology | Purpose |
| ---------- | ------------------------------- |
| Python | CLI tooling and context builder |
## Running with Docker
You can also run nao using Docker:
```bash theme={null}
# Pull the image
docker pull getnao/nao:latest
# Run the container
docker run -d \
--name nao \
-p 5005:5005 \
-e NAO_DEFAULT_PROJECT_PATH=/app/example \
-e OPENAI_API_KEY=sk-... \
getnao/nao:latest
```
Access the UI at [http://localhost:5005](http://localhost:5005).
## Join the Community
Connect with other nao users and contributors.
Watch releases and follow development.
## Ways to Contribute
There are many ways to contribute to nao:
* **Code contributions** — Fix bugs, add features, improve performance
* **Documentation** — Improve docs, add examples, fix typos
* **Bug reports** — Report issues you encounter with detailed reproduction steps
* **Feature requests** — Suggest new features and improvements
* **Community support** — Help answer questions in Slack and GitHub Discussions
# Authentication
Source: https://docs.getnao.io/nao-agent/enterprise/authentication
Single sign-on for nao Enterprise - Microsoft Entra ID and any OIDC provider (Okta, Auth0, Keycloak, OneLogin)
nao Enterprise supports single sign-on (SSO) so your users sign in to nao with the same identity provider they already use for the rest of your stack. SSO is gated by the `sso` feature flag on your Enterprise license - install the license first (see [Overview](/nao-agent/enterprise/overview#installing-the-license)).
The providers documented on this page - **Microsoft (Azure AD / Entra ID)** and **generic OIDC** - require the license. Google, GitHub, and GitLab are available as login providers on any deployment, licensed or not: see [Admin setup](/nao-agent/chat/admin/setup#configure-github-sso) for those.
## Microsoft SSO (Azure AD / Entra ID)
Microsoft SSO is implemented via OAuth against Azure AD (Entra ID). When configured, the nao login page exposes a **Sign in with Microsoft** button, and new users are auto-provisioned the first time they sign in (subject to your auto-create policy).
### Step 1: Register an application in Azure
1. Open the [Azure portal](https://portal.azure.com/) -> **Microsoft Entra ID** -> **App registrations**.
2. Click **New registration**. Give it a name (e.g. `nao SSO`).
3. **Supported account types**: pick **Single tenant** unless you need cross-tenant access.
4. **Redirect URI**: type **Web**, value `https:///api/auth/callback/microsoft`.
5. Click **Register**.
Note the **Application (client) ID** and **Directory (tenant) ID** from the **Overview** page - you'll need both.
### Step 2: Create a client secret
1. In your new app, open **Certificates & secrets** -> **Client secrets** -> **New client secret**.
2. Pick an expiry that matches your rotation policy.
3. Copy the **Value** immediately - it is only shown once.
### Step 3: Configure API permissions
1. Open **API permissions** -> **Add a permission** -> **Microsoft Graph** -> **Delegated permissions**.
2. Add `openid`, `profile`, `email`, `offline_access`.
3. If you plan to use [Redshift RLS via Entra ID](/nao-agent/enterprise/permissions#redshift-row-level-security-via-entra-id), expose your Redshift API scope under **Expose an API**:
* Set the **Application ID URI** to `api://` (using the Application (client) ID from Step 1).
* **Add a scope** - Azure pre-fills the name as `user_impersonation`. The name is arbitrary, but it must match the scope you configure in Step 4.
* This exposed scope is the one nao requests at runtime - it is not just a permission. It determines the **audience** (`aud`) of the token nao sends to Redshift, which Redshift validates against its native IdP app integration.
4. If your tenant requires it, click **Grant admin consent**.
### Step 4: Configure nao
Set the following environment variables on your nao deployment and restart:
```bash theme={null}
AZURE_AD_CLIENT_ID=
AZURE_AD_CLIENT_SECRET=
AZURE_AD_TENANT_ID=
```
When `AZURE_AD_CLIENT_ID`, `AZURE_AD_CLIENT_SECRET`, and `AZURE_AD_TENANT_ID` are set and the license has the `sso` feature active, the **Sign in with Microsoft** button appears on the login page.
Optionally, set `AZURE_AD_TOKEN_SCOPE` to control the scope nao uses to silently acquire downstream access tokens:
```bash theme={null}
AZURE_AD_TOKEN_SCOPE=api:///user_impersonation
```
* **Leave it unset for SSO-only deployments.** It defaults to `/.default`, which is fine when nao does not need to call a downstream resource.
* **For Redshift native IdP federation, set it to the exposed API scope** whose audience Redshift expects - for example `AZURE_AD_TOKEN_SCOPE=api:///user_impersonation`, matching the scope you exposed in Step 3.
* The resulting token's `aud` claim must match the audience configured in the Redshift native IdP app integration. If they differ, Redshift rejects the token.
### Step 5: Verify
1. Open your nao deployment's login page in a private window.
2. Click **Sign in with Microsoft** and complete the Microsoft prompt.
3. You should land in nao with a user account provisioned from your Microsoft identity (email + display name).
4. As an admin, open **Settings -> Team** to confirm the user appears with the role you expected.
The Microsoft refresh token issued at sign-in is stored encrypted by nao and reused to silently obtain access tokens for downstream services like Redshift. Users do not have to re-authenticate when they query the warehouse.
### Mapping Microsoft groups to nao roles
Group-to-role mapping is not yet exposed in the UI. New SSO users are auto-provisioned with the role set by the `DEFAULT_USER_ROLE` environment variable (`admin`, `user`, or `viewer`; defaults to `user`, shown as **Member** in the UI). See [Default role for new users](/nao-agent/chat/admin/setup#default-role-for-new-users). Change a user's role afterwards from **Settings -> Team**. For automated mapping, contact us - the rules go through the license payload.
## Generic OIDC (Okta, Auth0, Keycloak, OneLogin, ...)
For identity providers other than Microsoft, nao supports single sign-on via any OIDC-compliant provider. One integration covers Okta, Auth0, Keycloak, OneLogin, and any provider that exposes a standard `.well-known/openid-configuration` discovery document. When configured, the login page shows a **Continue with `{provider name}`** button (the label comes from `OIDC_PROVIDER_NAME`, default `SSO`), and new users are auto-provisioned on first sign-in (subject to your auto-create policy).
### Step 1: Register an application in your provider
Create an OIDC / OAuth 2.0 application in your identity provider and note the **client ID** and **client secret**. Register the following redirect URI, replacing `{OIDC_PROVIDER_ID}` with the value you set in Step 2 (defaults to `oidc`):
```
https:///api/auth/oauth2/callback/{OIDC_PROVIDER_ID}
```
### Step 2: Configure nao
Set the following environment variables on your nao deployment and restart. `OIDC_DISCOVERY_URL` depends on your provider:
```bash theme={null}
OIDC_DISCOVERY_URL=https:///oauth2/default/.well-known/openid-configuration # required
OIDC_CLIENT_ID= # required
OIDC_CLIENT_SECRET= # required
OIDC_PROVIDER_ID=oidc # optional, used in the callback URL (defaults to "oidc")
OIDC_PROVIDER_NAME=SSO # optional, shown on the login button (defaults to "SSO")
OIDC_SCOPES=openid,profile,email # optional, comma-separated (defaults shown)
OIDC_AUTH_DOMAINS=example.com # optional, comma-separated email-domain allowlist
OIDC_PKCE=true # optional, defaults to true
```
```bash theme={null}
OIDC_DISCOVERY_URL=https:///.well-known/openid-configuration # required
OIDC_CLIENT_ID= # required
OIDC_CLIENT_SECRET= # required
OIDC_PROVIDER_ID=oidc # optional, used in the callback URL (defaults to "oidc")
OIDC_PROVIDER_NAME=SSO # optional, shown on the login button (defaults to "SSO")
OIDC_SCOPES=openid,profile,email # optional, comma-separated (defaults shown)
OIDC_AUTH_DOMAINS=example.com # optional, comma-separated email-domain allowlist
OIDC_PKCE=true # optional, defaults to true
```
When `OIDC_DISCOVERY_URL`, `OIDC_CLIENT_ID`, and `OIDC_CLIENT_SECRET` are set, the SSO button appears on the login page. When they are unset, the button is hidden. Set `OIDC_AUTH_DOMAINS` to restrict sign-up to specific email domains.
### Step 3: Verify
1. Open your nao deployment's login page in a private window.
2. Click **Continue with `{provider name}`** and complete your provider's prompt.
3. You should land in nao with an account provisioned from your OIDC identity (email + display name).
4. As an admin, open **Settings -> Team** to confirm the user appears with the role you expected.
### Okta group-to-role mapping
For an issuer such as `https://.okta.com/oauth2/default`, add a groups claim to the authorization server:
1. In Okta, open **Security -> API -> Authorization Servers -> default -> Claims**.
2. Add a claim named `groups`, include it in the **ID Token** with **Always**, and set its value type to **Groups**.
3. Filter groups with a prefix such as `nao-`.
Then configure nao and restart:
```bash theme={null}
OIDC_GROUPS_CLAIM=groups
OIDC_GROUP_ROLE_MAPPING=nao-admins:admin,nao-context:context_admin,nao-users:user,nao-viewers:viewer
```
The valid roles are `admin`, `context_admin`, `user`, and `viewer`. When several groups match, the most privileged role wins. Changes apply the next time the user signs in.
To verify the claim and resolved role, open **Settings -> Enterprise -> Single sign-on token**. If `groups` is absent, confirm that Okta includes the claim in the **ID token**, not only the userinfo response.
### Launching nao from your provider's app dashboard
OIDC only defines app-initiated sign-in, so clicking a nao tile in Okta's **My Apps** (or an equivalent dashboard) does not hand tokens to nao. The provider just redirects to nao with an `iss` query parameter and expects nao to start the flow.
nao handles this at `GET /api/sso/start`: it starts the authorization request server-side and redirects the browser to your provider, so users go straight from the tile into nao without seeing the login page. Register it as the initiate-login URI in your provider:
```
https:///api/sso/start
```
In Okta, open the app's **General Settings**, set **Login initiated by** to `Either Okta or App`, check **Display application icon to users**, and set **Initiate login URI** to the URL above. If the tile still lands on the nao login page, the initiate-login URI is missing.
Okta hides the **Assignments** tab when **Federation Broker Mode** is enabled on the app, which also keeps the
tile off **My Apps**. Disable it under **General -> Federation Broker Mode** to assign users and groups manually.
## Okta via SAML
The generic OIDC integration above covers Okta's OIDC apps. If your team needs Okta over SAML specifically, email [claire@getnao.io](mailto:claire@getnao.io) - we can scope a custom rollout.
## Removing SSO
To disable SSO temporarily, unset `AZURE_AD_CLIENT_ID` (or remove the license). The login page falls back to email + password authentication. Existing SSO users retain their accounts and can be migrated to email + password from **Settings -> Team**.
# Overview
Source: https://docs.getnao.io/nao-agent/enterprise/overview
nao Enterprise license, supported features, and how to get a quote
nao Enterprise is the licensed tier of self-hosted nao. It unlocks features that are restricted to organizations with a signed license (currently SSO, Microsoft Entra ID-based row-level security on Redshift, and white-label branding), and runs on the same self-hosted deployment you already use for OSS nao.
nao Enterprise applies only to **self-hosted deployments**. nao Cloud organizations get the equivalent features as part of their Cloud plan and do not need a separate license.
## What's included
| Capability | OSS | Enterprise |
| ------------------------------------------------------------------------------------------ | --- | ---------- |
| Open-source agent + chat UI, all LLM providers | ✅ | ✅ |
| Slack / Teams / WhatsApp / Telegram / Mattermost bots | ✅ | ✅ |
| Self-hosting on your infrastructure | ✅ | ✅ |
| Admin / Context Admin / Member / Viewer roles | ✅ | ✅ |
| Google, GitHub, and GitLab login providers | ✅ | ✅ |
| Microsoft SSO (Azure AD / Entra ID) | - | ✅ |
| [Generic OIDC SSO](/nao-agent/enterprise/authentication) (Okta, Auth0, Keycloak, OneLogin) | - | ✅ |
| Per-user identity passthrough to Redshift (RLS) | - | ✅ |
| [White-label branding](/nao-agent/enterprise/white-label) (logos, favicon, app name) | - | ✅ |
| Priority support and roadmap input | - | ✅ |
The list of features available on a given license is encoded in the signed license payload itself. The currently shipped feature flags are `sso` and `white-label`; new features are added as they ship.
## Getting a license
nao Enterprise licenses are issued per company on a yearly subscription. To request a quote and a license key:
1. Email [claire@getnao.io](mailto:claire@getnao.io) with your company name, expected user count, and which features you need (SSO, Redshift RLS, both).
2. We send back a quote and, once signed, a license key tied to your `subscriptionId`.
3. You install the key as the `NAO_LICENSE` environment variable on your nao deployment (see below).
Trial licenses are available on request.
## Installing the license
The license is a signed token. Verification happens **offline** at server startup against the public key bundled in the build, so your nao deployment never has to phone home to validate the license.
1. In your deployment environment (e.g. Cloud Run, ECS, Kubernetes), set:
```bash theme={null}
NAO_LICENSE=
```
2. Restart the nao backend. On startup, the server logs the license verification result.
3. Sign in as an admin and open **Settings -> Enterprise**. The page shows the license status, expiry date, company name, and the list of activated features.
### License statuses
The Enterprise settings page reports one of four statuses:
* **Active** - signature valid and within expiry. Enterprise features are enabled.
* **Expired** - signature valid but the expiry date has passed. Renew the license to re-enable Enterprise features.
* **Invalid** - `NAO_LICENSE` is set but verification failed (bad signature, malformed token, key mismatch). Check that you copied the full key and that you're on a recent nao build.
* **Unlicensed** - `NAO_LICENSE` is not configured. The deployment runs in OSS mode.
When a license expires or becomes invalid, the gated Enterprise features are disabled cleanly - the rest of nao keeps running as the OSS build.
## Next steps
Configure Microsoft SSO for your nao deployment
Per-user identity passthrough and Redshift RLS
Replace nao branding with your own logos, favicon, and app name
# Permissions
Source: https://docs.getnao.io/nao-agent/enterprise/permissions
Per-user identity passthrough to your warehouse and row-level security on Redshift
nao Enterprise can pass each end user's identity through to the warehouse, so queries run **as the user asking them** instead of as a single shared service account. This unlocks native row-level security (RLS) policies you've already configured on the warehouse - the agent inherits those policies automatically, with no nao-side rule duplication.
Today this is supported on **Amazon Redshift via Microsoft Entra ID**. Other warehouses can be added on request.
## Why per-user identity matters
When nao runs queries with one shared connection, every user sees the same rows: the warehouse can't tell them apart. That's fine for analytics agents inside a small team, but it doesn't fit organizations that already enforce RLS or column-level security at the warehouse layer (sales reps see only their accounts, finance sees only their region, etc.).
With per-user identity:
* Each chat message is executed against the warehouse using the asking user's access token.
* Redshift identifies the user and applies whatever native policies you've defined (RLS, column masking, role-based grants).
* nao does not need a parallel rules system - the warehouse stays the source of truth for who can see what.
## Redshift row-level security via Entra ID
Use this when your Redshift cluster is configured to authenticate end users via Microsoft Entra ID (federated single sign-on).
### Prerequisites
* Microsoft SSO is configured for your nao deployment (see [Authentication](/nao-agent/enterprise/authentication#microsoft-sso-azure-ad--entra-id)).
* Your Redshift cluster is set up with [Microsoft Entra ID federation](https://docs.aws.amazon.com/redshift/latest/mgmt/redshift-iam-access-control-idp-connect-azure-ad.html) and your Azure app registration includes the Redshift scope.
* Your RLS policies are already defined on the Redshift side (Redshift's native [row-level security](https://docs.aws.amazon.com/redshift/latest/dg/t_rls.html) feature).
### Configure the connection
In `nao_config.yaml`, set `auth_mode: azure_entra_id` on the Redshift database:
```yaml theme={null}
databases:
- name: redshift_prod
type: redshift
host: your-cluster.region.redshift.amazonaws.com
port: 5439
database: analytics
auth_mode: azure_entra_id
user: "{{ env('REDSHIFT_USER') }}" # optional, sync-only
password: "{{ env('REDSHIFT_PASSWORD') }}" # optional, sync-only
schema_name: public
```
Two auth modes are available:
* `password` (default) - traditional service-account auth. All queries run as the same Redshift user.
* `azure_entra_id` - per-user identity. Sync-time queries (metadata, previews, query history) use the optional `user`/`password` if provided. Runtime queries from the chat agent **always** flow through the end user's Azure access token.
### How it works
1. The user signs in to nao with Microsoft SSO (Entra ID).
2. nao stores the user's encrypted refresh token.
3. When the agent runs a query, nao silently exchanges the refresh token for an Azure access token scoped to your Redshift API.
4. The query is sent to Redshift over the IAM-federated connection with that token.
5. Redshift identifies the user and enforces its native RLS policies.
If a user does not have access to a given row or table, Redshift returns the same response it would return for any other client - the agent surfaces the result as-is.
### Verifying it works
1. Sign in as a user with restricted access.
2. Ask the agent a question that should hit RLS-protected data.
3. The query result should reflect the user's policy. Cross-check with the same query run directly against Redshift as that user.
4. Sign in as a user with broader access and confirm the result set widens accordingly.
The sync-time `user`/`password` credentials in `azure_entra_id` mode are never used at runtime - they only exist so `nao sync` can read schema metadata and query history. If your security policy forbids any service-account access, leave them unset and provide warehouse metadata through another sync source.
## Other warehouses
BigQuery, Snowflake, Databricks, and other warehouses also support identity-federated auth. We add per-user identity passthrough on request as we see customer demand. Email [claire@getnao.io](mailto:claire@getnao.io) with your warehouse + identity-provider combination and we'll scope it for your deployment.
# White-Label Branding
Source: https://docs.getnao.io/nao-agent/enterprise/white-label
Replace the default nao branding with your own logos, favicon, and app name
White-label branding lets admins replace the default nao chrome with custom branding. Once configured, your users see your company identity across the chat sidebar, login page, browser tab, and favicon.
White-label branding requires an **Enterprise license** with the `white-label` feature enabled. See the [Enterprise overview](/nao-agent/enterprise/overview) for licensing details.
## What you can customize
| Asset | Where it appears |
| ------------------- | -------------------------------------------------- |
| **Sidebar logo** | Top-left corner of the chat sidebar |
| **Login page logo** | Sign-up, login, and OAuth consent pages |
| **Favicon** | Browser tab icon |
| **Tab title** | Browser tab text |
| **App name** | Used as alt text fallback when logos cannot render |
| **Brand color** | Buttons, links, badges, and accents across the app |
## Configuration
1. Sign in as an admin and go to **Settings -> White-label**.
2. Upload your logo files (PNG, JPG, SVG, WebP, GIF, or ICO, up to 512 KB each).
3. Enter your app name and tab title.
4. Set your brand color (see below).
5. Click **Save**. Changes apply immediately for all users.
You can update text fields without re-uploading images and vice versa. To remove an individual asset, click the remove button next to its preview - a "Marked for removal" hint confirms the change before you save.
## Brand color
Pick a custom brand color with the color picker or by entering a hex code. Once set, nao applies it everywhere across the app - buttons, links, badges, and accents - so the product matches your brand.
* A live preview shows how the color renders before you save.
* The color adapts automatically to both light and dark mode, keeping enough contrast so text on top stays readable.
* The brand color is intentionally **not** applied on the login and sign-up pages.
## Supported formats
* **Images**: PNG, JPG, SVG, WebP, GIF, ICO
* **Max size**: 512 KB per asset
## How it works
* Assets are stored in the nao database (consistent with other uploaded content).
* Every read is gated through the `white-label` license feature. If the license lapses or the feature is removed, the default nao branding is restored automatically.
* The `/branding/:kind` endpoint serves assets with a 60-second cache header.
## Visibility
The **White-label** entry appears under the **Enterprise** header in the settings sidebar for all non-cloud admins. Deployments without an active Enterprise license see the settings page with an inline Enterprise nudge but cannot save changes.
License setup, feature list, and how to get a quote
# Quickstart
Source: https://docs.getnao.io/nao-agent/quickstart
Deploy your first analytics agent in 1 minute
## Step 1: Install nao-core package
```bash theme={null}
pip install nao-core
```
**Drive setup from your agentic CLI.** If you use Claude Code, Codex, Cursor, or any other agent that loads `SKILL.md` files, you can install the published nao skills and have the agent walk you through Steps 2–6 interactively:
```bash theme={null}
nao skills add getnao/nao
```
Then ask the agent to "use the setup-context skill". See [Skills](/nao-agent/context-engineering/skills) for what each one does and when to use it.
## Step 2: Initialize a nao project
```bash theme={null}
nao init
```
This command will ask you:
* To name your project (defaults to the current folder name - press Enter to accept)
* If you want to connect a database *(optional)*
* If you want to set up an LLM *(optional)*
* If you want to add a git repository to the agent context *(optional)*
You can skip any optional question and configure it later in your `nao_config.yaml` file. Slack, Notion, MCP servers, and skills are no longer prompted during `nao init` - add them directly in `nao_config.yaml`.
### Non-interactive mode
For CI pipelines or agentic workflows (Claude Code, Cursor, Codex), run `nao init` without any prompts:
```bash theme={null}
nao init --yes
```
| Flag | Description |
| --------------------------- | --------------------------------- |
| `--yes` / `-y` / `--no-tty` | Skip all prompts and use defaults |
| `--name` / `-n` | Set the project name from the CLI |
In `--yes` mode:
* If `nao_config.yaml` already exists, it is reused as-is and only the folder structure is scaffolded.
* If no config exists, a minimal one is created with the project name (from `--name` or the current directory name).
* Databases, LLM, and integrations are not configured. Edit `nao_config.yaml` afterward or use the `setup-context` skill.
This will create:
* A new folder with your project name
* An architecture for your context files
* A `nao_config.yaml` configuration file
* A `RULES.md` file
* A `tests/` folder with a starter `test_example.yml` test file
## Step 3: Verify your setup
cd to the project folder and run:
```bash theme={null}
nao debug
```
This command checks your configuration and displays any issues.
It tests every database declared in `nao_config.yaml`, then tests each LLM provider listed under `llm.providers`. Connection checks are available for OpenAI, Anthropic, Gemini, Mistral, OpenRouter, Ollama, AWS Bedrock, and Google Vertex.
* **Ollama**: lists the models available on the local Ollama instance.
* **AWS Bedrock**: reports the resolved region, then lists foundation models using your `aws_profile` / `AWS_PROFILE` credentials. If a bearer token is set as the API key, nao reports it as configured without listing models.
* **Google Vertex**: reports the resolved `gcp_project` and `gcp_location` and the credential source it picked up (service account JSON, key file, or application default credentials). It fails if `gcp_project` is not set.
If you set `base_url` on a provider to point at an OpenAI-compatible proxy such as LiteLLM, `nao debug` uses that base URL for the connectivity test instead of the provider default, so the check reflects the endpoint your agent actually calls. This applies to the `openai`, `anthropic`, and `openrouter` providers.
## Step 4: Synchronize your context
```bash theme={null}
nao sync
```
This will populate your context folder with your context files (data, metadata, repos, etc.).
## Step 5: Launch the chat and ask questions
You have two options to access the chat UI:
### Option 1: Using nao chat command
```bash theme={null}
nao chat
```
This will start the nao chat UI. It will open the chat interface in your browser at `http://localhost:5005`.
Two native engines are not shipped inside the `nao-core` package because they are around 100 MB each: the DuckDB engine used by `nao test`, and the micro-VM sandbox runtime. nao downloads whichever one it needs on first use and caches it under `~/.nao/native`, so the first run of a command that needs one is slower than the rest.
To pull the sandbox runtime ahead of time, run `nao chat --sandbox` and then enable **Sandboxes** in **Settings** -> **Experimental**. Set `NAO_NATIVE_REGISTRY` to fetch these from an npm mirror instead of `registry.npmjs.org`.
### Option 2: Using Docker
Instead of `nao chat`, you can use Docker to run the UI:
**With built-in example:**
```bash theme={null}
docker run -d \
--name nao \
-p 5005:5005 \
-e BETTER_AUTH_URL=http://localhost:5005 \
getnao/nao:latest
```
**With your project:**
```bash theme={null}
docker run -d \
--name nao \
-p 5005:5005 \
-e BETTER_AUTH_URL=http://localhost:5005 \
-v /path/to/your/project:/app/project \
-e NAO_DEFAULT_PROJECT_PATH=/app/project \
getnao/nao:latest
```
Access the UI at `http://localhost:5005` and add your LLM API key in the settings.
From there, you can start asking questions to your agent!
## Step 6: Evaluate your agent
`nao init` scaffolds a starter test file at `tests/test_example.yml`. You can add more test files with questions and expected SQL in YAML format, then measure your agent's performance:
```bash theme={null}
nao test
```
View results in tests panel:
```bash theme={null}
nao test server
```
Learn how to build comprehensive test suites and evaluate your agent
## What's Next?
Install five published skills to let your agentic CLI automate setup, rules, tests, and audits
Learn how to build and customize your agent's context
Deploy your agent in production
Explore the chat interface features
Use our managed cloud service
# Deployment-managed context files
Source: https://docs.getnao.io/nao-agent/self-hosting/context-source
Control where a self-hosted nao container loads its context from with NAO_CONTEXT_SOURCE
A self-hosted nao container needs to know where its [context](/nao-agent/context-builder/overview) lives. The `NAO_CONTEXT_SOURCE` environment variable picks one of three sources. When the deployment owns the context, the in-app **Settings -> Git** page turns into a read-only summary instead of a connection wizard, so no one can accidentally repoint a managed deployment from the UI.
## Context sources
| `NAO_CONTEXT_SOURCE` | Where context comes from | Default when |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- |
| `local` | A directory mounted into the container at `NAO_DEFAULT_PROJECT_PATH` (default `/app/context`), containing a valid `nao_config.yaml`. | Self-hosted / single-project mode |
| `git` | A Git repository the container clones on startup from `NAO_CONTEXT_GIT_URL`. | You set it explicitly |
| `api` | Projects deployed dynamically with the [`nao deploy`](/nao-agent/cloud/deploy) CLI command. | `NAO_MODE=cloud` |
If `NAO_CONTEXT_SOURCE` is unset, nao defaults to `api` in cloud mode and `local` otherwise. An unknown value stops the container at startup.
## Git-managed context
Set `NAO_CONTEXT_SOURCE=git` to have the container clone your context repository when it boots. `NAO_CONTEXT_GIT_URL` is required; the rest scope and authenticate the clone:
```bash theme={null}
NAO_CONTEXT_SOURCE=git
NAO_CONTEXT_GIT_URL=git@github.com:your-org/your-nao-context.git
NAO_CONTEXT_GIT_BRANCH=production # optional, defaults to main
NAO_CONTEXT_GIT_SUBPATH=tools/nao # optional, sparse-checkout a subdirectory
NAO_CONTEXT_GIT_PLATFORM=gitlab # optional, for self-hosted hosts nao can't identify
NAO_CONTEXT_GIT_SSH_KEY={{ secret_ssh_key }} # SSH URLs
# NAO_CONTEXT_GIT_TOKEN={{ secret_pat }} # HTTPS URLs (optional)
```
The container picks the auth scheme from the URL: `git@…` / `ssh://…` needs `NAO_CONTEXT_GIT_SSH_KEY`, and `https://…` accepts an optional `NAO_CONTEXT_GIT_TOKEN`. GitHub and Bitbucket host keys are pre-pinned, so SSH connects without a known-hosts prompt. See [Deployment guide](/nao-agent/self-hosting/deployment-guide) for the full clone and secrets walkthrough, and [Multiple projects](/nao-agent/self-hosting/multiple-projects) for running several context repositories from one deployment.
`NAO_CONTEXT_GIT_PLATFORM` (`github`, `gitlab`, or `bitbucket`) only matters when the host name doesn't give the platform away - a self-hosted GitLab at `git.example.com`, for instance. nao uses it to know how to open a review request.
## Editing a deployment-managed repository
The context repository stays editable from the browser even though the deployment owns it. Admins can change context files in the [File Explorer](/nao-agent/chat/admin/file-explorer), commit, and open a pull or merge request - **with no OAuth app and no per-user Git authorization**. nao pushes using the credential the deployment already has (`NAO_CONTEXT_GIT_TOKEN` or `NAO_CONTEXT_GIT_SSH_KEY`), while still authoring each commit as the nao user who made the change, with nao as co-author.
This works against GitHub, GitLab, and Bitbucket, including self-hosted instances.
## The read-only Git summary
When `NAO_CONTEXT_SOURCE=git`, the deployment is the owner of the context *setup*. **Settings -> Git** then shows a read-only summary of the deployment-managed repository instead of the [repository connection wizard](/nao-agent/chat/admin/git): admins can see which repository, branch, and subpath the container was configured with, but the connect and disconnect flow is disabled because the source is controlled by the deployment's environment, not the UI.
To let admins connect the context repository from the browser instead, leave `NAO_CONTEXT_SOURCE` on `local` (or `api`) and set the repository up in-app under [Settings -> Git](/nao-agent/chat/admin/git).
Changing `NAO_CONTEXT_SOURCE` takes effect on container restart, since the context is resolved at startup. Update the environment variable on your platform (Cloud Run, ECS, Kubernetes, …) and redeploy.
# Deployment Guide
Source: https://docs.getnao.io/nao-agent/self-hosting/deployment-guide
Deploy nao with Docker on your own cloud infrastructure
This is a 5 steps guide to create a nao context project, and deploy nao chat UI in your own infrastructure.
## Step 1: Create your nao context repository
**1. Create a new folder / repository and initialize a nao project**
```bash theme={null}
nao init
```
Run a first nao sync if you want to start populating content in your repo:
```bash theme={null}
nao init
```
**2. In `nao_config.yaml`, replace your secrets by environment variables**
Instead of hard‑coding credentials (service account keys, API tokens, database passwords) in your config file, reference them via environment variables.\
This keeps sensitive values out of Git, makes it easier to rotate keys, and lets you reuse the same config across local, staging, and production.
Example:
```yaml theme={null}
databases:
- name: bigquery-prod
templates:
- columns
- preview
- query_history
include: []
exclude: []
type: bigquery
project_id: nao-production
dataset_id: prod_silver
credentials_json: {{ env('GCP_SERVICE_ACCOUNT_KEY_JSON') }}
location: EU
```
You’ll later provide `GCP_SERVICE_ACCOUNT_KEY_JSON` via your deployment platform (for example, as a Secret in Google Secret Manager wired to an env var in Cloud Run).
**3. Init a git repository from your context folder**
Versioning your nao project in Git lets you review changes to context, roll back safely, and collaborate with your team using pull requests.
It also allows your deployed nao chat to directly sync with the contet in your GitHub repository.
```bash theme={null}
git init
git add .
git commit -m "Initial nao project"
```
Learn how to turn your nao project into a GitHub repo and follow best practices
## Step 2: Create Dockerfile
Create a `Dockerfile` in your repository, using the official [`getnao/nao` image](https://hub.docker.com/r/getnao/nao) as the base:
```dockerfile theme={null}
FROM getnao/nao:latest
# Copy your project files
COPY . /app/project/
# Set working directory
WORKDIR /app/project
```
Create a `.dockerignore`:
```text theme={null}
.env
venv/
.gitignore
.DS_Store
```
Commit and push the created files.
### Alternative: use `NAO_CONTEXT_GIT_URL` instead of baking the project
If your nao context already lives in a Git repository, you don't strictly need to copy the project into the container image.\
Instead, you can point the runtime to your Git repo via the `NAO_CONTEXT_GIT_URL` environment variable and let the container clone it on startup.
At a high level:
* **Image**: use the stock `getnao/nao` image (or the Dockerfile above without the `COPY` step)
* **Git repository**: the same repo you configured in `nao_config.yaml` / the [Repositories](/nao-agent/context-builder/repos) docs
* **Environment variable**: set `NAO_CONTEXT_GIT_URL` to the HTTPS URL of your context repository
Example environment variable value:
```bash theme={null}
NAO_CONTEXT_GIT_URL=https://github.com/your-org/your-nao-context.git
```
When this variable is set, the container will clone that repository into the default project path at startup, so you don't have to build a custom image just to include your context code.
#### Cloning over SSH
If `NAO_CONTEXT_GIT_URL` uses an SSH URL (`git@...` or `ssh://...`), set `NAO_CONTEXT_GIT_SSH_KEY` to the private deploy key. The container writes the key to a temporary file and clones with strict host key checking enabled.
Host keys for `github.com` and `bitbucket.org` are pre-pinned in the image, so SSH clones from GitHub and Bitbucket work without any extra setup. Other Git hosts are not pre-pinned and will fail the host key check.
You can still combine this with Git‑based deployments (for example Cloud Run building from your repo): in that case the Git repository is both the build source and the runtime context, and `NAO_CONTEXT_GIT_URL` makes the linkage explicit.
#### Authenticate with a GitHub deploy key (SSH)
For private repositories, use an SSH deploy key instead of a personal access token. nao clones context from both **GitHub** and **Bitbucket**. Generate an `ed25519` key pair, add the public key as a deploy key on the GitHub or Bitbucket repo (read-only is enough), and pass the private key through `NAO_CONTEXT_GIT_SSH_KEY`:
```bash theme={null}
NAO_CONTEXT_GIT_URL=git@github.com:your-org/your-nao-context.git
NAO_CONTEXT_GIT_SSH_KEY="-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----"
```
For HTTPS auth with a personal access token, use `NAO_CONTEXT_GIT_TOKEN` instead:
```bash theme={null}
NAO_CONTEXT_GIT_URL=https://github.com/your-org/your-nao-context.git
NAO_CONTEXT_GIT_TOKEN={{ secret_pat }}
```
The container picks the auth scheme from the URL: `git@…` / `ssh://…` requires `NAO_CONTEXT_GIT_SSH_KEY`, `https://…` accepts an optional `NAO_CONTEXT_GIT_TOKEN`. GitHub and Bitbucket host keys are pre-pinned in the entrypoint, so SSH connects to either without a known-hosts prompt.
#### Pick a branch or subpath
Two more environment variables let you scope what the container clones:
* `NAO_CONTEXT_GIT_BRANCH` - branch to check out (defaults to `main`).
* `NAO_CONTEXT_GIT_SUBPATH` - clone only a subdirectory of the repo via sparse checkout. Useful when your nao project lives next to other code in a monorepo.
```bash theme={null}
NAO_CONTEXT_GIT_URL=git@github.com:your-org/data-platform.git
NAO_CONTEXT_GIT_BRANCH=production
NAO_CONTEXT_GIT_SUBPATH=tools/nao
NAO_CONTEXT_GIT_SSH_KEY={{ secret_ssh_key }}
```
Add `NAO_CONTEXT_SOURCE=git` to mark the deployment as the owner of this setup: **Settings -> Git** then shows a read-only summary instead of the repository connection wizard. See [Deployment-managed context files](/nao-agent/self-hosting/context-source).
## Step 3: Create a PostgreSQL database
1. Create a PostgreSQL instance - here I'm using Cloud SQL.
2. Allow unencrypted network traffic (or configure SSL).
3. Enable **Private API** connections.
4. Note your connection string / instance connection name.
## Step 4: Deploy nao on your cloud infrastructure
In this guide we’ll use **Google Cloud Run** as a concrete example, but the same pattern applies to other container platforms (ECS, Kubernetes, etc.).\
You’ll build a Docker image from your nao project and deploy it to Cloud Run, connect it to a managed PostgreSQL instance (Cloud SQL), and load secrets via Google Secret Manager.
### 4.1 Configure Environment Variables
Create secrets in Google Secret Manager for all sensitive values:
* `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`
* `GCP_SERVICE_ACCOUNT_KEY_JSON` (full JSON content of the BigQuery service account, or other warehouse provider secrets)
* `DB_URI` with your PostgreSQL URI – used for storing your app DB. For example:
```text theme={null}
postgres://[user_name]:[password]@[host]:[port]/[database]
```
* `BETTER_AUTH_SECRET` – a long random string used to sign authentication sessions. Generate one with:
```bash theme={null}
openssl rand -base64 32
```
**This is critical.** If you don't set `BETTER_AUTH_SECRET` to a stable value, a new secret will be generated on every container restart, which invalidates all existing sessions and forces every user to sign in again after each redeploy or restart. Generate the value **once**, store it in Secret Manager, and reuse it across deployments.
* Any other secrets used in `nao_config.yaml` (e.g. Notion key)
#### SMTP for transactional email
SMTP is what nao uses for any outbound email: user invitations, forgot-password reset links, and automations that deliver their output by email. It is optional. If you do not configure it, nao still runs, but those email features are unavailable.
Set the variables below to enable email. Email stays disabled unless `SMTP_HOST`, `SMTP_MAIL_FROM`, and `SMTP_PASSWORD` are all set.
| Variable | Required | Description |
| ---------------- | -------- | ----------------------------------------------------------------------- |
| `SMTP_HOST` | yes | SMTP server hostname |
| `SMTP_MAIL_FROM` | yes | From address on outgoing emails |
| `SMTP_PASSWORD` | yes | SMTP password, store it as a secret |
| `SMTP_PORT` | no | Defaults to `587` |
| `SMTP_SSL` | no | Set to `true` for an implicit TLS connection |
| `SMTP_USER` | no | SMTP authentication username. Falls back to `SMTP_MAIL_FROM` when unset |
Set `SMTP_USER` when your provider issues an SMTP username that is not the From address. AWS SES is the common case: it gives you a generated IAM SMTP username, while the From address has to be a verified identity on your domain.
```bash theme={null}
SMTP_HOST=email-smtp.eu-west-1.amazonaws.com
SMTP_PORT=587
SMTP_USER=AKIAIOSFODNN7EXAMPLE
SMTP_MAIL_FROM=nao@your-company.com
```
For providers where the login is the same as the From address, leave `SMTP_USER` unset and nothing changes.
#### Permanent storage
nao gives every user a durable folder for file attachments and the exports the agent writes. It is on by default with `NAO_STORAGE_BACKEND=local`, which writes to `./storage` inside the container.
On a container platform like Cloud Run, that path is **ephemeral** - a restart loses everything users saved. Either mount a durable volume at `NAO_STORAGE_LOCAL_PATH`, or switch to the S3 backend, which is what you want above one replica anyway.
```bash theme={null}
NAO_STORAGE_BACKEND=s3
NAO_STORAGE_S3_BUCKET=my-nao-storage
NAO_STORAGE_S3_REGION=eu-west-1
```
Credentials are optional - without them nao uses the default AWS credential chain, so an attached IAM role works with no further configuration. Set `NAO_STORAGE_BACKEND=none` to turn the feature off entirely. See [Files and Storage](/nao-agent/chat/capabilities/files-and-storage#configuring-storage) for the full list of variables.
#### Disable model providers
Set `DISABLED_PROVIDERS` to a comma-separated list of provider kinds to keep them out of a deployment entirely, even when ambient credentials would otherwise surface them. The common case is Amazon Bedrock auto-registering on EKS because IRSA sets `AWS_WEB_IDENTITY_TOKEN_FILE` on every pod:
```bash theme={null}
DISABLED_PROVIDERS=bedrock
```
A disabled provider never registers from environment credentials and is ignored even if it is configured in the database or `nao_config.yaml`, so it disappears from the model list. Use `openaiCompatible/` to target a single named OpenAI-compatible instance. An unknown provider id fails startup with a clear error.
### 4.2 Configure Cloud Run service
1. Create a new Cloud Run service.
2. Use your GitHub repository as the source and set up Cloud Build to build the Dockerfile.
Create service from a GitHub repository.
Choose your context Git Repository.
Setup synchronization with your main branch.
For Cloud Run deployments sourced from GitHub, Cloud Build will automatically rebuild and redeploy on each push to the configured branch.
So your context will always be up to date with the context in the main branch.
3. In the service configuration, add these configurations:
**Container:**
Container port: `5005`
**Environment variables:**
```bash theme={null}
NAO_DEFAULT_PROJECT_PATH=/app/project
BETTER_AUTH_URL=https://placeholder.run.app # will be updated after first deploy
```
**Secrets (from Secret Manager):**
* `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`
* `GCP_SERVICE_ACCOUNT_KEY_JSON`
* `DB_URI`
* `BETTER_AUTH_SECRET` – wire it from Secret Manager so the same value is reused across every revision. Without this, users get signed out on every restart.
**Connections:**
* Add Cloud SQL connection to your PostgreSQL instance.
**Networking:**
* Activate "Connect to VPC for outbound traffic" / "Send traffic directly to a VPC"
4. Deploy the service.
5. Once the service is live, copy the Cloud Run URL and update:
```bash theme={null}
BETTER_AUTH_URL=https://your-cloud-run-url.run.app
```
6. Open this URL in your browser, confirm that the nao chat UI loads, and complete the first sign‑up flow.\
At this stage, only the very first user can sign up directly; additional users are added and authorized later via the admin setup and user management flows.
7. Map a custom domain (optional)
If you prefer a friendly URL instead of the default Cloud Run URL:
1. In Cloud Run, go to **Domain mappings**.
2. Verify your domain.
3. Add the subdomain you want to use (e.g. `sky.naolabs.io`).
4. Add the required DNS records at your DNS provider.
5. Update `BETTER_AUTH_URL` to the custom domain, for example:
```bash theme={null}
BETTER_AUTH_URL=https://sky.naolabs.io
```
## Version update notifications
nao checks the latest release on GitHub and shows an amber notification at the bottom of the sidebar when your deployment is running an older version. The notification links directly to the release page so you can review what changed.
* Visible to **admin users only** - non-admins do not see the notification.
* The GitHub check is cached for 1 hour to avoid rate limiting.
* Works for both Docker and CLI deployments.
* If the GitHub API is unreachable (e.g. offline environments), the notification is silently skipped.
## Step 5: Customize your setup
### 5.1 Add users to your app
Once your chat UI is deployed, invite teammates and manage access from the admin interface.
Invite users, configure authentication, and manage access
If a user is locked out and you have not configured SMTP, reset their password from the machine that runs the deployment:
```bash theme={null}
nao reset-password user@example.com
```
The command updates the local nao database directly and prints a temporary password once, which the user changes after signing in. It needs no running chat server and no mail server. Accounts that sign in through SSO or OAuth have no password to reset and the command exits with an error. See [Reset user passwords](/nao-agent/chat/admin/setup#reset-user-passwords-with-smtp) for the email-based flow.
### 5.2 Automate nao sync with GitHub Actions
Automate `nao sync` so your context stays up to date in Git.
Set up GitHub Actions workflows to automatically sync your context
### 5.3 Connect a Slack bot
Expose your analytics agent directly in Slack so teams can ask questions where they work.
Connect your deployed agent to Slack channels
### 5.4 Enable Google OAuth for user sign‑in
To allow users to sign in with Google and control which domains can self‑register:
Configure Google OAuth for domain-based sign-up
### 5.5 Host several projects on this instance
Serve more than one project from the same deployment and domain, with a project switcher.
Run several nao projects on one instance with `NAO_MODE=cloud`
# Kubernetes (Helm)
Source: https://docs.getnao.io/nao-agent/self-hosting/kubernetes
Deploy nao on a Kubernetes cluster with the official Helm chart
nao ships a Helm chart for running the container on Kubernetes. It covers the deployment, service, secrets, an optional PostgreSQL subchart, persistent volumes for context and projects, and optional autoscaling and pod disruption budgets.
The chart lives in [`helm/`](https://github.com/getnao/nao/tree/main/helm) in the nao repository and is published to GHCR.
## Install
```bash theme={null}
helm install nao oci://ghcr.io/getnao/nao/charts/nao --version 0.1.0 \
--namespace nao --create-namespace \
--set secrets.betterAuthSecret="$(openssl rand -base64 32)"
```
`--version` is the **chart** version. The nao image tag comes from the chart's `appVersion`; override it with `--set image.tag=` to run a different nao release without waiting for a chart release.
To install from a clone of the repository instead:
```bash theme={null}
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm dependency update ./helm
helm install nao ./helm \
--namespace nao --create-namespace \
--set secrets.betterAuthSecret="$(openssl rand -base64 32)"
```
## Choose a context mode
`config.contextSource` picks where the project comes from. It maps to [`NAO_CONTEXT_SOURCE`](/nao-agent/self-hosting/context-source).
The project directory is mounted from a PersistentVolume. Pre-populate the PVC with a valid nao project - a directory containing `nao_config.yaml`.
```yaml theme={null}
config:
contextSource: local
contextPath: /app/project
persistence:
enabled: true
size: 1Gi
# existingClaim: my-nao-context-pvc
```
The project is cloned from a repository at pod startup, and optionally pulled again on a schedule.
```yaml theme={null}
config:
contextSource: git
contextPath: /app/project
contextGitUrl: https://github.com/your-org/your-nao-project.git
contextGitBranch: main
refreshSchedule: "0 * * * *" # optional, pull every hour
secrets:
contextGitToken: ghp_... # required for private repositories
```
Projects are pushed dynamically with [`nao deploy`](/nao-agent/cloud/deploy). A writable volume is created at `/app/projects`.
```yaml theme={null}
config:
contextSource: api
projectsPersistence:
enabled: true
size: 5Gi
```
## Database
The chart deploys a bundled `bitnami/postgresql` by default. To point at your own instance, disable it and set a URI:
```yaml theme={null}
postgresql:
enabled: false
secrets:
dbUri: "postgres://user:password@my-postgres-host:5432/nao"
```
SQLite works for single-node or testing deployments (`dbUri: "sqlite:./db.sqlite"`), but use PostgreSQL for anything real.
`postgresql.auth.password` is empty by default. Set it before going to production, and set `secrets.betterAuthSecret` to a value you generated yourself.
## Ingress
Set `ingress.enabled=true` to expose nao through an Ingress controller. At least one host with one path is required. Point `config.betterAuthUrl` (and any SSO redirect URIs) at the public URL the ingress serves.
```yaml theme={null}
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: nao.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: nao-tls
hosts:
- nao.example.com
```
## External secrets and extra env vars
By default the chart renders a Secret from the `secrets.*` values. To load secrets from a Secret you manage yourself (GitOps, External Secrets Operator, Sealed Secrets), set `existingSecret` to its name. The chart then renders no Secret of its own and the whole `secrets.*` block is ignored, so the referenced Secret must carry the same keys (`BETTER_AUTH_SECRET`, `DB_URI`, provider API keys, and so on).
```yaml theme={null}
existingSecret: my-nao-secret
```
The pod-template checksum only tracks the chart-rendered Secret, so rotating an external Secret does not restart pods on its own. Pair it with a rollout trigger (stakater/reloader, ESO templated annotations) or run `kubectl rollout restart` after rotation.
Inject any environment variable the chart has no first-class key for with `extraEnv` (appended to the container) or `extraEnvFrom` (extra `configMapRef` / `secretRef` sources):
```yaml theme={null}
extraEnv:
- name: NAO_STORAGE_BACKEND
value: s3
extraEnvFrom:
- secretRef:
name: my-extra-secret
```
## Values reference
| Key | Default | Description |
| ------------------------------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `replicaCount` | `1` | Number of pod replicas |
| `image.repository` | `getnao/nao` | Container image repository |
| `image.tag` | `""` (chart `appVersion`) | Image tag |
| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
| `config.serverPort` | `"5005"` | Port the backend listens on |
| `config.betterAuthUrl` | `"http://localhost:5005"` | Public URL used for auth callbacks |
| `config.contextSource` | `"local"` | Context mode: `local`, `git`, or `api` |
| `config.contextPath` | `"/app/project"` | Mount path for the nao project |
| `config.contextGitUrl` | `""` | Repository URL (git mode) |
| `config.contextGitBranch` | `"main"` | Branch to clone (git mode) |
| `config.refreshSchedule` | `""` | Cron expression for a periodic git pull |
| `config.dbSsl` | `false` | Require TLS on the database connection |
| `config.enableUserLogin` | `true` | Email and password login |
| `config.enableUserSignup` | `false` | Allow self sign-up |
| `config.defaultUserRole` | `"user"` | Role for new users: `admin`, `user`, or `viewer` |
| `config.githubSso` | `false` | Enable "Sign in with GitHub" |
| `config.gitlabSso` | `false` | Enable "Sign in with GitLab" |
| `config.gitlabBaseUrl` | `""` | Self-hosted GitLab instance URL |
| `config.betaAutomationsEnabled` | `true` | [Automations](/nao-agent/chat/capabilities/automations) |
| `config.betaContextRecommendationsEnabled` | `false` | [Recommendations](/nao-agent/context-engineering/recommendations) |
| `existingSecret` | `""` | Load all secret env vars from a Secret you manage instead of rendering one (the `secrets.*` block is then ignored) |
| `extraEnv` | `[]` | Extra env vars appended verbatim to the nao container |
| `extraEnvFrom` | `[]` | Extra `envFrom` sources appended after the chart's ConfigMap and Secret |
| `secrets.betterAuthSecret` | `""` | **Required.** Auth session secret |
| `secrets.openaiApiKey` | `""` | OpenAI API key |
| `secrets.anthropicApiKey` | `""` | Anthropic API key |
| `secrets.naoLicense` | `""` | Enterprise license, required for the SSO providers |
| `secrets.redisUrl` | `""` | Redis connection string |
| `secrets.dbUri` | `""` | Database URI (ignored when `postgresql.enabled=true`) |
| `secrets.contextGitToken` | `""` | Git token for private repositories |
| `service.type` | `ClusterIP` | Kubernetes service type |
| `service.port` | `80` | Service port |
| `ingress.enabled` | `false` | Expose nao through an Ingress controller |
| `resources.requests` | `500m` CPU, `512Mi` | Requests |
| `resources.limits` | `2` CPU, `2Gi` | Limits |
| `autoscaling.enabled` | `false` | Enable the HorizontalPodAutoscaler (`minReplicas` 1, `maxReplicas` 5) |
| `podDisruptionBudget.enabled` | `false` | Enable the PodDisruptionBudget |
| `persistence.enabled` | `false` | Context PVC (local mode) |
| `projectsPersistence.enabled` | `false` | Projects PVC (api mode) |
| `postgresql.enabled` | `true` | Deploy the bundled PostgreSQL |
The chart's [`values.yaml`](https://github.com/getnao/nao/blob/main/helm/values.yaml) is the full list. Any nao environment variable not exposed as a value can still be set through the chart's config and secret templates.
Running more than one replica, permanent storage must be shared across pods: use the S3 backend, or a read-write-many volume. See [Files and Storage](/nao-agent/chat/capabilities/files-and-storage#configuring-storage).
## Operate
```bash theme={null}
helm upgrade nao ./helm --namespace nao -f my-values.yaml # upgrade
helm history nao --namespace nao # revisions
helm rollback nao --namespace nao # roll back
helm test nao --namespace nao # connection test
```
## Next steps
Environment variables and the full self-hosting walkthrough
How `NAO_CONTEXT_SOURCE` resolves the project at startup
# Host Multiple Projects
Source: https://docs.getnao.io/nao-agent/self-hosting/multiple-projects
Run several nao projects on a single deployment, under one domain, with a project switcher
A single self-hosted deployment can serve **one** project by default - the one you mount with `NAO_DEFAULT_PROJECT_PATH`. If you want to host **several projects on the same instance and domain** (for example one project per team, per warehouse, or per client) and let users switch between them, run the deployment in **multi-project mode**.
## How it works
nao organizes hosting as **organization -> projects -> members**:
* One instance owns one **organization**.
* The organization owns **any number of projects**. Each project is the unit that holds its own context folder, data connections, chats, members, and settings.
* Users switch between the projects they belong to from the **workspace switcher** in the sidebar. No separate URL or subdomain per project - everything lives under the same domain.
Each project's context is pushed to the instance with [`nao deploy`](/nao-agent/cloud/deploy). Projects are identified by `project_name` within the organization: deploying a new name **creates** a project, deploying an existing name **updates** it in place.
Multi-project hosting reuses the same organization and project model as [nao Cloud](/nao-agent/cloud/overview). Enabling it on your own infrastructure is a one-line switch, described below - it does not depend on nao's hosted service.
## Single-project vs multi-project
| | Single-project (default) | Multi-project |
| --------------------- | --------------------------------------------------------- | -------------------------------------------- |
| `NAO_MODE` | `self-hosted` | `cloud` |
| Context source | A mounted folder or git repo (`NAO_DEFAULT_PROJECT_PATH`) | Pushed with `nao deploy` |
| Projects per instance | One | Many |
| Project switcher | Not used - always serves the default project | Users switch between projects they belong to |
`NAO_DEFAULT_PROJECT_PATH` and `NAO_MODE=cloud` **cannot be set at the same time** - the container exits on startup if both are present. Multi-project mode replaces the single mounted folder with projects pushed via `nao deploy`, so remove any `NAO_DEFAULT_PROJECT_PATH` (and the project volume mount) when you switch.
## Step 1: Enable multi-project mode
Set a single environment variable on your deployment:
```bash theme={null}
NAO_MODE=cloud
```
When `NAO_MODE=cloud`, the container automatically:
* switches the context source to `api` (projects arrive over `nao deploy` instead of a mounted folder),
* unsets `NAO_DEFAULT_PROJECT_PATH`,
* creates a projects directory (`/app/projects` by default, configurable with `NAO_PROJECTS_DIR`) where each deployed project is stored in its own folder.
Everything else from the [Deployment Guide](/nao-agent/self-hosting/deployment-guide) stays the same - the PostgreSQL database, `BETTER_AUTH_SECRET`, LLM keys, and Cloud Run / container configuration are all shared across every project on the instance.
Because projects are pushed over the API rather than baked into the image, you don't need the `COPY` step or the `NAO_CONTEXT_GIT_URL` variables from the single-project guide. Use the stock `getnao/nao` image and deploy your projects afterward.
## Step 2: Create an organization API key
`nao deploy` authenticates with an **organization-level** API key, which can write to every project in the organization.
1. Open the deployed chat UI and sign in as an admin.
2. Go to **Settings** -> **Organization** -> **API Keys**.
3. Click **Create key**, name it, and copy the value - it is shown only once.
See [Per-organization API keys](/nao-agent/cloud/overview#per-organization-api-keys) for details.
## Step 3: Deploy each project
From each project folder (the one containing `nao_config.yaml`), push it to the instance:
```bash theme={null}
nao deploy https://your-nao-domain.com --api-key nao_sk_...
```
The `project_name` in each project's `nao_config.yaml` determines its identity in the organization:
* A **new** `project_name` creates a new project on the instance.
* An **existing** `project_name` replaces that project's context folder in place (every deploy is a full replacement, not a merge).
Repeat for every project you want to host. For example, to host three projects on one domain:
```bash theme={null}
# from ~/projects/sales
nao deploy https://your-nao-domain.com --api-key nao_sk_... # project_name: sales
# from ~/projects/finance
nao deploy https://your-nao-domain.com --api-key nao_sk_... # project_name: finance
# from ~/projects/marketing
nao deploy https://your-nao-domain.com --api-key nao_sk_... # project_name: marketing
```
Full command flags, exclusions, `.naoignore`, and create-vs-update behavior
## Step 4: Switch between projects
Once more than one project exists, the **workspace switcher** appears in the sidebar. Users pick the active project there, and their chats, context, and data connections are scoped to that project. The selection is remembered per user.
Deploying a new project automatically adds every existing organization member to it. To scope who can see each project, manage members per project under **Settings** -> **Project** -> **Team**:
Manage organization members, per-project teams, and roles
## Keeping projects in sync
Each project is a normal nao context folder, so the usual workflow applies: edit context locally (or in git), run `nao sync`, then `nao deploy` to push. To automate this, run `nao deploy` from CI on each push to the branch that holds a project.
Automate context sync and deploys with GitHub Actions
## Notes and limitations
* **One domain, no per-project URLs.** Projects are not exposed as subpaths or subdomains. Selection is per user through the switcher, not the URL.
* **Switching requires `NAO_MODE=cloud`.** In default `self-hosted` mode the server always serves the single default project, even if extra project rows exist - the switcher won't change what's served.
* **API keys are organization-scoped.** One key can create or overwrite any project in the organization. Treat it as a privileged secret and rotate it from the same **API Keys** page.
# LLM Observability
Source: https://docs.getnao.io/nao-agent/self-hosting/observability
Send traces of every LLM call to Langfuse
nao can export a trace of every LLM call to [Langfuse](https://langfuse.com): prompt, response, latency, tokens, cost, model, and tool calls. It's off by default; running without it needs no action.
## Configuration
| Variable | Description |
| --------------------- | ---------------------------------------- |
| `LANGFUSE_PUBLIC_KEY` | Project public key, starts with `pk-lf-` |
| `LANGFUSE_SECRET_KEY` | Project secret key, starts with `sk-lf-` |
| `LANGFUSE_BASE_URL` | Where to send traces |
All three are required. If any is missing, tracing stays off and nao starts normally. `LANGFUSE_BASE_URL` has no default by design, so traces are never sent to a destination you didn't name.
On Langfuse Cloud, use the host shown in your project settings - keys are region-bound, so a URL from another region fails authentication.
Self-hosted Langfuse works identically - use your instance URL, including an internal one like `http://langfuse-web:3000`. It must be version 3.63.0 or newer.
Traces include the **full text** of prompts and responses, so user questions and the agent's answers are sent to whichever instance you configure. Warehouse data itself is not - see [What is never sent](#what-is-never-sent). Self-host Langfuse if prompts and answers can't leave your infrastructure. A Langfuse secret key also grants API access to read the project's traces - treat it as sensitive.
## What is never sent
Query data never reaches Langfuse. Everything belonging to the `execute_sql` and `read_query_result` tools - the SQL text, the returned rows, and any error message - is replaced with `[redacted]` before a trace leaves nao.
The calls stay visible in the trace: you can still see that a query ran, how long it took, and whether it failed. Only the contents are gone.
Redaction covers both places that data used to appear: the tool call itself, and the conversation history replayed on every following model call. Everything else is unchanged - questions, answers, other tools, timings, and token usage. It is always on, with no setting to turn it off.
## Set the variables
**`nao chat`** - add them to a `.env` file in your project folder. nao loads it on startup and passes it to the agent:
```bash theme={null}
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_BASE_URL=https://cloud.langfuse.com
```
**Docker** - add `-e` flags:
```bash theme={null}
docker run -d \
--name nao \
-p 5005:5005 \
-e BETTER_AUTH_URL=http://localhost:5005 \
-e LANGFUSE_PUBLIC_KEY=pk-lf-... \
-e LANGFUSE_SECRET_KEY=sk-lf-... \
-e LANGFUSE_BASE_URL=https://cloud.langfuse.com \
getnao/nao:latest
```
**Docker Compose** - same three lines in the `.env` file next to `docker-compose.yml`, which already forwards them. Then run `docker compose up -d`.
## Verify
On startup nao logs `✓ Langfuse tracing enabled → `. Send a message in the chat and a `nao-agent` trace appears in your Langfuse project.
Traces are batched and ingested asynchronously, so one can take a little while to show up. That delay is normal.
## What you see in Langfuse
Traces are named after the operation that produced them:
| Trace name | What it covers |
| ----------------------- | --------------------------------------------------------------------------------------- |
| `nao-agent` | The main chat agent: every user message, including its tool calls |
| `nao-generate-title` | Automatic chat title generation |
| `nao-compaction` | History compaction when a conversation grows long |
| `nao-memory-extraction` | Extraction of [memories](/nao-agent/chat/capabilities/memory) from a conversation |
| `nao-automation-title` | Title generation for [automations](/nao-agent/chat/capabilities/automations) |
| `nao-live-story` | [Story](/nao-agent/chat/capabilities/stories) refreshes |
| `nao-cron-nlp` | Parsing a natural-language schedule into a cron expression |
| `nao-test-verification` | Answer verification during [evaluation](/nao-agent/context-engineering/evaluation) runs |
Each trace carries the chat as the Langfuse **session** (so Sessions groups a whole conversation), the nao user as the **user**, and the model provider as a **tag**. Model name and any custom sampling settings (temperature, top-p, top-k) are attached as metadata.
# Keyboard Shortcuts
Source: https://docs.getnao.io/nao-ide/features/keyboard-shortcuts
## SQL Editor Shortcuts
| Shortcut | Action |
| --------------------------- | --------------------------------- |
| ⌘ + ⏎ | Execute SQL query |
| ⌘ + click | Jump to table/column definition |
| Space | Trigger table/column autocomplete |
| ⌘ + Z | Undo accept/reject edit changes |
## Agent Chat Shortcuts
| Shortcut | Action |
| ------------------------------------------ | -------------------------- |
| ⌘ + L | Send selected code to chat |
| ⌘ + ⇧ + ⏎ | Accept agent suggestion |
| ⌘ + ⇧ + ⌫ | Reject agent suggestion |
| ⏎ | Send message |
| ⇧ + ⏎ | New line in message |
## Navigation Shortcuts
| Shortcut | Action |
| ------------------------------------------ | ----------------------------- |
| ⌘ + P | Quick open file |
| ⌘ + B | Toggle sidebar |
| ⌘ + J | Toggle integrated terminal |
| ⌘ + ⇧ + J | Open/close data preview panel |
| ⌘ + ⇧ + E | Focus on explorer |
**Customization**
Shortcuts listed here use the macOS `⌘` key; feel free to remap them in your local keybinding settings.
# Work with dbt
Source: https://docs.getnao.io/nao-ide/features/work-with-dbt
## Setup dbt
To get the most of using dbt in nao, you should set up your dbt config within nao.\
See the [Set up dbt](../get-started/set-up-dbt) page for a step-by-step guide.
## Preview, run, build dbt models
You can directly preview dbt models in nao, if your dbt is setup well and you added your data connection.\
Just click on the **execute SQL** button on the top bar, or press ⌘ + ⏎.
**See compiled SQL**
**Compiled SQL:** When executing a dbt model, you will also have a tab to review the compiled SQL used by nao.
On top of your dbt model, you will also have a dropdown to run, build, test dbt models and their dependencies.
## Run dbt tests
If a dbt model has tests defined, you will be able to execute them directly from the preview panel.\
On **Database** mode, the tests will be executed on the materialized dbt model.\
On **Code** mode, the tests will be executed on the preview of current dbt code.
## View dbt lineage
You can **open dbt lineage** from the top right icon.\
If you currently have a dbt sql file opened, the lineage will be zoomed in 1+model+1.\
If not, it will open full lineage.
Within lineage, you can **expand lineage** nodes to see further model dependencies.\
You can also use **selection form** on the top right to select a precise part of lineage.\
You can **click on a node** of the lineage to open the corresponding sql file of each model.
## Navigate dbt dependencies
When a dbt model file is open, you will have access to its **dependencies** in the top tool bar. It will show if the model has upstream and/or downstrea dependencies.
**Navigate model dependencies**
Hover on dependencies to get the full list and navigate to them.
You can also navigate between dbt models & columns with ⌘ + click:
* Use ⌘ + click on ref names to open the corresponding sql file
* Use ⌘ + click on a CTE name to jump to the CTE definition
* Use ⌘ + click on a column to jump to the previous definition of this column
## Auto-complete on dbt models
**Auto-complete sources & refs**
When writing from in a dbt context, nao suggest a shortcut to add **sources** and **refs**.
It will then suggest the list of available refs/sources in your dbt repository.
**Auto-complete columns from sources & refs**
When selecting columns from a source of a ref, nao will extract the list of available columns.
It will then suggest the list of available columns for the specifc ref/source you are working on.
**nao AI autocomplete uses your dbt models schemas**
Context of available dbt models/columns will also be passed to the AI auto-complete.\
This will allow the AI autocomplete to suggest SQL relevant to your underlying dbt models.
## Agent dbt tools
nao agent has several tools specifically made for dbt use:
* Create dbt models
* Create dbt sources
* Create dbt documentation & testing
* Get model / column dependencies in lineage
* Execute dbt commands
Check our [agent tools](/nao-ide/nao-ai/agent-tools) for an exhaustive list of all agent tools.
# Work with git
Source: https://docs.getnao.io/nao-ide/features/work-with-git
nao helps you work with git by generating commit messages and handling git operations through the AI agent.
## AI generated commits
In the commit form of nao, there's a button for **"auto-generate commit message"**. Click it to have nao AI analyze your changes and generate a clear, descriptive commit message automatically.
## Let nao AI handle git
The nao agent can handle all git operations for you:
* **Stage** files for commit
* **Commit** changes with appropriate messages
* **Push** commits to remote repositories
* **Pull** latest changes
* **Create PR** (Pull Request) for your changes
Simply ask nao AI in the chat to perform any git operation, and the agent will execute it for you with your approval.
# Work with SQL
Source: https://docs.getnao.io/nao-ide/features/work-with-sql
nao can replace your data warhouse console.
It is directly connected to your data and let you preview, query, auto-complete your SQL.
## Browse your database
You can browse your available data from nao left panel in "Warehouse" tab.
From the data tree you can:
* View all tables available
* Search for a table
* Preview a table and see its list of columns
* Start a query on a table by clicking on `<…>` icon
Press ⌘ + B to open or close the left sidebar at any time.
## Write SQL queries
### Data warehouse console features
nao provides same features as any data warehouse console:
* **Auto-complete of tables and column names**: every time you type "space", a dropdown with available tables/columns will appear.
* **BigQuery dry run**: for BigQuery connections, nao will show dry run results, with errors and cost of query in GB.
### AI auto-complete
On top of regular auto-complete, nao AI tab provides **AI suggestion** to complete your SQL code.
These suggestions are **based on the context of your data/columns metadata** so as to suggest code that actually matches your data schema.
### Fix SQL in chat
When you SQL has an error, just hit the "Fix in chat" button to send query error to the agent and have it fix it.
## Run SQL queries
In nao, you can run any SQL query by pressing ⌘ + ⏎ (**cmd + enter**).\
You can also execute queries with dbt syntax (if configured). See the [dbt setup guide](/nao-ide/get-started/set-up-dbt) for details.
**Multi-query execution:**
* If you have multiple SQL queries in a single file, **nao will execute the query where your cursor is**
* You can also select a portion of SQL and run it with ⌘ + ⏎
## Create SQL worksheets
You can create **SQL worksheets** within nao, for adhoc query writing.\
These worksheets will be **saved within nao**, in the "SQL worksheets" tab.
To create a worksheet, you can either click on the **"+" button** from an SQL file, or go to the **"worksheet" tab** on the left panel.
From this panel, you will also be able to **rename, delete, or open your worksheets**.
## Data preview
In nao data preview you can do:
* **Sorting**: Click column headers to sort data ascending or descending
* **Pagination**: Navigate through large result sets with client-side pagination controls. The main preview pane defaults to 50 rows per page, and the side panel defaults to 100 rows per page. Controls only appear when the result set is larger than the page size.
* **Cell Selection**: Click to select individual cells
* **Row Selection**: Select entire rows for analysis
* **Column Selection**: Select entire columns
* **Zoom in Cell**: Double click on a cell to see the full value when it's truncated
* **Sum/Count Display**: See the sum and count of selected cells/rows in the preview
* **Export to csv**: Export query results to a csv file
**Keyboard Shortcut**
Open or close the data preview panel with ⌘ + ⇧ + J.
## Data diff
When you change SQL code, nao allows you to **preview directly corresponding changes in the data**. Click on **"table diff"** in the preview section to see a highlighted version of additions / deletions in your data.
**What is comparison point for the diff?**
This table diff is based on the **last commit of your branch**.\
It will show you data differences between running code in the last commit and current code of the file.
# Work with terminal
Source: https://docs.getnao.io/nao-ide/features/work-with-terminal
## Open and manage the terminal
* Press ⌘ + J to toggle the terminal drawer at any time.
* Use the `+` icon in the terminal header to open multiple shells (bash, zsh, python, etc.).
* Each workspace remembers the shells you had open, so you can pick up where you left off.
## Fix terminal errors in chat
Whenever a terminal command fails, nao highlights the error and shows a **Fix in chat** button. Click it to send the command and its stderr to nao AI—the agent will propose a corrected command or patch, and you can apply it back to the terminal with one click.
## Let nao AI run terminal commands
From the chat sidebar you can ask nao AI to run terminal commands on your behalf (for example, “run tests” or “install dependencies”). The agent will:
1. Suggest the command it plans to run.
2. Wait for your approval.
3. Execute the command in the integrated terminal and stream back the output.
You can read more about command execution permissions and guardrails in the [nao agent tools](/nao-ide/nao-ai/agent-tools).
# Download
Source: https://docs.getnao.io/nao-ide/get-started/download
Download the latest version of nao IDE
nao is available for macOS and Windows.
Click here to download the latest version for your platform
## Available Platforms
* **macOS** (Apple Silicon & Intel)
* **Windows**
# Video tour
Source: https://docs.getnao.io/nao-ide/get-started/nao-first-tour
Welcome to your one minute tour of nao! Watch the video below to quickly discover the main features and interface of nao.
# Sandbox
Source: https://docs.getnao.io/nao-ide/get-started/nao-sandbox
Use the nao sandbox to try every feature without touching your production environment. The sandbox ships with sample data, a configured dbt project, and a DuckDB warehouse so you can explore nao instantly.
## What's in the Sandbox?
The sandbox comes with:
* A pre-configured jaffle shop dbt project to explore and experiment with
* A DuckDB connection with sample data ready to query
* Full access to all nao features
**What you can test**
Work with dbt:
* Create and modify dbt models
* Preview model results directly in nao
Explore data:
* Create SQL worksheets
* Use AI-powered tab and agent for assistance
**DuckDB Instant Preview**
With DuckDB connection, you can experiment instant preview within nao (queries in worksheets)
# Setup
Source: https://docs.getnao.io/nao-ide/get-started/quickstart
Get up and running with nao in just 4 steps.
Download the latest version of nao from the download page.
Set up a warehouse connection to query it from the IDE and give context to nao AI. Follow the data connection guide.
Open your repository or folder so the agent can read files and make edits.
Configure dbt integration for native model preview, lineage, and dbt tools in agent. See the dbt setup guide.
Want to explore nao without configuring your own project? Head to the nao Sandbox and try every feature with our pre-configured environment.
**Need help?** Check out our [FAQ](/nao-ide/support/faq) or [contact support](mailto:claire@getnao.io).
# Release Notes
Source: https://docs.getnao.io/nao-ide/get-started/releases
View the latest releases and changelog for nao IDE
Stay up to date with the latest features, improvements, and bug fixes in nao IDE.
See what's new in the latest version of nao IDE
# Data connection
Source: https://docs.getnao.io/nao-ide/get-started/set-up-data-connection
Data connection in nao is key.
It allows you to browse, view, query your data directly in the IDE.
It gives context of your data schema to nao AI featurse.
**Data privacy:**\
When you connect a data warehouse to nao, your connection stays between your computer and the data warehouse. **No data content is ever sent to nao servers or the LLM.** Only metadata (like table and column names) is used to provide warehouse context to nao features.
Read more about how your data is secured in our [security documentation](/nao-ide/support/security).
## Add a data connection
To add a connection:
Go to Settings > Warehouse Connections > Add Connection
Display name for this connection inside nao.
Comma-separated project IDs. Supports wildcards (e.g. `project_*`) and exclusions (`!project-dev`).
Optional BigQuery location. Leave blank to use the project default.
Optionally scope access to specific datasets by listing them explicitly.
Choose between **SSO via OAuth** or **Service Account**. SSO requires your BQ project ID; service accounts use key files.
Required only when **Service Account** authentication is selected. Upload the JSON key for a service account with at least the **BigQuery Data Viewer** (Reader) role.
**Connect multiple BigQuery projects**
Select multiple project-ids and datasets using the following patterns:
* Comma separated values (project1,project2): Select specific projects by listing them explicitly (no space after the comma)
* Wildcard prefix (project\_\*): Select all projects that start with "project\_"
* Exclusion (!project2): Select all projects except "project2"
**Set query size limits**
You can limit the size of queries executed within nao.
If you do, nao will perform a dry run of every query, and cancel query execution if it's above the GB limit.
The limit will be effective for **both user queries and AI agent queries**.
Go to Settings → Warehouse Connections and set the query size limit field to the maximum rows you want nao to run automatically.
Name that appears inside nao; can be any label you prefer.
Snowflake account identifier without `.snowflakecomputing.com`.
Target warehouse for query execution.
Optional role override for this connection.
Optional list of databases to load. Supports comma-separated values, wildcards (`db_*`), and exclusions (`!db2`).
Optional schemas to load. Accepts the same comma, wildcard, and exclusion patterns as databases.
Choose between **Password**, **SSO**, or **Key Pair Authentication**.
Only required when **Password** authentication is selected.
Password for the Snowflake user. Approve the DUO prompt both when testing and saving the connection.
Snowflake user that owns the private key. Required for **Key Pair Authentication**.
Upload the PEM private key file corresponding to your Snowflake user.
Optional passphrase protecting the private key.
**MFA reminder**
Password-based logins require approving the DUO push twice: once for **Test Connection** and again for **Save**.
Friendly name for the connection inside nao.
Hostname or IP where Postgres is reachable.
Listening port for your Postgres instance.
Target database to connect to.
Database user nao should authenticate with.
Password for the database user. Stored securely in nao.
Enable SSH tunneling. When toggled, provide the SSH host, port, username, and a private key or password.
Turn on SSL/TLS. Choose SSL, TLS, or add channel binding depending on your Postgres setup.
Name that appears inside nao for this workspace connection.
Databricks workspace hostname.
HTTP path for the SQL warehouse or cluster you want nao to use.
Optional catalog that nao should select by default.
Optional schema to use when one is not specified in queries.
Choose between **OAuth User-to-Machine (U2M)** or **Personal Access Token**.
Required only when using the **Personal Access Token** authentication method.
Friendly label for this Redshift connection.
Redshift cluster endpoint.
Port Redshift listens on (defaults to 5439).
Default database nao should use.
Database user with access to the schemas you need.
Password for that database user.
Enable SSH tunneling and provide SSH host, port, username, and private key/passphrase.
Turn on SSL/TLS. Choose SSL, TLS, and optional channel binding per your security requirements.
Friendly label for this ClickHouse connection.
Choose between **HTTPS** or **Local**.
Full URL to your ClickHouse instance, including protocol and port. Use `http://localhost:8123` for Local connections or `https://your-instance.clickhouse.cloud:8443` for HTTPS connections.
ClickHouse user with access to the databases you need.
Password for the ClickHouse user.
Optional database to load. If not specified, nao will discover available databases.
Friendly name for the Athena connection inside nao.
AWS region where your Athena workgroup runs.
Access key for the IAM user or role you created for nao.
Secret key paired with the access key.
Optional default Athena database to load.
Choose any label for the DuckDB connection inside nao.
Select **File Database** to point to a `.duckdb` file, or **In-Memory Database** to create a temporary in-memory database.
Required when **File Database** is selected. Browse to an existing file or provide a new path to create one.
Optional comma-separated list of schemas to load.
Enable to prevent nao from making any modifications to the DuckDB database.
MotherDuck connections use the same flow as DuckDB. Open the **DuckDB** tab in this section and follow that form to connect to your MotherDuck warehouse.
To check your data is connected, open the left panel then click on the data warehouse icon. Here you will see all the tables from your data connection.
When you click on a table, you will be able to preview it directly within nao.
## Multiple data connections
You can connect multiple databases at once in nao.
You can choose to:
* Set a connection as a global connection (available across all folders in nao)
* Attach a connection to a specific workspace
To do this, hover over a connection and you'll see the options:
"Attach to workspace" or "Make global"
**Only one connection is active at once in nao.**
You can choose the active connection either from the data tree on the left or from the nao settings / data connections menu with the small circles.
# dbt setup
Source: https://docs.getnao.io/nao-ide/get-started/set-up-dbt
nao is directly integrated with dbt.
Set up your dbt connection to: preview dbt models, get lineage, give dbt tools to nao AI.
Learn more about nao x dbt features: [Work with dbt](/nao-ide/features/work-with-dbt).
To configure dbt, open the "dbt configuration" panel.
It is available in the home page of nao, or at the left of the bottom bar.
## dbt configuration panel
Activate dbt detection.
Choose an existing interpreter / virtual environment or create a fresh virtual environment for the project.
Select the dbt-core version and install any warehouse adapters (Snowflake, BigQuery, etc.) your project needs.
Set the folders that contain `dbt_project.yml` and `profiles.yml`. nao will try to find them automatically.
Populate secrets or configs inline, or reference a `.nao.env` / `.env` file. Existing `.env` values are imported automatically.
Pick the dbt target defined in `profiles.yml` (for example `dev`, `prod`). You can change it later on the bottom left bar.
Enable the **dbt defer** toggle. Provide the production `manifest.json` you want to defer to.
When editing a dbt model, switch on **Defer mode** so previews fall back to production data when dev data is missing.
Read more about defer in the dbt docs.
**Check dbt is setup properly**
When your dbt project is configured correctly, you'll see a checkmark in the lower-left status bar. If it shows a cross instead, click the **dbt project** button to reopen the setup panel and fix any missing steps.
## dbt integration requirements
* **dbt Core only**: nao currently supports dbt Core projects. If you're on dbt Cloud, run a dbt Core project locally to integrate with nao.
* **Local environment**: The dbt project must live on the same machine where nao runs; remote dbt environments are not supported.
# Welcome to nao IDE
Source: https://docs.getnao.io/nao-ide/get-started/welcome
nao IDE is the AI data editor designed for data teams — connected to your warehouse and business context, powered by an agent that writes code with data quality in mind.
Watch a quick video tour to discover nao's main features and interface
## Set up
Download nao, set up your data connection, connect your data stack
Connect nao to your data warehouse
## Core features
Watch a 1 minute tour of the product
nao AI features: agent, tab, indexing
## Learn
Browse a collection of useful prompts to accelerate your workflow.
# Prompt Library
Source: https://docs.getnao.io/nao-ide/learn/prompt-library
Explore a collection of useful prompts for getting the most out of nao. Use these examples to accelerate your workflow and discover new ways to interact with your data.
Explore a collection of useful prompts for getting the most out of nao. Use these examples to accelerate your workflow and discover new ways to interact with your data.
## dbt
### Create a dbt model
**Context:** None\
**Template:**
```
Create a dbt model called {model_name} that {description}, as well as its yaml documentation.
{Start from existing dbt models {ref_names}}
{Final output should include these columns: {column list}}
{Create this model in the {layer_name} layer / {folder_name} folder}
```
### Create staging dbt model from table
**Context:** Data warehouse table\
**Template:**
```
Create a staging model from this table, as well as its yaml documentation.
```
### Convert SQL to dbt model
**Context:** SQL file\
**Template:**
```
Create a dbt model called {model_name} from this SQL code, as well as its yaml documentation.
{Create this model in the {layer_name} layer / {folder_name} folder}
```
### Explain a dbt model
**Context:** dbt model SQL file\
**Template:**
```
Explain what this dbt model does, including:
- Business purpose and use case
- Data sources and transformations
- Key metrics and calculations
- Downstream usage
```
### Improve a dbt model
**Context:** dbt model SQL file\
**Template:**
```
Review this dbt model and suggest improvements for:
- Performance optimization
- Code readability and maintainability
- dbt best practices compliance
- repository best practices compliance
```
### Write documentation of a model
**Context:** dbt model SQL file\
**Template:**
```
Add yaml documentation for this dbt model, following these rules:
- Add documentation in the relevant yaml file if it already exists, otherwise create it in the relevant folder.
- Compare dbt model schema and yaml documentation, so that documentation is up to date with dbt model
- Follow existing yaml best practices of repository.
- Reuse existing documentation already written for some columns.
```
### Write tests for a model
**Context:** dbt model SQL file\
**Template:**
```
Add dbt tests for this dbt model, following these rules:
- Add basic tests in the relevant yaml file if it already exists, otherwise create it in the relevant folder.
- Add unique and not null tests for key/relevant columns only
- Follow existing yaml tests best practices of repository.
```
### Write custom dbt test
**Context:** dbt model SQL file\
**Template:**
```
Create a custom dbt test for this dbt model that validates {business_rule}.
The test should render all the lines where {conditions when the test fails}
```
### Write dbt unit tests
**Context:** dbt model SQL file\
**Template:**
```
Generate unit tests for this dbt model using dbt's unit testing framework.
Include test scenarios for {test_scenarios} with mock input data and expected outputs.
```
### Get dependencies of a dbt model
**Context:** dbt model SQL file\
**Template:**
```
Show me all the upstream and downstream dependencies for this dbt model.
Include both direct and indirect dependencies with their relationship types.
```
### Get dependencies of a column in dbt lineage
**Context:** None\
**Template:**
```
Find all models that use the column {column_name}.
Show me the complete lineage and how this column flows through the data pipeline.
```
### Rename a column through the lineage
**Context:** dbt model SQL file\
**Template:**
```
Rename the column {column_name} to {new_column_name} in {this model / all models}
Identify all downstream models that will be impacted and provide the necessary changes.
Update all related documentation and tests accordingly.
```
### Remove a column through the lineage
**Context:** dbt model SQL file\
**Template:**
```
Remove the column {column_name} from {this model / all models}
Identify all downstream models that will be impacted and provide the necessary changes.
Update all related documentation and tests accordingly.
```
### Add a column in final layer
**Context:** dbt model SQL file\
**Template:**
```
Add a new column {column_name} in this model.
If column is not directly available in this model, add the necessary ref to the model to make it available. Make sure you follow the layers rules in doing so.
Update all related documentation and tests accordingly.
```
### Turning a model into incremental
**Context:** dbt model SQL file\
**Template:**
```
Convert this model to use incremental materialization.
Set up appropriate unique keys, incremental strategy, and handle late-arriving data.
```
### Optimize data model
**Context:** None\
**Template:**
```
Audit dbt project for better consistency and scalability:
- Detect code logics that can be factorized
- Detect dbt models that can be broken into smaller logics
- Detect dbt models that are not used
- Detect inconsistencies in fields / metrics definitions
- Detect inconsistencies in the different data layers rules
```
### Rename a dbt model
**Context:** dbt model SQL file\
**Template:**
```
Rename this model to {new_model_name}.
Update all references, dependencies, and documentation throughout the project.
```
## SQL
### Write SQL query
**Context:** None\
**Template:**
```
Write a SQL query that {description}.
{Start from these tables: {table_names}}
{Final output should include these columns: {column list}}
{Create this SQL query in {folder_name} folder / an SQL worksheet}
```
### Improve an SQL query
**Context:** SQL file\
**Template:**
```
Review this query and suggest improvements for:
- Performance optimization
- Code readability and maintainability
- repository best practices compliance
```
### Explain an SQL query
**Context:** SQL file\
**Template:**
```
Explain what this SQL query does, including:
- Business purpose and use case
- Data sources and transformations
- Key metrics and calculations
```
### Fix an SQL error
**Context:** SQL file\
**Template:**
```
Fix this SQL error:
Error: {error_message}
```
## Data exploration
### What are the values for a column?
**Context:** Data warehouse table\
**Template:**
```
What are the distinct values in the column {column_name}?
```
### What's the value distribution for a column?
**Context:** Data warehouse table\
**Template:**
```
Analyze the distribution of values in {column_name} from this table.
```
### What's the granularity of a table?
**Context:** Data warehouse table\
**Template:**
```
Determine the granularity of this table.
Identify the primary key and explain what each row represents in business terms.
```
### Json keys extraction in a column
**Context:** Data warehouse table\
**Template:**
```
Extract the JSON keys from the column {json_column} in this table.
Output the list of all the keys.
```
### Find relevant table for an analysis
**Context:** None\
**Template:**
```
What are the most relevant tables and columns in the data warehouse to analyze: {analysis to run / information to find}
```
### Run analytics on your data
**Context:** None\
**Template:**
```
Run this analysis: {analysis description}
Write SQL query, and execute it to answer the question.
```
## Data quality checks
### Are there null values in a column?
**Context:** Data warehouse table\
**Template:**
```
What's the percentage of null values in this column: {column_name}?
```
### Are there outlier values in a column?
**Context:** Data warehouse table\
**Template:**
```
Detect any outlier values in this column: {column_name}
```
### Data diff between dev and production
**Context:** None\
**Template:**
```
Profile these 2 tables of dev and production and compare them.
Provide a summary of differences in number of rows, schema, value distribution.
```
## Git
### Write PR description
**Context:** None\
**Template:**
```
Generate a pull request description for the changes made.
Include:
- Business description of changes
- Summary of code changes
- Summary of data output changes
```
## Data service desk
### Write full PR to answer to a business user request
**Context:** None\
**Template:**
```
Create all necessary code changes for this business request:
{business_request}
Including models, tests, documentation, and PR description.
Make sure you check the code changes don't break any dependencies in the repository. Run any data quality checks needed to validate the code changes.
```
### Run full analysis to answer a business user request
**Context:** None\
**Template:**
```
Run full analysis to answer this business request:
{business_request}
Find the relevant tables/columns to answer the request, and write the corresponding queries.
Answer with answer in plain english + raw query results to illustrate answer.
```
## Pandas
### Write pandas code to clean data
**Context:** Data warehouse table\
**Template:**
```
Write python code to import this data as a pandas dataframe.
Prepare the dataset with the following transformations:
- Filter data on these rules: {filtering rules}
- Create these new columns: {columns to create}
```
## Machine Learning
### Write python code for feature engineering
**Context:** Data warehouse table\
**Template:**
```
We want to create a ML model for {describe ML model}
Write feature engineering Python code to prepare the dataset for this ML model:
- Import this data as a pandas dataframe
- Filter data on these rules: {filtering rules}
- Create these new columns: {columns to create}
- Generate features relevant for the model
```
### Write python code to create forecasting model
**Context:** Data warehouse table\
**Template:**
```
We want to create a ML model for forecasting {describe forecasting target}
Write Python code to:
- Import this data as a pandas dataframe
- Prepare the dataset for this ML model
- Write the training and test framework
- Implement ML model
- Measure performance of ML model
```
### Write python code to create scoring model
**Context:** Data warehouse table\
**Template:**
```
We want to create a ML model for scoring {describe scoring target}
Write Python code to:
- Import this data as a pandas dataframe
- Prepare the dataset for this ML model
- Write the training and test framework
- Implement ML model
- Measure performance of ML model
```
# Rules Library
Source: https://docs.getnao.io/nao-ide/learn/rules-library
Collection of .naorules examples and templates to customize your nao agent
## Rules Library
The Rules Library provides examples and templates for creating `.naorules` files to customize your nao agent's behavior. Use these as starting points and adapt them to your team's needs.
## SQL Style Rules
### Standard SQL Formatting
```
- Always use CTEs for complex queries
- Use snake_case for all table and column names
- Include comments explaining business logic
- Prefer explicit JOINs over implicit joins
- Use meaningful aliases for tables
```
### dbt-Specific Rules
```
- Follow dbt style guide for model structure
- Always include model documentation
- Use ref() instead of direct table references
- Group related models in the same folder
- Include tests for critical models
```
## Code Style Rules
### Python Style
```
- Follow PEP 8 style guide
- Use type hints for function parameters
- Include docstrings for all functions
- Maximum line length: 100 characters
```
### General Code Rules
```
- Write self-documenting code
- Keep functions focused and small
- Use descriptive variable names
- Comment complex business logic
```
## Team-Specific Rules
### Documentation Standards
```
- Always include README.md in new projects
- Document all environment variables
- Keep changelog updated
- Write clear commit messages
```
### Workflow Rules
```
- Create feature branches for all changes
- Run tests before committing
- Review code before merging
- Update documentation with code changes
```
## Domain-Specific Rules
### Data Quality Rules
```
- Always validate data types
- Check for null values in critical columns
- Include data quality tests in dbt models
- Document data sources and transformations
```
### Analytics Rules
```
- Use consistent metric definitions
- Document calculation methods
- Include time zone information in date fields
- Validate results against known benchmarks
```
## Getting Started
1. Copy a template that matches your needs
2. Customize it for your team's standards
3. Save it as `.naorules` in your project root
4. The agent will automatically use these rules
**Best Practices**
* Start with a few key rules and expand over time
* Review and update rules regularly
* Share rules with your team for consistency
* Test rules by asking the agent to follow them
# Use Cases Library
Source: https://docs.getnao.io/nao-ide/learn/use-cases-library
Watch real-world examples and use cases of nao in action
## Use Cases Library
Explore real-world examples of how teams use nao to accelerate their data workflows. Watch our YouTube channel for tutorials, demos, and use case walkthroughs.
Subscribe to our YouTube channel for video tutorials, use case demonstrations, and tips for getting the most out of nao.
## Featured Use Cases
Our YouTube channel covers a wide range of use cases including:
* **Data Analysis**: Distribution analysis, retention analysis, conversion analysis
* **dbt Workflows**: Creating dbt models, setting up dbt with AI, refactoring models
* **Business Reporting**: Creating business reports with nao
* **Root Cause Analysis**: Using nao to investigate data issues
* **Product Demos**: Full feature demonstrations and walkthroughs
## Video Categories
* **Product Updates**: Latest features and improvements
* **Use Cases**: Real-world examples and workflows
* **Tutorials**: Step-by-step guides
* **Data News**: Industry insights and best practices
**Stay Updated**
Subscribe to our [YouTube channel](https://www.youtube.com/@nao-labs) to get notified when we publish new videos with use cases and tutorials.
## Related Resources
* [Prompt Library](/nao-ide/learn/prompt-library) - Collection of useful prompts
* [Rules Library](/nao-ide/learn/rules-library) - .naorules examples and templates
* [nao Agent Documentation](/nao-ide/nao-ai/agent-tools) - Learn about agent capabilities
# Chat
Source: https://docs.getnao.io/nao-ide/nao-ai/agent-chat
nao agent is your **AI-powered assistant** with access to both your codebase repository and your data connection metadata.
nao agent can access and search your **codebase and your data warehouse metadata**.
It can execute queries on your warehouse, run commands for you, and integrate with your data stack through MCPs.
You can also use nao from a mobile browser, with a responsive chat experience adapted for smaller screens.
A typical agent flow is:
* A [prompt](#prompting) you send to the agent
* Enriched with [context](/nao-ide/nao-ai/agent-context) & [indexing](/nao-ide/nao-ai/indexing) of your codebase and data schema
* A specific [agent mode](#agent-mode)
* A [foundational model](/nao-ide/nao-ai/agent-models)
* Your own [rules](/nao-ide/nao-ai/agent-rules)
* [Tools](/nao-ide/nao-ai/agent-tools) executed by the agent
## Prompting
**Smart prompt suggestions**
As you type in the chat, nao will suggest relevant prompts based on:
* Your current context (open files, selected tables, etc.)
* Common workflows and use cases
* Your project structure and data schema
For more prompt templates, check out our [prompt library](/nao-ide/learn/prompt-library).
## Agent modes
There are 3 different agent modes: **Chat**, **Edit**, **Plan**.
You can switch modes direclty within the agent prompt form, at any point of a conversation.
### Chat
In Chat mode, the agent will answer questions and suggest code changes.
You can then:
* **Apply changes**: Click "Apply" button to see the suggested changes directly applied in your files
* **Accept/reject changes** Accept/reject each bit of code or all of the code suggested.
### Edit
In Edit mode, the agent will **auto-apply** suggestions. You can always review the changes and decide whether to accept or reject them.
**Edit mode requires an open folder**
The agent can only edit files in open folder and cannot create new files outside of an open workspace.
### Plan
In Plan mode, the agent will first write a plan to answer your request. This mode is mostly useful for long-tasks that require thoughtful planning.
In this mode, the agent won't apply any changes - you can switch to edit mode for the agent to execute the drafted plan.
**Shortcuts to accep / reject / undo apply changes:**
Accept changes: ⌘ + ⇧ + ⏎
Reject changes: ⌘ + ⇧ + ⌫
Undo accept: ⌘ + Z
## Multiple chats
You can run **multiple chats** at the same time within nao.
They will keep **running in parallel**, even if you switch between multiple chats. You will see a chat is running with a blue dot on it - when it goes away it means the chat has finished its task.
Once you have created multiple chats, you will be able to **rename** them by double clicking it, and **reorder** chat tabs. Every tab will also show you the active changes in the specific chat.
## Context window usage
The prompt bar shows your current context-window usage while you chat.
This helps you monitor how much context is already in use before sending the next message.
## Chat History
All your chat conversations are saved within nao.
Each conversation is linked to one folder / repository and can only be retrieved from there.
You can access previous conversations to view them, and continue them from where you left off.
All your chat history is accessible from this icon on top right of your chat:
# Context
Source: https://docs.getnao.io/nao-ide/nao-ai/agent-context
## Default context
By default, nao agent has always access to your codebase info and data warehouse metadata (list of tables and their schema).
You can give some more precise context:
* Files from your repositories
* Data warehouse tables
* SQL Worksheets
## Context Picker
The context picker is a powerful tool that helps you add relevant context to your agent conversations. Click on "Add context" in the chat area to open it.
**What you can add via context picker:**
* **Files**: Browse and select files from your repository
* **Folders**: Add entire folders to provide broader context
* **SQL Worksheets**: Include saved SQL worksheets
* **Data Warehouse Tables**: Add table schemas (metadata only, not data content)
* **Schemas**: Select entire database schemas
**Table Context Note**
When you add a table to context, only the table's schema (column names, types, etc.) is sent to the agent, not the actual data. This keeps your data private while giving the agent the information it needs to write accurate SQL.
Automatic Context Addition
nao automatically adds relevant context to your conversations:
* **Latest table viewed**: The context picker shows the most recently viewed table - only sends their schema
* **Previewed tables**: When you preview a table, its schema is automatically added to the agent context
* **Open files**: Files you're currently viewing are available to the agent
## Drag and Drop context
You can drag and drop items directly into the chat to add them as context. This is a quick way to add files, tables, or other items without using the context picker.
## Cmd L
You can add lines of code as context to the chat as well.
Select code in your worksheet that you want to add. Then click "+ Add to Chat" when it appears, or alternatively use ⌘ + L.
You can send query results directly to the agent context using ⌘ + L. This allows the agent to analyze your data and provide insights based on actual query results.
**How to use**
1. Execute a SQL query in a worksheet or SQL file
2. Select the results or use ⌘ + L to send them to the chat
3. The agent receives the data and can analyze it
4. Ask questions about the data or request visualizations
This feature is particularly useful when you want the agent to:
* Analyze query results
* Create visualizations from your data
* Identify patterns or anomalies
* Generate insights and recommendations
**Privacy Note:** You control what data is shared with the agent.
## Infinite Chat
nao supports long-running chat conversations without requiring you to manually start over when a thread gets large.
**Benefits**
* No conversation length restrictions
* Preserve the active part of the conversation as chats grow
* Continue complex multi-step workflows without interruption
* Keep a usable summary of earlier context instead of dropping it entirely
When a conversation approaches a model's context window, nao automatically compacts the older part of the thread into a summary while keeping your current turn verbatim. This helps the agent stay grounded in the latest exchange without losing the important history of the conversation.
This makes long chats more reliable for multi-step work such as debugging, analytics investigations, and iterative code changes.
This makes it easier to ask questions about your data without manually adding context each time.
## Data security
# Models
Source: https://docs.getnao.io/nao-ide/nao-ai/agent-models
## Models
You can choose several LLM providers to use in nao agent: Claude, GPT, Gemini, Mistral, OpenRouter, Ollama, and AWS Bedrock.
Available Models
**Claude (Anthropic)**
* Claude 3.7 Sonnet
* Claude 4 Sonnet
* Claude 4.5 Sonnet
* Claude Sonnet 4.6
* Claude Opus 4.7
**GPT (OpenAI)**
* GPT-5
* GPT-5.1
* gpt-oss
* Azure-hosted GPT deployments through the Azure OpenAI provider
**Gemini (Google)**
* Gemini 2.5 Flash
* Gemini 2.5 Pro
* Gemini 3 Pro
**OpenRouter**
* Any model on the [OpenRouter model list](https://openrouter.ai/models), addressed by its `vendor/model` slug (for example `deepseek/deepseek-v4-flash`)
* One `OPENROUTER_API_KEY` covers every vendor OpenRouter fronts
* Useful for models nao has no built-in entry for
**Ollama (Local)**
* Any model served by your local Ollama instance
* No API key required
* Default base URL: `http://127.0.0.1:11434`
* You can override the base URL in Advanced Settings
**AWS Bedrock**
* Various models available through AWS Bedrock (requires AWS credentials or API keys)
> 1. Go on nao settings at top right
>
>
>
> 2. Go to "Agent" section then "Model Providers"
>
> Enable the LLM providers you want (we strongly recommend Claude)
>
>
>
> 3. In the chat, change the LLM you want whenever
>
>
Change Model from Chat
You can switch between different LLM models directly from the chat interface. Click on the model selector in the chat header to choose a different model for your conversation.
AWS Bedrock Integration
nao supports AWS Bedrock models, allowing you to use Amazon's AI services. You can connect using either:
* **API Keys**: Configure Bedrock API keys in Settings > Agent > Model Providers
* **AWS Credentials**: Connect using your AWS credentials for enhanced security
Azure-hosted OpenAI and Anthropic models
nao also supports Azure-hosted models:
* Use **Azure OpenAI** for Azure GPT deployments
* Use **Anthropic** for Claude deployments hosted on Azure AI Foundry
* When Azure gives you a full endpoint, paste the URL prefix only. For example, use `.../openai` instead of `.../openai/v1/`, and use `.../anthropic/v1` instead of `.../anthropic/v1/messages`
For the exact field-by-field setup, see [Admin Setup](/nao-agent/chat/admin/setup#choose-llm-models).
OpenRouter
Enable **OpenRouter** in Settings > Agent > Model Providers and paste your OpenRouter API key, then add the `vendor/model` slugs you want in the model list. To declare the same providers and models in version control instead, see [OpenRouter in nao\_config.yaml](/nao-agent/context-builder/configuration#openrouter).
Using Your Own API Keys
**Enterprise Feature**
Using your own API keys for LLM models is available only in Enterprise plans. This allows you to:
* Use your own OpenAI, Anthropic, Bedrock or Gemini API keys
* Use Ollama without API keys when running models locally
* Maintain compliance with your organization's security policies
Companies can use their own API keys. [Reach out to us](https://getnao.io/calendar.html) if you want to use your own keys.
>
# Rules
Source: https://docs.getnao.io/nao-ide/nao-ai/agent-rules
You can customize your nao agent's behavior by providing custom rules and guidelines. nao supports multiple ways to configure agent behavior:
.naorules File
The **`.naorules`** file is the primary way to customize your agent. Create this file at the root of your project and write your rules in plain English. These rules will be automatically applied to all conversations with the agent.
**What you can configure:**
* **Code style**: SQL formatting preferences, naming conventions, code structure
* **Agent behavior**: Language, tone, response style, documentation habits
* **Tool-specific rules**: Include your data stack documentation (Airflow, dbt, etc.)
* **Project standards**: Team conventions, best practices, domain-specific knowledge
**Example .naorules content:**
```
- Always use CTEs for complex SQL queries
- Use snake_case for all table and column names
- Include comments explaining business logic
- Prefer explicit JOINs over implicit joins
- Follow our dbt style guide for model structure
```
claude.md File
The agent can also read `claude.md` files in your repository. This works similarly to `.naorules` and provides another way to give project-specific guidelines to the agent. You can use both `.naorules` and `claude.md` files together - the agent will read and apply rules from both.
**Multiple Documentation Files**
You can have multiple documentation files that the agent reads:
* `.naorules` - Primary rules file (recommended)
* `claude.md` - Alternative/additional rules file
* Both files are read automatically if present in your project root
Creating .naorules
> 1. Go on nao settings at top right
>
> 2. Go to "Agent" section\
> Scroll down to Chat -> Nao Rules. Click "+Add" to create .naorules at root folder
>
> 3. Write in plain english your custom rules!
>
**Best Practices**
* Keep rules focused and well-organized
* Use clear headings and sections for different rule categories
* Include examples where helpful
* Update rules as your standards evolve
* Test rules by asking the agent to follow them in a conversation
# Tools
Source: https://docs.getnao.io/nao-ide/nao-ai/agent-tools
## nao Tools
**Codebase**
* Search codebase
* Get content of a file
* Grep tool (search code and data warehouse)
* Search/replace tool
* Read full content of notebooks
**dbt**
* Get dependencies of a dbt model
* Find source yaml of a dbt model
* Find documentation yaml of a dbt model
* Execute dbt commands (run, build, test)
* Create dbt models, sources, documentation, and tests
**Warehouse**
* Search datawarehouse
* Get schema of a table
* Data profiling (count rows, null values, value distribution)
* Get view query definition
**Analytics**
* Execute a query on the warehouse
* Generate charts from query results in the chat
* Send generated chart images directly in Slack conversations
* Analyze query results sent from worksheets
* Save and share full analysis outputs as stories
### Stories
Stories let you turn an analysis into a reusable artifact:
* Save a chat output as a story
* Re-open stories from the sidebar
* Share stories with a dedicated link
* Revisit story versions and published context
**Terminal**
* Run terminal commands
* Fix terminal errors in chat
* Execute Git operations
* Run build scripts and tests
## MCPs
nao supports MCP (Model Context Protocol), allowing the agent to integrate with external tools and services through standardized protocols. MCPs enable the agent to access additional capabilities beyond the built-in tools.
**What are MCPs?**
MCPs provide a standardized way for AI agents to interact with external systems, APIs, and tools. This allows nao to extend its capabilities by connecting to your custom tools and services.
MCP integration is available in the agent, allowing you to leverage external tools and services directly within your nao conversations.
Pre-integrated MCPs
nao comes with several pre-integrated MCPs that you can use immediately:
* **Notion**: Access and manage Notion pages and databases
* **Git**: Interact with Git repositories and perform version control operations
* **Airbyte**: Connect to Airbyte for data integration workflows
* **Omni**: Integration with Omni tools and services
* **Tableau**: Access Tableau workbooks and data sources
* **Elementary**: Data quality and monitoring tools
* **Select Star**: Data catalog and discovery platform
These MCPs are available out of the box and can be enabled in your nao settings.
Adding Custom MCPs
You can add custom MCPs to extend nao's capabilities with your own tools and services:
1. Go to Settings > Agent > MCPs
2. Click "Add Custom MCP"
3. Configure your MCP connection details directly in the mcp.json file
4. The agent will have access to your custom MCP tools
## Permissions
You can configure how the agent handles permissions for different actions in Settings > Agent > Chat:
* **Query Execution**: Choose to always allow, always ask, or use auto-run settings
* **SQL safety filter**: Optionally enforce `SELECT`-only SQL execution in agent runs
* **Command Execution**: Control whether the agent asks before running terminal commands
* **Data Sharing**: Set default behavior for sharing query results with the LLM (always share, always ask, or use lock toggle)
These settings give you fine-grained control over agent behavior while maintaining security for sensitive operations.
Page on data x agent security
# Indexing
Source: https://docs.getnao.io/nao-ide/nao-ai/indexing
nao indexes your codebase to provide fast, relevant answers and code suggestions, with the context of your existing repository and data schema.
nao indexes your codebase to provide fast, relevant answers and code suggestions, with the context of your existing repository and data schema.
**How does this help nao agent?**
* It gives the agent the full context of your repository – its structure, and code style
* It gives the agent precise context on your data schema – to make sure the generated code matches your data
* It allows the agent to answer questions about your codebase or data warehouse
## What is indexed?
* All **files** in your repository – except those in `.gitignore` or `.naoignore`
* All **table schemas** in your data warehouse
## Is indexing automatic?
If you selected **"auto-indexing"** at onboarding:
* When opening a new folder / data connection, the indexing will be launched automatically and will take a few minutes to complete.
* Re-indexing will be automatically triggered every 10 minutes.
If not:
* You can trigger manual indexing from the settings / AI models panel
* You can also re-activate auto-indexing from this panel
## How can I check the indexing status?
* Go to the **settings / AI models panel** to check the status of both codebase & warehouse indexing
## Privacy & Security
* Only the **indexing output** – i.e. embeddings – are stored in nao server. Your codebase and data schema are not stored at all. The content of your data is **never accessed** in this process.
* See our [Privacy Policy](https://getnao.io/security/privacy-policy/) for more details.
# nao Tab
Source: https://docs.getnao.io/nao-ide/nao-ai/nao-tab
nao Tab triggers whenever you're editing a code file. To accept the propositions, click on tab or enter.
nao Tab triggers whenever you're editing a code file.
To accept the propositions, click on tab or enter.
The tab suggests code with this context:
* File content
* codebase
* data imported in the form of the query if SQL
# FAQ
Source: https://docs.getnao.io/nao-ide/support/faq
Frequently asked questions about nao
You can view the query definition behind a database view using the agent. Ask the agent to "show me the query for view \[view\_name]" or use the agent's warehouse tools to get the view query definition.
To allow the agent to create files, you must first open a folder or repository in nao. The agent can only create and edit files within an open workspace.
You can open the terminal in nao using ⌘ + J.
To analyze a CSV file in nao:
1. Connect to DuckDB (which supports CSV files directly)
2. Import your CSV file into DuckDB
3. Use the agent to query and analyze the data
4. Alternatively, you can use SQL worksheets to write queries against your CSV data
**DuckDB CSV Support**
DuckDB can read CSV files directly, making it easy to analyze CSV data without importing into a traditional database.
nao works exclusively with **dbt Core**. dbt Cloud is not supported. Make sure you're using dbt Core in your local environment. See [Set up dbt](/nao-ide/get-started/set-up-dbt) for configuration details.
When dbt is fully configured in nao you unlock:
* Execute & preview dbt models directly in the IDE
* Auto-complete for model columns plus quick navigation to refs
* Column-level lineage in the agent experience
Even without configuring dbt you still keep the agent's ability to create or edit dbt models, sources, documentation, and tests.
Your chat history is automatically saved and can be accessed from the chat interface. All your conversations are maintained per workspace and are available in the chat sidebar. You can:
* Access previous conversations from the chat history
* Continue conversations from where you left off
* Reference past conversations and their context
Yes! You can execute SQL queries using ⌘ + ⏎. This runs the query at your cursor position.
**Multi-query execution:** If you have multiple queries in a file, nao will execute the query where your cursor is located.
nao requires a local environment and cannot work with remote dbt environments. Your dbt project must be set up locally on your machine where nao is running.
To update nao:
1. **macOS**: Download the latest version from the [download page](https://getnao.io/download/) and install it
2. **Windows**: Download the latest version from the [download page](https://getnao.io/download/) and install it
Updates are typically available on the releases page. nao will notify you when updates are available.
For invoice information, please contact [claire@getnao.io](mailto:claire@getnao.io) or reach out through our [Support](/nao-ide/support/intro) page.
No, there is currently no Linux version of nao. However, a Linux version is planned for release in **2026**.
**Current Platforms**
nao is currently available for:
* macOS (Apple Silicon and Intel)
* Windows
Linux support is coming in 2026.
You can connect multiple databases simultaneously in nao. Go to Settings > Warehouse Connections > Add Connection to add additional databases. Each connection can be set as global (available across all workspaces) or attached to a specific workspace. See [Set up data connection](/nao-ide/get-started/set-up-data-connection) for details.
Yes, you can use nao without a data connection. The agent can still help with codebase-related tasks, dbt model creation, and general coding assistance. However, features like SQL execution, data preview, and data analysis require a data connection.
nao takes data privacy seriously:
* Only table schemas (metadata) are sent to the agent, not actual data
* You control what data is shared with the LLM through the share/preview options
* Codebase indexing only stores embeddings, not your actual code
* Your data content is never accessed during indexing
See our [Privacy Policy](https://getnao.io/security/privacy-policy/) for more details.
nao supports multiple AI models including:
* **Claude** (3.5 Sonnet, 3.7 Sonnet, 3 Opus, 3.5 Haiku)
* **GPT** (GPT-4o, GPT-4o-mini, GPT-5, GPT-5.1, gpt-oss)
* **Gemini** (2.5 Flash, 2.5 Pro)
* **Mistral** (Large, Small)
* **AWS Bedrock** (various models)
You can switch between models directly in the chat interface. See [Choose LLM provider](/nao-ide/nao-ai/agent-models) for more information.
You can customize the agent using a `.naorules` file in your project root. This file allows you to set:
* Code style preferences (SQL formatting, naming conventions)
* Agent behavior (language, tone, documentation habits)
* Project-specific standards and best practices
The agent can also read `claude.md` files for additional guidelines. See [Customize your agent with nao rules](/nao-ide/nao-ai/agent-rules) for details.
Yes, Enterprise plan users can use their own API keys for LLM models (OpenAI, Anthropic, Bedrock, or Gemini). This allows you to control costs and maintain compliance with your organization's security policies. [Contact us](mailto:claire@getnao.io) to set up Enterprise access.
You can share query results with the agent using ⌘ + L. After executing a query, select the results and use the shortcut, or click "Share" when viewing results. You can also use "Preview local" to view results privately without sharing with the LLM.
nao supports the following databases:
* **BigQuery** (Service Account, SSO)
* **Snowflake** (User Password, SSO, Key Pair)
* **Postgres** (SSH, SSL, TLS)
* **Redshift** (SSH, SSL, TLS)
* **ClickHouse** (HTTPS)
* **Databricks** (Personal Access Token, OAuth U2M)
* **DuckDB** (Local file)
* **MotherDuck**
* **Amazon Athena** (AWS Access Keys)
See [Set up data connection](/nao-ide/get-started/set-up-data-connection) for connection details for each database.
To preview dbt models, ensure your dbt project is set up in nao, then simply click the execute button (or press ⌘ + ⏎) while viewing a dbt model file. nao will compile and execute the model, showing you the results. See [Work with dbt](/nao-ide/features/work-with-dbt) for more information.
Yes, you can open multiple folders/repositories in nao, but only the first one will be in the agent's context. Each workspace maintains its own:
* Data connections
* dbt configuration
* Chat history
* Indexed codebase
You can view dbt lineage by clicking the lineage icon in the top right toolbar. If you have a dbt model file open, the lineage will be centered on that model. You can expand nodes, navigate dependencies, and click on models to open their files. See [View dbt lineage](/nao-ide/features/work-with-dbt#view-dbt-lineage) for details.
nao automatically reconnects to BigQuery if the refresh token expires. If you experience connection issues, check your connection settings in Settings > Warehouse Connections and verify your credentials are still valid.
Yes, you can export query results to CSV format. In the data preview panel, click the download icon to save your results as a CSV file. See [Interactive Data Preview](/nao-ide/features/work-with-sql#interactive-data-preview) for more details.
To set query size limits for BigQuery:
1. Go to Settings > Warehouse Connections
2. Select your BigQuery connection
3. Set the query size limits in the connection settings
This helps prevent expensive queries and control costs. See [BigQuery Query Size Limits](/nao-ide/features/work-with-sql#bigquery-query-size-limits) for details.
* **Free Plan**: 5 prompts per day, basic features, limited AI models
* **Free Trial**: 20 prompts per 3 hours, full feature access, all AI models (converts to Free after trial)
* **Pro Plan**: Unlimited prompts, all features, all AI models, team collaboration, priority support
See [Pricing and Plans](/nao-ide/support/pricing-and-plans) for complete details.
To add team members:
1. Go to Settings > Team Management
2. Click "Add Team Member"
3. Enter their email address
4. They'll receive an invitation to join your workspace
To add more seats to your license, go to Settings > Billing > Manage Subscription > Add Seats. See [Managing Your Team](/nao-ide/support/pricing-and-plans#managing-your-team) for details.
No, nao requires an internet connection to function. The AI models, codebase indexing, and data warehouse connections all require network access.
If you're having connection issues:
1. Verify your credentials are correct
2. Check that your firewall/network allows connections
3. For BigQuery, ensure your refresh token hasn't expired
4. For Snowflake, verify your account, warehouse, and role settings
5. Check the connection status in Settings > Warehouse Connections
If problems persist, contact [support](mailto:claire@getnao.io).
nao indexes all files in your repository except those in `.gitignore` or `.naoignore`. This includes:
* SQL files
* dbt models
* Python scripts
* Configuration files (YAML, JSON, etc.)
* Documentation files
* Any other text-based files
Binary files and files in `.gitignore` or `.naoignore` are excluded from indexing.
You can exclude files from indexing by:
1. Adding them to your `.gitignore` file (they'll be automatically excluded)
2. Creating a `.naoignore` file in your project root with patterns for files/folders to exclude
The `.naoignore` file uses the same syntax as `.gitignore`.
Yes, nao works seamlessly with Git. You can:
* Run Git commands in the terminal (git add, commit, push, etc.)
* Have the agent execute Git operations
* View diffs and changes
* The agent can help with Git workflows
The terminal in nao works the same as VS Code's terminal.
When the agent is working, you can see:
* A blue dot indicator on the active chat tab
* Real-time reasoning display (for reasoning models like GPT-5, Gemini 2.5 Pro)
* Tool execution status in the chat
* File changes as they're suggested
The agent shows its progress and reasoning in real time.
MCPs (Model Context Protocol) allow nao to integrate with external tools and services. nao comes with pre-integrated MCPs including Notion, Git, Airbyte, Omni, Tableau, Elementary, and Select Star. You can also add custom MCPs in Settings > Agent > MCPs. See [MCPs](/nao-ide/nao-ai/agent-tools) for details.
Click on the model selector in the chat header to switch between different LLM models. You can change models at any time during a conversation, though starting a new chat when switching to a significantly different model is recommended for best results.
Yes! The agent can create various types of charts from query results:
* Bar charts
* Line charts
* Pie charts
Simply ask the agent to "show me a chart" or "create a visualization" of your data. See [Agent tools](/nao-ide/nao-ai/agent-tools) for examples.
When your dbt project is configured correctly, you'll see a checkmark (✓) in the bottom left of nao. If there's an issue, you'll see an X. Click on the dbt status indicator to open the configuration panel and troubleshoot. See [Check dbt setup status](/nao-ide/get-started/set-up-dbt#check-dbt-setup-status) for details.
The nao sandbox is a pre-configured environment with:
* A sample dbt project (jaffle shop)
* DuckDB connection with sample data
* Full access to all nao features
It's perfect for testing nao without setting up your own project. See [nao Sandbox](/nao-ide/get-started/nao-sandbox) to get started.
Yes, the agent can read the full content of notebooks and help you work with them. You can add notebooks to the agent context and ask questions about them or request modifications.
For BigQuery queries, the cost is displayed in the bottom bar even before you run the query. It shows the estimated cost and indicates if the dry run is successful. This helps you understand query costs before execution.
nao supports many keyboard shortcuts:
* ⌘ + ⏎: Execute SQL query
* ⌘ + L: Send selected code/results to agent
* ⌘ + ⇧ + J: Open/close data preview
* ⌘ + J: Open terminal
* ⌘ + click: Jump to definition
See [Keyboard Shortcuts](/nao-ide/features/keyboard-shortcuts) for a complete list.
Double-click on the chat tab name to rename it. This helps you organize multiple conversations and find specific chats later.
Yes, you can use nao in "data connection only" mode without opening a repository. This allows you to:
* Create SQL worksheets
* Execute queries
* Use the agent for data analysis
* Preview tables
However, codebase-related features require an open repository.
If you're on the Free plan (5 prompts/day) or Free Trial (20 prompts/3 hours), you'll see a message when you reach your limit. You can:
* Wait for the limit to reset (midnight for Free, 3 hours for Trial)
* Upgrade to Pro for unlimited prompts
Enable the "Edit" button in the chat to allow the agent to auto-apply changes to your files. Once enabled, the agent will edit your code directly based on your requests. You can always review and accept/reject changes. See [Edit mode](/nao-ide/nao-ai/agent-chat#edit) for details.
# Get Support
Source: https://docs.getnao.io/nao-ide/support/intro
If you need any support, you can write us directly or join our Slack community.
If you need any support, you can write us directly: [claire@getnao.io](mailto:claire@getnao.io)
You can also join our Slack! [Join here](https://join.slack.com/t/naolabs/shared_invite/zt-33txohwoj-7grxIbyLZsgxLoDeC4D~vw)
# Pricing and Plans
Source: https://docs.getnao.io/nao-ide/support/pricing-and-plans
Learn about nao's pricing plans, limitations, and how to manage your team.
## Plans Overview
nao offers different plans to suit your needs:
### Free Plan
* **5 prompts per day**
* Access to core features
* Limited to basic AI models
* Community support
### Free Trial
* **20 prompts per 3 hours**
* Full access to all features
* All AI models available
* Full support during trial period
* Automatically converts to Free plan after trial ends
### Pro Plan
* **Unlimited prompts**
* Full access to all features
* All AI models available
* Priority support
* Team collaboration features
**What counts as a prompt?**
Each message you send to the nao agent counts as one prompt. This includes:
* Questions and requests to the agent
* Follow-up messages in a conversation
* Commands that trigger agent responses
## Plan Limitations
### Free Plan Limitations
* 5 prompts per day (resets at midnight)
* No access to premium AI models
* Limited to single user
* Basic support only
### Free Trial Limitations
* 20 prompts per 3-hour window
* Trial period is time-limited
* After trial, automatically downgrades to Free plan
### Pro Plan Benefits
* Unlimited prompts
* Access to all AI models including reasoning models
* Team collaboration
* Priority support
* Advanced features
## Managing Your Team
### Adding Team Members (Pro Plan)
To add team members to your Pro plan:
1. Go to Settings > Team Management
2. Click "Add Team Member"
3. Enter the team member's email address
4. They will receive an invitation to join your workspace
**Team Member Access**
Team members added to your Pro plan will have:
* Access to your shared workspaces
* Ability to use all Pro features
* Shared data connections (if configured)
* Access to team chat history (if enabled)
### Adding Seats to Your License
To add more seats to your Pro license:
1. Go to Settings > Billing
2. Click "Manage Subscription"
3. Select "Add Seats"
4. Choose the number of additional seats
5. Complete the payment process
**Seat Management**
* Each seat allows one user to access your Pro workspace
* Seats can be reassigned if a team member leaves
* Billing is prorated when adding or removing seats
### Removing Team Members
1. Go to Settings > Team Management
2. Find the team member you want to remove
3. Click the "Remove" button next to their name
4. Confirm the removal
**Important:** Removing a team member will revoke their access immediately. They will lose access to all shared workspaces and data connections.
## Upgrading Your Plan
To upgrade from Free to Pro:
1. Go to Settings > Billing
2. Click "Upgrade to Pro"
3. Select your billing cycle (monthly or annual)
4. Complete the payment process
**Billing**
* Monthly billing: Charged monthly
* Annual billing: Charged once per year (save with annual plans)
* All plans can be canceled at any time
## Need Help?
If you have questions about pricing or need assistance with your plan:
* Visit our [Pricing page](https://getnao.io/pricing/) for the latest pricing information
* Contact support: [claire@getnao.io](mailto:claire@getnao.io)
* Check our [support page](/nao-ide/support/intro) for more resources
# Security
Source: https://docs.getnao.io/nao-ide/support/security
Information about nao's security practices and data handling
## Security
For detailed information about nao's security practices, data handling, and compliance, visit our [Security page](https://getnao.io/security/).
For security-related questions or concerns, please contact [claire@getnao.io](mailto:claire@getnao.io).
**Security Resources**
* [Security Overview](https://getnao.io/security/)
* [Trust Center](https://compliance.getnao.io/overview)
* [Privacy Policy](https://getnao.io/security/privacy-policy/)
* [Terms and Conditions](https://getnao.io/security/terms-and-conditions/)