Adapter#
An adapter is a directory of source files governed by adapter.yaml. The Trainer stores versions of those files
in Git. The acting integration turns their exports into host instructions, skills, roles, and callable tools.
There are two file paths to understand: deployment creates files for the acting host; evolution edits a working copy of the adapter sources.
The adapter structure#
The complete example in docs/_examples/file_adapter/ contains:
file_adapter/
adapter.yaml required: identity, exports and specification
ADAPTER.md required: context for the acting agent
agents/reviewer.md optional: one file per subagent
skills/verify-output/
SKILL.md optional: one skill
tools.py optional: tool implementations (the tool list is in adapter.yaml)
EVOLVER.md template guide for the learner
README.md usage and dependencies
Every adapter has adapter.yaml and ADAPTER.md (the manifest’s sections and rules: adapter.yaml reference). ADAPTER.md is a fixed name and is always exported as the acting
agent’s context; exports.context may be omitted. Everything else is optional and declared in adapter.yaml.
EVOLVER.md is learner guidance; it is not inserted into the acting agent’s context. tools.py is a convention:
each tool’s entrypoint names its file and function.
A subagent is one Markdown file, in the same format as Claude Code’s subagent files: YAML frontmatter with name
and description, and the body as its instructions.
---
name: reviewer
description: Check a result against the task requirements.
---
Check whether the result meets the stated requirements. Explain any mismatch.
These declarations connect the subagent and skill directories to the loader:
exports:
agents: agents
skills: skills
Acting interface#
Which files are written into the host?#
The connector reads the adapter manifest and renders or copies its exports into a separate host workspace:
Adapter source |
Deployment operation |
Codex output |
Claude Code output |
Pi output |
|---|---|---|---|---|
|
Insert its text into a managed instruction block. |
|
|
|
|
Copy the skill directory. |
|
|
|
|
Convert the subagent into native host files: frontmatter gives name and description, the body becomes its instructions. |
|
|
refused: Pi has no subagents |
|
Configure a framework MCP server that loads the source adapter. |
|
|
|
This is how the adapter reaches the acting system. The host reads the generated instruction, skill, and role files; tool configuration makes the declared tools available through its tool interface. Merely placing the adapter directory next to a task does not make an agent use it.
For the example above, the generated workspaces contain:
codex-workspace/
AGENTS.md
.codex/
config.toml
agents/reviewer.toml
skills/verify-output/SKILL.md
.hermit-deploy.json
claude-workspace/
CLAUDE.md
.mcp.json
.claude/
agents/reviewer.md
skills/verify-output/SKILL.md
.hermit-deploy.json
pi-workspace/ # only for an adapter without roles
AGENTS.md
.pi/
mcp.json
skills/verify-output/SKILL.md
.hermit-deploy.json
Only declared exports produce corresponding content. A prompt-only adapter has no tool-server entry, skills, or roles to install. The generated deployment report identifies the actual paths and role names.
Context is inserted into project instructions#
ADAPTER.md is the source. AGENTS.md or CLAUDE.md is the host-facing result. The connector inserts the
rendered context between markers like these:
<!-- hermit:file-example:start -->
<!-- Rendered adapter instructions appear in this managed block. -->
<!-- hermit:file-example:end -->
The snippet shows the boundaries, not the complete generated contents. Redeployment replaces that adapter’s block and preserves project text outside it. Existing adapter headings are nested under the generated section title.
The deployment command prepares a workspace; it does not launch the agent. Its output must be empty or a workspace
previously generated for the same host. The source adapter and output workspace cannot contain one another.
The generated .codex/config.toml, .mcp.json or .pi/mcp.json is rewritten on deployment; it is not a general configuration
merger. Global host configuration is not modified.
Tools run from the adapter source#
Tool code is not copied into AGENTS.md. The generated MCP configuration starts a framework wrapper with the
adapter’s absolute path:
python -m hermit.runtime.mcp_server --adapter /absolute/path/to/adapter ...
That wrapper exposes the declared tool schemas, loads the Python functions, and returns their results to the acting agent. The source adapter and its runtime dependencies must remain available.
Each tool declaration names an entrypoint such as tools.py:count_words and object input/output JSON Schemas.
The Python function may be synchronous or asynchronous. Subagents can also come from a Python exporter that returns
the list instead of a directory of files; the context is always the ADAPTER.md file.
An independent service is another integration option. The framework service can host adapter tools, shared state,
and reaction logic with its own lifecycle. The pipeline must start it and integrate any control channel with the
acting host. Ordinary deploy does not start that service.
Custom acting pipelines#
A program that runs its own agent uses an adapter through Session, the Python counterpart of deploy and
convert:
from hermit import Session
session = Session("docs/_examples/file_adapter")
system_prompt = base_prompt + session.context() # ADAPTER.md as the Evolver last wrote it
tools = task_tools + session.tool_specs("openai") # or "anthropic"; the adapter's tools beside the task's
...
for call in message["tool_calls"]: # a chat-completions turn
if session.has_tool(call["function"]["name"]):
messages.append(session.respond(call)) # the tool message; errors come back as text
session.call(name, arguments) runs one tool synchronously (also inside a running event loop), acall is the
coroutine, agents() and skills() give the other exports. Resources a tool may receive through its reserved
_resources parameter are bound at construction: deployment= (a host.json or its dict), learning_material=,
evidence= (a record directory), or any mapping under resources=. audit_log= writes one JSON line per call,
the same record hermit serve writes. The task interface leaves host integration to the
pipeline; it does not automatically implement every export.
Outside a run’s rollout sandbox (a production program, say), Session(adapter, sandbox=True) keeps the adapter’s code
out of the program: the tools run in a hermit serve inside a sandbox (no network, the adapter read-only,
nothing writable but the evidence store), reached over MCP, and the audit log is still written by the program. Pass a
configured hermit.sandbox.Sandbox instead of True to grant more. deployment, learning_material and
evidence go with it; other resources are refused, and so is an adapter whose subagents come from a Python exporter.
Close the session, or use it in a with block, to stop the server.
Evolution interface#
What does the Evolver edit?#
At the start of each step, the Trainer exports the accepted Git version into ordinary working files. The acting batch and the learner receive separate copies:
Location in a run |
Purpose |
Who changes it? |
|---|---|---|
|
Template reference for the specification. |
Prepared by the Trainer. |
|
Adapter supplied to the current acting batch. |
Prepared by the Trainer; the pipeline treats it as input. |
|
Candidate files being learned. |
Evolver edits these files. |
|
Git worktree holding the recorded adapter version. |
Trainer updates and commits it. |
|
Method-specific search or learning state. |
Evolver; separate from the adapter. |
The learner’s path is ctx.workspace.adapter. A scripted Evolver can use normal file operations; a learner agent
can edit the same files through its file tools. For example, this method appends a predefined instruction:
from hermit.evolver.base import StepContext, StepResult
def step(self, ctx: StepContext) -> StepResult:
path = ctx.workspace.adapter / "ADAPTER.md"
path.write_text(path.read_text() + "\nCheck the result against the task requirements.\n")
return StepResult(status="done", summary="Added a result-checking instruction")
This demonstrates the file handoff, not a learning strategy. A real method chooses its edits from the available
evidence. It leaves the selected candidate in the directory and returns status and usage through StepResult.
The Evolver edits ADAPTER.md or other permitted source files. It does not edit the acting workspace’s generated
AGENTS.md as its learned artifact. After acceptance, a later pipeline execution loads or redeploys the new source
version. An already-running host does not automatically receive a live update.
How does the specification limit edits?#
The template declares the writable content and fixed content. Here is the example’s actual specification:
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
For this template:
Proposed edit |
Result |
|---|---|
Rewrite |
Allowed, within its format and size limit. |
Edit |
Allowed if its frontmatter keeps |
Add or edit a skill under |
Allowed, within the declared file and size limits. |
Edit |
Rejected: it is fixed here. |
Delete |
Rejected: every adapter has one; repair restores it. |
Add an undeclared file elsewhere in the adapter. |
Rejected: it is outside the learnable paths. |
Change the tool declarations in |
Rejected: the exports remain fixed against the template. |
Another template can permit tool-code changes or other learnable files. The scope comes from that template; it is not a hard-coded restriction to prompts.
The candidate directory is writable, but not every edit is acceptable. Fixed-file protection is enforced by the
specification check after the learner returns, rather than by making each fixed file physically read-only inside
the working copy. The template’s EVOLVER.md explains the intended editing rules to the learner.
Who checks and records the candidate?#
The Trainer compares changed files with the template specification. It checks fixed paths, allowed paths, formats, manifest sections, and export loading. If a candidate violates the specification, it records the invalid version, restores affected files from the template or previous adapter, checks again, and records the repair. The next batch uses the accepted version. The learner sees any repair feedback in its next learning material.
The manifest sections template, exports, structure, check, and evolver_tools stay fixed. origin and the
final CARD.md are written by the Trainer. See failure handling.
Specification check#
The callable interface is check_spec(adapter, root) -> CheckResult. The result contains passed and issues
naming the affected path, check, and problem. It establishes conformance and loadability; it does not measure task
performance.
The built-in load check renders context, loads role declarations, and imports declared tools. It does not launch the acting host or service. Additional runtime requirements belong in the template’s custom check or task setup.
A template may add a checker for requirements not expressed by the manifest:
from pathlib import Path
def check(adapter_path: str) -> list[dict]:
if not (Path(adapter_path) / "ADAPTER.md").read_text().strip():
return [{"path": "ADAPTER.md", "problem": "instructions must not be empty"}]
return []
Declare it as check: check.py:check. The checker is loaded from the template root. Return a nonempty issue list
to reject a candidate; a dictionary with an issues list is also accepted. It runs in a subprocess with a
120-second limit and network isolation when bubblewrap is available.
Minimal example#
From the repository root after installation, use fresh output directories:
mkdir -p .demo
cp -R docs/_examples/file_adapter .demo/file-adapter
hermit deploy --adapter .demo/file-adapter --workspace .demo/file-codex --host codex
hermit deploy --adapter .demo/file-adapter --workspace .demo/file-claude --host claude-code
Inspect .demo/file-codex/AGENTS.md, .demo/file-claude/CLAUDE.md, and the generated roles, skills, and tool
configuration. No model is called by these deployment commands.
After editing the source copy, check it against the template:
python -m hermit.spec .demo/file-adapter --root docs/_examples/file_adapter
Then rerun the deployment command to refresh host files. This is the same source-edit → check → deploy boundary used during training, with the Trainer additionally managing candidate acceptance and Git versions.
Directory, Git version, and runtime state#
The portable artifact is the adapter directory. The Trainer keeps a Git tree of its versions, with run branches,
checkpoint tags, and a final tag. The learner’s exported working copy is not itself a Git worktree and does not
need a .git/ directory. The Trainer performs the version-control operations.
Deployment consumes the adapter files. Learner state, execution logs, and a service’s per-run state stay outside
the package unless a template explicitly makes some evidence or memory part of its learned content.
CARD.md records provenance and evaluations; it is not acting context.