adapter.yaml reference#

adapter.yaml is the manifest at the root of every adapter directory. It names the template the adapter comes from, declares what the adapter exports to an acting agent, and states which files a learner may change. The rules below are the ones hermit.adapter.manifest and hermit.adapter.loader enforce; a manifest that breaks one does not load, and a candidate whose manifest drifts from its template fails the specification check.

The file has six sections and no others:

Section

Who writes it

Who reads it

template

the template author

everyone: identity and compatibility

exports

the template author

the acting side: hosts, deploy, custom pipelines

structure

the template author

the Trainer’s specification check; the learner, through EVOLVER.md

check

the template author

the specification check

evolver_tools

the template author

the Evolver’s tool endpoint

origin

the Trainer

people and the tree

A seventh, serve, is proposed and not yet read by anything; its stdio command, hermit serve DIR, exists and is what convert writes into a plugin.

The first five are fixed across a tree: every adapter committed under a template carries them byte-for-byte as the template’s root does (check 3 of the specification check). An acting integration needs only template and exports; flatten() returns exactly those, with context filled in.

Customizing an adapter#

Most people never write a manifest from scratch: they take a template and change one thing. A run names a template either by its built-in id (template: prompt-only@1.0) or by a directory holding an adapter.yaml (template: ./my_template), so the way to customize is to copy a template directory, edit it, and point the run at the copy. The built-in templates are under hermit/algorithms/templates/; finished adapters with their cards are under examples/. What you want to change decides which part you touch:

You want to

Change

Where

Different starting instructions, skills or roles

the learnable files themselves: ADAPTER.md, skills/, agents/

no manifest change; the learner starts from your text

A new tool for the acting agent

add its Python to the directory and declare it

exports.tools; the file goes under fixed

Let the learner edit more, or less

the list of learnable paths and their limits (size, file count, frontmatter)

structure

Keep a file exactly as shipped

list it under fixed

structure

Reject candidates by a rule of your own (a lint, a test, a forbidden word)

a checker function run after the built-in checks

check

Give the learner a helper during training (a renderer, a validator) that the agent never sees

a tool declaration under evolver_tools

evolver_tools

Change how a learner should edit, not what it may edit

EVOLVER.md

no manifest change

Two rules hold throughout. Every file in the adapter must be either learnable or fixed, so adding a file means listing it. And the first five sections are the template’s identity: edit them in the template root before training starts, not in a trained adapter, because every commit of a tree must carry them unchanged. If the change is an edit to the acting-side texts only, keep the template’s id and version; if it changes the manifest, give the copy its own template.id (or a new version) so its tree is not mistaken for the original’s. Run hermit check --config run.yaml afterwards: it loads the template, the task and the Evolver and reports what does not fit.

The sections below are the reference for each field.

A minimal manifest:

template:
  id: prompt-only
  version: "1.0"
exports: {}
structure:
  learnable:
    - path: ADAPTER.md
      format: {text: true, max_bytes: 40000}
  fixed: [EVOLVER.md, README.md]

The complete example from docs/_examples/file_adapter/adapter.yaml:

template:
  id: file-example
  version: "1.0"
  contract: "1"
exports:
  agents: agents
  skills: skills
  tools:
    - name: count_words
      description: Count whitespace-separated words in text.
      entrypoint: tools.py:count_words
      input_schema:
        type: object
        properties:
          text: {type: string}
        required: [text]
        additionalProperties: false
      output_schema:
        type: object
        properties:
          count: {type: integer}
        required: [count]
        additionalProperties: false
