Agotalk Role Package — Creator Guide (v0.4)

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.

0. Authoring principles

Four principles sit above every rule in this guide. If a later section seems to conflict with one of these, the principle wins and the guide has a bug.

Treat the user's machine as brand new

Assume nothing is installed, nothing is downloaded, nothing is configured. A package that works because you happen to have ffmpeg, a font, or a model file on your laptop is a package that breaks for everyone else — and the failure lands on the user, who did nothing wrong.

tells the user exactly what is missing and where it comes from.

from a CDN when the app opens.

Use the environment Agotalk gives you, do not install into the user's system

Agotalk ships a curated, version-locked toolchain and puts it first on PATH. Reach for it before you reach for the user's machine:

AvailableTypical use
python (3.11), nodescripts your skills invoke
ffmpegaudio/video probe, convert, extract frames
magick (ImageMagick)image convert, resize, inspect
rg, fd, jqsearch files, find files, query JSON
sqlite3local structured data
git, curl, bashversion control, HTTP, scripting
GNU coreutilstimeout, shuf, nproc, GNU date/stat/readlink

Third-party libraries do not go through the user's system package manager. Declare them with dependency_mode = "locked" and a lockfile; Agotalk installs them into an environment keyed to your role and removes them with the app. See the contract's Dependencies section. A package that runs pip3 install has moved its problem onto the user's computer.

Use local HTML when text is the wrong shape for the content

A page in the package can carry what a chat message cannot: a grid of options, a rendered preview, a form, a comparison table, a chart. Ship it as a self-contained page and point the user at it. This is what the numbered-gallery pattern below is a special case of.

Make your HTML readable to the agent

Agotalk's browser tools let the agent observe a page and act on it, so the user can click around and the agent can then read what they chose. That only works if your markup gives the agent stable handles.

inspect_browser builds a ref for every interactive element by this rule (input, textarea, select, button, a[href], [role], [contenteditable]):

Element hasIts refStability
id="pick-3"id:pick-3stable — survives re-render and reordering
only name="style"name:style:7index-bound — shifts if the list changes
neitherdiv:12positional — breaks on any layout change

So: give every interactive element a meaningful, stable id. It costs one attribute and it is the difference between the agent clicking your option 3 and the agent guessing at coordinates.

Along with ref, the agent reads back checked, value, selectedText, disabled, label, role, text, and type. Use them:

<!-- The agent can read: ref=id:template-3, checked, and the label text -->
<label for="template-3">03 · Editorial</label>
<input type="radio" id="template-3" name="template" value="03-editorial" />

<!-- Status regions are collected separately: [role=status], [aria-live], <output> -->
<output id="selection-summary" role="status" aria-live="polite">No template selected</output>

or a [role="status"] region — not only in styling. A tile that is "selected" purely because it has a CSS class is invisible to the agent.

convention and expect Agotalk to honor it.

1. Three capabilities — pick exactly one path

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

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

kind = "application" with an [entry] that is a complete, valid static_browser entry. Applications get a Launch button.

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:

entry.kind = "static_browser" passes the contract parser. Unknown entry kinds fail loudly with ROLE_ENTRY_KIND_UNSUPPORTED.

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: typing-word-game, english-conversation-teacher, visual-media-assistant Import-only examples: any Path A directory (role.md without agotalk.role.toml)

The live published examples are typing-word-game (https://roles.agotalk.com/roles/typing-word-game), a Path B application, and english-conversation-teacher (https://roles.agotalk.com/roles/english-conversation-teacher), a Path C conversational role. Their official sources live in the separate agotalk-roles repository (https://github.com/42atom/agotalk-roles), not in the Agotalk app repository. Clone it and put your package under roles/<role-id>/ — every npm run role:* command in these docs runs from that checkout.

2. Package layout

<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.
  agapps/              # optional browser apps, one subdirectory per app
  contents/            # optional scripts, templates, locks, or existing apps
  assets/              # optional presentation media (covers, screenshots)

Rules:

(example: typing-word-game). The directory name IS the role id.

contain the software itself.

declares runtime dependencies.

english-conversation-teacher ships curriculum under resources/). Keep everything inside the package directory.

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 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: what the role does, its boundaries, and how a session should start. The Role Library shows title and summary from this file. title must describe a profession, capability, or system identity — not a person's name. Package files do not name the conversational agent; name a member in the app instead. 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: 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):

