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:
- Locale keys look like
enorzh-CN(^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$), at most 8 locales per manifest. - Each locale table must declare exactly
titleandsummary— a complete override pair. Same limits as the top-level fields: title up to 120 characters, summary up to 500. - Top-level
title/summarystay the canonical fallback for any locale without an entry. Write them in the role's primary language. - The publisher projects
i18nonto the catalog role entry, and/api/v1/roles(list and detail) exposes it asi18n: an object keyed by locale, ornullwhen absent. The field is additive; consumers that ignore it keep working. - Role-level
i18nrefreshes on every publish of that package — the manifest is the only source. Publishing a version whose manifest omits[i18n]clears the catalog entry.
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.