Skip to main content
Authoring is invite-only private beta. Everything on this page needs an Aethis API key with authoring scopes, plus your own model-provider key for the generation steps. Request access, or start on the open evaluate tier with your first decision — no key required. The two tiers are set out on Capabilities and access.
Authoring is invite-only private beta — request access. Generation uses an LLM to compile your sources into constraint logic; you supply an Anthropic API key per request and it is never stored.

The TDD loop

Rule generation is test-driven. The engine generates rules from your source text. Your test suite validates the output. Failing tests become the basis for targeted guidance — feedback that points to the specific source clause the engine missed. The loop repeats until all tests pass.
Rules are compiled from your source text and guidance — not reverse-engineered from your tests. Tests validate the output. Better tests catch more edge cases; better guidance converges faster.

Step 1 — Create a ruleset

If you completed Phase 2 (field vocabulary), you already have confirmed field names and a project_id — write your test cases using those names and skip to Step 2. If starting fresh on a single-section domain, you need test cases before creating the ruleset. Two approaches:
  1. Inspect an existing ruleset in the same domain with aethis_schema to learn the field naming convention, then write tests using those names.
  2. Make your best guess at field names, create the ruleset with preliminary tests, then run aethis_discover_fields to confirm. If names don’t match, add corrected tests via aethis_create_ruleset with a new project — the cost is low.
Either way, the field names in your test cases must exactly match what the engine discovers. Phase 2 exists to prevent this mismatch.
Returns:
Write test cases after running aethis_discover_fields. Test field names must exactly match the engine’s discovered field names. A mismatched name causes the engine to treat that field as absent — the test may silently pass or fail for the wrong reason.

Step 2 — Generate and test

Takes 60–120 seconds for most sections. Returns (first attempt — partial failure):
Two failures: the boundary condition at age 4 is wrong (strict > instead of ), and the school type restriction isn’t compiled. Each failure includes a hint pointing to the likely cause.

Step 3 — Refine with guidance

Add guidance that references the specific source clause:
aethis_refine adds the guidance, then makes the minimal edit to fix the failing tests — seeded from the section’s active ruleset and keeping the passing tests green, rather than re-authoring the whole section. Returns (second attempt — all passing):
All tests pass after one refinement. Move to publish.

Guidance variants

Add guidance without regenerating

Useful when you want to accumulate several pieces of guidance before triggering a generation run:
Then trigger generation separately:

Check accumulated guidance

Returns:

Domain-level guidance

Add once, applies to all projects in the domain — no need to repeat cross-section principles on each ruleset:

Guidance examples

Real guidance from the UK Free School Meals household criteria section (11 tests, 8 qualifying routes). Each hint addresses a specific compilation gap.
Problem: The engine treats all qualifying criteria as AND conditions — a household must meet all routes to qualify, when it should be any one.Guidance:
Problem: The Universal Credit route passes when receives_universal_credit is true, ignoring the income cap.Guidance:
Problem: Looked-after children are being checked against benefit criteria when they should bypass them entirely.Guidance:
Problem: The Child Tax Credit route qualifies anyone receiving CTC, missing the requirement that they must not also receive Working Tax Credit.Guidance:
Pattern: Every effective hint references a specific regulatory clause, names the fields involved, and states the logical relationship (AND, OR, NOT, unconditional). Vague feedback (“fix the UC check”) doesn’t converge.

Diagnosing a specific failure

If a test is failing but the hint isn’t clear enough, use aethis_explain_failure to get a deeper diagnosis:
Returns:

Test coverage strategy

Good coverage catches failures before production:
  • Boundary values — test just below, at, and above every threshold (age 3, 4, 15, 16)
  • Every Enum value — one test per enum value for each Enum field
  • Every code path — combinations of fields that exercise a distinct branch
  • Five tests minimum per section — more is better; sparse test suites let edge-case bugs through

Date arithmetic: years_between

Source text frequently expresses age or duration thresholds in terms of a date of birth or a start date (“must be 18 or over”, “at least 5 years’ continuous residence”). Never ask for or store an age field directly — dates drift out of sync with a stored age the moment time passes, and a re-run of the same ruleset on a later day silently changes the answer. Compile the threshold from the date field instead:
years_between(start, end) returns the number of completed whole years between two Date fields — true “has the Nth anniversary been reached on or before end” logic, leap-year correct (29 February birthdays included). It is not days_between(start, end) / 365, which drifts by roughly a day every four years and silently produces off-by-one-year answers near a birthday. Both helpers compile to the same compiled rule evaluation as every other operator in this reference (see What you can express for the full operator table) — there’s no separate date-math code path to reason about at decide time.

Step 4 — Publish