structure:
  learnable:
    - path: ADAPTER.md
      format: {text: true, max_bytes: 4000}
    - path: agents/*.md
      format: {text: true, max_bytes: 4000, frontmatter: [name, description]}
    - path: skills/**
      format: {text: true, max_bytes: 4000, max_files: 8}
  fixed:
    - tools.py
    - EVOLVER.md
    - README.md

template#

Identity of the template this adapter belongs to. Required.

Key

Required

Rule

id

yes

lowercase slug: [a-z][a-z0-9-]{0,63}. Names the template in run.yaml (template: prompt-only@1.0), in the deployment’s managed block and MCP server name, and in the tree

version

yes

nonempty string. With id it makes the id@version reference

name

no

display name; defaults to id

contract

no

the manifest contract this file follows; "1" today. Not validated, carried for forward compatibility

exports#

What the acting agent receives. Required, may be empty ({}): then the adapter exports its context only.

Key

Value

Rule

context

ADAPTER.md

optional and fixed: the context is always the file ADAPTER.md at the adapter root, which must exist. Naming any other file is an error

tools

list of tool declarations

names unique; each tool’s code lives inside the adapter

agents

a directory, or {entrypoint: file.py:function}

roles (subagents). A directory holds one Markdown file per role, YAML frontmatter with name and description, the body its instructions. An exporter function returns that list instead

skills

a directory

skill folders copied to the host as they are (skills/<name>/SKILL.md)

reactions

{entrypoint: file.py:function}

service integration only: a function the framework service calls on events; deploy ignores it

concurrent_tool_calls

boolean

service integration only: run the adapter’s tool calls concurrently instead of one at a time

All paths are relative to the adapter root and must stay inside it.

Only declared exports produce anything on the acting side: a manifest with exports: {} deploys as an instruction file and nothing else. How each export reaches Codex or Claude Code is in Acting interface.

Tool declaration#

- name: count_words
  description: Count whitespace-separated words in text.
  entrypoint: tools.py:count_words
  input_schema:
    type: object
    properties: {text: {type: string}}
    required: [text]
    additionalProperties: false
  output_schema:
    type: object
    properties: {count: {type: integer}}
    required: [count]
  annotations: {readOnlyHint: true}

Key

Required

Rule

name

yes

[a-z][a-z0-9_]{0,63}, unique within the manifest; the name the agent calls

description

yes

nonempty; shown to the agent as the tool’s description

entrypoint

yes

file.py:function, a .py file inside the adapter; the function takes the input’s keys as keyword arguments, may be async, and returns a dict

input_schema

yes

a JSON Schema object (type: object); arguments are validated against it before the call

output_schema

yes

a JSON Schema object; the result is validated against it

annotations

no

MCP tool annotations passed through to the host (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)

Schemas may use local $refs only; remote and dynamic references are rejected. A tool function may declare a reserved _resources parameter to receive framework bindings (the evidence store, deployment settings) from a pipeline that supplies them; the agent cannot pass it.

structure#

The specification: which files a learner may change, in what form, and which must stay as the template ships them. Required.

structure:
  learnable:
    - ADAPTER.md                                   # a bare path: any content
    - path: agents/*.md
      format: {text: true, max_bytes: 4000, frontmatter: [name, description]}
    - path: skills/**
      format: {text: true, max_bytes: 4000, max_files: 8}
  fixed:
    - tools.py
    - EVOLVER.md
    - README.md

learnable: a list of entries, each a path or {path, format}. path is a glob relative to the adapter root: * matches within one path segment, ** across segments, and a directory name covers everything below it. A file may be changed, added or deleted only if it matches a learnable path and no fixed path.

Format key

Meaning

text: true

the file must decode as UTF-8

max_bytes: N

size limit per file

max_files: N

at most N files may match this entry

frontmatter: [key, …]

the file starts with YAML frontmatter carrying these keys (implies text)

fixed: paths (files or directories) that must exist exactly as in the template root; changing, deleting or adding a file under one fails the check. adapter.yaml and CARD.md are always framework-owned and need no entry.

Every file in an adapter must be either fixed or covered by a learnable entry. A file outside both is a check failure, and the Trainer removes it when it repairs a candidate. See How does the specification limit edits?.

check#

check: check.py:check

Optional. A template’s own checker, file.py:function loaded from the template root (not the candidate), run after the built-in checks: check(adapter_path) -> list[dict], each issue {path, problem}; a nonempty list rejects the candidate. It runs in a subprocess, 120 seconds, network isolated when bubblewrap is available. See Specification check.

evolver_tools#

evolver_tools:
  - name: render
    description: Render the context the way the host will see it.
    entrypoint: preview.py:render
    input_schema: {type: object, additionalProperties: false}
    output_schema: {type: object}

Optional. Tools the learner gets during training, in the same form as a tool declaration, with the entrypoint resolved against the template root. They join check_spec (always present), the task’s evolver_access.tools and the run’s budget / llm on the Evolver’s tool endpoint; they are never exported to the acting agent.

origin#

origin:
  template: file-example
  version: "1.0"

Written by the Trainer into every adapter it commits: the template id and version the tree started from. Not part of the fixed sections; a hand-written adapter has none.

serve (design, not implemented)#

The manifest does not accept a serve section. The implemented hermit serve DIR command starts a per-session stdio MCP tool server, normally launched by a generated host entry. Generic standalone startup, health checks, discovery, and relay lifecycle remain a design proposal. Do not add the proposed fields or flags to a working configuration.

What is not in the manifest#

  • The content format of ADAPTER.md. Beyond structure’s size and text rules, the context is free text; no frontmatter or sections are required or interpreted.

  • Dependencies. A tool’s imports must be importable where the adapter runs; README.md is the place to say what they are.

  • Learner guidance. EVOLVER.md tells the learner how to edit within structure; it is not exported.

  • Run settings. Budgets, sandbox, data and the Evolver are in run.yaml; see Configuration.