# agentcookie.toml manifest reference

> The v2 adoption manifest schema: `schema_version`, `[secrets]`, `[aliases]`, `[[files]]`, project kinds, discovery roots, and the three integration tiers.

- Repository: mvanhorn/agentcookie
- GitHub: https://github.com/mvanhorn/agentcookie
- Human docs: https://grok-wiki.com/public/docs/mvanhorn-agentcookie-137da38edfae
- Complete Markdown: https://grok-wiki.com/public/docs/mvanhorn-agentcookie-137da38edfae/llms-full.txt

## Source Files

- `docs/spec-agentcookie-secrets-bus-v2-adoption.md`
- `internal/secretsbus/manifest_v2.go`
- `internal/secretsbus/discovery.go`
- `internal/secretsbus/pp_cli_adapter.go`
- `pkg/agentcookieadoption/adoption.go`

---

---
title: "agentcookie.toml manifest reference"
description: "The v2 adoption manifest schema: `schema_version`, `[secrets]`, `[aliases]`, `[[files]]`, project kinds, discovery roots, and the three integration tiers."
---

`agentcookie.toml` is the v2 adoption manifest parsed by `internal/secretsbus/manifest_v2.go` and rendered by `pkg/agentcookieadoption/adoption.go`. Each manifest declares one project's participation in the secrets bus, points at the env-shaped file (or `[[files]]` payloads) that should ride to the sink, and is consumed by `secretsbus.Discover` from a fixed priority list of well-known directories. The wire envelope shape from v1 is unchanged; v2 only adds the declarative onramp.

## Top-level schema

A manifest is strict TOML 1.0. The decoder is `BurntSushi/toml`, the validator is `validateManifestV2`, and the renderer is `pkg/agentcookieadoption.Render`. Unknown top-level fields trigger a stderr warning but do not fail parse, so v2.0 clients tolerate v2.1+ additions.

<ParamField body="schema_version" type="int" required>
  Must be exactly `2`. `schema_version = 1` is rejected with a pointer at the v1 per-CLI sync override format; anything else fails with `schema_version must be 2`.
</ParamField>

<ParamField body="name" type="string" required>
  Project slug. Pattern: `^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$|^[a-z0-9]$`. Lowercase ASCII letters, digits, and hyphen only; no leading or trailing hyphen; 1-64 chars. `..` substrings are explicitly rejected as defense-in-depth.
</ParamField>

<ParamField body="display_name" type="string" required>
  Human label, 1-200 chars, any printable UTF-8. Surfaces in `agentcookie discover` and `agentcookie secret list` headers. Never used as a path segment.
</ParamField>

<ParamField body="description" type="string">
  Optional one-liner, 200 char ceiling.
</ParamField>

<ParamField body="project_kind" type="string">
  Optional. One of `cli`, `skill`, `service`, `other`. Any other value is a hard parse error.
</ParamField>

<ParamField body="homepage" type="string">
  Optional URL. Not validated; surfaced verbatim.
</ParamField>

<ParamField body="signed_by" type="string">
  Reserved for v2.1 signature verification. v2.0 parses the field but emits a `signed_by field is reserved for v2.1; ignored in v2.0` warning.
</ParamField>

## File layout

```toml
# agentcookie.toml: secrets-bus adoption manifest v2
schema_version = 2
name = "last30days"
display_name = "last30days"
description = "Brand intelligence skill"
project_kind = "skill"
homepage = "https://github.com/mvanhorn/last30days-skill"

[secrets.file]
path = "~/.config/last30days/.env"

[sync]
default = true

[sync.keys]
SETUP_COMPLETE = false
FROM_BROWSER = false
INCLUDE_SOURCES = false

[aliases]
TESLA_AUTH_TOKEN = "OAUTH_BEARER"

[[files]]
source = "~/.config/tesla-pp-cli/config.toml"
key = "TESLA_CONFIG_TOML"
target = "tesla-pp-cli/config.toml"
optional = false
env = "TESLA_CONFIG"
```

## `[secrets.*]` source block

Exactly one of `[secrets.file]`, `[secrets.command]`, or `[secrets.keychain]` is allowed. Declaring more than one is a hard error (`at most one [secrets.*] block allowed; N found`). A manifest with no `[secrets.*]` block is rejected unless it declares at least one `[[files]]` item.

| Block | Status in v2.0 | Reject message |
|-------|----------------|----------------|
| `[secrets.file]` | Supported | — |
| `[secrets.command]` | Reserved | `[secrets.command] source kind not yet supported in v2.0 (reserved for v2.1)` |
| `[secrets.keychain]` | Reserved | `[secrets.keychain] source kind not yet supported in v2.0 (reserved for v2.1)` |

### `[secrets.file]`

```toml
[secrets.file]
path = "~/.config/my-tool/auth.env"
```

