> ## 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.

# Packs

What a B3OS pack contains, how a declarative HTTP action is written with CEL templates, and how packs are hashed, signed, published and verified.

A pack is a versioned bundle with a `b3pack.json` manifest at its root. The manifest names the pack, declares the hosts it may reach and the credentials it may use, and describes each action and trigger.

## A small pack

This pack has one action, which calls the public Open-Meteo geocoding API and needs no credential:

```json b3pack.json
{
  "pack": "io.b3os/open-meteo",
  "version": "1.0.0",
  "title": "Open-Meteo",
  "description": "Place search and current weather from the Open-Meteo API.",
  "publisher": { "id": "io.b3os", "name": "B3OS" },
  "license": "MIT",
  "engine": "^1.0",
  "egress": [{ "host": "geocoding-api.open-meteo.com" }],
  "actions": [
    {
      "key": "search-places",
      "title": "Search places",
      "description": "Find places by name. Returns coordinates and time zone for each match, best match first.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name"],
        "properties": { "name": { "type": "string", "minLength": 2, "maxLength": 100 } }
      },
      "outputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": ["places"],
        "properties": { "places": { "type": "array" } }
      },
      "effects": { "class": "read" },
      "impl": {
        "kind": "http",
        "request": {
          "method": "GET",
          "url": "https://geocoding-api.open-meteo.com/v1/search",
          "query": { "name": "${{ input.name }}", "format": "json" }
        },
        "response": {
          "map": { "places": "${{ has(response.body.results) ? response.body.results : [] }}" }
        }
      }
    }
  ]
}
```

Its action is called as `io.b3os/open-meteo/search-places@1.0.0`, and its MCP tool name is `open-meteo__search-places`.

## Manifest fields

| Field | Meaning |
|---|---|
| `pack`, `version` | `<namespace>/<name>` and a semantic version without build metadata. The namespace is the publisher's reverse-DNS name. |
| `title`, `description`, `license`, `publisher` | What the pack is and who publishes it. Verification status lives in the registry, never in the manifest. |
| `engine` | The pack runtime it needs, as a caret range; the v1 runtime is `1.0.0`. |
| `egress` | Every host any action or trigger may reach. Actions may narrow it. A new host in a later version is a major change. |
| `connections` | The credentials the pack can use, by key, each with a type (`api_key`, `bearer`, `basic`, `oauth2`) and the hosts it is injected for |
| `actions` | Up to 256 actions: `key`, `title`, `description`, `inputSchema`, `outputSchema`, `effects` and `impl` |
| `triggers` | Up to 64 webhook, poll or schedule triggers |
| `signature` | Written by the publish tool: the content hash and the publisher's Ed25519 signature |

## How a declarative action runs

1. The input is validated against `inputSchema`, and schema defaults fill in absent fields.
2. The request template is evaluated. Expressions are CEL inside `${{ }}`, with `input` and the connections' non-secret `params` in scope. Text spliced into the URL is percent-encoded, so an input can never add a path segment or a query.
3. The final URL's host must match the action's egress list.
4. The egress proxy adds the credential for each connection the action requires, only on requests to that connection's hosts: `api_key` where the pack's `inject` says, `bearer` and `oauth2` as `Authorization: Bearer`, `basic` as `Authorization: Basic`. Templates never see secrets and cannot set the injected header.
5. A response counts as success on a 2xx status unless `response.success` says otherwise. `response.map` builds the output, which must match `outputSchema`; the default map is the response body. On failure, the first matching `errors[]` entry names the pack's error, reported in the problem's `pack_error`.

Packs whose logic needs code are written in JavaScript and run in a separate runtime with no credentials, reaching the network only through the egress proxy. In the beta those are first-party packs only.

## Signing and verification

Every pack version has a content hash over all of its files (the manifest serialized with RFC 8785, without its signature) and an Ed25519 signature by the publisher's registered key over `b3pack-sig-v1`, the pack, the version and the hash. Before loading a version, a node recomputes the hash, compares it with the registry's record, and checks the signature. A pack that fails is not loaded, and calls to it fail with [`pack_unavailable`](/errors#pack_unavailable).

## The registry

| Call | Does |
|---|---|
| `GET /v1/actions/packs` | Packs the node serves, with their latest version and action and trigger counts |
| `GET /v1/actions/packs/{namespace}/{name}` | A pack version with its actions, triggers, connections and egress hosts |
| `GET /v1/actions/packs/{namespace}/{name}/versions/{version}/manifest` | The exact `b3pack.json`, as published and signed |
| `GET /v1/actions/registry/packs` | The registry at `beta-api.b3os.org`, with every published version |
| `POST /v1/actions/registry/packs` | Publish a version as a gzip tarball (`actions:write`) |

To see what a pack does before an agent calls it, read its manifest:

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/packs/io.b3os/open-meteo \
      -H "Authorization: Bearer $B3OS_API_KEY"
    curl -s https://beta-api.b3os.org/v1/actions/packs/io.b3os/open-meteo/versions/1.0.0/manifest \
      -H "Authorization: Bearer $B3OS_API_KEY"
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const pack = await b3os.actions.getPack("io.b3os", "open-meteo");
    console.log(pack.version, pack.actions.map((a) => a.key));
    const manifest = await b3os.actions.getPackManifest("io.b3os", "open-meteo", pack.version);
    console.log(manifest.egress);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    pack = b3os.actions.get_pack("io.b3os", "open-meteo")
    print(pack["version"], [a["key"] for a in pack["actions"]])
    manifest = b3os.actions.get_pack_manifest("io.b3os", "open-meteo", pack["version"])
    print(manifest["egress"])
    ```
  **MCP**

`actions_describe` gives an agent one action's schemas, effect class and connections. Whole packs and manifests are read from code.

Versions are immutable once published. In the beta only first-party B3OS packs (`io.b3os`) can be published; public publishing comes later, with signed provenance and a review of each version's new capabilities.