mloda 0.11: Plugin Resolution Errors Evolve from One-Line Messages to Full Elimination Trails

mloda 0.11 transforms plugin resolver errors from cryptic one-liners into full candidate elimination diagnostics.
mloda 0.11 overhauls its plugin resolver's error reporting by replacing uninformative one-line messages with complete elimination trails showing every candidate plugin and the exact gate where it was rejected. The article examines three key design principles: modeling rejection reasons as structured data rather than strings, isolating exceptions per candidate using the bulkhead pattern, and ensuring pre-check diagnostics and runtime errors share a single source of truth.
A Common but Tricky Problem: The Resolver Just Says "Not Found"
In the field of Feature Engineering, an increasing number of tools are adopting plugin-based architectures to decouple feature definition from computation. Feature engineering is a critical stage in the machine learning pipeline that transforms raw data into model-ready features, typically accounting for 60-80% of the workload in data science projects. As feature counts grow from dozens to thousands, monolithic feature computation code becomes unmaintainable, driving the industry toward plugin-based architectures — where each feature or feature family is defined by an independent plugin and managed through a unified registry. This pattern is also reflected in feature platforms like Feast and Tecton.
mloda is an open-source feature engineering layer under the Apache-2.0 license, built around a plugin-based resolver: you request a feature by name, and the resolver decides which plugin should handle the computation. At its core, mloda's resolver is a Service Locator that dynamically looks up and binds to specific computation implementations at runtime based on feature names — similar in principle to how a dependency injection container works, but oriented toward data transformations rather than general-purpose services.
The elegance of this design lies in its flexibility — but the cost is often debugging pain. Before mloda 0.11, a failed feature request would return nothing more than a terse one-liner:
No feature groups found for feature name: 'sales__mean_aggr'.
Use resolve_feature(name, options=...) to debug feature resolution.
This message answers virtually none of the truly useful questions: Was the name misspelled? Was the domain misconfigured? Was a required option missing? The only recourse for the developer was to crack open the resolver source code and reverse-engineer the failure reason line by line. For anyone who has ever maintained a plugin system, this kind of "black-box" error message is all too familiar.

