AGENTS.md, a permission map, plugins, skills and a handful of environment variables on top of it, and that layer is what actually decides whether the agent works.
A profile is that layer, versioned. It rides on a task beside the harness it configures:
The directory
dir resolves against the eval file. Anpord walks two directories and nothing else:
Everything outside those two directories is ignored unless the manifest names it, so a
README.md beside them documents the profile without shipping into the sandbox. node_modules and .git are skipped wherever they appear.
Files land before the agent runs, and after the harness has written its own credentials, so a profile can override what the harness put there.
The manifest
profile.json carries what is not a file:
profile.json
A value naming a file inside the profile directory is replaced by that file’s content, so
"systemPrompt": "system.md" ships the prompt and "systemPrompt": "Be terse." ships the sentence. env values are always literal. A value naming a file outside the directory is refused rather than read: a prompt that only exists on one machine is not part of the profile.
run belongs to the command harness, and a profile on any other base is rejected if it sets one. install is available to every base: fetching a skill package or a dependency is not a property of the harness that reads it afterwards. It runs after both file stages, so it can read what the profile shipped, and before the case’s own prepare.
Limits
Paths are relative, may not contain a
.. segment, and are at most 512 characters. Files are text: a file holding a NUL byte is skipped rather than shipped, so a checked-in binary does not fail the compile.
The per-file limit is not arbitrary. Two sandbox providers hand a file’s content to the machine inside a single shell argument, which caps a file near 128 KiB once encoded.
System prompts
Each base takes a system prompt in its own way. The prompt is always written into the sandbox; whether the harness is given its path or its text depends on the base:
The last two rows are the weaker ones. An agent that reads its instructions file only when it feels like it will treat the prompt as a suggestion; if that matters, put the text in
workspace/AGENTS.md yourself and read the trajectory to check it landed.
Identity
The profile name is part of the cell key. Two profiles on one base, one model and one sandbox are two cells with two histories, exactly as two harnesses would be. The profile version is not. It is a content hash over the files, the system prompt, the environment, and the install and run commands, and it is compared the way a harness version is compared: an edited profile is a new reading on the same cell, measured against that cell’s baseline. Renaming a file changes the version. Renaming the profile does not — that starts a new cell. So the question a profile answers is the one a team cannot answer today: the prompt changed on Tuesday; did the agent get worse?Comparing fairly
A grid that puts a profile beside stock harnesses is only honest if the difference it measures is the one you meant. Ship the same skills to every column. A profile’s skills usually live under one agent’s directory —.claude/skills for Claude Code, .agents/skills for OpenCode — and a column that has them beside a column that does not is measuring the skills, not the harness. Write them into every directory the grid’s harnesses read, through workspace/.
Hold the model constant. Where you cannot — a profile pinned to a provider the other columns have no key for, or a base whose model ids do not overlap — the cells are not comparable, and the honest thing is to say so beside the grid rather than to read the delta.
Change one thing at a time. A run that moves the profile and the model together tells you something moved.