Agotalk Role Package File Contract (v0.3)

This is the public file contract for role packages. It is reference material, not a runtime protocol and not a shared source-code dependency.

Producer: a role repository, such as agotalk-roles, creates a package file. Consumer: the Agotalk App reads that package file. Boundary: the package bytes and this public contract. The producer must not call Agotalk app repository scripts, and the app must not require the role repository checkout.

Package Unit

A role package is a single top-level directory compressed as a .zip.

<role-id>/
  role.md
  agotalk.role.toml
  avatar.png
  intro.html
  skills/
  contents/
  assets/

Only role.md is required for a local import-only role. Publishing to roles.agotalk.com requires agotalk.role.toml.

<role-id> must use lowercase words joined by single hyphens:

visual-media-assistant
teacher-tori
words-john

The top-level directory name is the role identity. It must equal agotalk.role.toml field id when the manifest exists.

Required Files

role.md is the role behavior source. It contains front matter plus the role narrative. The only consumed front matter keys are:

---
title: "Role title"
description: "Short role summary"
---

avatar: in role.md front matter is retired. Put the avatar image at package root as avatar.png.

agotalk.role.toml is the machine-readable publishing contract. There is no second manifest.

Manifest

Minimal conversational role:

schema_version = 1
id = "teacher-tori"
version = "0.1.0"
title = "Tori"
summary = "A warm but exacting English teacher."
kind = "role"
license = "free"
min_app_version = "0.1.57"

[preview]
intro_html = "intro.html"

[state]
remembers_progress = true

[environment]
manager = "agotalk"
dependency_mode = "none"

Launchable static browser application:

schema_version = 1
id = "words-john"
version = "0.1.0"
title = "Words John"
summary = "Typing word game bundled as role package content."
kind = "application"
license = "free"
min_app_version = "0.1.57"

[entry]
kind = "static_browser"
target = "contents/word-pop-game/index.html"
address_bar = "hidden"

[preview]
intro_html = "intro.html"
cover_image = "assets/screenshots/word-pop-game-preview.png"
screenshots = ["assets/screenshots/word-pop-game-preview.png"]

[state]
remembers_progress = false

[environment]
manager = "agotalk"
dependency_mode = "none"

Manifest Fields

Required:

schema_version = 1
id = role id, same as directory name
version = SemVer
title = display title
summary = short display summary
kind = "role" | "application"
license = "free"

Publishing requires min_app_version, either in the manifest or as an explicit publisher option.

[environment] must declare:

manager = "agotalk"
dependency_mode = "none"

[state].remembers_progress must be boolean.

kind = "role" forbids [entry]. Its behavior is role.md.

kind = "application" requires [entry]:

kind = "static_browser"
target = "package-relative/path.html"
address_bar = "visible" # or "hidden"

All package paths are relative to the package root, must stay inside the package, and must point at regular files.

Localized Display Copy

[i18n."<locale>"] tables are optional and localize the display title and summary:

[i18n."zh-CN"]
title = "英语老师 Tori"
summary = "一位通过对话授课的英语老师,背后是剑桥与雅思的分级课程库。"

Rules:

Assets

avatar.png at package root is the role avatar. Recommended source size: 512x512 PNG.

Preview source images should be PNG:

cover_image: 1280x720
screenshots: 1 to 3 images, each 1280x720

Avoid JPEG for text-heavy screenshots. SVG is acceptable for logos/icons, not screenshots.

During publish, the role repository publisher projects these package-local files into website presentation assets:

avatar.png                  -> /api/v1/roles/<role-id>/versions/<version>/assets/avatar.webp
preview.cover_image         -> /api/v1/roles/<role-id>/versions/<version>/assets/cover.webp
preview.screenshots[0..2]   -> /api/v1/roles/<role-id>/versions/<version>/assets/screenshots/<n>.webp

The publisher normalizes orientation, converts website presentation assets to WebP, then stores the optimized bytes in R2 under role-assets/<role-id>/<version>/.... roles.agotalk.com reads the catalog presentation projection and serves only catalog-declared assets. Official role images are not copied into the website source tree.

Archive Rules

The published artifact is a .zip whose top-level directory is <role-id>/.

Do not include:

.DS_Store
.git/
node_modules/
.env

Symlinks are rejected. Non-regular files are rejected. File names must be canonical Unicode NFC and must not contain slash tricks.

The same role version is immutable. Republishing the same roleId + version must produce identical bytes and metadata. Different bytes for an existing version must fail.

Publishing Authorization

Publishing credentials belong to the role repository, not the Agotalk app repository.

Use a local ignored .env file in the role repository:

CLOUDFLARE_ACCOUNT_ID=...
CLOUDFLARE_R2_ACCESS_KEY_ID=...
CLOUDFLARE_R2_SECRET_ACCESS_KEY=...
AGOTALK_ROLE_R2_BUCKET=agotalk-roles
AGOTALK_ROLE_PUBLIC_ORIGIN=https://roles.agotalk.com

Commit only .env.example or documentation with field names. Never commit or paste secret values into progress logs.

Failure Semantics

Validation and publishing must fail loudly. Common error codes:

ROLE_PACKAGE_MANIFEST_MISSING
ROLE_PACKAGE_ID_MISMATCH
ROLE_PACKAGE_KIND_INVALID
ROLE_PACKAGE_ENTRY_REQUIRED
ROLE_PACKAGE_ENTRY_FORBIDDEN
ROLE_PACKAGE_PATH_OUTSIDE_ROOT
ROLE_PACKAGE_LEGACY_AVATAR_FIELD
ROLE_PUBLISH_MIN_APP_VERSION_MISSING
ROLE_VERSION_IMMUTABLE_CONFLICT
ROLE_PUBLISH_AUTH_MISSING
ROLE_PUBLISH_CATALOG_CONFLICT

No tool should silently substitute app versions, silently ignore a broken manifest, or publish from a dirty source tree.

Minimal Self-Check

Before publish:

npm run role:package -- --package roles/<role-id> --skip-remote
npm run role:auth-check
npm run role:package -- --package roles/<role-id>

The first command validates and packages locally. The second checks R2 credentials without printing secrets. The third compares the package against the public catalog and reports create or unchanged.