Interpreter command contract
The interpreter command contract is the most important design artifact an interpreter exposes. It’s the primary interface through which users express intent and through which Courier guarantees consistent behavior over time.
A well-designed contract simplifies complex operations and makes them understandable, safe, and composable. A poorly designed contract leaks engine complexity, creates hidden coupling, and forces users to reason about mechanics rather than outcomes.
This section describes both the principles of good interpreter contracts and the rules that govern their evolution.
Commands are contracts, not instructions
Interpreter commands must be treated as contracts, not as instruction sets.
A command doesn’t describe how work is performed. It describes the outcome the user wants to achieve, within the constraints of the interpreter. The command schema is therefore a user-facing promise, not an implementation convenience.
As a result:
- Command fields must have clear, stable meaning
- Field names must reflect user intent, not engine terminology
- The presence or absence of a field must not subtly change semantics
- Commands must not require users to understand engine behavior to use them correctly
If a command begins to resemble a sequence of flags, switches, or positional arguments, it’s likely expressing mechanism rather than intent.
Note
In rare cases, exposing options that closely mirror an underlying CLI or command structure may be justified when doing so provides clear, tangible benefit to advanced users and reflects a well-understood, stable interface. Such designs must be approached with caution and should be treated as deliberate exceptions rather than the default pattern.
When this pattern is necessary, interpreters should prefer one of two approaches:
- Offer dual modes of operation, providing a simple, intent-driven contract for common use cases alongside an explicit advanced mode that exposes lower-level controls.
- Provide separate interpreters for the same underlying engine, with one interpreter optimized for standard, opinionated usage and another explicitly designed for advanced or expert scenarios.
In all cases, the intent-driven contract should remain the primary and recommended interface. CLI-aligned or advanced interfaces must be clearly documented, explicitly opt-in, and designed with the understanding that they trade simplicity and stability for flexibility.
What makes a good interpreter contract
A good interpreter contract is one that users can understand, trust, and reuse without knowing anything about how it’s implemented.
Good contracts share the following characteristics:
Readable without context
A user should be able to understand what a command does by reading its schema and documentation, without needing to know the underlying engine or execution details.Intent-focused
The contract expresses what’s being accomplished, not how. It uses concepts meaningful to the user’s operational or business goal.One clear responsibility
Each command does one thing. If a command tries to serve multiple unrelated purposes, it becomes harder to reason about, validate, and evolve.Hard to misuse
Invalid or dangerous combinations of inputs are impossible or explicitly rejected. The contract guides users toward correct usage rather than relying on documentation alone.Predictable and symmetrical
Similar concepts are represented consistently across commands and interpreters. Users shouldn’t have to relearn patterns unnecessarily.Constraint as guidance
Limitations are intentional. By restricting what can be expressed, the contract helps users achieve safe, supported outcomes.
These qualities aren’t accidental. They result from deliberate design and from resisting the temptation to expose every underlying capability of an engine.
Shaping the contract around intent
Interpreter command schemas must be shaped around user intent, not around the surface area of the underlying engine.
Effective contracts:
- Use nouns and verbs that reflect operational or business goals
- Group related inputs into meaningful structures
- Prefer explicit fields over implicit defaults
- Make constraints visible and understandable
Poor contracts tend to:
- Flatten many unrelated options into a single namespace/command/term
- Expose engine flags directly
- Require users to know which combinations are valid
- Encode behavior through positional or boolean switches
If an engine offers multiple ways to achieve the same result, the interpreter contract should select one or define a clear abstraction rather than passing that choice on to the user.
Validation is a user experience concern
Validation isn’t merely a technical safeguard; it’s a core part of the user experience.
Interpreter commands must validate input early and explicitly. Invalid or ambiguous input should result in clear, actionable errors that explain what’s wrong and how to fix it.
Good validation:
- Fails fast, before execution begins
- Produces human-readable error messages
- Avoids surprising defaults or silent coercion
- Treats ambiguity as an error, not a guess
A validation error is a user experience failure. Clear validation protects users from unintended outcomes and prevents fragile workflows.
Validation isn’t limited to runtime execution. Interpreter command contracts must be able to be formally validated through a known, structured specification that can be consumed by user interfaces, automation systems, and customer tooling.
The contract specification must provide sufficient metadata to support the creation of guided user experiences, including the ability to render input forms, apply validation rules, and communicate constraints clearly. This includes (but isn’t limited to) field types, required versus optional inputs, allowed values, and descriptive intent.
Users shouldn’t be required to understand or author raw JSON structures to successfully interact with an interpreter through a user interface. The contract exists to make intent accessible and safe; requiring users to reason about serialization formats undermines that goal.
A well-designed interpreter contract enables validation and guidance before execution, regardless of whether the command is authored by a human, generated by automation, or constructed through a UI.
Stability and evolution rules
Interpreter command contracts are intentionally long-lived. Users build jobs, automation, and operational processes against them, often with the expectation that they will remain valid for extended periods.
Contracts must therefore evolve conservatively:
- Additive changes are preferred and safest
- Existing fields must not change meaning
- Fields must never be repurposed
- New behavior must be introduced through new commands or versions
- Deprecated behavior must be clearly documented and supported for a reasonable transition period
When in doubt, assume a contract will be relied upon longer than anticipated.
Avoid thin wrappers and flag bags
Interpreter commands must not act as thin wrappers around execution engines.
Passing raw flags, arguments, or engine-specific options directly through the contract tightly couples users to engine behavior and undermines the purpose of the interpreter. Such designs turn interpreter contracts into unstable proxies rather than stable interfaces.
Similarly, “flag bags”—large collections of loosely related options presented in a flat structure—make contracts difficult to understand, validate, and evolve. They shift complexity onto users and increase the likelihood of invalid or unsafe configurations.
In the rare cases where exposing lower-level options is necessary to meet advanced user needs, the shape of the contract becomes critical. In these situations, the contract should be expressed as a hierarchical, tree-shaped structure that constrains available options at each level. Each selection should progressively narrow the valid configuration space, guiding users toward valid combinations and preventing incompatible choices.
This approach effectively turns a complex operation into a guided experience, similar to tab completion or a step-by-step wizard. By eliminating the possibility of invalid states, the contract reduces configuration errors while still allowing advanced control.
Even in these cases, the interpreter should continue to limit surface area wherever possible and prefer intent-driven abstractions. Exposing complexity is a last resort, and when it’s required, it must be structured to protect users from misuse.
Design for composition and downstream use
Interpreter command contracts should be designed with composition in mind.
Commands should:
- Produce structured, well-defined output
- Use stable field names and types
- Make results easy to consume by subsequent job steps
- Avoid embedding meaning solely in logs or unstructured output
While Courier output rules can transform unstructured data downstream, interpreter contracts must not depend on such transformations to be useful. Structured output is the primary interface for composition.