Docs / Concepts
A contract says what your tool needs. A target definition says what a harness can give. Everything else is the arithmetic between them.
A bundle is a directory with a plugin.json and some handler scripts. The
contract lives under the dev.whippletree.v1 key and is a list of
requirements: things your tool needs the harness to do.
{
"name": "my-tool",
"extensions": {
"dev.whippletree.v1": {
"contractVersion": "1.0.0",
"requires": [
{
"id": "capture-gate",
"kind": "blocking-gate",
"event": "turn-end",
"minTier": "T1",
"hardRequired": true,
"loopGuardRequired": true,
"handler": "./handlers/capture.sh"
}
]
}
}
}
That requirement reads: before the agent finishes a turn, run
handlers/capture.sh, and if the harness cannot enforce that natively, do not
install at all. The rest of this page is what each of those fields means.
Five kinds, a closed set. The kind determines which other fields apply.
| kind | what it asks for |
|---|---|
| blocking-gate | Run a handler at a point where it can stop what happens next. Exit 2 blocks. |
| lifecycle-signal | Run a handler when something happens. It cannot block; the harness carries on regardless. |
| observation-signal | Run a handler when a tool of a given class is used, such as a file read. |
| executable-path | No handler. Asks only that a binary in the bundle is reachable at runtime. |
| skill | Ship a SKILL.md the harness will surface to the model. |
A requirement binds to one event. Nine are primitives, mapping to a harness's own hook:
session-start session-end turn-end
tool-pre tool-post subagent-start
subagent-stop compact-pre compact-post
Three more are aliases that expand to a primitive plus a tool class, because "tell me when a file is read" is what you actually mean:
| alias | expands to |
|---|---|
| file-read | tool-post filtered to the harness's read tool |
| file-write | tool-post filtered to its write tool |
| shell-exec | tool-post filtered to its shell tool |
The alias matters because harnesses disagree about what tools exist. Claude Code has a
dedicated Read tool. Codex has none, so file-read there degrades
to watching a broader matcher, which misses reads inside pipelines and heredocs. You wrote
the same requirement either way; Whippletree tells you which one you got.
A requirement lands at the best tier the harness can actually carry it at.
| tier | meaning |
|---|---|
| T1 | Native. A real hook, enforced by the harness itself. |
| T2 | Degraded. Approximated through a coarser mechanism, with the lossage stated in full. |
| T3 | Compiled to instructions. The model is told to run the step and usually will, but can skip it under pressure. |
| T4 | Observer. Reserved. Not implemented. |
You declare a minTier: the worst you are willing to accept. Comparing what
the harness achieves against what you declared gives the verdict.
| verdict | meaning | exit |
|---|---|---|
| SATISFY | Reached your declared minimum, or better. | 0 |
| DEGRADE | Below the minimum, but the requirement is soft. Installs, and says so. | 0 |
| REFUSE | Below the minimum and hardRequired. Nothing is installed. | 1 |
| ABSENT | The harness has no mechanism at all, and the requirement is soft. | 0 |
turn-end gate refuses there rather than
pretending.Refusing is not always what you want. Pair a blocking-gate with a
skill requirement through fallbackSkill, and on a harness with no
native gate the step is compiled into the skill's instructions instead of refusing.
That is T3, and Whippletree says the same sentence about it everywhere it appears, in the
preflight report and in the generated SKILL.md itself:
A target definition records the harness versions it was actually tested against. Probe something outside that range and preflight says so:
$ whippletree preflight ./my-tool --target codex
whippletree preflight · target codex (probed 0.100.0)
! probed 0.100.0 is below the tested range >=0.144.0
! the verdicts below were not verified against this version
A warning, never a refusal. A harness shipping a new version must not break every install that day, and Whippletree cannot know whether the change matters. What it can do is stop asserting a confidence it has not earned.