Skip to main content

exd explain

Static description of a flag's behavior — or of the whole namespace — walked the way the eval engine walks it, with codebase-specific pitfalls and configuration notes surfaced. Optionally renders a counterfactual outcome for a supplied context.

Synopsis

exd explain [<flag>]
[--env <env>]
[--manifest <path-or-uri>]
[--ctx <key>=<value>]...
[--format human|json]
[--http-backend curl|in-process]

Description

Always read-only; never touches the manifest.

Four shapes, distinguished by which of <flag> and --env are supplied:

  • Single-flag, single-env (exd explain <flag> --env <env>) — renders the flag in seven sections (variants & metadata, resolution walk, rules breakdown, segment closure, required context, pitfalls, notes). With --ctx, appends an eighth: the counterfactual outcome for that context — the same outcome exd eval --trace would produce.
  • Single-flag, all-envs (exd explain <flag>, no --env) — variants & metadata once at the top, then a per-env section repeating the resolution walk, rules breakdown, segment closure, required context, pitfalls, and notes for each environment. In typed-env mode ([namespace.environments] declared) every named env is iterated in declaration order; in untyped-env mode only the catch-all _ is iterated. JSON uses query: "explain-envs" with result.envs: { <env>: <trace> }.
  • Namespace-wide, single-env (exd explain --env <env>) — renders the namespace-wide structural view: every segment with its kind and the inverse segment→flags index, plus a per-flag digest (variants, rule chain summary, fall-through variant). No --ctx-bound outcome. JSON uses query: "explain-all".
  • Namespace-wide, all-envs (exd explain, no positional and no --env) — same segment index (env-independent); per-flag digest carries a per-env rule chain. The human renderer collapses envs that share _'s rules: the shared rule chain prints once under shared rules ([flag.environments._].rules):, followed by a per-env fall-through: list. Envs that declare their own [flag.environments.<env>].rules print separately under env \` (own rules):. JSON uses query: "explain-all-envs"withresult.flags[].envs: { : { rules, default_variant, unreachable_variants } }` — each env still carries its own rules array (the human-side collapse is presentation only). This is the "what's in this namespace, at a glance" index page across every env.

Catch-all in typed mode. When --env is omitted in typed-env mode, only the declared envs are iterated — the catch-all _ is not iterated separately because analyze_flag rejects "_" in typed mode and the _ block's contribution is already visible as steps 3 and 4 of every named env's resolution walk.

Explain vs. eval. Reach for exd explain when you want to understand the manifest's shape — every rule, segment, variant, and pitfall, with no runtime outcome. Reach for exd eval when you want to know what one (or every) context gets. exd explain --ctx … shows both for one flag.

Use cases

  • PR review. Before reviewing a TOML change, read the flag's full surface in one screen:

    exd explain onboarding-banner --env prod
  • Onboarding a new engineer. Hand them exd explain <unfamiliar-flag> --env prod instead of grep. They learn the rule order, the segment closure, and the required context all at once.

  • Agent navigation. An agent about to author code that branches on a flag runs exd explain --format json to discover (a) the variant set, (b) the required context attributes, (c) any pitfalls (catch-all rules being skipped, rule order changing bucketing), and (d) the configured-state notes (private attributes, untyped-env mode, public-eval gating, raw entity ids).

  • Pre-rollout audit. Inspect a flag the day before a 10% → 50% bump:

    exd explain onboarding-banner --env prod --ctx user.id=u_canary --ctx user.country=DE

    The trailing counterfactual outcome confirms what a canary user would see.

  • Pitfall discovery. exd explain is the only command that surfaces hint codes like catch_all_rules_skip and rule_order_changes_bucketing — easy traps that don't fire any lint diagnostic but bite at runtime.

Arguments and flags

Same flags as exd eval minus --trace. exd explain is always the trace command.

Argument / flagRequiredNotes
<flag> (positional)noFlag key. Omit to render the namespace-wide structural view. Unknown key → exit 1.
--env <env>noEnvironment slug. Omit to iterate every declared env (typed mode) or just the catch-all _ (untyped mode). Unknown slug in typed mode → exit 1.
--ctx <key>=<value>no, repeatableSee conventions § --ctx k=v parsing. When supplied, appends a counterfactual-outcome section (per env when --env is omitted).
--manifest <path-or-uri>noDefaults to ..
--format human|jsonnoDefaults to human.
--http-backend curl|in-processnoURI loads only.

Output sections (--format human)

