Skip to main content

Core concepts

This page defines every first-class object in exd. Other reference pages assume familiarity with these definitions.


Flag

A flag is the primitive unit of the system. Every flag lives inside a flag namespace and is uniquely identified by its key within that flag namespace.

The flag key is the filename stem of flags/<flag-key>.toml — there is no separate flag.key field. Pattern: [a-z][a-z0-9_-]*, max 63 characters. Immutable in practice (renaming requires coordinated SDK and rule updates).

Flag metadata fields:

FieldTypeDescription
typeenumOne of boolean, string, integer, float, json. Determines the type of all variant values.
descriptionstring (optional)Human-readable explanation of what the flag controls.
ownerstring (optional)Team or individual responsible for this flag.
lifecycleenumOne of development, active, retired. Affects linter diagnostics; does not disable evaluation.
tagslist of strings (optional)Arbitrary labels for search and filtering.
private_attributeslist of strings (optional)Context attributes the SDK strips from this flag's telemetry records.

The variant served at evaluation time is decided per environment: every flag declares a mandatory catch-all block [flag.environments._] plus zero or more per-environment blocks. There are no variant-selection fields at the [flag] level. See resolution.

Flag-namespace-level telemetry is the only telemetry switch. There is no per-flag telemetry_enabled.

Lifecycle states

StateMeaningLint effect
developmentUnder active construction.None.
active (default)In use.None.
retiredNo longer in use; awaiting deletion.W002 when any rule path exists; W003 when no rules exist.

Lifecycle has no effect on evaluation. A retired flag still evaluates normally; retirement is a signal to engineers to remove call sites and delete the file.

Full field reference: flag.md.


Variant

A variant is one of the named outcomes a flag can return. Every flag declares a closed set of variants; evaluation always resolves to exactly one variant.

PropertyDescription
keyString identifier for this variant. Unique within the flag. Pattern: [a-z][a-z0-9_-]*, max 63 characters.
valueThe actual value returned to calling code. Type-compatible with the flag's declared type.

Variants in schema 0.1 are scalar mappings (<key> = <value>). A richer table-form ([flag.variants.<key>] with nested value and description fields) is reserved for a future minor version and rejected today (E014).

Conventional names:

Use caseCommon variants
Boolean kill-switchon, off
Boolean experimentcontrol, treatment
Multivariate experimentcontrol, variant_a, variant_b, …
Numeric configurationdefault, relaxed, strict
JSON configurationdefault, <environment-tier>

Variants are declared once at the flag level and shared across all environments. Rules in any environment must reference only variant keys declared in the flag's variants map.

Full field reference: flag § Variants.


Rule

A rule is an ordered audience-to-variant mapping that lives inside a flag's per-environment block. Rules are evaluated top-to-bottom; the first rule whose audience matches the current entity wins and its variant is returned immediately.

A rule identifies its audience exactly one of two ways:

  • segment = "<segment-key>" — a named segment whose membership must evaluate to true.
  • predicate = { ... } — an inline predicate, evaluated as if it were a single-use segment.

E009 fires when a rule declares neither; E036 when it declares both.

FieldTypeRequiredDescription
segmentstringone of segment / predicate MUST be presentSegment key whose membership is tested.
predicateinline tableone of segment / predicate MUST be presentInline predicate expression.
variantstringyesThe variant key to return when this rule matches.
descriptionstringoptionalHuman-readable label. Strongly recommended for production rules.

Rules are declared as an ordered array under [[flag.environments.<env>.rules]] (including on the catch-all _). Position 0 is evaluated first. The linter detects duplicate-segment shadowing within the same array via W012.

Full field reference: flag § Rule fields.


Predicate

A predicate is a recursive boolean expression that defines audience membership. Evaluated against an evaluation context.

At the leaf level, a predicate is one of:

  • Attribute atom{ attribute = "<dot.path>", op = "<operator>", value = ... } (or values = [ ... ] for in / not_in).
  • Segment reference{ segment = "<segment-key>" }. Semantically equivalent to testing membership in that segment.

