# A bot on its own computer

Set up BasaltDB for a bot that runs on a machine you cannot reach. The bot makes a setup code, and you run it on your own computer, where your database password stays.

Use this page when your bot runs on a machine you cannot log in to, such as a server or a chat bot's host, and you only talk to it in a chat. The bot installs and runs the engine there. You run one command on your own computer, with the admin connection string. The admin connection string never reaches the bot's machine.

## Who does what

| Where             | Who  | What                                                                          |
| ----------------- | ---- | ----------------------------------------------------------------------------- |
| The bot's machine | Bot  | Runs `basalt init --remote`. Gets a setup code. Starts the engine afterwards. |
| The chat          | Both | The bot shows you the setup code and its fingerprint. You tell it when done.  |
| Your own computer | You  | Runs `basalt init --setup-code` with your admin connection string.            |
| Your Postgres     | You  | Receives the guard, the agent role and its tables, in one transaction.        |

## Before you start

- `basalt` is installed on the bot's machine and on your own computer. See [Install](/start/install/). Your own computer must be macOS or Linux.
- You know the Postgres connection string without its password. Supabase shows it with a `[YOUR-PASSWORD]` placeholder. See [Hosts](/guides/hosts/) for which string to pick.
- You can reach your Postgres from your own computer. The bot's machine needs to reach it too, because the engine connects from there.

## 1. The bot makes a setup code

The bot runs this on its own machine. The connection string has no password in it.

```sh
basalt init --remote --agent my_bot --operated-by=agent \
  --write public.leads --read public.lead_notes \
  --database-url "postgresql://postgres@HOST:5432/postgres"
```

- `--operated-by=agent` tells BasaltDB that the bot, not you, runs machine steps such as starting the engine.
- `--write` lists tables the bot may read and change. `--read` lists tables it may only read.
- If the connection string has a password, the command refuses it, does not write it anywhere, and tells you to change your database password. Never paste it to a bot.
- The command does not ask for an admin credential. It ignores `BASALT_ADMIN_DATABASE_URL`. It has no prompt.

The bot generates the agent's password and keeps it in a file only it can read. Only a verifier of that password goes into the setup code. Nobody can log in with a verifier.

The command prints the setup code in a block of its own, then its fingerprint:

```text
basalt-setup.1.eJxVjMEKwjAQRP9lzhJMPWjzK6WHEFcb...
Setup fingerprint: 3f9a 21c0 7d44 e1b2 90ac
```

The code is valid for 24 hours and works once. While the code waits, running the command again prints the same code. A change to the tables, or `--new-code`, makes a new code and retires the old one. An expired code is replaced by running the command again.

The bot shows you the code and the fingerprint in the chat. Use a code block, so the chat does not read the underscores as emphasis.

## 2. You run the code on your own computer

```sh
basalt init --setup-code SETUP_CODE
```

It asks for the admin connection string at a hidden prompt, or reads `BASALT_ADMIN_DATABASE_URL`. It is used once and never written anywhere.

Before it changes anything, the command checks the code and shows you a summary:

1. The code is whole, not expired, and not dated more than 5 minutes ahead of your computer's clock.
2. Your connection string names the same database as the one the bot's engine connects to. A mismatch is refused, not asked about.
3. It shows the fingerprint. **Check that it matches the fingerprint your bot gave you in the chat.** If it does not, stop and ask the bot for a new code. A code someone else posted in the chat would show a different one.
4. It says what will happen, in fixed words. A first setup reads `This creates the agent my_bot (role basalt_agent_my_bot). Its password comes from the code: only whoever made this code can log in as basalt_agent_my_bot.`
5. It lists what the bot can do: read and change rows in the tables you named, up to 10 rows at a time without asking you, 11 to 50 with your approval, never more. It cannot change your tables' structure. Tables outside `public` are flagged.
6. It asks the row-level security question for each table that hides its rows, as `basalt init` does. See [Hosts](/guides/hosts/#row-level-security).
7. It shows the SQL and asks `Run this? [y/N]`. Add `--yes` to skip the question, or `--dry-run` to print everything and run nothing.

Answer `y`. The setup runs in one transaction, so it is all or nothing: every table or none. If a step fails, your database is as it was.

If no passkey is registered, the command then opens a page to register one. Open it, confirm with your passkey and paste the code the page shows. See [Passkeys](/guides/passkeys/). Add `--add-passkey` to register another.

It ends with:

```text
Done. Tell my_bot to check its status.
```

## 3. The bot starts its engine

`basalt init --remote` does not install or start the engine. The bot does that on its own machine, by running `basalt engine --config PATH`, or by installing it as a service. Until your setup has run, the engine answers every call with `UNAVAILABLE` and the reason `SETUP_PENDING`, and tells the bot to ask you to run the code.

When the engine sees that your setup ran, it serves the bot normally. It needs no restart.

## 4. Tell the bot

Tell the bot in the chat that you ran the code. It calls its `status` tool and confirms it is ready. You can also check on the bot's machine:

```sh
basalt status --agent my_bot
```

## Change something later

Any change after the first setup goes the same way: the bot makes a new code, and you run it on your own computer.

| You want to                 | The bot runs                                                                | You run                         |
| --------------------------- | --------------------------------------------------------------------------- | ------------------------------- |
| Add or remove a table       | `basalt init --remote` with the new `--write` and `--read` lists            | `basalt init --setup-code CODE` |
| Give the bot a new password | `basalt init --remote --new-code`                                           | `basalt init --setup-code CODE` |
| Upgrade the guard           | `basalt init --remote`, which makes an upgrade code when the guard is older | `basalt init --setup-code CODE` |

`basalt upgrade` refuses on an engine your bot runs, because it would ask for your admin connection string on the bot's machine. See [Upgrade](/guides/upgrade/).

## If it does not work

Messages you can see on your own computer:

| Message                                                                                  | What to do                                                                                         |
| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `This setup code expired on <time>`                                                      | Ask the bot for a new code. It makes one by running `basalt init --remote` again.                  |
| `This setup code is damaged; copy it again.`                                             | The chat cut or changed the code. Copy it again from a code block.                                 |
| `This setup code was already used on <time>`                                             | A code works once. Ask the bot for a new one.                                                      |
| `A newer setup for my_bot already ran`                                                   | An older code is refused after a newer one ran. Use the newest code.                               |
| `This setup code is for database ...`                                                    | Your admin connection string points at another database. Use the one for the database in the code. |
| `... already exists. This code would replace its password`                               | The agent role already exists. Ask the bot for a new code for that agent.                          |
| `Update basalt on this computer to at least ...`                                         | Your `basalt` is older than the one that made the code. Install the current version.               |
| `This is not a Basalt setup code.`                                                       | The text is not a setup code. Copy the whole line that starts with `basalt-setup.1.`.              |
| `This setup code is for guard version N, but setup codes need guard version 16 or later` | The bot's `basalt` is too old. Ask it to upgrade `basalt` and make a new code.                     |
| `This setup code claims a time in the future`                                            | Check this computer's clock, or ask the bot for a new code.                                        |

## What keeps this safe

- The admin connection string is used only on your own computer. No command on the bot's machine asks for it or accepts it.
- The setup code holds no secret. It holds the table lists, the role name, a verifier for the bot's password, and the database it is for.
- The SQL that runs comes from your own `basalt`, not from the code.
- The fingerprint lets you check that the code you run is the one your own bot made.
- The code is valid for 24 hours and works once. Postgres enforces that, as well as your computer's clock.
- An existing agent's role is never taken over by a first-setup code.

## Approving on a bot's engine

An engine your bot runs serves its approval and undo pages from the BasaltVault portal (`portal.basaltvault.app`), not from the bot's machine. Ask the bot to undo a run, and it shows you a link. See [Undo a run](/guides/undo/) and [Passkeys](/guides/passkeys/).

## Next

- [Connect an agent](/start/connect-agent/): the short version, next to OpenCode, Claude Code and Codex.
- [Hosts](/guides/hosts/): the connection string for your database host.
- [Upgrade](/guides/upgrade/): keep the guard current.