In order, every invocation renders:

  1. Variants & metadata — variant table, owner, lifecycle, tags, and an unreachable variants list when one is declared but never produced. Surfaced first so the reader sees the menu of possible answers before walking through how the engine picks one.
  2. Resolution walk — the four-step plan from the eval engine. Each step is annotated would fire here for no-context input iff it deterministically matches with zero context attributes.
  3. Rules breakdown — for each rule reachable in the walk: variant, audience (segment or inline predicate), flattened infix predicate, source citation. The block ends with a default: -> variant '<name>' line — the variant the engine returns when no rule matches (env block's variant, falling through to _-block's variant).
  4. Segment closure (tree) — every segment referenced by the flag, transitively, rendered as a ├─ / └─ tree rooted at the segments named directly from rules. A segment referenced from multiple roots renders fully on first appearance and collapses to <name> (see above) afterwards. Bucket details and the resolved bucketing attribute are included.
  5. Required context — every attribute the flag's predicates touch, with inferred type and source citation. Same data exd schema emits.
  6. Pitfalls — codebase-specific hints for situations that may surprise a reader (catch-all rules being skipped, rule order changing bucketing). Only emitted when the situation actually applies.
  7. Notes — informational callouts about the configured state of the flag or namespace (public-eval gating, redacted attributes, raw entity ids, untyped-env mode). Not problems, just things worth knowing.

With --ctx, an eighth section appends:

  1. Counterfactual outcome — the same outcome block exd eval --trace would render.

Pitfall codes

Stable identifiers for situations that may surprise a reader of the flag — gotchas, not lint diagnostics, and never gate execution. Only emitted when the situation applies.

OrderCodeTrigger
1catch_all_rules_skip[flag.environments.<env>].rules AND [flag.environments._].rules are both non-empty. The shared rules under _ won't run for this env.
2rule_order_changes_bucketingTwo or more bucket-bearing segments appear in the rule chain with differing entity_id_attribute values. Rule order decides which value gets hashed for users matching more than one rule.

Note codes

Stable identifiers for configured state worth knowing — informational, not problems. Always emitted when the condition holds, regardless of whether anything is wrong.

OrderCodeTrigger
1public_evaluate_gatingTyped mode only: renders whether [namespace.environments.<env>].public_evaluate is on or off, with novice-friendly framing of what that means for browser/mobile eval.
2private_attributes_filtering[namespace].private_attributes ∪ [flag].private_attributes is non-empty. Lists the union with source attribution (namespace / flag).
3raw_entity_ids[namespace].raw_entity_ids = true. Evaluation logs carry the raw bucketing identifier, not its SHA-256 hash.
4untyped_env_modeNamespace.is_typed_env() == false. Any env name is accepted (including typos); public_evaluate cannot be configured.

Pitfalls and notes share the same { code, message, detail, remedy, data } shape so renderers and parsers treat them uniformly — they're surfaced in separate top-level arrays (pitfalls, notes) and rendered in separate blocks for the reader.

Examples

Bare explain

$ exd explain onboarding-banner --env prod
=== Variants & metadata
variants:
treat_a string "Glad to have you."
treat_b string "Welcome aboard."
control string ""
owner: growth-team
lifecycle: active
tags: [onboarding]

=== Resolution walk
[flag.environments.prod].rules
rule[0] segment `us-only` predicate: user.country == "US"
rule[1] segment `pro-tier` predicate: user.tier == "pro"
[flag.environments.prod].variant = control
[flag.environments._].rules SKIPPED — env declared its own rules
[flag.environments._].variant = control

=== Rules breakdown
rule[0]: treat_a
audience: segment `us-only`
predicate: user.country == "US"
source: flags/onboarding-banner.toml:14

rule[1]: treat_b
audience: segment `pro-tier`
predicate: user.tier == "pro"

default: -> variant `control` (returned when no rule above matches)

=== Segments referenced (tree)
├─ us-only predicate user.country == "US"
└─ pro-tier predicate user.tier == "pro"
bucketing attribute: (none)
(no rule references a bucket-bearing segment)

=== Required context (this flag in 'prod')
user.country string required by rule[0] inline predicate
user.tier string required by rule[1] inline predicate

=== Pitfalls
[catch_all_rules_skip] this flag has 2 shared rule(s) that apply to every other env — but they will NOT run in `prod`, because `prod` has its own 2 rule(s)
why: exd lets you define one shared set of rules for a flag that applies to every env, plus per-env overrides. ...
fix: if you meant for both sets to apply in `prod`, copy the shared rules into the env's own rules block. ...