Compound predicates use three boolean combinators:

CombinatorSemantics
andArray of sub-predicates; true iff every sub-predicate is true. Short-circuits on the first false.
orArray of sub-predicates; true iff any sub-predicate is true. Short-circuits on the first true.
notSingle sub-predicate; boolean negation.

Combinators can be nested to arbitrary depth (W005 warns above 5 levels). Circular segment references are lint errors (E012).

The full operator vocabulary lives at predicates § Operator quick reference.


Segment

A segment is a reusable, named audience. Flag rules reference segments by key, which avoids duplicating targeting logic across many flags and makes rollout audiences auditable as first-class objects.

The segment key is the filename stem of segments/<segment-key>.toml — there is no separate segment.key field. Pattern: [a-z][a-z0-9_-]*, max 63 characters.

A segment declares one or both membership constraints:

  • Predicate segment[segment.predicate] defines a boolean expression over the evaluation context.
  • Bucket segment[segment.bucket] defines a deterministic hash bucket range, used for gradual rollouts and experiments.

When both are present, membership requires both to pass (the predicate gates the bucket hash).

FieldTypeDescription
descriptionstring (optional)Human-readable explanation of who is in this segment.
predicatepredicate expression (optional, but one of predicate/bucket MUST be present)See predicates.
bucketbucket table (optional, but one of predicate/bucket MUST be present)See buckets.

Segments are scoped to their flag namespace. A flag in flag namespace payments cannot reference a segment in flag namespace growth; cross-namespace dependencies are not supported.

Full field reference: segment.md.


Environment

An environment is a named deployment context (e.g., production, staging, development) that flag rules MAY consult to vary behavior across operational tiers.

The environment list is optional. A flag namespace can:

  • Declare a typed roster in [namespace.environments] inside namespace.toml — buys typo-checked env names and per-env metadata (display_name, public_evaluate).
  • Omit both namespace.toml and the env list — untyped-env mode. The SDK accepts any env string and a flag's behavior is whatever its [flag.environments._] catch-all resolves to.

Every flag declares one or more [flag.environments.<env>] blocks. The block named _ is reserved as the catch-all and is mandatory:

BlockRole
[flag.environments._]Catch-all. Mandatory. MUST declare a fallback variant; MAY declare a rules array. Applies to any env without its own block.
[flag.environments.<env>]Per-env override. Optional. Same shape as _. If rules is declared, it completely replaces _'s rules for this env. If variant is declared, it replaces _'s default.

The SDK passes an environment string into every evaluation. Resolution walks the four-step algorithm to pick a variant: env's rules → env's variant_'s rules (skipped if env declared its own) → _'s variant. See resolution.

Environment slugs follow the slug pattern: [a-z][a-z0-9-]*. Underscores are NOT permitted in env slugs (unlike flag and segment keys).


Tenant

A tenant is the administrative boundary for one organization, business unit, or identity realm inside an exd installation. A single installation can host multiple tenants.

Each tenant maps to exactly one login authority:

  • SSO tenant — Users authenticate through a configured SSO provider such as OIDC or SAML. Tenant membership and the initial tenant-admin set come from the provider's identity claims.
  • Email-domain tenant — Users authenticate by email and are assigned to the tenant by their verified email domain. Every verified user in the tenant is a tenant admin.
PropertyDescription
slugGlobally unique, URL-safe identifier. Pattern: [a-z][a-z0-9-]*, max 63 chars. Immutable after creation.
display_nameHuman-readable name. Mutable.
login_modesso or email_domain.
sso_providerProvider identifier when login_mode = "sso".
email_domainVerified email domain when login_mode = "email_domain".
created_atServer-assigned timestamp.

A tenant can contain many flag namespaces. Tenant admins have full rights across every flag namespace in the tenant: create / delete namespaces, upload or roll back manifests, issue service tokens, and manage namespace admins. Tenant admins do not have rights in other tenants unless they are also admins there.

