Skip to main content
Small page, high-value. If you’re writing a /decide call and unsure whether to pass ruleset_id or rulebook_id, you’re in the right place.

The two objects

The platform has two user-facing nouns: a Rulebook and a Ruleset. Everything else (sections of legislation, authoring projects, the state machine that moves a ruleset towards live) describes how these two objects are managed, not extra primitives you create.
TermWhat it is
RulebookThe whole form — the unit /decide evaluates against. Owns a locked field vocabulary, a composition expression (outcome_logic), rulebook-level tests, and an integer version history. Identified by rulebook_id (rb_<id>) and optionally a slug (e.g. aethis/uk-fsm).
RulesetA named, versioned part of a Rulebook (e.g. child_eligibility). Holds the compiled DSL and per-ruleset tests. Each Ruleset has a state (drafttestinglivearchived); exactly one version per name can be live at a time. Identified by ruleset_id (an opaque per-version ID) and optionally by a per-version slug (e.g. aethis/uk-fsm/child-eligibility).
SlugStable human-readable alias, format <segment>[/<segment>...] (lowercase ASCII + hyphens). Applied to rulebooks and rulesets — survives regeneration of the underlying artefact.
We talk about “sections of legislation” in prose (“this ruleset encodes the household-qualifying-criteria section of the FSM regulations”) but a Section is not a separate object. The named Ruleset within a Rulebook is the encoded section. A concrete example — UK Free School Meals:
Rulebook  aethis/uk-fsm           ← the whole form; what /decide evaluates
  ├── locked Fields           {applicant.age, household.income, child.year_group, …}
  ├── outcome_logic           child_eligibility AND (household_criteria OR universal_infant)
  ├── rulebook-level tests    full-form personas (gate version cuts)
  ├── version history         v1, v2, v3, …
  └── Rulesets
        ├── child_eligibility       — versions v1..v3 (v3 = live)
        ├── household_criteria      — versions v1..v2 (v2 = live)
        └── universal_infant        — version v1 (live)

The ruleset state machine

A ruleset version flows through four states:
StateMeaning
draftFreshly generated; not yet test-passed; mutable. Iteration in draft doesn’t move the parent Rulebook.
testingRuleset-level tests pass; queryable via single-ruleset /decide for QA; not pinned by any live Rulebook version.
liveAt most one per (rulebook_id, ruleset_name). Pinned by the current Rulebook version. The decision authority.
archivedTerminal. Never resurrected. New work starts a fresh draft.
The only way to move a ruleset to live is aethis rulesets promote-to-live. The operation is atomic:
  1. Demote prior live ruleset of the same name → archived.
  2. Promote the candidate → live.
  3. Update the Rulebook’s live_ruleset_pins.
  4. Cut a new Rulebook version (v(n+1)) recording the change.
So iteration is cheap (do as much as you want in draft / testing); promotion is explicit and visible (every live change cuts a new Rulebook version).

ruleset_id vs rulebook_id on /decide

The public POST /decide endpoint has two separate fields on the request body, and they go through different resolution paths:
{
  "ruleset_id": "aethis/uk-fsm/child-eligibility",
  "field_values": { ... }
}
{
  "rulebook_id": "aethis/uk-fsm",
  "field_values": { ... }
}
  • ruleset_id — evaluates one ruleset version in isolation. Useful for testing a candidate before promoting. Anon OK on public rulesets.
  • rulebook_id — evaluates the composed rulebook: every live ruleset combined via the rulebook’s outcome_logic. This is the “real decision” path. Always requires x-api-key — rulebook evaluation is unconditionally scope-gated, regardless of rulebook visibility. Anonymous callers get HTTP 401. (For an unauthenticated taste, hit a public section ruleset by its slug — ruleset_id: "aethis/uk-fsm/child-eligibility" — and the path is anonymous.)
Sending ruleset_id: "aethis/uk-fsm" — a rulebook slug — returns Ruleset 'aethis/uk-fsm' not found. The resolver only looks in the ruleset collection for that field; it doesn’t fall through to rulebooks. If you want the composed decision, move the value into the rulebook_id field.
See interfaces/rest-api.mdx for the full endpoint contract.

Visibility — public vs private

visibilityAnon /decide with ruleset_id works?Shown in GET /rulesets?
publicyesyes
privateno — needs an API key with decideno (unless same tenant)
Every new ruleset defaults to private. The exception: publishing with a slug under the reserved aethis/* namespace auto-flips visibility to public. For everything else, call PATCH /rulesets/<id>/visibility after publish.

The aethis/* namespace

Reserved for first-party rulesets maintained by Aethis. External tenants attempting to publish slugs starting with aethis/ get HTTP 403 with reason_code: "reserved_namespace". This is intentional: aethis/* is where the canonical public demo rulebooks and rulesets live (see /getting-started/try-it and /getting-started/examples). The namespace doubles as an allow-list for “safe to reference from docs without fear of the tenant renaming it”.

Authoring lifecycle (one screen)

The four CLI commands that move a Ruleset between states:
# Create a draft (empty shell)
aethis rulesets create aethis/uk-fsm child_eligibility

# OR: populate via the project pipeline + publish into the rulebook
aethis generate --poll                              # produces a draft ruleset
aethis test                                         # → state=testing if green
aethis publish --rulebook aethis/uk-fsm \
               --ruleset-name child_eligibility    # stamps FKs, state stays testing

# Atomic promotion → live + new Rulebook version
aethis rulesets promote-to-live aethis/uk-fsm child_eligibility <rs_id>

# Archive a stale ruleset version (rare; happens automatically on promote)
aethis rulesets archive aethis/uk-fsm child_eligibility
aethis rulebooks decide aethis/uk-fsm -i '{...}' runs the composed evaluation against whatever set of rulesets is currently live in the rulebook.

See also

  • Decision envelope — what /decide returns and how to replay it
  • REST API — endpoint contracts + rate limits + auth
  • MCP tools — the agent-callable wrappers around these endpoints