Skip to main content
Successful decision responses identify the serving build in engine_version. Read that field from your response rather than relying on a version copied into a tutorial. What the deployed engine serves.
You are a coding agent. You want to answer ‘is this person eligible for X?’ This page is the full sequence. The three-call pattern:
  1. Discover the schema — what fields does the ruleset need?
  2. Collect values — either from user input, context, or an upstream tool.
  3. Decide — send field values, receive a decision envelope.
Optional fourth step: explain — when the decision is not_eligible and the user asks why. Uses the public Free School Meals (FSM) child-eligibility ruleset. No API key needed.

1. Discover the schema

The response (same shape across all four interfaces):
What the agent needs: a map from field_id → value. For enum fields, only values listed in enum_values are accepted. For int / bool, standard JSON. notes — each field can carry a list of structured FieldNoteOut entries. By convention each note has metadata.type ∈ {"why", "legal_background"} and a source citation. Conversational agents (e.g. an information-gathering paralegal bot) typically surface the WHY:-prefixed note verbatim when a user replies “why?” to a question, and draw on the LEGAL BACKGROUND:-prefixed note for follow-up questions about evidence and edge cases. Older bundles may return "notes": [] — your code should treat the array as optional. weight — higher = less preferred to ask. The decision engine uses this for optimal-path computation when there are multiple ways to satisfy a ruleset (e.g. ask the cheap nationality question before the expensive English-test one).

2. Collect field values

Agent-side logic — the API doesn’t care how you got the values. In practice: ask the user, read from a session object, or derive from upstream context. If a field is missing, the decision can still return (as undetermined) — just be ready to ask for it next.

3. Decide

All four return the same decision envelope:
Illustrative values — content_digest is a placeholder and the published ruleset_id / ruleset_version depend on what is live when you call. Field names and meanings are exact.ruleset_version is never "unknown" for a published leaf ruleset, and replay pins to content_digest, which changes on every republish. Both are served by api.aethis.ai today.
Three possible outcomes:
  • eligible — all criteria satisfied
  • not_eligible — one or more criteria failed
  • undetermined — engine couldn’t decide (missing field, discretionary clause, a blocking input error, or a case outside the compiled rules)
Check field_errors first. If it is non-null, an input could not be applied — an unknown field name or a value that failed its type — and the decision is forced to undetermined regardless of everything else in the envelope. A positive verdict never appears beside a blocking error, so treat a non-null field_errors as “fix the input and call again”, not as a partial answer. When you get undetermined with field_errors: null, inspect missing_fields. Add the missing values and call again. include_trace adds per-group outcomes so you can show the user why.

4. (Optional) Explain the decision

For a not_eligible where the user asks “why”:
The CLI’s --explain flag calls /decide with trace, then /explain-failure when the outcome is not_eligible, and renders both in one pass.
Returns human-readable reasons for the failure, keyed by criterion, plus a DSL mechanism hint.

Composing multiple sections (rulebook)

If your domain has more than one section and you want one decision over the composition (e.g. FSM: child eligibility AND (household criteria OR universal infant)), use the rulebook_id field instead of ruleset_id. Rulebook evaluation always requires an API key — see Nomenclature for why.
The response includes a section_results array with each section’s individual outcome.

Know what the outcome ranged over

section_results says which sections were evaluated. complete_determination says whether that is the whole of the question:
Under false the two directions are not symmetric. Branch on decision:
  • eligible + false — not terminal. Every modelled requirement is met, which is necessary and does not establish sufficiency. The unmodelled ones may still fail, so do not present this as a final answer.
  • not_eligible + false — still terminal. A necessary requirement failed, and no unmodelled requirement can repair that. Treating this one as provisional is its own error: it keeps a case open that cannot succeed.
The asymmetry follows from the word necessary in the declaration, not from how the rulebook composes its own sections, so it holds whatever outcome logic the rulebook uses.
A false here does not mean the decision is wrong. The engine evaluated the rules it was given and answered correctly about them — this field tells you how much of the question those rules covered, so your interface can make a claim it can justify. It is always null for a single-ruleset_id decide: completeness is a rulebook-level property.

Next steps