Once all tests pass:
Returns:
The name is auto-derived from the section ID (e.g. child_eligibility"Child Eligibility"). To override it, pass name in the publish call:
The ruleset_id is now ready for aethis_decide. Published rulesets are locked and versioned.
To update rules (e.g. for a legislative change), generate a new ruleset in the same project and publish again. The previous ruleset remains available by its specific version ID.

Sources: budget, duplicates, and lifecycle

Generation builds its context from every active source on the project, so source hygiene directly affects rule quality. Token budget. Every upload response includes estimated_tokens per file, a project_estimated_tokens running total, and the generation_token_budget it must fit (the model’s context window minus headroom reserved for the generation loop). Before any generation starts, the engine counts the exact prompt — if it exceeds the budget, the request is rejected with 422 token_budget_exceeded (including the count, the budget, and the largest sources) before any model cost is incurred. Duplicates. Re-uploading identical content (or a same-named file) is flagged in the response under possible_duplicates — surfaced, never silently merged. Conflicting versions of the same guidance are a correctness hazard: resolve them by superseding the stale copy. Lifecycle. GET /projects/{project_id}/sources lists every source with its status; PATCH /projects/{project_id}/sources/{source_id} sets it:
  • active — included in generation (the default)
  • superseded — replaced by a newer upload (optionally point at it with superseded_by); kept for provenance, excluded from generation
  • reference_only — kept on the project, excluded from generation
DELETE removes a source outright — but a source cited by any persisted ruleset’s provenance returns 409 rather than orphaning those citations. Prefer superseded when generated rulesets already cite it. The retained bytes behind a source stay downloadable at GET /projects/{project_id}/sources/{source_id}/raw (scope projects:read); see Provenance and citations.

Grounding report

Every generated rule cites the source passages it is grounded in. After generation, the job (and the generate-and-test response) carries a provenance_report:
  • totals — how many citations resolved against the uploaded sources (verified), cited passages that don’t exist (flagged), and rules or fields with no citation (uncited)
  • coverage — per source: how many passages are cited by at least one rule, plus a sample of passages nothing cites
The coverage list is the review signal golden tests can’t give you: a statutory exception that no rule cites — and no test exercises — stays green in testing but shows up here. The report never blocks generation; treat it as the reviewer’s punch list. The grounding report is not the same thing as a published citation. It scores how well generated rules trace back to your uploaded sources, and it never blocks. Binding a criterion to the verbatim text it rests on — verified against retained bytes, immutable, fail-closed — happens at publish time with source_targets. See Provenance and citations.

Handling generation timeouts

Rule generation takes 5–15 minutes for complex sections. If the client times out, the server continues generating. Do not re-trigger generation — it creates a duplicate run.
  1. Wait 10–15 minutes
  2. Call aethis_list_rulesets({ project_id: "proj_8CzLVwyx53rTGEJv" }) to check if a new ruleset appeared
  3. If a ruleset is present, run your test suite and publish
  4. If not, wait and check again

After all sections are published

If your domain has multiple sections, compose them into a rulebook — a container that references published rulesets and defines outcome logic.

Create a rulebook

Via the REST API:

Ruleset references

Each ruleset_ref has:

Outcome logic

Defines how section outcomes compose. Each section is assigned a letter (A, B, C…) in the order listed in ruleset_refs. Supported operators: AND, OR, NOT, parentheses for grouping.

robot_hints — guidance for a conversational agent

A rulebook optionally carries robot_hints: a flat map of beat name → natural-language guidance string, for a conversational agent (like Lisa) driving an applicant through the composed rulebook. Set it on create or update:
Keys are validated against a fixed, known set of beats — an unrecognised key is rejected with a 422 listing the allowed set, so a typo’d beat fails loudly at authoring time instead of silently being ignored by the consumer: Values are free-text strings — there’s no further schema on the guidance itself, only on the key set. robot_hints is additive and back-compat: a rulebook authored before this field existed simply returns robot_hints: null on /schema (below), so an older consumer doesn’t need to change anything.

Activate and evaluate

The /decide endpoint accepts either a ruleset_id (single section) or a rulebook_id (composed multi-section). When evaluating against a rulebook, the response includes section_results showing each section’s individual decision. The rulebook /schema response merges {rulebook_id, sections, fields, robot_hints, engine_version} across every composed section:
  • robot_hints — the rulebook’s guidance map (above), or null for a rulebook authored before the field existed.
  • engine_version — the same aethis-core@X.Y.Z string echoed on every /decide response and at /openapi.jsoninfo.version, so a consumer can record which engine build resolved this schema without a separate call. Useful for pinning replay: if you cache a schema response, engine_version tells you whether a later /decide ran against the same compiled build.
See the UK Free School Meals worked example for a complete multi-section rulebook with live rulesets.