Agent skills¶
Give a coding agent — Claude Code, Codex, Antigravity — the ability to call an ATRIUM tool, without teaching it the tool's code.
Scope
All five tools have an agent-skill branch. The two described below are
page-classification's and the translator's; the other three follow the same pattern.
What a skill is here¶
Each tool repository has a separate agent-skill branch — a reduced copy of the repository
that an agent can clone into its skills directory. It carries three things on top of the tool's
HTTP service:
| Part | What it does |
|---|---|
SKILL.md |
the instructions the agent reads: when to use the skill, how to start the server, how to call it, what to do on each error |
scripts/atrium_<verb>.py |
a zero-dependency client — Python standard library only — that uploads files to the service and prints the result |
scripts/server.sh |
starts the service (Docker Compose, or a local uvicorn) and waits until it answers |
The agent never imports the tool's code. It starts the service, then talks to it over HTTP — which is what lets the same client point at a hosted instance by changing one environment variable and nothing else:
flowchart LR
U["the user's request"] --> AG["coding agent<br/>reads SKILL.md"]
AG -- "runs" --> CL["scripts/atrium_<verb>.py<br/>stdlib-only client"]
AG -. "if no server answers" .-> SV["scripts/server.sh<br/>starts the service"]
CL -- "HTTP: /info, then the POST endpoint" --> API["the tool's HTTP service"]
API --> M["the tool's models<br/>or remote back-end"]
API -- "result, and the record if asked" --> CL
CL -- "table · CSV · JSON · file" --> AG
SKILL.md follows the Agent Skills convention the agents share: a YAML frontmatter
block with a name and a description — the text an agent uses to decide whether the
skill applies — followed by Markdown instructions for how to use it. Only the frontmatter
is read up front; the body and the scripts are loaded when the skill is chosen, so an
installed skill costs almost nothing until it is used.
Compared with its default branch, each skill branch leaves out what an agent does not
need — the test suite, most workflows, CONTRIBUTING.md, agent_dev_logs/, lint and
coverage configuration, and modules the service does not import — and keeps the
Dockerfile, the compose files, the shared service module and the healthcheck unchanged,
so the service it starts is the same one the default branch publishes. The translator's
branch adds a small demonstration web page at /frontend.
The two skills¶
name: atrium-page-classification
Classifies historical document page images (PNG/JPEG) and multipage PDFs into 11 structural categories (text, handwritten, tables, drawings, photos) using fine-tuned ViT / RegNetY / EffNetV2 models. Use this skill to route archival pages to the correct downstream processing pipeline (OCR, HTR, table extraction, image handling).
| Service endpoints | POST /predict_image (PNG, JPEG) · POST /predict_document (PDF) |
| Client | scripts/atrium_classify.py — routes by file suffix; anything else is skipped |
| Server URL | --base-url, or ATRIUM_PC_URL, default http://localhost:8000 |
| Limits | 10 MB per file · 50 pages per PDF — pre-checked by the client for size only |
| Output | a table, CSV or JSON of FILE, PAGE, RANK, LABEL, SCORE; page 1 for a single image |
| Client flag | Default | |
|---|---|---|
files… |
— | images and/or PDFs |
--base-url |
$ATRIUM_PC_URL or http://localhost:8000 |
|
--version |
all |
one revision, or all for the five-model ensemble |
--topn |
3 |
|
--format |
table |
table · csv · json |
--info |
print the models and categories the server offers, then exit | |
--document-json PATH |
a baseline record to add page_categories to — one input file only |
|
--document-json-out |
ask the service to originate a record even with no baseline | |
--document-json-out-file PATH |
save the returned record — one input file only |
What SKILL.md tells the agent, condensed: prefer --version all; report the top three and
say so when scores are close rather than asserting a single label; convert TIFF to PNG first;
downscale or split anything over the limits and tell the user; on exit 2 start the server and
retry once; on exit 3 look at /health?deep=true and the logs.
name: atrium-translator
Translates archaeological archival XML documents - ALTO OCR pages or AMCR metadata records - between languages (Czech-centric, default target English) via the LINDAT/CUBBITT NMT service, preserving the XML structure and returning the translated document as a file. Use this skill to make digitized historical documents readable in another language after OCR and quality filtering, keeping ALTO layout or AMCR metadata fields intact.
| Service endpoint | POST /translate |
| Client | scripts/atrium_translate.py — .xml inputs only |
| Server URL | --base-url, or ATRIUM_TR_URL, default http://localhost:8000 |
| Limits | 50 MB per file, one XML document per request — pre-checked by the client |
| Output | the translated document as a file: page.alto.xml → page_en.alto.xml, record.xml → record_en.xml; with a baseline record, multipart/mixed carrying the XML and the updated record |
| Needs at request time | outbound network — the default backend is LINDAT's translation API |
| Client flag | Default | |
|---|---|---|
files… |
— | ALTO pages or AMCR records |
--base-url |
$ATRIUM_TR_URL or http://localhost:8000 |
|
--source-lang |
auto |
detection per TextBlock |
--target-lang |
en |
|
--alto / --no-alto |
--alto |
--no-alto for AMCR metadata records |
-o, --output FILE |
the server-proposed name, in the current directory | - for stdout; one input file only |
--info |
print the service's capabilities and limits, then exit | |
--document-json PATH |
a baseline record to add translations to — one input file only |
|
--document-json-out-file PATH |
<stem>.document.json |
requires --document-json |
What SKILL.md tells the agent, condensed: use --no-alto for metadata — a mismatch produces
empty or mangled output rather than an error; keep auto unless the user names the language;
check that the returned XML parses and report its path rather than pasting it; split anything
over 50 MB. There is no GPU mode — the model runs at LINDAT, not here.
Both clients share one contract: exit 0 success · 1 a file not found or nothing
produced · 2 server unreachable · 3 HTTP error after retries. On 502, 503 or 504
they try three times in total, ten seconds apart. Every call uses one timeout — 300 s for the
classifier, 900 s for the translator.
Installing¶
The branch is the skill. Clone it where the agent looks for skills:
git clone -b agent-skill https://github.com/ufal/atrium-page-classification.git \
~/.claude/skills/atrium-page-classification
git clone -b agent-skill https://github.com/ufal/atrium-translator.git \
~/.claude/skills/atrium-translator
Restart the session; the skills appear as /atrium-page-classification and
/atrium-translator. For a single project, clone into .claude/skills/ inside it instead.
git clone -b agent-skill https://github.com/ufal/atrium-page-classification.git \
~/.codex/skills/atrium-page-classification
git clone -b agent-skill https://github.com/ufal/atrium-translator.git \
~/.codex/skills/atrium-translator
Picked up in the next session.
Clone the branch into the project, then add a pointer to it in the project's AGENTS.md
naming SKILL.md, scripts/server.sh and the client script.
Using a hosted service instead of a local one — export the URL and skip server.sh:
export ATRIUM_PC_URL="https://<hosted-instance>/atrium-pc"
export ATRIUM_TR_URL="https://<hosted-instance>/atrium-tr"
Without a URL, the skill starts a local server with scripts/server.sh. Update an installed
skill with git pull inside its directory.
The service contract the skills lean on¶
Every ATRIUM service implements the same meta-contract, from the hub-canonical
service/atrium_service.py — vendored byte-identically into both tools:
| Endpoint | Answers |
|---|---|
GET /info |
service (the repository name), version (from para_config.txt, never hard-coded), endpoints (the live route list), limits (at least max_upload_mb), plus capabilities — the classifier adds categories and available_models; the translator adds supported_formats |
GET /health |
{"status": "ok"}, always 200 while the process lives — liveness |
GET /health?deep=true |
503 with a detail when a dependency is degraded, or while draining |
GET /ready |
503 starting until warm-up completes; 200 when ready to serve; 503 draining after SIGTERM — readiness |
The error codes are harmonised, so a client can treat all five services alike:
| Code | Meaning | What a client does |
|---|---|---|
413 |
too large | report the limit; suggest splitting or downscaling |
415 |
unsupported media type | report the expected types |
422 |
unusable input | report; do not retry |
429 |
busy | report; the caller may retry later |
500 |
processing failure | report the detail; no blind retry |
502 · 503 · 504 |
warming up, draining, or a proxy | retry |
See Operations for how these endpoints behave inside a container and under Kubernetes.
What CI checks on a skill branch¶
Each agent-skill branch calls the hub's skill-validate.reusable.yml, which checks:
- Frontmatter —
nameis lowercase-hyphenated and equals the repository name;descriptionis at least 60 characters. - Referenced paths — every backticked path in the skill's docs exists on the branch.
- The client stands alone — it compiles and prints
--helpin a barepython:3.11-slim. - Documented endpoints are real — every
GET /xorPOST /xin the docs matches a route decorator, and the primary endpoints are served. - The live contract — in a
TestClient:/infonames the repository and version, advertises only real routes and amax_upload_mblimit;/healthanswers; the OpenAPI document validates.
Check 5 needs the service to import in CI; where it cannot — a service whose import pulls in
a large model stack, for example — the check reports a warning rather than failing. Starting
the server with server.sh, /ready and static mounts are outside the workflow's scope.
Writing a skill for another tool¶
Start from the hub's templates in docs/templates/skill/ — SKILL.template.md,
atrium_client.skeleton.py, server.template.sh, serviceREADME.template.md — and the
normative rules in docs/agent_skill_strategy.md: the service contract (§4), what the branch
must carry (§5), the client rules (§6) and the SKILL.md structure (§7). The templates carry
the conventions settled during the first rollout — starting the service by its compose
profile, polling /info until it answers, and the shared exit codes above.
Sources¶
This table records provenance: what this page was written from, not a build instruction.
| Source | What was taken from it |
|---|---|
atrium-page-classification@agent-skill — SKILL.md, README.md, scripts/ |
the page-classification skill |
atrium-translator@agent-skill — the same files |
the translator skill |
atrium-project/docs/templates/shared/atrium_service.py |
the meta-contract |
atrium-project/docs/skills_catalog.md, docs/agent_skill_strategy.md §§4–7 |
the normative contract |
atrium-project/.github/workflows/skill-validate.reusable.yml |
what CI checks |
atrium-project/docs/templates/skill/ |
the templates |