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

# Deploy an agent from GitHub

Install the B3OS Deploy app and each push deploys the repository's b3os.yaml. Or run b3os apply from a GitHub Actions workflow.

Version 1 of [b3os.yaml](/design/b3os-yaml#version-1-draft-deploy-with-b3os-apply) is a draft and can change. The research-agent template below has been deployed only against a test double of the B3OS API; against beta-api.b3os.org only its dry run has run.

There are two ways in. The [B3OS Deploy](https://github.com/apps/b3os-deploy) app deploys a repository's `b3os.yaml` on each push, with no workflow file and no key in the repository. Or a GitHub Actions workflow runs `b3os apply` with a project key, as the template below does.

## With the B3OS Deploy app

**Install the app**

On GitHub, install [B3OS Deploy](https://github.com/apps/b3os-deploy) on the account that owns the repository, for the repositories you choose. It reads their files and posts the plan; it cannot push. It asks to read contents and metadata and to write checks and pull requests, nothing else. In B3OS, only an owner or an admin of the organization can connect it.

**Link the repository**

Open [Deploy from GitHub](https://beta.b3os.org/app/deploy) in the console and pick the repository. Then choose the branch, the `b3os.yaml` to deploy (its path from the repository's root) and the application's name, the name in the file. Linking deploys the branch as it is now.

**Push**

Each push to the branch deploys. B3OS reads the commit through the app, plans it and posts the plan as a check, then builds, starts the new generation and cuts over once its readiness probe passes. A plan with a problem deploys nothing. The application's Deployments tab in the console shows each step as it runs.

**Open a pull request**

A pull request gets the plan as a check and a comment. Nothing deploys.

B3OS renews the live deploy's services itself, so no schedule runs in the repository. Redeploy and roll back from the application in the console.

## With a GitHub Actions workflow

The template is a whole repository for a research agent. Its harness, a small TypeScript service, runs in a sandbox on the B3OS node and calls the model served there, vision and a pack through the sandbox's grant, with no API key inside the sandbox. The Python the model writes runs in a second sandbox made for each question, which holds no credential and has no network ([head and hands](/design/b3os-yaml#head-and-hands-where-model-written-code-runs)). A file in the repository, `b3os.yaml`, declares all of it. A GitHub Actions workflow runs `b3os apply` on every push to `main`, and `b3os apply --keepalive` on a schedule, which keeps the harness running.

The template is not yet published as a public repository. Its own `README.md` shows the plan and the deploy output, recorded against the test double.

## What the agent does

You add a question to a table in your Postgres database. The harness claims it, and the agent reads pages with [vision](/vision), runs a routine that reads several pages at once, lists a page's links with a program that ships in the repository, works things out in Python it writes, and answers with the node's model. When the question names a GitHub repository, the harness files the answer there as an issue through the [GitHub pack](/actions/catalog/github).

| In `b3os.yaml` | In the template |
|---|---|
| a model | `b3os/default`, reached through the OpenAI SDK at `B3OS_API_URL` plus `/v1/infer` with a placeholder key, as [agents in sandboxes](/sandboxes/agents#call-b3os-from-inside) describes |
| a service | `harness`: built with `npm ci` from `agent/package-lock.json`, started as `node src/main.ts`, checked with `GET /healthz` on port 8080 |
| B3OS products | `vision`, and the routine `gather-sources` |
| a pack | `io.b3os/github`, with the stored connection labelled `github-bot` |
| a tool | `extract-links`, a program in `agent/tools/` that runs inside the harness's sandbox |
| a sandbox shape | `hands`: a `code-interpreter` sandbox the harness creates for each question that runs code, with egress `deny` and without secrets or a grant, and deletes when the question ends |
| an external resource | `research-db`, your Postgres; its URL reaches the harness as the secret `DATABASE_URL` |

The harness's sandbox gets only the scopes these entries imply. Its egress allows the database's host and port and nothing else on the internet: the model, vision, the routine and the pack are reached through B3OS. The hands get no B3OS access and egress `deny`. A host that is not in the allowlist does not resolve either: with no hosts at all, nothing outside B3OS resolves, not even in DNS ([egress with no hosts](/design/b3os-yaml#version-1-draft-deploy-with-b3os-apply)).

## Before you start

- A project key with `routines:read`, `routines:write`, `routines:run`, `sandboxes:read`, `sandboxes:write`, `sandboxes:run`, `vault:read`, `vault:run`, `actions:read`, `actions:run`, `infer:read`, `infer:run` and `vision:run`. Give it a spend cap and an expiry ([keys](/platform/keys)).
- A Postgres database the internet can reach.
- Two [connections](/vault) in the same project. The repository never holds a credential; it names these by label:

  ```bash
  printf %s "$DATABASE_URL" | b3os connect new --type bearer --label research-db
  b3os connect io.b3os/github --label github-bot
  ```

  The vault has no URL field, so the `postgres://` URL is stored as a bearer connection's token. A connection with no hosts is never added to any request; it reaches the harness only as the sandbox secret `b3os.yaml` names.

## Deploy it

**Copy the template**

Make a repository of your own from the template. A public template repository is not published yet; once it is, choose **Use this template** on its GitHub page rather than forking it. GitHub runs no workflow in a new fork until you turn workflows on, and in a fork of a public repository it keeps scheduled workflows off, so the keepalive would not run. In `b3os.yaml`, set `resources[0].hosts` to your database's host and port, such as `db.example.com:5432`. Keep the port: `b3os apply` copies the entry into the harness's egress allowlist, and an entry without a port opens only ports 80 and 443. The harness can reach that host and port directly and nothing else.

Keep the repository public if you can. The keepalive runs as a scheduled GitHub Actions job, which is free in a public repository and billed in runner minutes in a private one (the workflow file's header has the arithmetic).

**Add the secret**

In the repository's settings, add a repository secret named `B3OS_API_KEY` holding the project key. Add it as a repository secret, not an environment secret: the keepalive job uses no environment, so it could not read one. It is the only secret the workflow reads.

**Check that the workflow is on**

Open the repository's Actions tab. If GitHub asks you to enable workflows, enable them. Then choose the `b3os` workflow and, if GitHub shows **Enable workflow**, choose it. A workflow that is off fails silently: nothing deploys, and a running harness stops within its timeout.

**Push**

Commit and push to `main`. The `deploy` job checks out the commit and runs `b3os apply --yes`.

**Watch it deploy**

The job prints the plan, then one line per step: the routine published, the dependencies installed with `npm ci` and snapshotted, a new generation of the harness started with the commit's tracked files of `agent/`, and its readiness probe passed. Then a summary. If any step fails before the new generation is ready, the deploy is undone and the previous generation keeps running.

Soon after, the same workflow shows its first scheduled `keepalive` run. GitHub can start scheduled runs some minutes late, but if none has appeared after 30 minutes, the schedule is off.

**Open the console**

Open Applications in the [console](https://beta.b3os.org/app) and choose `research-agent`. The canvas shows the harness, its routine, the model, vision, sandboxes (the hands), the pack and the database. Ask a question with an `INSERT INTO questions (question, repo) VALUES (...)`; the harness's calls appear in the request log.

## Check the plan before you push

From a clone, with the key in `B3OS_API_KEY`:

```bash
b3os manifest validate     # offline: the file, its schema and its references
b3os apply --dry-run       # reads the project; changes nothing
```

The dry run, like the deploy, uses what is committed: commit `b3os.yaml` and the routine file first. Its marks are those of [the plan](/design/b3os-yaml#print-the-plan).

## How the harness stays up

Each deploy starts a new generation: a sandbox restored from the build snapshot, given the source and the environment, and started. The build is reused until `package.json` or `package-lock.json` changes. A generation is a sandbox with a timeout of at most 3,600 seconds ([sandbox limits](/sandboxes/limits)), and every keepalive renews it. When the workflow stops running, the harness stops within that timeout.

When a new generation is ready, the old one gets `SIGTERM`. The template's harness then stops the agent, puts its question back in the queue, closes the database and exits. Its `GET /healthz` answers 200 only while its work loop is moving, and each keepalive runs that probe, so a harness whose loop is stuck gets a new generation.

To roll back, run the workflow by hand with `ref` set to an older commit. From a clone, `b3os app rollback` does the same without a checkout: it deploys the commit of the previous deploy that went live (or of `--to dep_...`), says in its plan and its deploy that it is a rollback, and leaves your working tree and HEAD as they are; a commit the clone lacks is an error that names the fetch to run. Keepalive recreates a lost harness from the commit it ran, never from the newest one on `main`, so it does not undo a rollback, and it says so: `running caebec0 by rollback; HEAD is ee70e90`. The next `b3os apply` deploys HEAD again.

## What the template leaves out

- **An address.** The harness takes its work from the database; it gets no stable URL in this design. A service you give a public port is served at its preview URL under a path prefix (`/sbx/.../`), so absolute asset paths 404 and cookies are not kept; the plan says so in one line under the service, with a link to [ports](/sandboxes/ports#base-paths).
- **Previews.** Pull requests do not deploy.
- **More than one copy.** One generation serves at a time, apart from the overlap during a cutover.