<ParamField body="path" type="string" required>
  Path to an env-shaped file in v1 `secrets.env` grammar. `~/` expands to the user's home via `ResolveSecretsPath`. Bare `~` resolves to the home root. Must not contain `..`. Absolute paths outside the home are accepted but logged as a soft warning. Missing at push time omits the project (one stderr warning per push).
</ParamField>

The file is read fresh on every source-side push. Nothing is mirrored into `~/.agentcookie/secrets/<name>/`; the project file remains source of truth, so token rotation lands on the sink with the next push.

## `[sync]` and `[sync.keys]`

Filter what leaves the source machine. Same semantics as the v1 manifest `[sync]` table; filtering is applied source-side before the envelope is built, and `[sync.keys]` itself does not travel on the wire.

<ParamField body="sync.default" type="bool" default="true">
  Whether unlisted keys ship by default. The whole `[sync]` table or just `default` may be omitted; both are treated as `true`. The parser does a second map-decode to distinguish "omitted" from "explicitly false."
</ParamField>

<ParamField body="sync.keys" type="map[string]bool">
  Per-key overrides. `true` ships, `false` excludes, regardless of `default`.
</ParamField>

```toml
[sync]
default = false

[sync.keys]
TESLA_AUTH_TOKEN = true
```

`ShouldShipKey(key)` returns the per-key override when present, otherwise the resolved default.

## `[aliases]`

Maps a consumer-declared environment variable name to the synced bus key whose value it should receive. A CLI that reads `TESLA_AUTH_TOKEN` but whose bearer was imported under the bus key `OAUTH_BEARER` is wired automatically without any per-user `agentcookie secret alias` command.

```toml
[aliases]
TESLA_AUTH_TOKEN = "OAUTH_BEARER"
```

- Both the declared var (key) and the bus key (value) must satisfy `validEnvKey`: initial letter or underscore, then letters, digits, or underscores. Invalid names are a hard parse error.
- `agentcookie secret env <name>` applies aliases live on every call, so refreshed tokens track without restart.
- An explicit local `agentcookie secret alias` entry for the same declared var overrides the manifest alias.
- An alias whose bus key is absent emits nothing for that declared var (no-op).

## `[[files]]` carried-file items

Some secrets are not env-shaped: multiline PEMs and TOML configs do not fit one `KEY=VALUE` line. `[[files]]` carries arbitrary files sealed end-to-end and materializes them on the sink as 0600 files under `~/.agentcookie/`. `[[files]]` coexists with the single `[secrets.*]` block; it is not a second secret-source block, and a manifest may declare zero or more items alongside one `[secrets.*]` block (or use `[[files]]` alone).

```toml
[[files]]
source   = "~/.config/tesla-pp-cli/config.toml"
key      = "TESLA_CONFIG_TOML"
target   = "tesla-pp-cli/config.toml"
optional = false
env      = "TESLA_CONFIG"

[[files]]
source   = "~/.tesla/fleet-private.pem"
key      = "TESLA_FLEET_KEY_PEM"
target   = "tesla-pp-cli/fleet-key.pem"
optional = true
```

### Item fields

<ParamField body="source" type="string" required>
  Path to the file to read and base64-encode. `~/` expands. `..` is rejected.
</ParamField>

<ParamField body="key" type="string" required>
  Wire envelope key the base64 payload rides under. Must be a valid env var name. Duplicate `key` across items in the same manifest is a hard error.
</ParamField>

<ParamField body="target" type="string" required>
  Materialization path on the sink, relative to `~/.agentcookie/`. Validated by `validateMaterializeTarget`: must be non-empty, non-absolute, free of `..` segments, and stay inside the root after `filepath.Clean`. The sink re-applies this check before writing.
</ParamField>

<ParamField body="optional" type="bool" default="false">
  When `true`, the item is opt-in: discovery does not carry it unless its key is listed in `~/.agentcookie/file-optin/<name>.keys` (one key per line; blanks and `#` comments ignored).
</ParamField>

<ParamField body="env" type="string">
  Optional env-var name. When set, the sink emits `<env>=<absolute materialized path>` from `secret env`, pointing a path-reading consumer CLI at the carried file without per-machine path hardcoding.
</ParamField>

### Carriage on the wire

The envelope stays `map[string]map[string]string`. Each carried file rides as a single base64 payload plus reserved companion keys; a v2-aware sink decodes and strips them, a sink that does not understand the prefixes simply persists them (forward-degradation).

```text
per-CLI env map:
  <key>            = base64(file bytes)        # the payload
  _FILE_<key>      = <target>                  # materialization path
  _FILEENV_<key>   = <env>                     # optional env-var-name hint
```

Decoded payloads are capped at 256 KB (`maxCarriedFileBytes`); oversized files are refused, not truncated. The materialized file on the sink is plaintext 0600 owned by the sink user; on-sink protection is filesystem permissions.