Tenants are not declared in the manifest; they are server-side metadata. The CLI to manage them is exd-server-admin tenant.


Flag namespace

A flag namespace (or just "namespace" in identifier contexts like namespace.toml) is the flag-management boundary owned by a team inside a tenant. It is the unit of upload authorization, evaluation isolation, and manifest versioning.

PropertyDescription
tenant_slugThe tenant that owns this flag namespace. Immutable after creation.
slugUnique per-tenant. Pattern: [a-z][a-z0-9-]*, max 63 chars. Immutable after creation.
display_nameHuman-readable name. Mutable.
environmentsOrdered list of environment keys declared for this flag namespace.
telemetry_enabledBoolean (default true). When false, the server skips telemetry recording for all flags.
created_atServer-assigned timestamp.
last_upload_atTimestamp of the most recent successful manifest upload.
current_versionMonotonically increasing integer. Incremented on every successful upload.

A flag namespace belongs to exactly one tenant and cannot be moved between tenants. Namespace slugs are unique per-tenant, not globally — different tenants can each have a marketing namespace.

Flag namespaces can have many namespace admins. The user who creates a flag namespace is added as its first namespace admin by default. Namespace admins can manage that flag namespace, including uploading manifests, issuing namespace-scoped service tokens, and granting / revoking namespace-admin access for other users in the same tenant. Tenant admins can perform all namespace-admin actions in every flag namespace in their tenant.

Namespace service tokens are scoped to exactly one flag namespace. namespace-read tokens can download manifests and evaluate flags; namespace-write tokens can also upload manifests. namespace-client tokens are public-evaluation-only tokens bound to one environment. There are no cross-namespace rule dependencies; a flag in flag namespace alpha cannot reference a segment in flag namespace beta.

The flag namespace's manifest is the complete TOML file tree rooted at <namespace-slug>/. It is uploaded as an atomic unit. See directory-layout for the on-disk shape and packaging for upload.

Full field reference: namespace.md.


Naming conventions

Two character classes are used throughout:

ClassPatternUsed by
slug[a-z][a-z0-9-]*tenant slug, namespace slug, environment slug
key[a-z][a-z0-9_-]*flag key, segment key, variant key

Rules:

  • Must begin with a lowercase ASCII letter (not a digit, hyphen, or underscore).
  • Subsequent characters: lowercase ASCII letters, decimal digits, hyphens (and, for keys only, underscores).
  • Maximum length: 63 characters.
  • All comparisons are byte-exact case-sensitive.

Tenant and namespace slugs are immutable after creation. Namespace slugs appear in SDK configuration, manifest repository paths, service-token bindings, and audit log entries. Changing a namespace slug would require coordinated deploys across every SDK consumer; the supported approach is to create a new flag namespace with the desired slug, migrate flags and segments, issue new tokens, update SDK configuration, and delete the old one after consumers move.


User and admin roles

Users are human identities authenticated through a tenant's login authority. A user may belong to more than one tenant only if those tenants' login authorities admit the same identity.

exd defines two human admin roles:

RoleScopePermissions
tenant_adminOne tenantFull rights across all flag namespaces in the tenant.
namespace_adminOne flag namespaceFull rights inside that flag namespace, including manifest operations, token issuance, and namespace-admin management.

For SSO tenants, tenant admins are configured explicitly or derived from trusted SSO claims. For email-domain tenants, every verified user with that domain is a tenant admin, so namespace-admin lists are useful for audit and UI ownership but do not restrict any user in that tenant.

Service tokens are NOT human users and do NOT become admins. They carry explicit API scopes and are bound to a flag namespace, tenant, or installation-level superadmin scope at issuance time. See Access Control for the permission matrix and Tokens for the lifecycle model.


Evaluation context

The evaluation context is the typed key-value map provided by the caller at the time of flag evaluation. It is the input to every segment predicate and bucket assignment.

