Skip to main content

ADR-0004: Pluggable Agent Harness Adapter

Context and Problem Statement

Inside each sandbox (ADR-0003), something must actually drive the LLM: plan the task, call tools (read/write/edit/shell), iterate against tests, and open a pull request. This is the "harness." The harness ecosystem is young and churning — OpenHands, Crush, Pi, Aider, opencode, and others all exist, each with different strengths, and new ones appear regularly.

We must decide whether to build our own harness, adopt one, or abstract over several. We also must preserve the "bring your own model" promise: any OpenAI-compatible endpoint via OPENAI_BASE_URL / OPENAI_API_KEY.

Decision Drivers

  • Neutrality amid churn. The best harness in 12 months may not be the best today; we shouldn't marry one.
  • Bring your own model. All harnesses we support must accept an OpenAI-compatible endpoint and key.
  • Own the parts that are ours. Stet's differentiators are the markup→brief compilation, dispatch orchestration, and PR narration — not the inner agent loop.
  • Reproducible contract. Every harness must, given a brief + a repo checkout, produce a branch and (ideally) a PR, with a streamable transcript.
  • User choice. Power users have harness preferences; Stet should honor them.

Considered Options

  • Option A: Adopt OpenHands and wrap it.
  • Option B: Define a thin Harness adapter protocol and support multiple — OpenHands, Crush, Pi (and later Aider, opencode).
  • Option C: Build our own minimal harness against the OpenAI-compatible API.

Decision Outcome

Chosen option: Option B — a thin Harness adapter protocol with multiple first-class implementations: OpenHands, Crush, and Pi. The protocol contract is: given a compiled brief, a repo checkout in a sandbox, and a model endpoint, execute the task, stream a transcript, and produce a branch + PR. Stet owns everything around the harness (brief compilation per ADR-0006, workflow execution per ADR-0005, dispatch lifecycle, and PR narration per ADR-0008); the harness owns the inner loop.

All three targets are open, actively developed in 2026, and accept OpenAI-compatible endpoints, preserving bring-your-own-model.

Consequences

  • Good, because Stet stays neutral as the harness ecosystem evolves; adding a new harness is a new adapter, not a rewrite.
  • Good, because users can pick the harness that suits a task or their preference.
  • Good, because bring-your-own-model is preserved uniformly across harnesses.
  • Bad, because each harness has a different invocation/IO shape; normalizing them behind one protocol is real, ongoing work.
  • Bad, because transcript rendering must accommodate heterogeneous harness output formats.
  • Neutral, because a default harness must still be chosen for first-run UX (candidate: OpenHands, as the most batteries-included).

Confirmation

  • A Harness protocol exists; OpenHands, Crush, and Pi each have an adapter.
  • Each adapter runs inside a ContainerRuntime sandbox (ADR-0003), accepts an OPENAI_BASE_URL / OPENAI_API_KEY, and yields a branch + PR + streamed transcript.
  • Switching harnesses requires no change to brief compilation, dispatch, or narration code.

Pros and Cons of the Options

Option A: Adopt OpenHands, wrap it

  • Good, fastest path to a real PR; most complete out of the box.
  • Bad, couples the product to one harness's model and lifecycle.

Option B: Pluggable adapter (OpenHands, Crush, Pi)

  • Good, neutral, user choice, future-proof.
  • Bad, most normalization/design work; heterogeneous transcripts.

Option C: Build our own harness

  • Good, maximum control, smallest footprint.
  • Bad, reinvents sandboxing, tool-use, and PR flows the ecosystem already solved; distracts from Stet's actual differentiators.

More Information

Harness targets: OpenHands (https://github.com/OpenHands/OpenHands), Crush (https://github.com/charmbracelet/crush), Pi (https://github.com/earendil-works/pi). All LiteLLM/OpenAI-compatible. See ADR-0003 (sandbox they run in), ADR-0005 (workflows that invoke them), and SPEC-0003.