## Discovery roots and priority

`secretsbus.Discover` walks these locations in order. Within a directory, files are scanned in lexicographic order; the first registration of a given `name` wins. Lower-priority occurrences become soft-skipped registry entries with a stderr log.

| Priority | Path | `SourceKind` |
|----------|------|--------------|
| 1 | `~/.agentcookie/manifests/*.toml` | `explicit-manifest` |
| 2 | `~/.config/agentcookie/manifests/*.toml` | `explicit-manifest` |
| 3 | `/usr/local/share/agentcookie/manifests/*.toml` | `explicit-manifest` |
| 4 | `~/printing-press/library/*/.printing-press.json` | `pp-cli-derived` (synthesized in memory) |
| 5 | `--add-path <dir>` (via `agentcookie discover`) | `explicit-manifest` |
| 6 | `~/.agentcookie/secrets/<name>/` (no manifest) | `legacy-v1` |

Discovery is forgiving by design: a malformed manifest is soft-skipped with a per-file reason; `agentcookie secret import-from` remains the strict imperative path that hard-fails on bad input. In `agentcookie source --watch`, an fsnotify watcher rescans on create/write/rename with a 250 ms debounce.

## Collision rules

| Case | Outcome | Source |
|------|---------|--------|
| Two explicit manifests with the same `name` | Both rejected, hard error names both source paths | `tryRegisterExplicit` |
| Explicit manifest collides with PP-derived | Explicit wins; derived renamed to `<name>-pp` | `tryRegisterPP` |
| Two PP-derived collide | First-by-sort wins; later collisions get a 6-char sha256 suffix | `tryRegisterPP` |
| v2 wins, v1 bus dir for same name exists | v1 keys override per-key at push (preserves explicit user intent) | spec §10.3 |

## Project kinds

`project_kind` is an enum tag for `agentcookie discover` output and downstream tooling. Authoring guidance follows the values the validator accepts:

| Value | When to use |
|-------|-------------|
| `cli` | A Printing-Press CLI or any third-party command-line tool. |
| `skill` | A Claude or agent skill that reads its env from a dotenv. |
| `service` | A daemon, LaunchAgent, or background worker. |
| `other` | Anything else; still appears in the registry. |

Omit the field if none fit; an empty value is allowed.

## The three integration tiers

The discovery registry tags every entry with one of three `SourceKind` values, surfaced as the `TIER` column in `agentcookie discover`.

```text
+-----------------------+   +----------------------+   +---------------------+
| explicit-manifest     |   | pp-cli-derived       |   | legacy-v1           |
| author drops          |   | synthesized in mem   |   | bus dir only        |
| agentcookie.toml      |   | from .printing-press |   | (pre-v2 import-from)|
+----------+------------+   +----------+-----------+   +----------+----------+
           |                           |                          |
           v                           v                          v
                       secretsbus.Registry.Projects
```

### Tier A — `explicit-manifest`

An author-shipped `agentcookie.toml` lands in any of the priority-1/2/3 directories (or via `discover --add-path`). Tier A wins collisions, supports all v2 fields (`[aliases]`, `[[files]]`, `[sync]` filters), and is the recommended path for both first-party and third-party adopters.

### Tier B — `pp-cli-derived`

`DeriveManifestFromPP` synthesizes a `ManifestV2` from `~/printing-press/library/<cli>/.printing-press.json`. Field mapping:

| v2 field | Source |
|----------|--------|
| `schema_version` | always `2` |
| `name` | PP `cli_name` (must match the v2 slug rules) |
| `display_name` | PP `display_name` (falls back to `cli_name`) |
| `description` | PP `description` |
| `project_kind` | always `cli` |
| `[secrets.file].path` | `~/.config/<cli_name>/config.toml` |
| `sync.default` | `false` |
| `sync.keys[name]` | `auth_env_var_specs[i].sensitive` (else `true` when only `auth_env_vars` is present) |

The synthesized manifest is never written to disk. A PP CLI that ships its own `agentcookie.toml` in `~/printing-press/library/<cli>/` (or any priority-1/2/3 dir) upgrades itself to Tier A; the derived entry is then renamed `<name>-pp` and both rows appear in `discover` for comparison.

### Tier C — `legacy-v1`

A directory at `~/.agentcookie/secrets/<name>/` with no manifest behind it. These are entries created by the v1 imperative `agentcookie secret import-from`. The registry pointer has a nil `Manifest`; the source push pipeline reads keys directly from the bus directory rather than read-in-place. v2 entries for the same name still take precedence at the row level, but the v1 directory wins per-key during merge to preserve explicit user intent.

## Validation pipeline

`ParseManifestV2` runs two TOML decode passes: a typed decode against `ManifestV2`, then a generic `map[string]interface{}` pass used solely to distinguish an omitted `[sync].default` from an explicit `false`. After parse, `validateManifestV2` enforces:

<Steps>
  <Step title="schema_version == 2">
    `schema_version=1` returns the v1-spec pointer; any other integer fails with the upgrade hint.
  </Step>
  <Step title="name and display_name">
    Slug rules, `..` rejection, 200-char ceiling on `display_name` and `description`.
  </Step>
  <Step title="project_kind enum">
    Empty or one of `cli|skill|service|other`.
  </Step>
  <Step title="Exactly one [secrets.*] (or [[files]] only)">
    Multi-block rejected. Reserved blocks rejected with a v2.1 hint. Empty manifests with no `[[files]]` rejected.
  </Step>
  <Step title="[secrets.file].path">
    Required, no `..` segments.
  </Step>
  <Step title="[aliases]">
    Both sides of each pair must satisfy `validEnvKey`.
  </Step>
  <Step title="[[files]]">
    Per item: `source` required (no `..`), `key` env-var-shaped and unique within the manifest, `target` validated by `validateMaterializeTarget`, `env` env-var-shaped when set.
  </Step>
</Steps>

Unknown nested keys under `secrets.command` and `secrets.keychain` are filtered from the unknown-field warning list because their parent blocks are reserved.

## Authoring with `pkg/agentcookieadoption`

The `pkg/agentcookieadoption` package mirrors the manifest schema with a stable public-API surface so installers can author `agentcookie.toml` from Go. The package is intentionally duplicated rather than importing from `internal/` so external code has no internal-package dependency.

```go
import "github.com/mvanhorn/agentcookie/pkg/agentcookieadoption"

m := &agentcookieadoption.Manifest{
    SchemaVersion: 2,
    Name:          "my-tool",
    DisplayName:   "My Tool",
    ProjectKind:   "cli",
    Secrets: agentcookieadoption.Secrets{
        File: &agentcookieadoption.SecretsFile{Path: "~/.config/my-tool/.env"},
    },
    Sync: agentcookieadoption.Sync{Default: true},
}
if err := agentcookieadoption.Validate(m); err != nil { /* ... */ }
_ = agentcookieadoption.WriteTo(m, "/path/to/agentcookie.toml")
```

`WriteTo` calls `Validate`, renders canonical TOML via `Render` (deterministic key ordering, sorted `[sync.keys]`), and writes `0644`. The validator is a strict subset of `validateManifestV2`; richer constructs (`[aliases]`, `[[files]]`, the second-pass omitted-default detection) belong to the internal parser used by discovery.

## Verification

```bash
agentcookie discover
agentcookie discover --verbose          # list skipped manifests and reasons
agentcookie discover --json             # machine-readable: projects[] + skipped[]
agentcookie source --once               # push to the configured sink
ssh other-mac "agentcookie secret list" # confirm the slug appears with the expected keys
```

`discover` prints `NAME / TIER / READ-IN-PLACE / KEYS / COVERAGE`. A `MISMATCH` row in `COVERAGE` flags a CLI whose synced bus keys do not match the env var it actually reads — typically a missing `[aliases]` entry.

## Reserved and forward-compatibility behavior

| Surface | v2.0 behavior |
|---------|---------------|
| Unknown top-level field | Warned, ignored. |
| `signed_by` | Parsed, warned, ignored. |
| `[secrets.command]` / `[secrets.keychain]` | Schema-reserved; rejected at validate with the v2.1 hint. |
| `schema_version = 3+` | Hard-rejected per spec §12 (`schema version not supported; upgrade agentcookie`). |
| New `[secrets.*]` kinds in v2.1+ | Hard-rejected by v2.0 parsers (`source kind X not supported`). |

## Related pages

<CardGroup>
  <Card title="Secrets bus" href="/secrets-bus">
    How the manifests' values ride the encrypted push to `~/.agentcookie/secrets/<cli>/secrets.env`.
  </Card>
  <Card title="Secrets bus on-disk layout" href="/secrets-bus-layout">
    The v1 sink layout, file modes, and where `[[files]]` items materialize under `~/.agentcookie/`.
  </Card>
  <Card title="Adopt a CLI with agentcookie.toml" href="/adopt-a-cli-manifest">
    Author-side walkthrough: write the manifest, run `agentcookie discover`, migrate off `import-from`.
  </Card>
  <Card title="pkg/agentcookiesecret reader library" href="/go-reader-library">
    In-process Go API consumers use to read the bus values the manifest delivered.
  </Card>
  <Card title="Wire protocol v1" href="/wire-protocol">
    The `SyncEnvelope` shape that carries `[[files]]` payloads and companion keys.
  </Card>
  <Card title="CLI reference" href="/cli-reference">
    Flags and subcommands for `agentcookie discover`, `agentcookie secret`, and friends.
  </Card>
</CardGroup>