KeyStatusNotes
titleconsumedFalls back to the role id (directory name) if absent/empty.
descriptionconsumedFalls back to "" if absent/empty.
idignored as identityRole identity is ALWAYS the directory name (path.basename); a front matter id is tolerated but never overrides it.
avatarretiredHard-rejected at import (ROLE_PACKAGE_LEGACY_AVATAR_FIELD); installed roles only log. Use package-root avatar.png instead.
any other keytolerated, ignoredUnknown 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:

schema_version = 1                # fixed
id = "typing-word-game"           # must equal the top-level directory name
version = "0.1.0"                 # SemVer string
title = "Typing Word Game"
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 = "agapps/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):

schema_version = 1
id = "english-conversation-teacher"
version = "0.1.0"
title = "English Conversation Teacher"
summary = "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:

complete [entry]; conversational roles must not declare one at all.

Agotalk version your package needs; declare it only if you know you rely on a specific one.

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.

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.

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

package root and must stay inside it.

"free". Any other value fails with ROLE_CONTRACT_FIELD_INVALID.

environment.dependency_mode is "none" or "locked": - "none" — the package needs no third-party libraries. - "locked" — the package ships pyproject.toml + uv.lock at its root and Agotalk installs the declared dependencies for you. Do not write those two files by hand: npm run role:env-init -- --package roles/<role-id> --add <pkg> builds them with the same uv and Python your users run. See the contract's Dependencies section. Any entry kind that would imply a dev server or local backend is still rejected by the parser — apps ship as self-contained static builds.

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:

cannot spawn processes, read arbitrary files, run installed tools, or start a backend / dev server.

tmux, git, ffmpeg, mise, etc.). Those tools serve the agent's workspace shell, which is a separate channel from the role-package app.

fetch of package-relative resources, canvas, WebGL, Web Workers, service workers, FileReader, drag-and-drop, clipboard, and so on.

fonts, templates, or data from a CDN or any network endpoint when the app starts — bundle everything into the package.

A local static_browser page may register a narrow, structured surface for the role agent through window.agotalk.expose(...). The role can invoke only methods that its page explicitly registers and documents. This is not a general host bridge and does not grant machine access. Read the Page Agent API contract before using it.

For a page button that should wake the role agent, supported Agotalk App builds also accept window.agotalk.post("thread_notify", { message }) from a local file page in a thread-owned tab. Save the page action first, then send the notification. In the role's role.md or skills/, tell the agent to read the page through its documented expose(...) method and write its result back through another method. A writing app, for example, can save a review request, send { message: "请点评作文" }, then have its role call observe and submit_review. Check the post result: queued confirms input acceptance, not a completed review. Show a fallback instruction when the topic is unavailable or delivery fails. Standalone tabs and external pages cannot notify a thread. The Page Agent API contract gives the full boundary and response shape.

Two different things used to be lumped together here; they have different answers:

self-contained static build (vendored libraries, build output only) before packaging.

dependency_mode = "locked" and a lockfile; Agotalk prepares the environment before your code runs. Do not write a setup step that installs them. See the contract's Dependencies section.

Note the boundary: the static_browser page itself is still a sandboxed web page with no shell access, so declared dependencies serve the agent-side code in your package (scripts under contents/bin/, tools your skills invoke), not the page.

Putting browser apps into agapps/

agapps/ is optional. A role with no browser apps does not need the directory. When present, it groups the browser pages a role can use. Each application has its own directory and self-contained resources. There is no install step at launch.

<role-id>/
  agapps/
    <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:

applications. Other pages need explicit paths in role.md or skills/; this folder does not automatically create app shortcuts or a dock.

paths. Moving a page may change its browser storage scope; plan and verify data migration before changing its path.