=== Notes
[public_evaluate_gating] only your backend can evaluate this flag in `prod` (public evaluation is OFF)
why: exd has two kinds of API tokens: server tokens (kept secret on your backend) and public tokens (safe to ship in a browser or mobile bundle). ...
fix: if you intended browser or mobile clients to evaluate this flag in `prod`, turn on public evaluation for that env in `namespace.toml`. ...

With counterfactual context

$ exd explain onboarding-banner --env prod --ctx user.id=u_42 --ctx user.country=US
[...all seven sections as above...]

=== Counterfactual outcome
outcome: treat_a
via: rule[0] in env `prod` matched segment `us-only`
ctx: user.id="u_42", user.country="US"

JSON

$ exd explain onboarding-banner --env prod --format json
{
"query": "explain",
"query_version": "1",
"schema_version": "1",
"exd_version": "0.4.0",
"inputs": { "flag": "onboarding-banner", "env": "prod", "ctx": {} },
"result": {
"manifest_fingerprint": "marketing@a1b2c3",
"variants": { /* variant table */ }, // surfaced first to match human output ordering
"unreachable_variants": [],
"walk": [ /* per-step entries */ ],
"rules_breakdown": [ /* per-rule blocks */ ],
"default_variant": "control", // fall-through variant when no rule matches
"segment_closure": { // tree-friendly shape
"segments": [ /* per-segment {key, predicate, bucket, kind, references} */ ],
"roots": [ /* segment keys named directly from rules */ ],
"bucketing_attribute": null,
"bucketing_resolution": null
},
"metadata": { "owner": "growth-team", "lifecycle": "active", "tags": ["onboarding"] },
"required_context": [ /* per-attribute records */ ],
"pitfalls": [ // surprise gotchas
{ "code": "catch_all_rules_skip", "message": "...", "detail": "...", "remedy": "...", "data": { ... } }
],
"notes": [ // configured-state callouts
{ "code": "public_evaluate_gating", "message": "...", "detail": "...", "remedy": "...", "data": { ... } }
],
"outcome": null // present iff --ctx was supplied
},
"diagnostics": [],
"provenance": { "source": ["."], "engine": "static", "record_count": 0, "time_range": {} }
}

pitfalls[].code is drawn from the Pitfall codes table above; notes[].code from the Note codes table. Each entry has the same { code, message, detail, remedy, data } shape — message is the headline, detail explains why in plain language, remedy says what to do. segment_closure.roots plus segment_closure.segments[].references carry the parent-child links the human renderer uses to draw the tree.

provenance.engine is "static+ctx" when --ctx was supplied.

Namespace-wide form (no <flag> positional)

$ exd explain --env prod
namespace: marketing env: prod (typed) 3 segment(s), 2 flag(s)

segments:
pro-tier predicate used by: pricing-experiment
unused unused used by: (unused)
us-only predicate used by: onboarding-banner

flags:
onboarding-banner (string, variants: [control, treatment])
[flag.environments.prod].rules
rule[0] -> treatment segment `us-only`
fall-through -> variant `control`

pricing-experiment (boolean, variants: [off, on])
[flag.environments.prod].rules
rule[0] -> on segment `pro-tier`
fall-through -> variant `off`
$ exd explain --env prod --format json
{
"query": "explain-all",
"query_version": 1,
"inputs": { "flag": null, "env": "prod", "ctx": {} },
"result": {
"namespace": "marketing",
"env": { "name": "prod", "typed": true },
"segments": [
{ "key": "pro-tier", "kind": "predicate", "used_by": ["pricing-experiment"] },
{ "key": "unused", "kind": "unused", "used_by": [] },
{ "key": "us-only", "kind": "predicate", "used_by": ["onboarding-banner"] }
],
"flags": {
"onboarding-banner": {
"type": "string",
"variants": ["control", "treatment"],
"rules": [
{ "index": 0, "variant": "treatment",
"audience": { "kind": "segment", "segment": "us-only" } }
],
"default_variant": "control",
"unreachable_variants": []
}
}
},
"diagnostics": [],
"provenance": { "engine": "static", "record_count": 0, "source": ["."] }
}

The kind field on a segment is one of predicate, composite, bucket, or unused (the last when no flag references the segment, transitively).

Exit codes

CodeCondition
0Success.
1Unknown flag; manifest lint errors; typed-mode unknown env.
2Bad --ctx; manifest URI fetch failure.

See also

  • exd eval — single-shot variant lookup.
  • exd schema — context-shape extraction (same data as the "Required context" section).
  • exd fixtures — generate test inputs from this flag's rule chain.
  • Resolution algorithm — the four-step resolution algorithm.