Whippletree

Docs / Concepts

Concepts

A contract says what your tool needs. A target definition says what a harness can give. Everything else is the arithmetic between them.

The contract

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.

Requirement kinds

Five kinds, a closed set. The kind determines which other fields apply.

kindwhat it asks for
blocking-gateRun a handler at a point where it can stop what happens next. Exit 2 blocks.
lifecycle-signalRun a handler when something happens. It cannot block; the harness carries on regardless.
observation-signalRun a handler when a tool of a given class is used, such as a file read.
executable-pathNo handler. Asks only that a binary in the bundle is reachable at runtime.
skillShip a SKILL.md the harness will surface to the model.

Events

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:

aliasexpands to
file-readtool-post filtered to the harness's read tool
file-writetool-post filtered to its write tool
shell-exectool-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.

The tier ladder

A requirement lands at the best tier the harness can actually carry it at.

tiermeaning
T1Native. A real hook, enforced by the harness itself.
T2Degraded. Approximated through a coarser mechanism, with the lossage stated in full.
T3Compiled to instructions. The model is told to run the step and usually will, but can skip it under pressure.
T4Observer. 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.

Verdicts

verdictmeaningexit
SATISFYReached your declared minimum, or better.0
DEGRADEBelow the minimum, but the requirement is soft. Installs, and says so.0
REFUSEBelow the minimum and hardRequired. Nothing is installed.1
ABSENTThe harness has no mechanism at all, and the requirement is soft.0
REFUSE is the point. A security gate that silently becomes advice is worse than one that fails to install, because you will believe it is running. opencode has no blocking stop event, so a hard turn-end gate refuses there rather than pretending.

Instruction fallback

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:

best-effort, no harness-level enforcement on this target: the model is instructed to run the step and usually will, but can skip it under pressure

Probed versions

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.

Next