Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.aethis.ai/llms.txt

Use this file to discover all available pages before exploring further.

You are a coding agent. A /decide call returned something unexpected. Work through this page in order; each step is safe.

Step 1 — Read the envelope, not just the decision

Every /decide response carries an audit envelope:
{
  "decision": "undetermined",
  "slug": "aethis/uk-fsm/child-eligibility",
  "ruleset_version": "unknown",
  "engine_version": "aethis-core@0.8.1",
  "decision_id": "dec_abc123...",
  "inputs_hash": "sha256:5f4dcc3b...",
  "decision_time": "2026-04-19T22:00:00Z",
  "fields_provided": 1,
  "fields_evaluated": 2,
  "missing_fields": ["child.school_type"]
}
Look first at:
  • decisioneligible / not_eligible / undetermined
  • missing_fields — which fields the engine wanted but didn’t get
  • fields_provided vs fields_evaluated — if provided > evaluated, the extra fields are either unknown to this ruleset (probably a typo) or not relevant to the active path
  • slug resolved — if you sent a slug, this confirms what the resolver picked. Different from what you expected? Wrong slug, wrong tenant. (The dated ruleset_id is also returned — that’s the immutable version-pin if you need byte-exact replay.)
  • engine_version — same as your local expectation? Mismatched engine versions can change outcomes; pin the version if replay matters
See Decision envelope for the full contract.

Step 2 — Re-run with include_trace: true

Trace shows per-group status for every criterion group in the ruleset:
curl -X POST https://api.aethis.ai/api/v1/public/decide \
  -H "Content-Type: application/json" \
  -d '{
    "ruleset_id": "aethis/uk-fsm/child-eligibility",
    "field_values": { "child.age": 16, "child.school_type": "state_funded" },
    "include_trace": true
  }'
{
  "decision": "not_eligible",
  "trace": {
    "status": "not_eligible",
    "group_statuses": {
      "school_type_check": "satisfied",
      "age_check": "satisfied",
      "age_upper_check": "not_satisfied"
    }
  }
}
The offending group is now obvious. Reading the ruleset’s /explain output for that group tells you which clause of the source legislation it compiled from. Via MCP:
aethis_decide({
  ruleset_id: "aethis/uk-fsm/child-eligibility",
  field_values: { "child.age": 16, "child.school_type": "state_funded" },
  include_trace: true,
  include_explanation: true
})
include_explanation: true adds a structured explanation object to the response — the gate-level checklist plus the supporting facts that proved each satisfied criterion. Shape:
{
  "explanation": {
    "decision": "eligible",
    "decision_path": "age_under_19",
    "groups": [
      {
        "group": "section_a",
        "status": "satisfied",
        "criteria": [
          {
            "criterion_id": "age_under_19",
            "title": "Child is age 19 or under",
            "status": "satisfied",
            "supporting_facts": [{"field": "child.age", "value": 10}],
            "source_refs": ["EducationAct1996#s512"]
          }
        ]
      }
    ],
    "unused_facts": ["child.eye_colour"]
  }
}
  • groups[].status and criteria[].status are satisfied / not_satisfied / pending — the gate-level checklist.
  • supporting_facts lists the answers that proved each satisfied criterion. For an Or branch, only the satisfied disjunct’s facts appear (no over-reporting alternatives).
  • unused_facts lists field names you provided that no criterion in the ruleset references — the typo signal. If you sent child.school_kind instead of child.school_type, it shows up here.

Step 3 — If decision = not_eligible, use /explain-failure

# Use the concrete ruleset_id (from the decide envelope), not the slug —
# /explain-failure path resolution doesn't currently accept slugs.
curl -X POST https://api.aethis.ai/api/v1/public/rulesets/$BUNDLE_ID/explain-failure \
  -H "Content-Type: application/json" \
  -d '{
    "field_values": { "child.age": 16, "child.school_type": "state_funded" },
    "expected_outcome": "eligible"
  }'
Returns the minimal unsatisfied core — just the criteria that caused the failure, with source references and a mechanism hint for fixing the rule.

Step 4 — If you got a 4xx, check the error reason_code

Every error response has a reason_code under detail:
{
  "detail": {
    "error": "forbidden",
    "reason_code": "reserved_namespace",
    "message": "Slug namespace 'aethis/' is reserved for internal use."
  }
}
Full catalogue: Errors reference. The eight most common:
Statusreason_codeWhat to do
401(no body)Missing x-api-key on a scoped endpoint. Check Nomenclature — rulebook lookups need a key.
403reserved_namespaceYou tried to publish under aethis/* without an internal key.
403invalid_scopeYour key doesn’t have the scope the endpoint needs.
404(no reason_code)Ruleset/rulebook not found — check slug spelling and visibility.
409slug_conflictSlug is owned by another tenant. Pick a different namespace.
422invalid_slug_formatSlug must be ^[a-z][a-z0-9-]*(/[a-z][a-z0-9-]*)*$.
422outcome_logic validationYou sent {"expr": "..."} instead of an Expr AST. See Nomenclature and the Author recipe.
429(no body)Rate limit hit. Retry-After: 86400 on daily-tier exhaustion.

Step 5 — Still confused? Minimal reproduction

# Print everything — trace + explanation + timing + cache bypass
curl -X POST https://api.aethis.ai/api/v1/public/decide \
  -H "Content-Type: application/json" \
  -d '{
    "ruleset_id": "<your ruleset_id>",
    "field_values": { ... },
    "include_trace": true,
    "include_explanation": true,
    "include_timing": true,
    "no_cache": true
  }'
Then attach that output to whatever support channel you’re using. The decision_id is the handle — the server logs every decision keyed by that id.

Common gotchas

  • ruleset_id: "aethis/uk-fsm" — that’s a rulebook slug. Move the value to rulebook_id and add x-api-key. See Nomenclature.
  • Anonymous /decide on a private ruleset returns 404 — the engine doesn’t leak existence of private rulesets. If you own the ruleset, add your API key.
  • Slug resolves to an unexpected version — slug pointers transfer on re-publish. Check ruleset_id in the response envelope to see which ruleset you actually hit.
  • missing_fields is empty but decision is undetermined — discretionary clause (see system-wide discretion principle) or a case outside the compiled rules. The ruleset may need regeneration or an explicit rule for your case.

Next steps