This article highlights a critical, often overlooked, system design problem: ambiguous empty results. When a service or agent returns an empty value (like an empty array or null), it can signify both successful operation (e.g., "no findings") and various failure states (e.g., "check never ran," "tool unavailable"). This ambiguity can lead to incorrect inferences, data corruption, and silent failures, emphasizing the need for explicit communication of execution context within responses.
Read original on Dev.to #architectureIn distributed systems, an empty result, such as an empty array (`[]`) or `null`, often serves multiple purposes. It can correctly indicate a lack of findings (e.g., "no diagnostics found"), but it can also mask underlying issues like a failed execution, an unavailable service, or a process that never started. This ambiguity leads to systems inferring success when a failure occurred, which can propagate incorrect state throughout the architecture. The core issue is that "the difference was never transmitted."
Silent Failures and Bad Inferences
An ambiguous empty value can be ingested into crucial system components like deduplication keys, cache entries, idempotency guards, or append-only logs. If a system interprets an empty failure state as a clean success, this incorrect inference becomes durable. Later reads of this state will falsely certify code or processes, preventing re-investigation and masking real problems.
A common but flawed approach to resolve ambiguity is to introduce sibling API calls (e.g., a `server_status()` endpoint). This strategy fails for two main reasons: it relies on the caller remembering to check the status, undermining the goal of a self-contained, bounded tool surface; and it introduces race conditions, as a status observed at one moment does not guarantee the state during the subsequent request execution. Simple liveness or availability bits are also insufficient, as a process can be "alive" but still useless or operating on outdated data.
The solution lies in enriching the response itself with sufficient execution context. A return type should explicitly distinguish between different states, such as `observed`, `unavailable`, and `not_run`. This ensures that an empty `findings` array only implies a negative conclusion when the `state` is `observed` and the `scope` of the observation is clear. Rejecting schemas that allow `state: "observed"` without proper coverage information prevents the re-introduction of ambiguity.
{ "state": "observed", "scope": { "revision": "<content digest>", "target": "<path>" }, "findings": [] }Furthermore, deduplication keys and cache entries should incorporate the reason class, code revision, and environment fingerprint for operational failures. This ensures that even when skipping re-execution, the decision is based on a precise, justified, and time-bounded record, rather than a permanent, ambiguous abstention. While these solutions add complexity in terms of more fields, validation paths, and storage, they provide inspectable justifications for every skipped check and verifiable execution context for every empty result, significantly improving system reliability and observability.