Anti-patterns
This section describes common anti-patterns encountered when designing or implementing Courier interpreters. These patterns undermine intent-driven design, weaken contracts, and erode user trust. In many cases, they arise from convenience, familiarity with underlying tools, or pressure to just expose one more option.
Recognizing these anti-patterns early is critical. Most are difficult to reverse once users begin to depend on them.
Thin wrapper interpreters
Description:
The interpreter exposes the underlying engine almost directly, passing flags, arguments, or commands through with minimal abstraction.
Why it’s harmful:
- Couples users tightly to engine behavior
- Inherits engine instability and churn
- Turns the interpreter into an unstable proxy
- Forces users to understand mechanics rather than intent
Smell:
“If I already know the CLI, I know how to use the interpreter.”
Better approach:
Design an intent-driven contract that absorbs engine complexity and exposes only supported, meaningful outcomes.
Flag bags and flat option spaces
Description:
The interpreter contract exposes a large, flat set of loosely related options, often mirroring CLI flags.
Why it’s harmful:
- Makes valid combinations difficult to reason about
- Increases configuration errors
- Complicates validation and evolution
- Forces users to self-police correctness
Smell:
“Users need to read documentation to know which options can be combined.”
Better approach:
Use structured, hierarchical contracts that progressively constrain choices and guide users toward valid configurations.
Overloaded commands
Description:
A single command attempts to serve multiple unrelated purposes depending on which fields are supplied.
Why it’s harmful:
- Makes behavior ambiguous
- Complicates validation and error handling
- Obscures intent
- Breaks contract stability over time
Smell:
“This command behaves very differently depending on which options are present.”
Better approach:
Create separate commands or interpreters with clear, singular responsibility.
Encoding semantics in logs
Description:
The interpreter relies on stdout or stderr content to communicate outcomes, decisions, or data.
Why it’s harmful:
- Breaks automation and composition
- Encourages brittle parsing
- Makes outcomes ambiguous
- Treats logs as APIs
Smell:
“Downstream steps parse logs to determine success or extract values.”
Better approach:
Return all meaningful results as structured output or artifacts. Treat logs as diagnostic only.
Hidden side effects
Description:
The interpreter performs changes or external actions that aren’t clearly reflected in its output.
Why it’s harmful:
- Violates user expectations
- Prevents proper auditing
- Makes retries unsafe
- Undermines trust
Smell:
“The interpreter succeeded, but something unexpected changed.”
Better approach:
Make all side effects explicit through structured output and documentation.
Non-deterministic behavior without disclosure
Description:
The interpreter produces different outcomes or outputs under the same inputs without documenting or signaling this behavior.
Why it’s harmful:
- Makes automation unpredictable
- Breaks retries and re-execution
- Undermines confidence in results
Smell:
“Running the same job twice produces different results for unclear reasons.”
Better approach:
Prefer deterministic behavior. Where this isn’t possible, document and signal non-determinism explicitly.
Validation deferred to execution
Description:
Invalid input is only discovered during execution, often through engine failure.
Why it’s harmful:
- Produces confusing errors
- Wastes execution time
- Exposes engine internals
- Creates a poor User Experience (UX)
Smell:
“The error message comes from the underlying tool.”
Better approach:
Validate early using the interpreter contract and return clear, human-readable errors
Silent partial success
Description:
The interpreter completes some actions successfully but doesn’t clearly communicate partial failure.
Why it’s harmful:
- Leads to incorrect assumptions
- Breaks downstream logic
- Complicates remediation
Smell:
“It said success, but not everything worked.”
Better approach:
Explicitly represent partial success in structured output and detail what succeeded and what didn’t.
Escape hatches as the default
Description:
Advanced or unconstrained interpreters become the primary way users interact with a system.
Why it’s harmful:
- Normalizes unsafe behavior
- Undermines simpler, safer contracts
- Makes governance difficult
Smell:
“Everyone uses the advanced interpreter because it’s more powerful.”
Better approach:
Treat escape hatch interpreters as opt-in exceptions and invest in strong, intent-driven defaults.
By avoiding these anti-patterns, interpreter authors protect users from fragility, preserve long-term contract stability, and reinforce Courier’s core design principles: intent, constraint, and trust.