its HTML path inside the package, and its purpose in role.md or a skill. These are role instructions, not extra manifest fields. A kind = "role" package may carry AGApps without adding [entry].

dock is established. When it is not, introduce the role, check the actual installed HTML files, then call agapps_setup with agApps entries containing agAppId, agAppName, and agAppEntryRef. The entry ref is relative to the workspace, for example .agotalk/roles/<role-id>/agapps/<app>/index.html; replace the placeholders and verify the file exists. It is not a package-relative manifest path.

``json {"agApps":[{"agAppId":"word-pop","agAppName":"Word Pop","agAppEntryRef":".agotalk/roles/<role-id>/agapps/word-pop/index.html"}]} ``

If the role carries no apps, an empty agApps list establishes an empty dock when the host asks for initialization. If the dock is already established, skip automatic initialization; later additions or changes can use the same tool when requested. agapps_setup merges by ID and preserves other entries.

Do not package a member's dock configuration or user-saved app data. The host derives each shortcut's initial and color from its name and position; the package does not need icon or color fields. A successful agapps_setup only confirms that shortcuts were saved, not that the pages opened.

a bundler, the compiled bundle is what goes under agapps/<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.

the exact version you tested. Record the pinned versions (a comment in index.html or a note under skills/) so the package stays auditable.

[state] remembers_progress = true only if that data actually survives a reload; otherwise leave it false.

Offering many templates: ship a numbered gallery

If your role hands the user a choice among many generated-content templates (poster layouts, video styles, report skins, prompt presets), do not describe them in prose and do not make the user read a directory listing. Ship a single self-contained HTML gallery:

contents/templates/
  gallery.html        # thumbnails in a grid, each tile labelled with its number
  01-minimal/…
  02-editorial/…
  03-neon/…

complete instruction; "the neon one, the third from the left" is not.

as any other packaged page. It can be opened directly, and for kind = "application" packages it can be the entry.target itself.

offers the gallery instead of paraphrasing twenty template names into chat.

The point is bandwidth: a grid of thumbnails settles in one glance what a paragraph of descriptions cannot, and a number is the shortest unambiguous thing a user can type back.

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.

to maintain, regenerate, or extend the app's agapps/ files (for example, "rebuild the bundle with pnpm build", "generate sprites with ImageMagick"). The agent runs these in its workspace shell.

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 agapps/ or contents/ shipped with this role. They are NOT software. Put one Markdown file per distinct job, named after the job.

<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 agapps/, 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

typing-word-game-0.1.0.zip
  typing-word-game/
    role.md
    agotalk.role.toml
    avatar.png
    intro.html
    skills/
    agapps/
    contents/
    assets/

Rules:

entries, no .DS_Store.

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

bundled browser capabilities and package files. Agent-side code may additionally rely on the environment Agotalk prepares from dependency_mode = "locked".

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.

loudly. Do not guess silently.

9. Generation instruction (hand this to an agent)

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 a browser app, put it under agapps/<app>/ unless an existing
   app must keep its path for saved data. In role.md or a skill, name each app's
   stable agAppId, agAppName, HTML path, and purpose; explain first-session
   agapps_setup using the installed workspace-relative HTML path. No apps means
   no agapps/ directory is needed. Put other real content under contents/,
   agent how-to notes under skills/, and 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

agotalk.role.toml (when present).

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.

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.

environment.manager is exactly "agotalk". These three are fixed today; any other value fails the contract.

uv.lock and pyproject.toml sit at the package root. Do not "fix" a locked package back to "none" to pass this checklist — that silently drops the dependency declaration.

(entry.target, intro_html, cover_image, every screenshot).

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

package. Local-only packages are your own risk but must not be published.

(existing apps may retain their original paths), and other software lives in contents/.

name, HTML path, and purpose. All HTML, JS, CSS, and local assets are in the package; member dock configuration and user data are not.

register the real workspace-relative HTML paths with agapps_setup. Verify that each shortcut appears, click it, and complete one real page action. For a page with saved work, close and reopen it to verify recovery. A successful setup response alone is not a page-open test.

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.