# Agotalk Role Package — Creator Guide (v0.3)

This is the public creator guide for creating an Agotalk role package.
The stable package file contract lives at `/docs/role-package.md`.
Audience: AI agents and developers. Read this whole file before generating anything.

A role package is a plain directory of files, shipped as a plain zip.
The package truth is `role.md` plus the file tree. `agotalk.role.toml` is the
machine contract that adds identity, preview metadata, and (for applications)
a launchable entry. There is no other manifest. Do not invent one.

## 1. Three capabilities — pick exactly one path

Importable, launchable, and publishable are three different contracts.
Decide what you are building first:

- **Path A — minimal local candidate (importable only).**
  A single directory with a valid `role.md` and NO `agotalk.role.toml`.
  Agotalk imports it into the local Role Library as a candidate.
  Not launchable. Not publishable to the website (publishing requires the
  machine contract).
- **Path B — launchable application (importable + launchable + publishable).**
  `kind = "application"` with an `[entry]` that is a complete, valid
  `static_browser` entry. Applications get a Launch button.
- **Path C — conversational role (importable + publishable, no Launch).**
  `kind = "role"` with NO `[entry]` section at all. `role.md` is the sole
  behavior source: the role talks, it does not launch. The publisher accepts
  it exactly like an application. In the Role Library it appears as a normal
  selectable candidate without a Launch button.

Hard rule behind the paths, dispatched by `kind`:

- `kind = "application"` — `[entry]` is mandatory and only
  `entry.kind = "static_browser"` passes the contract parser. Unknown entry
  kinds fail loudly with `ROLE_ENTRY_KIND_UNSUPPORTED`.
- `kind = "role"` — `[entry]` is forbidden. Declaring any `[entry]` fails
  with `ROLE_CONTRACT_FIELD_INVALID`; a missing `role.md` fails with
  `ROLE_BEHAVIOR_FILE_MISSING`. There is no `system_prompt` entry kind and
  there never was a working one — the behavior contract is `role.md` itself.

Publishable examples: words-john, teacher-tori, visual-media-assistant
Import-only examples: any Path A directory (role.md without agotalk.role.toml)