Attribute value types:

TypeTOML / JSON representationExample
stringJSON string"us-east-1"
booleanJSON booleantrue
integerJSON integer42
floatJSON number with decimal3.14

Attribute keys are strings. The evaluation context is flat (no nested objects); hierarchical data is encoded as prefixed keys (e.g., "user.country"). The dot is a convention for organizing the schema, not a structural separator the SDK looks at.

The evaluation context is not persisted anywhere. It is used only during evaluation and is never stored in telemetry events (to avoid accidental PII capture). Only derived signals (variant key, rule matched) are recorded by default; the SDK can optionally include the non-private subset of context attributes via the context_attributes field.

A common convention is to include a dedicated entity identifier attribute (e.g., user.id or entity.id) for percentage-bucket segments. The bucket algorithm uses the entity identifier to assign deterministic segment membership; see buckets.

Full field reference: evaluation-context.


Manifest

A manifest is the complete TOML file tree describing a single flag namespace. It is the canonical source of truth for all flag definitions, segment definitions, environment declarations, and per-environment flag configuration.

Structure:

<namespace-slug>/
namespace.toml # OPTIONAL — omit to get sensible defaults and untyped envs
flags/
<flag-key>.toml
...
segments/ # OPTIONAL — only needed when rules reference named segments
<segment-key>.toml
...

Environments, when typed, are declared in the [namespace.environments] table inside namespace.toml, not as separate files. A flag namespace MAY also run in untyped-env mode (no namespace.toml, or no [namespace.environments]), in which case the env string passed to evaluation is consulted only by flags that declare per-env overrides.

A manifest is uploaded as an atomic unit via PUT /api/v1/tenants/{tenant}/namespaces/{namespace}/manifest. The server runs the linter on every upload and rejects the upload (HTTP 422) if any lint errors are present. On success, the server assigns the next monotonically increasing version number and invalidates the in-memory manifest cache.

The manifest is versioned: the server retains the full upload history. Clients can request a specific version by number. The If-Version: <n> upload header enables optimistic concurrency control for agent workflows. See packaging.

Each file in the manifest declares schema_version = "0.1" at the top level. The schema version follows a major.minor model. The server accepts any minor version within its current major; mixing minors across files in one flag namespace is allowed (with W008). See schema-versioning.

The manifest format is specified in full in reference/manifest/.


Evaluation result

An evaluation result is the structured object returned by any evaluation call — whether performed by the server via the HTTP API or by an SDK against its local manifest cache.

FieldTypeDescription
flag_keystringThe key of the flag that was evaluated.
flag_versionintegerThe manifest version from which this result was derived. Allows callers to correlate results to a specific upload.
valueflag's declared typeThe resolved value. Concrete type matches the flag's type field.
variant_keystringThe key of the variant that was selected.
rule_matchedstringThe reason the variant was selected — see below.

rule_matched values:

ValueMeaning
"rule:<i>" (env's rules)The <i>-th rule of [flag.environments.<env>].rules matched.
"default" (env's default)No env rule matched; [flag.environments.<env>].variant was returned.
"rule:<i>" (catch-all rules)The <i>-th rule of [flag.environments._.rules] matched (only reachable when the env did NOT declare its own rules).
"default" (catch-all default)Resolution fell through to [flag.environments._].variant.
"sdk_default"The SDK could not load the manifest and fell back to the caller's compile-time default.
"attr_type_mismatch"A runtime attribute type did not match the manifest's inferred type. See evaluation-context § Inferred attribute types.

The rule_matched field plus the matched rule's description is usually enough to understand any evaluation. Combine with flag_version to know which manifest snapshot was in effect.

When the server evaluates a batch of flags (POST /evaluate/all), it returns an array of evaluation results, one per flag in the flag namespace. Flags that encounter an internal error during evaluation are returned with their _-default variant and an error field describing the failure; the batch is never partially rejected.


See also