Skip to main content
Nobody runs a coding agent as it ships. A team layers a system prompt, an 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:
Two columns, one base, one model. The first is the harness as it ships; the second is the harness your team runs. Each is its own cell with its own baseline, so a change to the profile is measured against the profile’s own history rather than against stock.

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.
Never put a key in env. A profile is committed beside the eval file, sent with the run, stored unencrypted, and readable by anyone who can read the run. Provider keys belong in an env credential, which is sealed and injected into the sandbox at run time, and whose values override anything the profile sets under the same name.
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?
The run rail, the cell history and the CI gate all name the profile and the version change, so a failing check says which profile regressed rather than only which case did.

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.