The live published examples are `words-john`
(https://roles.agotalk.com/roles/words-john), a Path B application, and
`teacher-tori` (https://roles.agotalk.com/roles/teacher-tori), a Path C
conversational role. Their official sources live in the separate
`agotalk-roles` repository, not in the Agotalk app repository.

## 2. Package layout

```text
<role-id>/
  role.md              # REQUIRED. Role intro, boundaries, how to start. Import entry.
  agotalk.role.toml    # Path B/C: machine contract (identity, preview; entry for B only)
  avatar.png           # optional role avatar at the package root (recommended 512x512 PNG)
  intro.html           # optional rich presentation page (static HTML only)
  skills/              # optional agent instructions (*.md). No software here.
  contents/            # optional real software content: source, pages, locks
  assets/              # optional presentation media (covers, screenshots)
```

Rules:

- `<role-id>` is lowercase `a-z0-9` words joined by single hyphens
  (example: `words-john`). The directory name IS the role id.
- `skills/*.md` only explain how to use or modify `contents/`; they never
  contain the software itself.
- `intro.html` is presentation only. It never replaces `role.md` and never
  declares runtime dependencies.
- Extra content directories are allowed when they serve the role (example:
  `teacher-tori` ships curriculum under `resources/`). Keep everything inside
  the package directory.
- The role avatar is a single `avatar.png` at the **package root** (not under
  `assets/`), recommended 512×512 PNG. If omitted the role shows the
  default Agotalk avatar. Do NOT declare an `avatar:` field in `role.md` front
  matter — that field is retired (tk2332) and the import gate rejects any
  package still carrying it with `ROLE_PACKAGE_LEGACY_AVATAR_FIELD`.

## 3. role.md (required)

Front matter with `title` and `description`, then the role narrative: who the
role is, what it does, its boundaries, and how a session should start. The
Role Library shows title and summary from this file. For Path C roles this
file IS the entire behavior contract — write it with care.

The only front matter keys consumed are `title` and `description`. The
`avatar:` key is **retired** (tk2332): the avatar is the package-root
`avatar.png`, never a front matter value. A package whose `role.md` front
matter still contains `avatar:` is rejected at import with
`ROLE_PACKAGE_LEGACY_AVATAR_FIELD` — delete the line and place `avatar.png`
at the package root instead.

Front matter field status (the parser is the contract):

| Key | Status | Notes |
|---|---|---|
| `title` | consumed | Falls back to the role id (directory name) if absent/empty. |
| `description` | consumed | Falls back to `""` if absent/empty. |
| `id` | ignored as identity | Role identity is ALWAYS the directory name (`path.basename`); a front matter `id` is tolerated but never overrides it. |
| `avatar` | **retired (tk2332)** | Hard-rejected at import (`ROLE_PACKAGE_LEGACY_AVATAR_FIELD`); installed roles only log. Use package-root `avatar.png` instead. |
| any other key | tolerated, ignored | Unknown keys do not error; they are simply not read. Do not rely on them carrying meaning. |

## 4. agotalk.role.toml (machine contract)

Path B — launchable application:

```toml
schema_version = 1                # fixed
id = "words-john"                 # must equal the top-level directory name
version = "0.1.0"                 # SemVer string
title = "Words John"
summary = "Typing word game bundled as role package content."
kind = "application"              # dispatches the entry contract
license = "free"                  # v0.2: "free"
min_app_version = "0.1.57"        # optional: minimum Agotalk App version

[entry]                           # mandatory for kind = "application"
kind = "static_browser"           # the only kind that parses and publishes today
target = "contents/word-pop-game/index.html"  # relative, must stay inside the package
address_bar = "hidden"            # "visible" | "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        # declare true only if the app really saves progress

[environment]
manager = "agotalk"               # fixed
dependency_mode = "none"          # static content needs nothing else
```

Path C — conversational role (note: NO `[entry]` section):

```toml
schema_version = 1
id = "teacher-tori"
version = "0.1.0"
title = "Tori"
summary = "Tori — a warm but exacting English teacher."
kind = "role"                     # conversational: role.md is the behavior source
license = "free"
min_app_version = "0.1.57"        # optional: minimum Agotalk App version

[preview]
intro_html = "intro.html"

[state]
remembers_progress = true

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

Notes:

- The entry contract is dispatched by `kind`: applications must declare a
  complete `[entry]`; conversational roles must not declare one at all.
- `min_app_version` ("x.y.z") is optional publish metadata. When present it
  feeds the catalog; the publisher can also receive an explicit
  `--min-app-version`. One of the two must exist to publish — the publisher
  never silently substitutes its own version.
- `[preview]` is consumed by the Agotalk App import preview, and the publisher
  also projects `avatar.png`, `preview.cover_image`, and `preview.screenshots`
  into the website presentation assets (converted to WebP and served from the
  catalog) — package images DO drive the website. Recommended specs
  (not enforced by the parser):
  - `avatar.png` at the package root — 512×512, 1:1, PNG.
  - `cover_image` — 1280×720, 16:9, PNG.
  - `screenshots` — 1 to 3 images, 1280×720, 16:9, PNG.
  - 16:9 at 1280×720 matches og:image / Twitter card / Steam capsule
    conventions; authors can drop a 16:9 capture without re-cropping.
  - Keep each image under ~1 MB.
  - Avoid JPEG for screenshots (lossy edges blur text) and avoid SVG for
    screenshots (use SVG only for logos/icons). GIF is accepted but not
    recommended for detailed captures.
  - Keep source images PNG; the publisher converts website presentation
    copies to WebP automatically. Authors never pre-convert to WebP.
- `[i18n."<locale>"]` (optional) localizes the display `title`/`summary` per
  locale — e.g. `[i18n."zh-CN"]` with exactly `title` and `summary` keys
  (same length limits as the top-level fields). Locale keys look like `en` or
  `zh-CN`; at most 8 locales. Top-level `title`/`summary` are the canonical
  fallback — write them in the role's primary language. The catalog and the
  website language pages pick the locale entry when present.
- `static_browser` is the ONLY entry kind that exists today. Any other value
  (including anything you may have seen in older drafts) fails loudly with
  `ROLE_ENTRY_KIND_UNSUPPORTED`. Do not experiment with entry kinds that are
  not in this guide — the parser is the contract.
- All referenced paths (`entry.target`, `preview.*`) are relative to the
  package root and must stay inside it.
- `schema_version` must be exactly `1` and `license` is currently locked to
  `"free"`. Any other value fails with `ROLE_CONTRACT_FIELD_INVALID`.
- `environment.manager` must be exactly `"agotalk"` and
  `environment.dependency_mode` must be exactly `"none"`. Any entry kind that
  would imply a dev server, dependency install, or local backend is rejected
  by the parser — today only self-contained static apps ship as role
  packages.

## 5. Authoring app content and role-specific skills

This section is for Path B packages (and Path C roles that ship reference
content). It describes what the app running inside Agotalk can and cannot
do, and how to write the role-specific Markdown under `skills/`.

### What the app environment can and cannot do

A `static_browser` app opens inside Agotalk's bundled browser view. That
view is a sandboxed web page: it renders HTML/CSS/JS from files inside the
package, with **no access to the local machine**. Specifically:

- The page has **no Node/Electron bridge** and **no shell access**. It
  cannot spawn processes, read arbitrary files, run installed tools, or
  start a backend / dev server.
- The page **cannot reach Agotalk's bundled runtime tools** (node, python,
  tmux, git, ffmpeg, mise, etc.). Those tools serve the agent's workspace
  shell, which is a separate channel from the role-package app.
- The page may use ordinary browser APIs: `localStorage`, `IndexedDB`,
  `fetch` of package-relative resources, `canvas`, `WebGL`, Web Workers,
  service workers, `FileReader`, drag-and-drop, clipboard, and so on.
- The page runs fully offline at launch. Do not fetch scripts, styles,
  fonts, templates, or data from a CDN or any network endpoint when the
  app starts — bundle everything into the package.

If your application needs a local backend, a dev server, or a dependency
install step to run, **it cannot ship as a role-package app today**.
Convert it to a self-contained static build (vendored libraries, build
output only) before packaging.

### Putting content into contents/

`contents/` holds the runnable artifact of your app — the files Agotalk's
browser view will load. There is no install step at launch.

```text
<role-id>/
  contents/
    <app>/
      index.html        # entry.target points here
      app.js            # built or hand-written; vendored, not installed
      style.css
      vendor/           # third-party JS/CSS copied in at a pinned version
      assets/           # app-local media (icons, fonts, templates)
```

Rules:

- `entry.target` points at the `index.html` inside `contents/<app>/`.
- Ship the **built output**, not the toolchain. If you build from TS/JSX or
  a bundler, the compiled bundle is what goes under `contents/<app>/`;
  source and build instructions stay in a separate folder or repo. The
  package must never require `npm install` / `pnpm install` / `pip install`
  at launch.
- Third-party libraries must be vendored into `contents/<app>/vendor/` at
  the exact version you tested. Record the pinned versions (a comment in
  `index.html` or a note under `skills/`) so the package stays auditable.
- If the app persists user data via the browser APIs above, declare
  `[state] remembers_progress = true` only if that data actually survives
  a reload; otherwise leave it `false`.

### What "Agotalk's bundled runtime" means here

Agotalk ships a curated, version-locked toolchain (node, python, pnpm,
mise, tmux, git, ImageMagick, ffmpeg, rg, fd, jq, sqlite, and more) that
the **agent** can call from its workspace shell to do real work on the
user's machine. That is a powerful capability — but it belongs to the
agent channel, not to the role-package app.

- A Path B/C role's `skills/*.md` can instruct the agent to use those tools
  to maintain, regenerate, or extend the app's `contents/` (for example,
  "rebuild the bundle with `pnpm build`", "generate sprites with
  ImageMagick"). The agent runs these in its workspace shell.
- The app's own page (what the Launch button opens) cannot call them. Keep
  these two channels separate in your design: the agent maintains the
  artifact, the browser view runs it.

### Writing role-specific skills

`skills/*.md` are Markdown instructions that tell the agent how to use or
modify the `contents/` shipped with this role. They are NOT software. Put
one Markdown file per distinct job, named after the job.

```text
<role-id>/
  skills/
    <job-name>.md   # one file per job; e.g. rebuild-bundle.md, export-data.md
```

Each file should state: the job, the exact files or UI it touches, the
step sequence (including any bundled tools the agent should call from its
workspace shell), and how to verify it worked. Keep them short and
concrete — they are read by an agent at session time.

Hard rule (repeated from §2): `skills/` holds only Markdown. Any
executable, script, stylesheet, image, or binary belongs in `contents/` or
`assets/`, never in `skills/`.

Hard rule (repeated from §2): `skills/` holds only Markdown. Any
executable, script, stylesheet, image, or binary belongs in `contents/` or
`assets/`, never in `skills/`.

## 6. What publishing consumes

The official publisher runs the full contract parse dispatched by `kind`
(Path B and Path C both pass; Path A has no contract file and cannot publish),
then the catalog and website use: `id`, `version`, `title`, `summary`,
`min_app_version` (from the manifest or explicit publisher input), plus the
computed zip `sha256` and sizes. Website preview media is curated on the site
side. Publication itself happens through the official publisher pipeline;
this guide only defines the package you hand over.

## 7. Transport zip

```text
words-john-0.1.0.zip
  words-john/
    role.md
    agotalk.role.toml
    avatar.png
    intro.html
    skills/
    contents/
    assets/
```

Rules:

- The zip contains exactly one single top-level directory named `<role-id>`.
- Zip name convention: `<role-id>-<version>.zip`.
- Plain zip, regular files only: no symlinks, no absolute paths, no `..`
  entries, no `.DS_Store`.
- The zip is a transport container, not a second manifest.

Import behavior you must expect: Agotalk unpacks the zip into the local Role
Library (`roles-lib`) as a candidate, validates `role.md`, and shows the
candidate. Importing an id that already exists is rejected with
`ROLE_IMPORT_DUPLICATE` — versions never silently overwrite. A candidate never
becomes the active workspace role without explicit user action.

## 8. Hard boundaries

- The user's machine is never a dependency source. Only the Agotalk bundled
  runtime, this contract, and files locked inside `contents/` may be assumed.
- **Secrets, tokens and API keys must never be put into a package you intend
  to publish.** A published package is public — anything inside it is
  exposed. If you are building a package strictly for your own local use and
  you choose to embed your own keys, that is your call and your risk; but
  such a package must not be published through the official pipeline. The
  official publisher does not scan for secrets, so the discipline is yours.
- Never rely on network calls at launch time for a `static_browser` app.
- Never auto-activate the role or instruct Agotalk to bypass user confirmation.
- When a fact you need is missing (id, version, entry target), stop and say so
  loudly. Do not guess silently.

## 9. Generation instruction (hand this to an agent)

```text
Create an Agotalk role package.

1. Read https://roles.agotalk.com/role-package-creator.md fully first.
2. Pick the capability path: launchable web app for the website -> Path B
   (kind = "application" with a static_browser [entry]); conversational role
   for the website -> Path C (kind = "role", role.md as behavior, NO [entry]);
   local-only experiment -> Path A (role.md, NO agotalk.role.toml).
3. Choose <role-id> (lowercase a-z0-9 and hyphens) and create the directory <role-id>/.
4. Write role.md: front matter (title, description), then the role narrative,
   boundaries, and session start guidance. For Path C this file is the whole
   behavior contract.
5. If the role ships an app or content: put real files under contents/,
   agent how-to notes under skills/, presentation media under assets/.
6. Path B/C: add agotalk.role.toml exactly as specified above. Path B needs a
   complete [entry] with an existing HTML target inside the package; Path C
   must not contain any [entry] section.
7. Zip as <role-id>-<version>.zip with the single top-level directory <role-id>/.
8. Run the self-check below. Fix every failure before delivering. If a required
   fact is unknown, report it instead of inventing it.
```

## 10. Self-check before delivery

- [ ] Zip holds exactly one top-level directory; its name equals `id` in
      `agotalk.role.toml` (when present).
- [ ] `<role-id>/role.md` exists and carries title + description front matter.
- [ ] `role.md` **front matter** does NOT contain an `avatar:` key (retired;
      tk2332). The avatar, if any, is a package-root `avatar.png` (recommended
      512x512 PNG). An `avatar:` line inside the role narrative body is fine;
      only the front matter block is gated.
- [ ] Path A packages have NO `agotalk.role.toml`. Path B packages have a
      complete `[entry]` with `kind = "static_browser"` and an existing
      `.html` target inside the package. Path C packages declare
      `kind = "role"` and contain NO `[entry]` section at all.
- [ ] `schema_version` is exactly `1`, `license` is exactly `"free"`,
      `environment.manager` is exactly `"agotalk"`, and
      `environment.dependency_mode` is exactly `"none"`. These four fields are
      fixed today; any other value fails the contract.
- [ ] Every path referenced by `agotalk.role.toml` exists inside the package
      (`entry.target`, `intro_html`, `cover_image`, every screenshot).
- [ ] If you provide preview media, follow the recommended specs: avatar
      512×512 1:1 PNG, cover + screenshots 1280×720 16:9 PNG, each image under
      ~1 MB. If the website needs WebP, convert curated website media in the
      website release chain; the role package source contract stays PNG. (Not
      enforced by the parser.)
- [ ] `min_app_version` (when present) is a plain `x.y.z` string.
- [ ] No symlinks, no absolute paths, no `..`, no `.DS_Store`.
- [ ] If you plan to publish: no secrets, tokens or API keys anywhere in the
      package. Local-only packages are your own risk but must not be published.
- [ ] `skills/` contains only Markdown instructions; software lives in `contents/`.
- [ ] Stable failure codes you may see and must not paper over:
      `ROLE_IMPORT_ROLE_MD_MISSING`, `ROLE_IMPORT_DUPLICATE`,
      `ROLE_PACKAGE_LEGACY_AVATAR_FIELD`, `ROLE_CONTRACT_MISSING`,
      `ROLE_CONTRACT_INVALID`, `ROLE_CONTRACT_FIELD_MISSING`,
      `ROLE_CONTRACT_FIELD_INVALID`, `ROLE_BEHAVIOR_FILE_MISSING`,
      `ROLE_ID_DIR_MISMATCH`, `ROLE_ENTRY_KIND_UNSUPPORTED`,
      `ROLE_ENTRY_ADDRESS_BAR_INVALID`, `ROLE_ENTRY_TARGET_NOT_FOUND`,
      `ROLE_ENTRY_TARGET_NOT_FILE`, `ROLE_ENTRY_TARGET_OUTSIDE_PACKAGE`,
      `ROLE_ENVIRONMENT_INVALID`, `ROLE_PREVIEW_INVALID`,
      `ROLE_PREVIEW_NOT_FOUND`, `ROLE_PREVIEW_NOT_FILE`,
      `ROLE_PREVIEW_OUTSIDE_PACKAGE`, `ROLE_STATE_REMEMBERS_PROGRESS_INVALID`,
      `ROLE_IMPORT_ZIP_TOP_LEVEL_INVALID`, `ROLE_IMPORT_ZIP_ENTRY_INVALID`,
      `ROLE_KIND_NOT_LAUNCHABLE`.
