The Harbor agent
Subclass Harbor’sBaseInstalledAgent, so the agent’s commands run inside the container next to your app. (BaseAgent, which runs on the host and reaches the container through environment.exec, is only for a product that can’t run in the container.) lmnr-cli pins Harbor 0.25.0; see Harbor’s custom agents guide for the full interface.
install()stays empty with a prebuilt image. It runs in every trial, and anything it would install belongs in the image. Per-trial setup goes at the start ofrun().run()stays thin: start the stack, seed baseline and case data, run the driver.- Run commands with
exec_as_agent, orexec_as_rootwhen they need root. Theagents[].envvalues fromharbor.yamlreach every command the agent runs, so useenv=only for per-command extras. - Read the model from
self.model_name, so--modelworks. - Import only
harbor, the standard library, and modules in the suite directory. The class runs in the Harbor process (your machine or Laminar’s orchestrator), not in the container; everything else belongs in the image. - Optionally override
populate_context_post_run(context), which runs after the trial’s logs are copied to the host, to parseresult.jsoninto the agent context.
The driver
The driver the agent runs does two things: trigger, then wait for a terminal state.- For a synchronous entry point (an HTTP call or a function that returns the result), call it with a timeout and record the response.
- For an asynchronous one (enqueue a job, the app writes a status later), poll the status the app writes until it’s terminal, with a hard timeout. Don’t sleep for a fixed time.
- Map the result to an outcome the verifier can grade, with
failedandtimeoutas distinct outcomes so they’re never mistaken for a model verdict. Write the full detail (status, error, ids, duration) to/logs/agent/result.json. - Exit
0whenever you produced an outcome, includingfailedandtimeout. A non-zero exit makes the trial an error, which should mean the harness broke.
timeout_sec, and both below the task’s [agent] timeout_sec.
The verifier
tests/test.sh is the entry point. It can call anything: a Python script comparing against expected.json, a rubric, an LLM judge with its key in [verifier.env]. It writes the reward to /logs/verifier/reward.txt. A discrete outcome (pass or fail, event or no event) is the easiest place to start.
Where it runs is set by environment_mode under [verifier] in task.toml:
"shared"(the default) runs in the agent’s container after the agent finishes and can read anything the run left behind, such as/logs/agent/result.json."separate"runs in a fresh container that sees only what the task declares: files listed in the top-levelartifacts = [...], plus snapshots of runtime state taken by[[verifier.collect]]commands before the environment is torn down. Use it when the verifier needs its own dependencies or must not see the app’s internals.
tests/, never in environment/.
Next steps
Capture the trajectory
Get the app’s LLM and tool calls into every trial’s trace.
Build a production harness
Run the real services, seed realistic data, and keep the eval honest.
The evals folder
Reference for
lmnr.yaml, harbor.yaml, task.toml, and secrets.Troubleshooting
Fixes for stale images, missing env vars, and empty traces.