Configuration#
A YAML file connects a task, template, Evolver, and training budget. check, train, and eval read the same
configuration. Paths are relative to that file’s directory, and ${VAR} in path values is expanded.
Start from an existing recipe#
The OfficeQA recipe uses this config. Its dataset and model-access prerequisites are described in the recipe; this file alone does not supply them.
# Smoke: 4 training questions in 2 steps, 2 validation questions, 4 test questions.
data:
tasks: ../utils/tasks.jsonl
train: ../utils/splits/smoke_train.txt
val: ../utils/splits/smoke_val.txt
test: ../utils/splits/smoke_test.txt
step_size: 2
shuffle_seed: 42
task_info: ../utils/task-info/
agent:
name: ../utils/agent/agent.py:OfficeQAAgent
concurrency: 4
judge:
path: ../utils/judge.py
template: prompt-only@1.0
start_from: ../utils/seed_adapter # SkillOpt's initial OfficeQA skill
evolver:
name: gepa
llm: # the optimizer: GLM 5.3 at medium effort (the paper: GPT-5.5, medium)
model: fireworks/accounts/fireworks/models/glm-5p3 # <backend>/<model id>; the price is the backend's
extra_body: {reasoning_effort: medium}
sandbox:
enabled: true
network: restricted
allow: [] # the Evolver's own model endpoint is always reachable
read: []
env: [] # the Evolver's model key variable is always passed; nothing else is
profiles: # the agent's sandbox (rollout; eval and check take it too)
rollout:
read: [../utils/documents] # the source documents (prepare.py links them)
mask: [../utils/.private] # the judge's answers, beside the task
trainer:
run_name: smoke-gepa
output_dir: runs
tree_dir: runs/trees/smoke-gepa # one adapter tree per run: runs can train side by side
epochs: 1
budget: {usd: 5, hours: 3}
call_timeout_min: 60
eval:
repeats: 3 # the target samples: three passes over the test questions
agent.name names the acting agent: a built-in one (react, codex, claude-code) or the task’s own class or
function. judge.path names its scoring module.
template chooses the adapter’s files and editing rules. evolver.name chooses the learning method.
The llm section configures the run’s model tool, which the GEPA learner uses.
Models are named by reference, <backend>/<model id> (fireworks/accounts/fireworks/models/glm-5p3-flash,
vllm/Qwen/Qwen3-30B-A3B-Instruct-2507-FP8); the backend decides the endpoint, the key variable and the price,
so the same config runs against another provider or a local vLLM or SGLang server with one override:
hermit train --config run.yaml model=vllm/.... See Model backends.
The acting agent owns its own model configuration. Changing llm.model does not change the acting agent’s model;
a built-in agent takes it from agent.params.model, and so does OfficeQA’s own agent (GLM 5.3 Flash when unset).
The top-level model is the default for both.
For every field, its default, type, and path rules, use the complete field reference. Method defaults are included in the method guides.
Defaults and overrides#
The framework merges hermit/defaults.yaml, your YAML, and command-line overrides, in that order.
Only write settings you want to override:
hermit train --config examples/officeqa/gepa/smoke.yaml trainer.run_name=smoke-02 trainer.epochs=2
hermit eval --config examples/officeqa/gepa/smoke.yaml --adapter final trainer.run_name=smoke-02
Use the same run name for training and evaluation. Unknown configuration keys are rejected. Fields inside
evolver.params are defined by the selected Evolver.
What each section controls#
Section |
Meaning |
|---|---|
|
Task JSONL file, split files, tasks per step, whether to shuffle the train split ( |
|
Directory explaining the task and objective to the learner. |
|
A built-in agent ( |
|
Maximum concurrent task executions during rollout and evaluation. |
|
The agent’s settings: for a built-in agent its model, prompt and tools; for the task’s own agent whatever it reads. |
|
Scoring module/file and function name; the default name is |
|
Built-in template such as |
|
Optional tag, commit, or adapter directory from the same template; defaults to the root. |
|
Built-in name or a path to an Evolver definition. |
|
Parameters layered over that Evolver’s defaults. |
|
Request a continued learner session between steps; the Evolver must support it. |
|
Additional task resources and tools given to the learner. |
|
Optional model endpoint, model ID, key environment variable, prices, and |
|
Run name and directories, epochs, budgets, checkpoint frequency, and recovery mode. |
|
Repetitions per test task in the separate evaluation command. |
Starting from an adapter versus resuming a run#
start_from initializes a new run from an existing adapter. It does not load the previous Evolver’s state.
trainer.resume_mode: auto resumes an unfinished run with the same name from its last completed step, including
saved learner state. disable starts over and renames the previous run directory and branch.
Use auto or disable; selecting a recovery step/tag through resume_mode is not implemented. To branch a new
experiment from a checkpoint, use start_from and a new run name. See Trainer.
Budgets and checkpoint frequency#
trainer:
epochs: 3
save_freq: {steps: 5, epochs: 1}
budget: {usd: 20, tokens: null, hours: 2}
call_timeout_min: 15
allow_early_stop: false
The Trainer accounts for rollouts and learner calls. It stops at the next point where it observes a limit, so
in-flight work can overshoot it. Dollar accounting needs reported usage and applicable prices. The separate
check probe and eval are outside this training budget. See Trainer for the observation points.
Sandbox, network and warnings#
Every process the framework starts runs in a sandbox, in one of five roles. Only the Trainer itself and the judge (which scores in the Trainer, on the task’s answer) stay outside. It requires Linux and bubblewrap.
Role |
What runs in it |
|---|---|
|
The Evolver’s learner program (a coding agent or a script). |
|
The task pipeline during training: each task’s agent runs in its own worker process. |
|
The same worker, for |
|
The same worker, for the probe of |
|
A tool the template or task declares for the Evolver; its code is never imported in the Trainer. |
A custom Evolver’s own Python code runs in the Trainer, as before; what it starts goes through the learner sandbox.
It is a positive allowlist: inside, only what is granted exists. Read-only: the system’s /usr and a few /etc
files, the Python interpreter, the Python distributions the framework depends on (not every package installed beside
it), the hermit package (not the checkout around it), the learner program’s installation, the Evolver’s
directory, the granted accesses and sandbox.read. Read-write: the adapter, state, scratch and output directories.
The task’s data, other runs and the user’s home are not there at all.
The agent’s roles (rollout, eval, check) use a preset for task code, which needs far more than a learner: its
helpers beside the config, its benchmark’s packages, the variables its launcher exported, the endpoints those name.
What the preset keeps from the agent is the run’s secrets:
read-only: the task’s project (the deepest directory holding the config, the data, the task info, the judge and the agent; an example’s
<method>/config and its../utils/), the whole Python environment, the adapter under test,sandbox.readandprofiles.rollout.read; a granted directory holding a virtual environment (an external benchmark’s.venv) comes with the Python installation that environment was made from. “Every package” means the environment’s site directories and what their.pthfiles add, not the directory a program was started from;read-write: only
.logsbeside the agent’s own code (examples/officeqa keeps its logs there), a work directory per call (its HOME, logs and workspaces) andprofiles.rollout.write; the agent’s code, the config and the task’s helpers are read-only, so an agent cannot change what later calls run;covered, wherever they fall in the above and under every name they are shown by (a directory reached through a link): the task, split and judge files read as empty; every
runsand.logsdirectory in the project and any directory holding a run (this run’s and the other methods’ beside it, whose judge feedback can carry answers, found again for each call), the other calls’ work, the tree directory, andprofiles.rollout.mask(a judge’s answers kept beside the task) are empty. A project too large to search is shown path by path instead;environment: inherited, unless
profiles.rollout.envlists the variables (sandbox.envis the learner’s);network: open, unless
profiles.rollout.network: isolated, which allows the agent’s bound model endpoint (a127.0.0.1endpoint is forwarded) and the role’sallowthrough its own proxy;a CLI agent (codex, claude-code) gets its CLI as installed: the links to it, its package, the interpreter it names (node) and what a short wrapper script runs; its login is copied into its own
CODEX_HOMEorCLAUDE_CONFIG_DIRfor the call and removed after it;the adapter’s tools never run in the agent’s process, nor in any process it starts (a benchmark’s own runner, a CLI agent’s tool server): a
Sessionmade there serves them from a sandbox of its own, as strict as the learner’s (no network but the loopback endpoints the environment names, none of its keys, the framework’s packages), under the run’senforce; an adapter without tools starts nothing.
Every sandbox has its own process namespace: the host’s processes, their environment and their files are not
reachable through /proc. agent.timeout_min stops a task call that runs longer and records it as an error. The
agent is never imported in the framework’s process while its role is sandboxed.
The result’s log is copied into the learner’s material outside any sandbox, so it must lie where the call could
write (its work directory, the agent’s logs, the role’s write); another path is dropped with a warning, and a link
is never followed.
eval and check take profiles.rollout unless they have one of their own. A profile path may name an environment
variable ($SLB_WORKDIR, set by a launcher); an unset one is an error. The result comes back on a pipe only the worker
can write, so nothing the agent starts can report a result. A pipeline that needs what the preset does not give (a
GPU, a docker socket) gets it through the profile’s read or write, or runs unconfined with
profiles.rollout.enabled: false. The examples’ configs show each case: officeqa grants its documents (a link
prepare.py makes) and covers the answers beside them (.private/), clbench grants its checkout
and task directory from the launcher’s variables.
Wherever else the framework executes the adapter, the learner’s work, it does so in a sandbox: the spec check’s
deploy and template checks (the adapter and the template read-only, no network, no keys), the mwm Evolver’s
maintenance, admission gates and context rendering (in the learner’s own sandbox), and a CLI acting agent’s deploy —
rendering the adapter’s exports and the tool server it starts — so the adapter’s modules never run with the agent’s
login or network. run_agent runs a copy of the adapter the learner names, made inside the learner’s sandbox. (A
standalone deploy, convert or serve the user runs for their own host, outside a run, still renders on the host.)
The learner and tool roles are strict: sandbox.network, sandbox.env and the framework’s own packages. A
declared tool that needs more says so in profiles.tool (examples/clbench_database/mwm’s sql tool reads the
registered database and writes the task directory).
Setting |
Effect |
|---|---|
|
Wrap launched learner programs in the sandbox; false runs them unconfined. |
|
Default. Refuse to run when bubblewrap is missing. |
|
Run unconfined when bubblewrap is missing, and record that the sandbox was not applied. |
|
Default. A separate network namespace with loopback only; see below. |
|
Use the host network, unrestricted. |
|
Hosts reachable through the allowlist proxy when the network is isolated; empty means no proxy. |
|
Additional filesystem paths those programs may read. |
|
Additional Python distributions to show, by name, beside the framework’s own dependencies. |
|
Environment variables passed on, in addition to required runtime variables; null inherits the environment. |
|
Per role: |
|
Trace every file a sandboxed process opens, with strace running outside, beside its record. |
The older values still work: network: restricted means isolated, and network: none means isolated without the
proxy.
With an isolated network the only ways out are forwarded ports: every 127.0.0.1 URL in the learner’s command or
environment (the run’s tool endpoint, a tool gate, a model meter) and the allowlist proxy. Each is re-created inside
and relayed over a Unix socket, so a program that ignores the proxy variables has no route out. Before the learner
starts, a check inside the sandbox confirms that an outside connection fails, that every forwarded port answers, and
that the task’s data file and the home’s .ssh and .bashrc are absent; the learner does not start unless it passes.
On an open network (the agent’s default) the same check runs for the paths only: what must be absent is, and every
covered secret is empty.
Every wrapped program leaves a record, sandbox/<role>-<n>.json in the run’s directory (eval writes its own beside
its results): what was bound read-only and read-write, what was covered, which ports were forwarded, which
environment variables passed, and the result of that check, which for a worker also proves the covered paths empty.
Framework-internal checks that run the adapter’s own modules (the spec check, a CLI agent’s deploy render, a sandboxed Session) leave records under their own labels — spec-, render-, adapter-, session- — beside the role records; they are not configurable roles. Each role’s proxy writes one line per request, allowed or refused, to sandbox/network*.jsonl. With audit: files,
<role>-<n>.strace lists the file accesses; strace is bubblewrap’s parent, out of the sandbox’s reach.
Giving more read paths or inheriting credentials changes what the learner can access. Warnings are recorded with the run.
evolver_access.send_material_out declares that sending learning material to an outside service is allowed; it is
checked when an Evolver declares that need. It is not a network filter. Likewise, the framework does not supply test
tasks through the learner interface, but broad filesystem grants or disabled isolation can expose them.
What is checked at startup?#
Config parsing checks required fields and basic values. train also loads the template, data, pipeline, judge, and
Evolver and checks the Evolver’s declared needs and session-resume support. It does not run the full standalone
check command for you. Use Commands for preflight checks, the optional probe, and evaluation.