The 0.11 Improvement: From "Result" to "Elimination Process"
The core change in mloda 0.11 transforms the same failure from a terse conclusion into a complete elimination trail. This mechanism is essentially a multi-stage filter pipeline: the resolver maintains a list of candidate plugins and passes them through nine successive gates — domain matching, option validation, type compatibility, and more. Each gate is a predicate function that returns pass or reject. This design draws inspiration from overload resolution in compilers — the C++ compiler generates similar candidate elimination reports when selecting function overloads.
Now the same request returns:
No feature groups found for feature name: 'sales__mean_aggr'.
Requested domain: 'marketing'.
Feature group(s) eliminated while matching 'sales__mean_aggr':
- AggregatedFeatureGroup (domain): declares domain 'default_domain', but the run requested 'marketing'
- PandasAggregatedFeatureGroup (domain): declares domain 'default_domain', but the run requested 'marketing'
- PolarsLazyAggregatedFeatureGroup (domain): declares domain 'default_domain', but the run requested 'marketing'
The difference is immediately obvious. The new version doesn't just tell you "not found" — it lists every candidate plugin the resolver considered along with which gate eliminated each one. The design of nine stage labels is also reminiscent of HTTP content negotiation with its multi-dimensional matching: a server must simultaneously consider Content-Type, language, encoding, and other dimensions to select the best response, and a mismatch on any single dimension eliminates a candidate. In the example above, the root cause is clearly exposed: the request specified the marketing domain, but all three aggregation plugins declared default_domain, so they were all eliminated at the "domain matching" gate.
Developers no longer need to guess or read source code — the error message itself constitutes a complete diagnostic path. This is a textbook example of "making implicit debugging knowledge explicit" through engineering improvement.
Three Implementation Details Worth Learning From
For any developer maintaining a plugin registry or resolver, mloda 0.11's implementation offers several design ideas worth considering.
1. Rejection Reasons Exist as Data First; Text Is Just the Rendering Layer
mloda doesn't cobble errors together as strings directly. Instead, it first records each "rejection" as structured data: a stage label (one of nine possible values) plus the specific reason for that candidate. The final text report is rendered from this data.
This "data first, text second" approach is crucial. Modeling rejection reasons as structured data rather than strings is an important practice in Observability engineering. In modern systems, frameworks like OpenTelemetry have demonstrated the enormous advantages of structured telemetry data over plain-text logs: they can be indexed, queried, aggregated, and visualized. When rejection reasons are structured records with stage labels, teams can easily track "which elimination stage fired most frequently over the past week," thereby identifying systemic configuration issues. This pattern has also been successfully applied in the Rust compiler's error reporting system: rustc models diagnostics as structured objects with error codes, suggested fixes, and related locations, enabling IDEs to consume this data directly and offer inline fix suggestions.
This means elimination information is not only human-readable but also machine-consumable — useful for automated diagnostics, log analysis, and even visualization. The nine stage labels form a well-defined failure taxonomy, giving "why was it eliminated" a unified semantic framework.
2. A Single Plugin Error Won't Take Down the Entire Report
In any real-world plugin ecosystem, there will always be poorly written plugins. mloda applies per-candidate exception isolation on each candidate's match hook: if a plugin's matching logic throws an exception, the exception is contained within that candidate's scope and won't turn the entire diagnostic report into a blank page.
This approach is known in distributed systems design as the Bulkhead Pattern, named after the watertight compartments in ships — even if one compartment floods, the others remain intact. Netflix's Hystrix library popularized this pattern in the microservices world, using thread pool isolation to prevent a single downstream service failure from cascading. In plugin systems, this principle is equally critical: third-party plugin code quality is unpredictable, and any plugin might crash due to null pointers, type errors, or infinite loops. If the diagnostic logic doesn't wrap each plugin's match call in try-catch isolation, a single defective plugin would abort the entire diagnostic process, leaving users back at square one with zero information.
This point is extremely important in practice. A robust diagnostic system should be able to provide useful information even when some components are broken — otherwise, one bad plugin could swallow all the diagnostic clues for every other candidate.
3. Pre-checks and Real Runs Never Drift Apart
mloda provides a pre-check interface, mlodaAPI.diagnose, that never throws exceptions; the actual run throws a typed FeatureResolutionError containing exactly the same facts.
This design guarantees that there is never any semantic drift between the pre-check report and the runtime exception. It solves a common engineering problem: when check logic and execution logic live in two separate code paths, they almost inevitably diverge over time. Terraform's plan/apply model has long suffered from this issue — plan shows a safe change, but apply fails due to validation logic not covered by plan. mloda eliminates this problem by having diagnose and actual resolution share the same matching engine, embodying what's known in software engineering as the "Single Source of Truth" principle.
It's also worth noting the deliberate design choice that diagnose never throws exceptions — it follows the CQS (Command-Query Separation) principle that "queries should not produce side effects," ensuring the diagnostic operation itself is safe and repeatable. The elimination reasons you see during diagnosis are exactly the same as those you get when a real run fails. This avoids a common pitfall in many systems: diagnostic tools and the real execution path use separate logic, so the diagnostic says everything is fine while the run fails.
An Open Design Question: Full Chain or Most Likely Cause?
Interestingly, mloda's author also raised an open question when sharing this improvement: for plugin or registry-based systems, should you display the full rejection chain, or only provide the most likely root cause?
This is a classic trade-off in error message design, with different answers across multiple mature systems:
- Full chain: Maximizes information, suitable for complex scenarios and advanced users, but when the number of candidates is large, the report can become verbose and actually obscure the key information. The TypeScript compiler takes this approach, showing the complete type incompatibility path.
- Most likely cause: More concise and user-friendly, but requires the system to judge "which reason matters most" — and if that judgment is wrong, it can lead users down the wrong path. The Elm language goes to this extreme, investing significant engineering effort to generate precise single root-cause suggestions.
Academia has also studied this: Shneiderman's "Information Density Theory" suggests that expert users prefer high-density information to support pattern recognition, while novice users need low-density, highly guided prompts. A possible compromise is Progressive Disclosure: show only the most likely root cause by default, but provide an option to expand the full chain. Kubernetes' kubectl command uses a similar strategy — default output is concise, and adding flags like --v=6 progressively increases verbosity.
mloda chose the full chain — presenting all candidates along with the first gate where each one failed. Given that feature resolution failures are often multi-dimensional (domain, options, types, etc.), the full chain does indeed reduce trial-and-error cycles. However, for large-scale systems with many candidates, some form of tiering or highlighting mechanism may be needed in the future to balance information density with readability.
Takeaway
mloda 0.11's update may seem like just an error message improvement, but it reflects the mindset a mature engineering system should have: embed the knowledge needed for debugging into the system's output, rather than leaving users to reverse-engineer the source code.
Whether or not you use mloda, the three design principles behind it — structured rejection reasons as data, per-candidate exception isolation, and pre-check/runtime fact consistency — are well worth serious consideration for any developer building plugin-based architectures or resolver systems. Great error messages are, in themselves, the best documentation.
Related articles

Google Antigravity + Gemini 3.7 Flash: An Efficient Approach to Multi-Agent Collaboration
Explore how Google's Antigravity orchestration platform and Gemini 3.7 Flash model work together to solve complex multi-agent math and engineering problems.

Max Plan Shifts from Subscription to Credits — Has Your Usage Actually Shrunk?
AI coding subscriptions shift from session-time to API credits. A $100 Max plan now offers $300 in credits at a 3:1 ratio — has actual usage really shrunk?

OpenAI Cuts Off Cursor: The Full Story Behind the Feud and China's Push for Open-Source, Affordable AI
OpenAI cuts Cursor's model access over Musk's acquisition; Cursor pivots to Claude. Meanwhile, Chinese AI models like Qwen, GLM, and Hunyuan push open-source affordability, accelerating AI democratization.