The agent-roles catalog source is no longer hardcoded in app code and no longer supports a local filesystem directory. The provider fetches only from an http(s):// base URL read at runtime from AI_AGENT_ROLES_CATALOG_URL; an empty or non-http value yields a 502 (catalog unavailable). The image ships a per-branch default for that URL (set in CI), still overridable at runtime via the env var. - provider: drop readLocal + node:fs/node:path; readRelative requires http(s) and 502s otherwise; remote fetch/streaming-cap/SSRF guards unchanged. - environment.service: keep AI_AGENT_ROLES_CATALOG_URL (default ''); comment reflects the per-branch build-time default that is runtime-overridable. - Dockerfile: add ARG+ENV AI_AGENT_ROLES_CATALOG_URL in the installer stage as the image default. - CI: develop.yml builds with the develop raw URL; release.yml defines the main raw URL once in workflow env and references it from both build steps. - tests: replace local-fixture tests with remote-mock happy/malformed bundle tests and a non-http => 502 case; path-traversal block uses an https source. - docs: update .env.example, CHANGELOG (#222), agent-roles-catalog/README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.0 KiB
Agent roles catalog
This directory is data, not application code. It holds the content of an "agent roles catalog": reusable agent role definitions (system prompts plus a little metadata), grouped into bundles and translated into one or more languages. A separate server reads these files and serves them; nothing here is executable application logic except the validation script.
File layout
agent-roles-catalog/
index.json # the catalog manifest: bundles, languages, role versions
bundles/
<bundle-id>/
<lang>.json # one file per declared language (e.g. ru.json, en.json)
scripts/
check.mjs # validates the catalog (no dependencies)
package.json # defines the `check` script
README.md
Currently shipped bundles:
editorial— the editorial suite (structural-editor, line-editor, fact-checker, proofreader, narrator), languagesru,en.research— a singleresearcherrole, languagesru,en.
How it's served
The server does not bundle this data; it reads it at request time from a single
configured location, the AI_AGENT_ROLES_CATALOG_URL env var
(EnvironmentService.getAiAgentRolesCatalogSource()), an http(s):// base URL
to the catalog's raw files. The server fetches <base>/index.json for the
manifest and <base>/bundles/<bundle-id>/<lang>.json for each opened bundle
file (REMOTE only).
That base URL is provided as a per-branch default in the Docker image (set in
CI: a develop build points at the develop raw URL, a release build at the
main raw URL) and can be overridden at runtime via the
AI_AGENT_ROLES_CATALOG_URL env var. Local-filesystem sources are no longer
supported; if the value is unset the catalog is unavailable.
The fetched JSON is re-validated server-side (the catalog is treated as
untrusted input). See .env.example for the variable and the CHANGELOG for the
rollout.
index.json schema
{
"schemaVersion": 1,
"bundles": [
{
"id": "editorial", // unique bundle id; matches bundles/<id>/
"name": { "ru": "...", "en": "..." }, // localized display name
"description": { "ru": "...", "en": "..." },
"languages": ["ru", "en"], // which <lang>.json files must exist
"roles": [
{ "slug": "structural-editor", "version": 1 }
// ...
]
}
]
}
version lives here, in index.json, per role. Bump it whenever a role's
content (instructions, name, description, etc.) changes, so consumers can detect
updates.
Bundle (<lang>.json) schema
{
"schemaVersion": 1,
"language": "ru",
"roles": [
{
"slug": "structural-editor", // REQUIRED, unique across the whole catalog
"emoji": "🧱",
"name": "...", // REQUIRED, localized
"description": "...", // localized
"instructions": "...", // REQUIRED, the system prompt, localized
"autoStart": true, // whether the role starts working immediately
"launchMessage": "..." // first message sent on launch (or null)
}
]
}
Notes:
modelConfigis intentionally absent; the server treats an absentmodelConfigasnull.- A role's
slug,emoji, andautoStartare identical across all language files of the same bundle. Onlyname,description,instructions, andlaunchMessageare translated.
Slug uniqueness
Every slug must be UNIQUE ACROSS THE WHOLE CATALOG, not just within a
bundle. A slug appears once per language file of its bundle (same slug in
ru.json and en.json), but no two different bundles may share a slug.
scripts/check.mjs enforces this.
How to add things
Add a role to an existing bundle
- Add an entry to that bundle's
roles[]inindex.jsonwith a new uniqueslugandversion: 1. - Add a role object with the same
slugto every<lang>.jsonof the bundle, translatingname,description,instructions, andlaunchMessage. - Run the check (see below).
Add a bundle
- Add a bundle object to
index.json(id,name,description,languages,roles). - Create
bundles/<id>/<lang>.jsonfor each declared language, with one role object perroles[]entry. - Run the check.
Add a language to a bundle
- Add the language code to that bundle's
languages[]inindex.json. - Create
bundles/<id>/<lang>.jsoncontaining every role of the bundle, translated. - Run the check.
Change a role's content
Edit the role in the relevant <lang>.json file(s) and bump that role's
version in index.json.
Validating
From this directory:
node scripts/check.mjs # or: npm run check
It fails (exit code 1) if any slug is duplicated across the catalog, if a
bundle's index roles[] don't match the slugs present in each language file, if
a declared language file is missing, or if any role is missing a required field
(slug, name, instructions). It prints OK on success.