CHANGELOG.md of the package it belongs to and is labelled with that package. Subscribe via the RSS feed for this page.
Adds the Authoring Coach surface to MCP (aethis-mcp#57, workspace epic #514) —
skill-building feedback for rule authors, advisory only, never a gate.Engine gate: the
POST /api/v1/public/projects/{id}/review endpoint and the
ambient review_hint fields are produced by aethis-core (epic phases P1/P4).
This release must not be published to npm until that endpoint is live on
api.aethis.ai; a released client calling a not-yet-deployed route would 404.aethis_review_project(new tool). Reviews an authoring project against the deterministic authoring-coach rubric and renders the report: a score, per-check evidence across grounding / process / lifecycle, strengths, and the single highest-leverage next skill. Advisory only — it never blocks publishing. The deterministic layer needs no LLM key;coach=true(with an Anthropic key, via the usualanthropic_key_env/anthropic_key_keychain/anthropic_keyforms) adds an opt-in LLM-synthesised coaching narrative on top. All server free-text (evidence / strengths / next-skill message / coaching) is fenced withfenceUntrustedbefore it reaches the model.- Ambient
review_hintrender.aethis_generate_and_test,aethis_refine, andaethis_publishnow render a one-line coach hint when the server includes one on the response. The hint is computed entirely server-side (aethis-core P4); the client only renders it (fenced), never computes it. X-Aethis-Client: mcp/<version>on every request. The client now sends a per-surface identifier header so the engine can attribute telemetry (e.g.review_hint-shown counts) to MCP vs CLI vs SDK.- 31 tools, up from 30.
tests/tool-endpoint-map.tsand the drift suite are updated in the same change. Note: the drift suite’s live-alignment checks stay red against staging until the/reviewendpoint deploys there (expected epic ordering); the offline structural checks pass. - Tests. New mocked unit coverage in
tests/client.test.ts(reviewProject request shape, the client-id header) andtests/server.test.ts(aethis_review_projectrender + fencing, coach key resolution, ambient hint render on generate/publish).
- feat:
aethis review [<project>]— the Authoring Coach report for a project. Runs the server-side rubric and prints an authoring score, 2–3 evidence-cited strengths, and the single highest-leverage next improvement (with its docs link and the lever that fixes it). Defaults to the current project in.aethis/state.json; pass aproj_…id to review any of your projects from anywhere.--verboseshows the full per-check table;--json(and any piped/--output jsoninvocation) emits the rawReviewReport. The deterministic report needs only your API key;--coachopts into LLM mentoring prose billed to your own Anthropic key (ANTHROPIC_API_KEY). Advisory only — the exit code is always 0 regardless of score. NewAethisClient.review(). - feat: every request now sends
X-Aethis-Client: cli/<version>so the server can attribute per-surface telemetry (CLI vs MCP). The header carries no credentials and no PII, and is set once at client construction for all commands. - Requires aethis-core with the
/api/v1/public/projects/{id}/reviewendpoint live (epic aethis-workspace#514, P1). The public release of this version is held until that endpoint is live onapi.aethis.ai.
- feat(models):
robot_hints+engine_versionon the rulebook schema;engine_versionon the ruleset schema. NewRulebookSchemaResponsemodel (rulebook_id,sections,fields,robot_hints,engine_version) forGET /api/v1/public/rulebooks/{id}/schema—robot_hintsis the rulebook’s natural-language conversational-agent guidance keyed by beat (general_context,preamble,session_start,postamble,session_end,stuck),Nonefor a rulebook authored before the field existed.SchemaResponse(ruleset schema) gainsengine_version: str | None = Nonefor parity, also back-compat (defaultsNonewhen the engine doesn’t send it — true of the ruleset schema route today). - feat(models):
graph/GraphResponsefor the new/graphendpoint. NewGraphResponse(ruleset_id/rulebook_id,slug,name,graph,mermaid) andRulesetGraph(nodes,edges,sections,stats) model the ruleset/rulebook dependency graph (field → criterion → group → outcome) plus its rendered Mermaid diagram. Node/edge shape varies by nodetype, so nodes/edges stay loosely-typed dicts rather than a rigid per-type schema — deliberately permissive so a legacy or empty graph (nodes: []) still parses. - feat(client):
get_graph(ruleset_id)(sync + async) — wrapsGET /api/v1/public/rulesets/{id}/graph, returningGraphResponse. Public rulesets can be inspected without an API key, same asget_schema(). - feat(decide):
include_graph_overlayparameter ondecide()/decide_rulebook()(sync + async), and a matchinggraph_overlay: dict[str, Any] | None = Nonefield onDecideResponse. Setinclude_graph_overlay=Trueto get this decision’s per-criterion status stamped onto the ruleset’s dependency graph, in the same shapeget_graph()returns. - All additions are additive and backwards-compatible: every new field defaults to
None/False/an empty collection, so a legacy response (norobot_hints, noengine_version, nograph_overlay) still deserialises unchanged.
Propagates the aethis-core 0.37–0.40 authoring batch to the MCP surface
(aethis-mcp#49, workspace epic #327). Engine gate: live on
api.aethis.ai
0.45.2, confirmed via the drift suite’s live-alignment checks before this
release.aethis_graph(new tool). Fetches the ruleset-map graph — either for a single published ruleset (ruleset_id, may be public/anonymous for a public showcase ruleset) or a composed rulebook (rulebook_id, always requires an API key) — the same mutual-exclusivity shape asaethis_decide. Returns{ruleset_id|rulebook_id, slug, name, graph: {nodes, edges, sections, stats}, mermaid}: each node’sdisplay.sentence/display.routes/display.exprshows how that branch composes, andmermaidis a ready-to-render diagram string.include_graph_overlayonaethis_decide(additive). Stamp a specific decision’s per-criterion outcome (satisfied/not_satisfied/pending) onto that same graph and return it asgraph_overlayin the decide response — a “you are here” map for those inputs. Off by default; the response is unchanged when omitted.aethis_create_rulebook/aethis_update_rulebook(new tools). Create an empty draft Rulebook (name/domain/slug/description) or update one, both acceptingrobot_hints— beat-keyed natural-language guidance for the conversational agent (active beats:general_context,preamble,session_start,postamble,session_end,stuck; reserved:persona,conversational_style,section_transition). An unknown beat is rejected client-side before the round-trip, mirroring aethis-cli’s_validate_robot_hints(v0.23.0). Rulebook composition (outcome_logic,ruleset_refs) is a larger surface not covered by these two tools yet.years_betweenin the DSL helper reference (README). Documents the new completed-whole-years, leap-correct date operator (mirrors aethis-coreOperator.YEARS_BETWEEN, commit3607558) alongsidedays_betweenso generation can use it for age-from-date-of-birth instead ofdays_between(...) / 365(division isn’t supported anyway).- 30 tools, up from 27.
tests/tool-endpoint-map.tsand the drift suite are updated in the same change; every new operation/field/param is verified against the liveapi.aethis.aiOpenAPI document (engine 0.45.2). - Tests. New mocked unit coverage in
tests/client.test.ts/tests/server.test.tsfor the graph client methods, the create/update rulebook client methods,robot_hintsbeat validation (known + unknown + reserved), andinclude_graph_overlaypass-through; the nightly staging integration lane gains a realaethis_graphfetch, aaethis_decide include_graph_overlayround-trip, and anaethis_create_rulebook→aethis_update_rulebookrobot_hintsround-trip (with best-effort archive cleanup of the probe rulebook).
- feat(rulebooks):
aethis rulebooks graph <id>— fetch and render the rulebook-level ruleset-map dependency graph (field -> criterion -> group -> outcome). Prints a node-count summary + a table of nodes (id, type, the criterion’s human-readabledisplay.sentence, field count);--mermaidprints the raw Mermaid diagram source for piping into a renderer;--output jsonreturns the full payload ({rulebook_id, graph: {nodes, edges, sections, stats}, mermaid}), including each node’sdisplay.routes/display.exprfor programmatic consumers. This endpoint requires a valid API key even for a public rulebook (confirmed against the live engine) — unlike the ruleset-level graph below, there’s no anonymous path. NewAethisClient.get_rulebook_graph(). - feat(rulesets):
aethis rulesets graph <ruleset_id>— the single-ruleset analogue, open for public rulesets with no API key required (load_client_or_anon). Same table/--mermaid/--output jsonshape. NewAethisClient.get_ruleset_graph(). - feat:
--include-graph-overlayonaethis decideandaethis rulebooks decide— stamps the decision’s per-criterion status onto the rule-map graph, returned as agraph_overlayfield on the response (--output jsonto inspect it). Additive request flag; a plain-text hint is printed when the overlay is present and JSON wasn’t explicitly requested. - feat(rulebooks):
aethis rulebooks schemasurfacesengine_version. The schema response already carries the aethis-core build that served it (e.g.aethis-core@0.45.2); the CLI now prints it as a header line ahead of the schema payload instead of leaving it buried in the JSON. - Requires aethis-core 0.40.0+ (live on
api.aethis.ai/staging.api.aethis.aias of this release) for/graph,include_graph_overlay, andengine_versionon/schema.robot_hints(shipped v0.23.0) is unaffected by this release.
Fixed
- An API key with
expires_atset no longer 500s every request: the Mongo-stored (tz-naive) expiry is normalized to UTC before comparison, so an expired key gets its intended 401api_key_expired. Latent since the expiry field existed — no key carried a non-None expiry until 2026-07-16 (issue #275).
- feat(errors): typed 401/403/429 exceptions carrying the structured error envelope.
classify_responsenow raisesAethisAuthError(401),AethisPermissionError(403), orAethisRateLimitError(429) — each a subclass ofAethisAPIError, so existingexcept AethisAPIErrorhandlers keep catching them (non-breaking).AethisErrorgains.reason_code,.missing_permissions, and.hint, lifted out of the public API’s structured envelope ({"detail": {"error", "reason_code", "missing_permissions", "hint", ...}}), so a caller can branch onerr.reason_code == "denied_missing_permission"or readerr.missing_permissionswithout re-parsingerr.body. Plain-string and FastAPI-422-list details are untouched (fields stayNone/[]). Constructor stays backwards-compatible (new args default toNone). - test(staging): live integration lane against
staging.api.aethis.ai. Newtests/integration/(markerstaging, excluded from the PR gate) mints a real API key the way a user does — Clerk sign-in ticket → frontend-API JWT →POST /api/v1/keys/→ teardown — and exercises every public method onAethis+AsyncAethis(decide, decide_rulebook, list_rulesets, get_schema, whoami, explain, explain_failure, get_source, sync/async session flows) plus live 401/403 typed-error assertions and a contract cross-check. Reports red (never green-by-skip) when creds are missing or staging/contract is unreachable. - test(parity): recorded-live fixture parity.
tests/shapes.compare_shapediffs the mockedconftestfixture builders (make_decide_response,make_schema_response,make_ruleset_summary) against real staging payloads so the mocked suite can’t silently drift from reality; the builders were updated to match the current engine shape (slug/rulebook_id,graph_overlay/timing, richernext_question/schema fields). - chore(ci): coverage floor (
--cov-fail-under=45) +stagingmarker + nightlystaging-integration.yml(report-only,workflow_dispatch+schedule, uploads aqa-run-recordartifact for thesdk-staginglane). The coverage flags live in the CI command, not inaddopts, so a barepytest/uv run pytestworks withoutpytest-cov(which is only in thedevextra) installed; the floor is still enforced in CI.
Test-infra only — no runtime/behaviour change to the server or its tools.
- Tool-schema drift suite (
tests/drift.test.ts). Guards that the 27server.tool()input schemas never silently drift from the engine. Reads each tool’s real zod shape (no vendored schema copy) and compares field names, types, and required-ness against the deployed staging OpenAPI document — the oracle. An explicit, checked-intests/tool-endpoint-map.tsrecords the tool → operation correspondence and field renames (e.g.force → force_unsafe); a tool missing from the map, an unknown extra tool, an unclassified input field, a mapped operation absent from the engine, or a mapped body field the engine no longer has all fail loud. Runs in the PR gate (network-tolerant) and nightly (network-required). - Staging integration lane (
tests/integration/, nightly). Runs the built server as a subprocess with a freshly minted staging key and drives it over the real MCP protocol:tools/list(== 27), a read-only core loop, andaethis_decideagainst a public showcase ruleset; a negative path proves an invalid key returns a structured error result while the server stays alive. Keys are minted via the self-serve path (server-default scopes), namede2e-dx-mcp-*, and revoked + swept in teardown. staging-integration.yml— nightly + manual, report-only, emits a QA-run-shaped run record artifact for downstream ingestion; missing secrets or unreachable staging fail red, never skip-green.
- feat: authorization errors now render the server’s
hintand the missing scope readably. A403 denied_missing_permission(and401) previously printed the raw error object on commands that render their own errors (projects,whoami, …); the CLI now renders one clean line naming the missing permission plus, on its own dim line, the server’s follow-up hint (e.g. how to request access). The top-level handler and the per-command renderer now share one formatter (aethis_cli.output.format_error_detail/render_api_error), so every command surfaces the same readable message. The hint is rendered with markup disabled (so a hint containing[brackets]isn’t dropped) and non-stringmissing_permissionsitems are coerced (so a server quirk can’t turn the error into a traceback). - test: new staging integration lane (
tests/integration/, markerstaging). Acquires an API key the self-serve way (a fenced e2e user’s session → mint with the server’s default scopes, noscopesfield), drives the CLI core loop against deployed staging (whoami/status,projects list/archive,rulesets/explain/fields/decideagainst a public showcase ruleset), and asserts the negative paths a caller actually sees — a scope-reduced key’s 403 and a revoked key’s 401 — with the error envelopes checked against the machine-readable public-API contract. Report-only nightly workflow (staging-integration.yml); never gates a merge. Run locally with the one-liner intests/integration/README.md. - test: the spacecraft authoring e2e moved to its own weekly lane (
authoring-e2e-weekly.yml). It drives the LLM authoring pipeline, so it is kept out of the nightly LLM-free cadence; the model is passed explicitly viaX-Anthropic-Key, generation is bounded by an explicit iteration cap (SPACECRAFT_GENERATION_TIMEOUT), and themanualmarker stays as the local escape hatch.
Added
- Read-only cross-tenant
/api/v1/admin/*router (epic aethis-workspace#480, P1):/admin/keys,/admin/usage(daily rate-limit counters),/admin/generation-jobs(+/{id}with trace),/admin/decisions(list excludesfield_values;/{decision_id}full record),/admin/publish-audits,/admin/rulebooks,/admin/rulesets(metadata; DSL source additionally requiresrulesets:source). Gated by the new internal-onlyadmin:readscope ANDapi_key.internal == trueAND a hard-fail underDISABLE_AUTH=true(503) — scope alone is deliberately not the boundary.admin:readis never self-servable (ALLOWED_SCOPES) and never an alias target. Uniform{items, next_cursor, limit, skipped_invalid}envelope, cursor pagination, server-side max page size, default 30-day lookback on time-series lists, per-doc validation (one malformed record never 500s a list), typed filters (tenant_id=Noneonly via explicitanonymous_only=true, never a wildcard). Newadminrate-limit category enumerated in every tier (internal ≈ unlimited). Routes stay in the live/openapi.jsonbut out of the published public contract.
Added
- Decision log (epic aethis-workspace#480, P0): every
/decidecall can now be persisted server-side as aDecisionRecord(collectiondecisions), fulfilling theDecideResponsedocstring’s deferred “server-side audit persistence”. Gated byDECISION_LOG_ENABLED(default off, fail-closed) with TTL retention viaDECISION_LOG_TTL_DAYS(default 90) — the TTL index is declared in code and asserted at boot (logging disables loudly if the live index lacksexpireAfterSeconds). The write is fire-and-forget off the hot path: bounded pending-task set, client-side insert timeout, fully exception-isolated (no decision-log failure can alter a/decideresponse). Write failures log structured ERROR, increment a counter, and can alert viaDECISION_LOG_ALERT_WEBHOOK(Google Chat, rate-limited; unset = off). DecideRequest.caller_ref— optional opaque caller metadata (flat string→string dict, ≤2 KB, no$/dotted keys) stored on the decision record for the caller’s own attribution (e.g. tda-server sends{firm, application_id}). Never an authorization key or cross-principal predicate (defect shape DS-25); invalid values are dropped with a warning, never rejected.
Added
FieldDefinition(and the/schema+/decidenext_question/optimal_pathenvelope) now carries an optionalx_ui_widget: Optional[str]authoring override. Currently only"free_text"is recognised: it tells downstream consumers (Lisa’sexpected_inputemission in tda-server) to suppress the schema-derived structured-answer affordance (chips / select / date-picker) for that field and render a plain text composer, even though the field has a typedsort. Defaults toNone— purely additive. (#255, epic aethis-workspace#422)
Added
/decide: thenext_question(and eachoptimal_pathentry) now carries optionalsortandenum_valuesfields, exposing the field’s answer type (Int/Bool/String/Enum/Date/Duration) and, forEnumsorts, its allowed values. Lets callers render typed input affordances (yes/no chips, a date picker, an option list) without a second/schemaround-trip. Both default toNone, so the change is additive — existing consumers are unaffected. Mirrors thesort/enum_valuespair already on/schema’sFieldInfo. Populated on the ruleset path; rulebook/decidecallers continue to source the type from/schema. (#254, epic aethis-workspace#422)
- docs: correct stale paper citation in the construction-insurance demo. The
demo cited the withdrawn v3.6/v3.7 claim that GPT-5.4 at
reasoning_effort=lowscores 7/11 on the exception-chain subset; the paper withdrew that result in v3.8 (instrumented replication: 11/11). The demo now attributes 7/11 to GPT-5.3 only and pins the paper citation at v3.11. No code changes.
- fix(errors): attach the API’s
detail(and fullbody) toAethisAPIError. On the primary error path,classify_responseparsed the 4xxdetailonly to log it, then raisedAethisAPIError("Aethis API returned 422")— blinding callers to why the request failed. The exception message now reads"Aethis API returned 422: <detail>"when a detail is present, andAethisErrorgained.detail/.bodyattributes carrying the parsed payload (bothNonefor timeouts / connection errors). Constructor signatures stay backwards-compatible (new args default toNone). - fix(models):
DecideResponse.explanationis a single object, not a list. The field was typedlist[dict] | Nonebut the engine returnsOptional[Dict[str, Any]]({decision, groups: [...], unused_facts: [...]}), a latentValidationErrorfor any caller that actually requested one. Retyped todict[str, Any] | None. - feat(decide):
include_explanationparameter ondecide()/decide_rulebook()(sync + async). The engine has always acceptedinclude_explanationonPOST /decide, but the SDK never sent it, leavingDecideResponse.explanationpermanentlyNone. Passed through in the request payload alongsideinclude_trace; defaults toFalse. - feat(models): typed
FieldNoteandNextQuestion.notes. The engine attaches structured author guidance (note_text,source,metadata) to eachnext_question; the SDK silently dropped it. Adds theFieldNotemodel (exported from the package) andnotes: list[FieldNote]onNextQuestion, defaulting to[]so older responses without notes keep parsing. - feat(client):
list_rulesets(limit=20, offset=0)(sync + async) — wrapsGET /api/v1/public/rulesets, returning the previously-exported-but-unreachableRulesetSummarymodel. Anonymous callers get public rulesets; an API key additionally surfaces that key’s own rulesets.limitis clamped by the engine to 1-50. - docs(readme,
_base): capability-table + docstring fixes. README’s “What’s included” table now listsexplain_failure,decide_rulebook,list_rulesets,include_explanation, andFieldNote; thebuild_headersdocstring no longer names a nonexistent/next_questionendpoint.
Cross-surface review batch (aethis-mcp#50).
- Surface
next_question.notes(additive).aethis_next_questionnow renders a Notes block after the question when the ruleset author attached notes to it (each note carriesnote_text,source, andmetadata). Notes are labelled bymetadata.type(e.g.why,legal_background) when present, and each note’s text is wrapped withfenceUntrusted(...)since it is author-provided server content. Output is unchanged when no notes are present. The tool description now mentions the Notes block. - Fence
aethis_list_guidanceoutput. Theguidance_textandsourcefields returned by the server were interpolated into the tool result unfenced, unlike every sibling handler. They are now wrapped withfenceUntrusted(...)under theUNTRUSTED_PREFACEwarning (GHSA-ph7q-r9q4-922g hardening). - Send a single provider header. The per-request LLM key was sent under both
X-Anthropic-KeyandX-OpenAI-Key. It is now sent only asX-Anthropic-Key, matching howresolveLlmKeyresolves the key. - Correct the stale latency figure. Two guidance strings claimed decisions are
<5ms; corrected to<1msto match the README and the canonical figure. - Docs: rewrote the
CLAUDE.mdarchitecture section to the real layout (src/index.ts+src/client.ts+src/credentials.ts, tests undertests/) instead of the non-existentsrc/server.ts+src/tools/tree.
- fix: network errors now render one actionable line, not a raw traceback. When the API is unreachable, times out, or a DNS/TLS error occurs, every command now prints
Could not reach the Aethis API at <url>: <reason>.plus a “check your connection” hint and exits non-zero, instead of dumping anhttpxstack trace. The top-level handler catcheshttpx.HTTPError(the umbrella over connect/timeoutRequestErrors), matching the graceful handlinglogin/accountalready had. - feat: non-interactive environments bypass confirmation prompts. A truthy
AETHIS_NONINTERACTIVEorCIenv var (values1/true/yes, case-insensitive) now flips the whole process non-interactive, so destructive commands (account revoke,rulesets archive,projects archive,rulebooks archive,rulebooks tests delete) proceed without waiting on stdin, so a background job or CI step no longer hangs on a[y/N]prompt. The bypass prints a one-line notice so it’s never silently active. The explicit per-command--yes/-yflags keep working unchanged. New sharedaethis_cli.prompts.confirm_or_aborthelper. - docs: refreshed the worked examples in
decide/explain/fieldshelp to use public showcase rulesets (aethis/spacecraft-crew-certification,aethis/consumer-credit-prequalification) instead of product-specific slugs. - chore:
make installusesuv pip install -e ".[dev]"(matching the README) instead of barepip. - minor: dropped the unused upgrade-command strings from
update_check._detect_install_method(it now returns just the detected method; the concrete upgrade argv is still built byupdate’s_upgrade_argv);decidereads the decision field with a safe default so a payload withoutdecisionrenders asunknownrather than raisingKeyError.
- feat(rulebooks): declare
robot_hints:in a rulebook file and push them to the engine. Rulebook authors can now provide natural-language guidance for the conversational assistant alongside the rulebook’s other configuration.aethis rulebooks create <name> --file rulebook.yaml— a new--file/-foption reads arobot_hints:block (a sibling ofname/domain/outcome_logic) from arulebook.yaml/.jsonand sends it on create. CLI flags still ownname/domain/slug/description; only the hints are taken from the file. No--file(or a file without arobot_hints:key) is a clean no-op — behaviour is unchanged.aethis rulebooks set-logic <id> -f rulebook.yamlnow also accepts a wrapped form: when the top-level object carries anoutcome_logic:key, a siblingrobot_hints:block is pushed in the same update. A bare Expr AST file (the prior shape) is still accepted unchanged.robot_hintsis a mapping of beat-name to a natural-language string. Active beats:general_context,preamble,session_start,postamble,session_end,stuck. Reserved beats (accepted, not yet acted on):persona,conversational_style,section_transition. Unknown beat keys and non-string values are rejected client-side with a clear message before the round-trip.- New optional
robot_hintsparameter onAethisClient.create_rulebook()/update_rulebook(); omitted from the request body when not supplied, so calls against an older engine are unaffected. - Requires aethis-core with the rulebook
robot_hintsfield (aethis-core#220); mid-deploy to staging at time of writing. Against an engine without it, the field is ignored/rejected server-side.
- feat(fields):
aethis fieldsis now a command group for the full field-authoring loop. Bareaethis fields [-b <ruleset>]still shows a ruleset’s field schema (unchanged); three subcommands manage the localfields/fields.yaml:aethis fields discover— uploads the project’ssources/(creating the project if needed), runs server-side LLM field discovery, and merges the proposals intofields/fields.yamlso you start from a real draft instead of a blank file. Existing entries are preserved — only new keys are appended — so hand-authored labels/questions/hints are never clobbered. Prints the completeness score and any critical gaps. Needs an LLM key (ANTHROPIC_API_KEY), same asgenerate; without one it fails with a clear message naming the env var instead of a raw server header error. NewAethisClient.discover_fields().aethis fields pull— syncs the server’s authoritative produced fields (key + type + enum values) back intofields/fields.yamlso local matches reality after a generate. Local-onlylabel/hintsare preserved; fields absent from the server schema are kept and reported rather than silently dropped.aethis fields validate— checksfields/fields.yamlbefore upload: validtype(int/bool/string/enum/date/duration), no duplicate keys,enumrequiresenum_values. The same validation now also runs insideaethis generate, per contributing file (rulebook + ruleset), so duplicate keys within a file fail fast before any server state changes.discover/pullonly ever write a vocabulary that re-validates: an unknown server type or anenumwith no values falls back tostringinstead of producing a file the nextvalidate/generatewould reject. Writes also preserve any hand-authored keys the tool doesn’t model (e.g.description) rather than dropping them.
- feat(generate): the field spec/produced diff is surfaced after generation. After a successful
aethis generate, the CLI compares the pinned field vocabulary against what the engine actually produced and prints pinned-but-not-produced / produced-but-not-pinned fields (with a pointer toaethis fields pull) instead of the drift passing silently. - feat(init): rulesets can declare rulebook membership explicitly. A
rulebook:key in a ruleset’saethis.yaml(a path to the enclosing rulebook) now declares membership directly; the directory-position convention (<rulebook>/rulesets/<ruleset>/) remains the fallback. Theinitscaffold documents the key. - perf: source uploads are now idempotent.
discoverandgenerateshare one project-resolution + upload path, and a per-file mtime ledger in.aethis/state.jsonmeans adiscoverfollowed by agenerate(or repeated generates) only re-uploads sources that actually changed instead of re-pushing the wholesources/tree each time. - fix(generate): don’t lose the ruleset id on a fast success. The poll loop occasionally saw the job flip to
successa beat beforelatest_ruleset_idwas populated, writing a null id to state and leavingfields pull/ the field diff with nothing to work from. It now re-polls briefly for the id and only records a real one — never clobbering a prior good id with null. - example + e2e:
examples/community-grants-rulebook/is a generic rulebook (one shared field) with two member rulesets, andtests/e2e/test_rulebook_hierarchy_e2e.py(gated by themanualmarker) drives discover/validate/generate/pull against a live API and asserts the shared rulebook field propagates into both members. - No engine change required — all endpoints (
/fields/discover,/rulesets/{id}/schema,/fields/spec) are already served by aethis-core and used by the MCP server.
- feat(init): field definitions get a real home (
fields/fields.yaml).aethis initnow scaffolds afields/directory with afields.yamlfor declaring the field vocabulary (key +type+ optionallabel/question/hints). Previously fields had no dedicated home and only surfaced implicitly as theinputs:keys insidetests/scenarios.yaml.aethis generatereadsfields/fields.yaml, pins the field keys/types via the project field-spec endpoint, and routes each field’s label/question/hints through guidance so a field is defined once. - feat(init):
--kind rulebookscaffolds a rulebook.aethis init <name> --kind rulebooklays down a rulebook directory with sharedguidance/andfields/plus arulesets/directory for member rulesets. When a ruleset lives under a rulebook (<rulebook>/rulesets/<ruleset>/),aethis generatepropagates the rulebook’s guidance hints and field vocabulary into the ruleset — the rulebook definition wins on shared field keys — so a common field (e.g. date of birth) is defined once at the rulebook level and the end user is asked for it only once.--kinddefaults toruleset, so existing behaviour is unchanged.- New
AethisClient.set_field_spec()(project field-spec endpoint, already served by aethis-core / used by the MCP server). No engine change required.
- New
- feat(rulebooks list): anonymous fallthrough to the public rulebook catalogue. With no cached API key,
aethis rulebooks listnow lists the cross-tenant public catalogue (rulebooks with public visibility, active status) instead of printing the v0.19.1 pointer message — completing the parity withaethis rulesets list. A dim one-liner (“No API key — showing public rulebooks…”) distinguishes the anonymous view; with a key, the tenant listing is unchanged.- New
AethisClient.list_public_rulebooks(); use withmake_anonymous_clientso a cached key doesn’t promote the call to an authenticated tenant listing. - Requires aethis-core v0.29.0+ on the target API (live on api.aethis.ai). Against an older engine the anonymous path surfaces the server’s 401 cleanly.
- New
- fix(rulebooks list): stop prompting browser sign-in for anonymous users.
aethis rulebooks listwith no cached API key used to trigger the lazy-auth browser login — bad first-contact DX for a read-only browse command. Rulebooks are tenant-scoped, so an anonymous caller has nothing to list; the command now prints a pointer to the anonymous public catalogue (aethis rulesets list) and toaethis login, and exits 1 without ever opening a browser.- True anonymous fallthrough (listing public rulebooks without an account, mirroring
aethis rulesets list) needs engine support for a public rulebook catalogue and is tracked separately; this release removes the login prompt in the meantime.
- True anonymous fallthrough (listing public rulebooks without an account, mirroring
- feat(update):
aethis update— self-update the CLI to the latest release. Detects how the CLI was installed (uv tool, pipx, or pip) and runs the matching upgrade command.aethis update --checkreports whether a newer release exists without installing anything.- Editable (development) installs are refused with a pointer to
git pull && uv syncinstead of clobbering the checkout. - The exit-time “new release available” banner now points at
aethis updaterather than a method-specific command. - fix: the banner’s uv upgrade hint was
uv tool install --upgrade aethis-cli, which re-resolves from scratch and silently drops any extra--withrequirements (e.g. plugin packages installed alongside the CLI). Both the banner’s install-method detection andaethis updatenow useuv tool upgrade aethis-cli, which honours the original install receipt. - A successful (or no-op)
aethis updaterefreshes the banner’s 24h cache, so the notice goes quiet immediately after updating.
- Editable (development) installs are refused with a pointer to
aethis_refine now performs finding-driven incremental re-authoring: it
seeds generation from the section’s active ruleset and asks the engine for the
minimal edit to fix failing test cases while keeping passing tests green,
instead of re-authoring the whole section from scratch. aethis_generate_and_test
is unchanged (from-scratch authoring).Why this matters: fixing one wrong case in a published ruleset previously meant a
full-section regenerate — expensive, and prone to silently regressing carefully
tuned behaviour (e.g. caseworker-review criteria that intentionally yield
undetermined). Refine keeps the blast radius to the criteria that actually need
to change; the full-suite gate still guarantees no regression ships.Requires aethis-core with the mode parameter on /generate (engine ≥ the
release shipping seed-from-existing refine). Older engines ignore the body and
fall back to from-scratch generation.client.generate()/generateAndTest()accept an optionalmodeand send{mode:"refine"}on the generation request body.
- feat(refine):
aethis refine+aethis generate --mode refinefor incremental, seed-from-existing re-authoring. Instead of re-authoring a whole section from scratch, refine seeds generation from the section’s active ruleset and makes the minimal edit to fix failing tests while keeping passing tests green.aethis refine [--hint "..."] [--seed-ruleset-id <id>]— the phase-3 TDD-loop command: optionally add a guidance hint, then refine. Defaults to seeding from the section’s active ruleset.aethis generate --mode refine [--seed-ruleset-id <id>]— the same capability via a flag ongenerate;--mode fresh(default) is unchanged from-scratch authoring.AethisClient.generate()gains optionalmode/seed_ruleset_id; a no-arg call still sends no body, so it stays backwards-compatible against engines without the parameter.- Requires aethis-core with the
modeparameter on/generate(live onapi.aethis.ai). Against an older engine the flags no-op (empty body = fresh).
Add the rulebook tier to the MCP read surface. Closes
aethis-mcp#43 for the
two endpoints the engine exposes today; the public-catalogue equivalent
(
aethis_discover_rulebooks) is deferred until aethis-core ships a no-auth
rulebooks catalogue endpoint.Why this matters: until now, an agent connected via MCP could see the parts
(rulesets via aethis_discover_rulesets / aethis_list_rulesets) and
evaluate the whole (aethis_decide with rulebook_id), but had no way to
find a rulebook or inspect how its rulesets compose. Concrete failure
mode from a real 2026-05-27 session: asked whether aethis/uk-fsm was
“three rulebooks or one rulebook with three rulesets”, the MCP gave no
read path that could answer.Added
aethis_list_rulebooks— lists rulebooks in the current tenant (auth-required, tenant-scoped). Returns the fields needed to distinguish one composed rulebook from N independent rulesets:rulebook_id,slug,name,domain,status,version,outcome_logic(the composition Expr AST),ruleset_refs, timestamps. Mirrorsaethis_list_rulesets.aethis_rulebook_schema— fetches one rulebook’s composition, bridged rulesets (with names + slugs + ruleset_ids), and aggregated input fields. Accepts either a slug (aethis/uk-fsm) or an opaquerb_*id. Mirrorsaethis_schemabut at the rulebook tier.AethisClient.listRulebooks()/getRulebookSchema()— wrapGET /api/v1/public/rulebooks/andGET /api/v1/public/rulebooks/{slug-or-id}/schema. The schema helper preserves the literal/in slugs (soaethis/uk-fsmhits the engine’s{namespace}/{name}matcher) and URL-encodes opaque ids.
Deferred
aethis_discover_rulebooks— the cross-tenant public catalogue equivalent ofaethis_discover_rulesets. The engine’s/api/v1/public/rulebooks/endpoint is tenant-scoped + auth-required on prod today; no anonymous catalogue variant exists. Will land once aethis-core adds it.
- feat(output): gh-style machine-readable output mode (
--output json,--json fields,--jq). Every list/show command (and the decision commands) now emit structured JSON on demand, soaethis rulesets list --output json | jq '.[0].slug'just works instead of trying to scrape ANSI-coloured Rich tables.--output table|json— pick the format. Default:tableon a TTY,jsonwhen piped (matches gh’s pipe-friendly autodetect).--json FIELDS— implies--output json; takes a required comma-separated value (--json id,name) that limits the payload to those fields. (gh’s bare---jsonintrospection trick is not yet exposed — Click/Typer’s option parser can’t cleanly distinguish “flag with no value” from “flag followed by positional”, so it’s deferred to a future--list-fieldsflag.)--jq EXPR— pipe JSON output throughjqbefore printing. Requires thejqbinary on PATH; clear error with install hint if missing.- Commands migrated:
rulesets list/show,rulebooks list/show/get-fields/tests list/schema/explain/decide,projects list/show,account keys,profile list,guidance list,fields,explain,decide,status. Each command has a sensible JSON shape —status --output json | jq .identity.key_idreturns the live key id without rooting through any prose. - Footer hints (
Try: aethis ...) are suppressed in JSON mode so pipes get clean output. - New module
aethis_cli/render.pyis the single emit point; new test filetests/test_render.pycovers the matrix.
- breaking(guidance export):
--outputrenamed to--output-fileto avoid clashing with the new global--outputflag. Short form-ounchanged. Affects scripts that pipe to a named file:aethis guidance export --output foo.yaml→aethis guidance export --output-file foo.yaml(or-o foo.yaml).
- fix(status, whoami): read the multi-profile credentials file the same way every other command does.
aethis login --api-key ...writesprofiles.<name>.api_keyto~/.config/aethis/credentials(the multi-profile schema introduced in v0.10), butaethis statusandaethis whoamihad stale local resolvers that only looked for a flat top-levelapi_key(andwhoamiwas looking at the wrong filename,credentials.yaml). Result: after a freshaethis login,aethis statusreportedno API keyandaethis whoamireportedNo Aethis API key configured, even though the same key worked foraethis projects list,aethis generate, and every other authoring command.- Both commands now route through the canonical
resolve_cached_key()helper inauth_helpers.py, which honoursAETHIS_API_KEYenv → active profile → keychain → legacy.yamlfile. - The
_resolve_cached_keysymbol is renamed toresolve_cached_key(public). The legacy_resolve_key_silent(status_cmd) and_resolve_api_key_lax(whoami_cmd) are removed. - Regression test in
tests/test_status_cmd.pywrites a real multi-profile credentials YAML to a tempXDG_CONFIG_HOMEand asserts both commands surface the key.
- Both commands now route through the canonical
- chore(server): tighten MCP instructions against decision extrapolation. Adds a “Reporting decisions” section to the server
instructionsblock (visible to every client model as part of its system prompt on connect). New rules forbid asserting facts that are not in the tool response, generalising a single-ruleset decision to a composite outcome, naming rulesets/rulebooks not yet observed in the session, and offering follow-up calls against unverified slugs. Triggered by a real user trace where a model summarising auk-fsm-child-eligibilitydecision closed with an offer to run the broaderaethis/uk-fsmrulebook “to get the complete household-level decision” — the rulebook exists but currently 422s on prod (emptyruleset_refs, see aethis-core#90), so the offer overstated what would actually happen. Advisory, not enforced — but client models reliably honourinstructionsblocks.
- feat(explain-failure):
Aethis.explain_failure()+AsyncAethis.explain_failure()— wrapsPOST /api/v1/public/rulesets/{ruleset_id}/explain-failure, returning the failing criterion and a targeted fix hint for a mismatched/decideresult. Acceptsfield_values,expected_outcome("eligible"|"not_eligible"|"undetermined"), and an optionaltest_name(default"test"). Return type isdict[str, Any]to matchexplain()/get_source()— can be tightened once the response shape stabilises. Note:ruleset_idmust be the concrete identifier (not a slug); the underlying endpoint does not currently resolve slugs. Previously, callers had to drop to rawhttpxfor this endpoint — flagged inrecipes/evaluate-a-case.mdxandrecipes/debug-a-decide.mdx.
- docs(readme): rulebook surface advertised on the PyPI landing page. The v0.5.0 release shipped
decide_rulebook()andrulebook_idonDecideResponse, but the README still framed the SDK as ruleset-only. Adds a dedicated “Composed rulebook” section with a runnable UK FSM example, the always-scope-gated note, and the async equivalent. - docs(install): switch
pip installtouv addper workspace no-pip rule. The PyPI landing page is a public-facing surface bound by.claude/rules/no-pip.md. Addsuv pip installas a venv-friendly alternative. - docs(engine_version): update sample audit-field comment from
aethis-core@0.10.0toaethis-core@0.27.0— matches live prod engine. - docs(beta): clarify that decision endpoints are anonymous only for single rulesets — rulebook decide is always scope-gated, so the SDK’s “anonymous when no key” claim needed a footnote.
- feat(rulebook):
Aethis.decide_rulebook()+AsyncAethis.decide_rulebook()— evaluate a composed multi-ruleset rulebook through the SDK. Mirrorsdecide()but sendsrulebook_idin the payload. Accepts either an opaquerb_<id>or a slug (e.g.aethis/uk-fsm). Requires an API key — rulebook evaluation is always scope-gated. Closes #14. Requires aethis-core v0.27.0+ live on the target API for slug-form rulebook paths. - feat(models): add
rulebook_id: Optional[str]toDecideResponse— surfaces the rulebook identifier when the response was a composed-rulebook decide. Backwards-compatible: ruleset-only decides keeprulebook_id=None.
- docs(readme): v0.27.0 accuracy pass. Three fixes for fresh-developer accuracy:
- Documented
rulebook_idas an alternative toruleset_idonaethis_decide— mutually exclusive; composed-rulebook evaluation always requires an API key. - Quickstart example: corrected field name from
speciestospace.crew.species(the actual field ID in the spacecraft-crew-certification ruleset). - Windsurf config path: corrected from
.windsurf/mcp.jsonto~/.codeium/windsurf/mcp_config.json(canonical path per aethis-cli README).
- Documented
Add rulebook surface to
aethis_decide — closes the converged-2-term
client-completeness gap for MCP. The tool now accepts either
ruleset_id (single ruleset) or rulebook_id (composed rulebook),
mutually exclusive. Mirrors aethis-sdk-python v0.5.0 and
aethis-cli rulebooks decide.Added
aethis_decidetool acceptsrulebook_idas an alternative toruleset_id. Pass an opaquerb_<id>or a slug likeaethis/uk-fsm. Composed-rulebook evaluation is always scope-gated by the engine — anonymous callers get HTTP 401.AethisClient.decideRulebook(rulebookId, fieldValues, options?)— parallel todecide(); sendsrulebook_idin the/decidepayload.
Changed
aethis_decidedescription and schema updated to reflect both paths. Tool validates that exactly one ofruleset_id/rulebook_idis provided.
Requires
- aethis-core v0.27.0+ live on the target API for slug-form rulebook
paths. The
rulebook_idbody field on/decidehas been supported since aethis-core v0.18.x.
- docs(readme): v0.27.0 accuracy pass. Three fixes for fresh-developer accuracy:
- Install block: removed the
pip installfallback (uv and pipx are the recommended forms per workspace policy). Development section:pip install -e ".[dev]"→uv pip install -e ".[dev]". - Added Rulebooks command-group section documenting the converged 2-term model surface shipped in v0.14.0–v0.16.1 (
aethis rulebooks+aethis rulesetspromote-to-live). - Updated engine_version example to
aethis-core@0.27.0(was absent; clarified to current production version).
- Install block: removed the
- docs(rulebooks set-logic): the docstring example for
field_ref.keynow matches engine behaviour. Phase A.16 (aethis-core v0.26.0+) added per-section aggregate group synthesis, sofield_ref.key = <ruleset_name>resolves to the AND of that ruleset’s groups. The unscoped group-name and scoped<ruleset_name>.<group>forms remain available for advanced compositions. Requires aethis-core v0.26.0+ live on the target API.
- feat(rulebooks):
aethis rulebooks set-logic— set the composition expression on a rulebook. The composition expression (server fieldoutcome_logic) is an Expr AST that combines per-ruleset outcomes into the rulebook’s final decision. Previously settable only via raw PATCH; now exposed via the CLI for multi-ruleset rulebooks (e.g. UK FSM’schild_eligibility AND (household_criteria OR universal_infant)).aethis rulebooks set-logic <id> -f logic.yaml— load from YAML/JSON fileaethis rulebooks set-logic <id> --logic '<json>'— inline JSON- Exactly one of
--file/--logicis required; both forms reject non-object payloads at the client side so server validation isn’t the first line of defence.
- feat(rulesets): ruleset lifecycle commands scoped to a rulebook. Phase B.1b of the converged 2-term model. Adds four new sub-commands under
aethis rulesets:aethis rulesets list <rulebook>— list rulesets in a rulebook (grouped byruleset_namewith version counts, live version, and observed states). The legacy-p <project_id>and--publicmodes are preserved while the project-scoped authoring pipeline retires in a future phase.aethis rulesets create <rulebook> <ruleset_name> [-n "Display name"]— create a new draft Ruleset inside the rulebook. The display name auto-derives fromruleset_nameif not provided (child_eligibility→Child Eligibility).aethis rulesets show <rulebook> <ruleset_name>— full version history for one ruleset name (bundle_id, version, state, created), with live version highlighted.aethis rulesets promote-to-live <rulebook> <ruleset_name> <ruleset_id> [--note "..."]— atomically promote atesting-state ruleset version tolivevia the Phase A.4 service. Auto-cuts a new rulebook version; previous live ruleset is archived.
- feat(client): four new
AethisClientmethods —create_ruleset_in_rulebook,list_rulesets_in_rulebook,show_ruleset_in_rulebook,promote_ruleset_to_live. - Requires aethis-core v0.20.0+ live on the target API (Phase A.8 endpoints).
- feat(rulebooks): new
aethis rulebookscommand group. First user-facing surface for the converged 2-term authoring model (workspace PR #64, aethis-core PRs #133-139). A Rulebook is the whole form — the execution unit — that owns a locked field vocabulary, composition logic, rulebook-level test cases, and an integer version history.aethis rulebooks list— list tenant rulebooksaethis rulebooks show <id-or-slug>— full configurationaethis rulebooks create <name> --domain <d> [--slug ...]— create draftaethis rulebooks set-fields <id> -f fields.yaml— replace locked vocabularyaethis rulebooks lock-fields <id>/unlock-fields <id>/get-fields <id>aethis rulebooks tests add <id> -f scenario.yaml— embed full-form test caseaethis rulebooks tests list <id>/delete <id> <tc_id>aethis rulebooks activate <id>/archive <id>— lifecycleaethis rulebooks decide <id> -i '{...}' [--explain]— evaluate composed rulebookaethis rulebooks schema <id>/explain <id>— combined schema + explanations
- feat(client): new
AethisClientmethods for every rulebook REST endpoint (create / list / show / update / activate / archive / set-fields / lock-fields / unlock-fields / get-fields / add-test / list-tests / delete-test / decide-rulebook / get-rulebook-schema / explain-rulebook). - Requires aethis-core v0.19.0+ live on the target API (the Phase A.6 endpoints).
- The legacy
aethis projects/aethis generate/aethis test/aethis publishcommand tree is unchanged in this release — replacement lands in the next minor (Phase B.1b: ruleset lifecycle + project retirement). No backward-compat shims are planned past public release.
- feat(models): add
name: Optional[str]to ruleset response models — surfaces the human-readable section name introduced in aethis-core v0.18.0. AddsRulesetSummary(anonymous catalogue /GET /api/v1/public/rulesets) andRulesetListItem(project-scoped /GET /api/v1/public/projects/{id}/rulesets) as typed models, and adds the samenamefield toSchemaResponse. Backwards-compatible: pre-backfill rulesets serialise withname=None.
Add optional
name parameter to aethis_publish tool — lets clients
override the human-readable section name when publishing a ruleset.
Companion to aethis-core v0.18.0’s PublishRequest.name field.Added
aethis_publishtool now accepts an optionalnameparameter. When supplied, it overrides the section name stored on the ruleset (default is a titlecase ofsection_id, e.g."english_language"→"English Language"). Section names are surfaced in rulebook responses so end users can see which sections compose a rulebook.AethisClient.publish()now accepts a thirdname?: stringargument and includes it in the POST body when set.
Tests
server.test.ts— two newaethis_publishcases: forwardingnameto the client and echoing it in output; confirmingnameis omitted when not provided.client.test.ts— two newpublish()cases: body containsnamewhen provided; body is absent when neitherlabelnornameis set.
Surface the human-readable section
name in aethis_list_rulesets and
aethis_discover_rulesets tool output. The engine has been returning
name on both RulesetSummary (public catalogue) and RulesetListItem
(project-scoped) responses since aethis-core v0.18.0; the MCP server
already forwarded every API field verbatim via JSON.stringify, so the
data was reaching the LLM, but the tool descriptions didn’t advertise
the field. The descriptions now mention name so models know to read
and surface it to users (e.g. “Knowledge of language and life in the
UK” instead of just b_123…).Changed
aethis_list_rulesetstool description now mentions the human-readablenamefield returned alongside ruleset ID, status, version, field count, and rule count.aethis_discover_rulesetstool description now listsnamein the documented response shape.
Tests
aethis_list_rulesetsandaethis_discover_rulesetsserver tests assert thenamefield passes through to the LLM-facing JSON output.
- feat(rulesets): show the human-readable section
namecolumn inaethis rulesets listoutput (both the public showcase and project-scoped tables). Surfaces the new field from aethis-core v0.18.0.
- feat: pluggable auth providers. Profiles now carry an optional
auth_mode(default"api_key") andaudiencefield. The newaethis_cli.auth_providersmodule exposes a process-local registry; plugins (e.g.aethis-cli-internal) canregister_provider("gcloud_id_token", ...)to add staff/internal auth schemes without touching the published package.AethisClientaccepts an optionalauth_providercallable, andmake_authed_client(...)picks the right provider based on the active profile’s mode. - feat:
aethis statusnow prints the active profile name + auth mode (plus audience when set). For non-api_keymodes it shows “provider-minted at request time” instead of calling/me, which is X-API-Key-only. - chore: un-hide the
--base-urlglobal flag inaethis --help(it was already implemented, justhidden=True).
Security hardening pass. Bundles the v0.5 security review fixes into one
release. Closes #33, #34, #35; addresses GHSA-ph7q-r9q4-922g (disclosed
on publish).
Security
- GHSA-ph7q-r9q4-922g (high) — prompt injection via unsanitised API
response text in
aethis_explain_failure.formatExplainFailurenow wraps every API-supplied free-text field (diagnosis,dsl_hint, criteriontitle/rule_text/source_refs) in an<api_response>fence and prepends a one-line preface telling the model the contents are data, not instructions. Literal closing tags inside payloads are neutralised so a payload cannot break out of the fence. The samefenceUntrustedhelper has been applied to other free-text API surfaces (aethis_next_question,aethis_discover_sections,aethis_refine_sections,aethis_discover_fields,formatTestResults). - #33 —
src/credentials.tsnow resolves the credentials file viafs.realpathand asserts the canonical path sits under$HOME(or under an absoluteXDG_CONFIG_HOMEthe user controls); refuses withUnsafeCredentialsErrorotherwise. Permissions check now matchesssh/aws-clibehaviour: any group/other bit set on the credentials file → refuse withPermissions 0NNN ... too open. Run: chmod 600 <path>. - #34 —
progress_detailfrom the polling API is sanitised before it lands on stderr: control characters are stripped (TAB preserved) and the body is capped at 120 visible chars +…. Full-fidelity output is gated behindAETHIS_MCP_VERBOSE=1. Prevents the server from injecting terminal escape sequences or PII into anything that captures the MCP process stderr.
Changed
- #35 — Authoring tools now accept safer per-call key forms.
- New
anthropic_key_env: string— name of an env var the MCP server reads at call time. Preferred. The raw value never appears in the tool call so it does not land in the MCP host’s session transcript JSONL. - New
anthropic_key_keychain: string— macOS keychain reference, eitherservice:accountor justaccount(service defaults toaethis-anthropic-key). - Raw
anthropic_key/openai_keyarguments remain accepted for backwards compatibility but are now marked[sensitive — do not echo or log]in the schema; deprecated in tool descriptions. resolveLlmKey(exported fromsrc/credentials.ts) consolidates the resolution chain and throwsMissingLlmKeyErrorif every form is empty.- Applies to
aethis_generate_and_test,aethis_refine,aethis_discover_fields,aethis_refine_fields,aethis_discover_sections,aethis_refine_sections.
- New
Docs
- README: new “Passing your Anthropic key safely” section showing env / keychain forms first; raw-key form marked deprecated.
- CLAUDE.md: new gotchas covering safe-key resolution and the untrusted-content fencing helper.
- fix(decide):
aethis decide --explainno longer crashes withAttributeError: 'str' object has no attribute 'get'. The CLI previously treated the engine’sexplanationfield as a flatlist[dict], but the public decide route returns a layered{decision, decision_path?, groups: [{group, status, criteria: [{title, status, supporting_facts?, ...}]}], unused_facts}shape. The “Rules” block now walks the actual structure and renders each group + criterion with PASS/FAIL marks, supporting fact field/value pairs underneath satisfied criteria, and a final list of unused fields (provided answers that no satisfied criterion referenced — useful for catching field-name typos).
- fix(login): default
AETHIS_CLERK_CLIENT_IDto the OAuth Application registered on theclerk.aethis.aiClerk instance. The previous default belonged to a different Clerk app, soaethis loginreturnedinvalid_clientagainst the dev-tools domain set in 0.12.1. - fix(account): default
AETHIS_CLERK_DOMAINtoclerk.aethis.aiforaethis account generate(matching the 0.12.1 change toaethis login); previously still pointed at the immigration domain.
- feat:
decide,explain, andfieldsno longer prompt for sign-in when no API key is present. Public rulesets are now accessible with zero setup — the CLI silently uses an anonymous client and lets the server return an error only if a private ruleset is requested. - fix: hide
--base-urlglobal flag fromaethis --help(internal dev override;AETHIS_BASE_URLenv var unchanged) - docs: reorder
aethis --helpto lead with the no-auth explore flow, then authoring
- docs: surface
aethis-skillsas the optional agent workflow layer on top of MCP.
- fix: default Clerk domain changed from
clerk.aethis.legaltoclerk.aethis.aiso developer portal users can authenticate viaaethis login(closes aethis-cli#40)
- fix: align
package.jsonrepository metadata with GitHub provenance so npm Trusted Publishing can verify the package source.
- fix: pin
zodto v3 so the MCP SDK tool registration types match the build-time schema shape;npm publishnow runs theprepublishOnlyTypeScript build successfully.
- fix: update
examples/session.pyto useAETHIS_RULESET_IDenv var (was deprecatedAETHIS_BUNDLE_ID) and replace stale internal default with the publicaethis/construction-all-risksslug
- docs: fix stale
bundle/bundle_id/aethis_create_bundleterminology indocs/demo-construction-insurance.mdanddocs/agentic-decision-systems.md— these files were not caught by the v0.3.0 rename sweep. All references now useruleset/ruleset_id/aethis_create_ruleset - chore: bump
server.jsonversion to 0.4.1 (was lagging behindpackage.json) - security: regenerate
package-lock.json— bumpshono4.12.12 → 4.12.18,fast-uri3.1.0 → 3.1.2,ip-address10.1.0 → 10.2.0,postcss8.5.8 → 8.5.14; clears all 6 open Dependabot alerts (closes #19)
- feat: new
aethis_discover_rulesetstool — lists the cross-tenant public showcase catalogue (no authentication required). Mirrors the no-auth policy ofaethis_decide/aethis_schema/aethis_explain. Returns slug, ruleset_id, description, field_count, rule_count for each entry; the slug or ruleset_id can then be passed to the existing decision tools. Distinct fromaethis_list_rulesets, which remains tenant-scoped and authenticated. Tool count: 24 → 25. - feat:
client.discoverRulesets(limit, offset)wrappingGET /api/v1/public/rulesets. - docs:
aethis-decideprompt and server-instructions now point ataethis_discover_rulesetsfor first-time discovery (no key) before falling back toaethis_list_projects→aethis_list_rulesetsfor authenticated tenant browsing.
- fix: remove
examples/demo_core.sh(internal dev script referencingaethis-coreby name and a private API path — not intended for public release) - fix: update
tests/e2e/test_spacecraft_e2e.pyto resolve the spacecraft fixture fromexamples/spacecraft-crew-rules/instead of an internal path; drop internal service name from comment - docs: fix “rule bundle” → “ruleset” in
examples/spacecraft-crew-rules/README.md
- feat(updater): gh-style update-check banner. On startup the CLI
kicks off a background thread that queries PyPI; if a newer release
is available it prints a one-line notice to stderr at exit:
“A new release of aethis-cli is available: 0.11.0 → 0.12.0 — to
upgrade, run: <method-aware command>”. Detects whether the install
came via uv tool, pipx, or pip and renders the matching upgrade
command. Result is cached for 24 h at
~/.config/aethis/update_check.json. Suppressed automatically when stderr is not a TTY (CI, piped output). Disable withAETHIS_DISABLE_UPDATE_CHECK=1. The check never blocks the command — failures are silent.
- feat(rulesets):
aethis rulesets list --publiclists the cross-tenant public showcase catalogue (no auth required). When run with no--project-idand no project context, falls through to the public catalogue automatically with a one-line hint — so a fresh signup sees something the moment they install the CLI instead of an empty list. Combine withaethis fields -b <slug>/aethis explain -b <slug>/aethis decide -b <slug>to fully exercise a ruleset without an API key. - feat(profiles): named credential profiles with both per-invocation
flag (
aethis --profile new-dev …) and sticky default (aethis profile use new-dev). Manage withaethis profile list/use/add/remove. Reserved profile nameanonymousforces unsigned mode — handy for testing what a fresh signup sees without losing your admin key.aethis login --profile <name>writes into the named slot. Credentials file format upgraded to{active_profile, profiles: {...}}; legacy single-key files are read transparently and rewritten to the new shape on next save. - feat(client):
AethisClient(unsigned=True)andmake_anonymous_client()helper for paths that must hit the anonymous surface without accidentally sending a cached key. - feat(client):
client.list_public_rulesets(limit, offset)wrappingGET /api/v1/public/rulesets.
- feat(publish): thread
--forcethrough to the server-side TDD gate introduced inaethis-core0.11.0.client.publish()gains aforce_unsafe: bool = Falsekeyword;aethis publish --forcenow passesforce_unsafe: truein the request body so the server-side gate is bypassed (and apublish_force_bypassaudit event is recorded). Older engines ignore the field — no breakage. Without--force, the new gate refuses publishing over a failing test suite even when the CLI’s own test gate is bypassed (e.g. by a direct curl that doesn’t use the CLI). Closes the cli/server asymmetry that nearly shipped a 10/11 ruleset to a canonicalaethis/*slug on 2026-05-07.
- docs: link to the test-driven authoring guide on docs.aethis.ai and surface the publish-gate guarantee (rulesets cannot publish with a failing test) in the private-beta callout. Reference surface only — no code changes
- docs: surface the test-gate guarantee —
aethis_publishrefuses to publish a ruleset with a failing test, derived from positioning bible §5/§7. Strengthens the existing Note to an Important callout and annotates the publish line in the four-stage workflow - docs: drop
force=truemention from troubleshooting — surfacing the override on the public README undermines the “cannot be published with failing tests” guarantee. The API parameter remains in the engine; whether to deprecate it is tracked separately - docs: fix tool count (25 → 24); tools table sums to 24 (5 + 7 + 8 + 2 + 2). Fixed in README header and in CLAUDE.md
- docs: link to docs.aethis.ai/agents/onboarding from Install section
- docs: link to docs.aethis.ai/agents/onboarding from MCP one-liner section
- docs: remove positioning paragraph above Install — reference surface (per aethis.os/positioning/surface-types.md); the tagline is enough
- docs: add private-beta callout for authoring endpoints (decision endpoints remain anonymous)
Changed
- docs: align README with positioning bible — add problem/solution/methodology intro paragraph before Install section.
- docs: add
aethis-bible:markers to derived copy blocks (sourced frompublic-messaging.md §3/§4). - fix: terminology audit found no deprecated “rule bundle” or
<5msinstances in README; no replacements needed.
Changed
Aethis(api_key=...)andAsyncAethis(api_key=...)now acceptapi_key=None(or no argument) for the developer beta. Evaluation endpoints (/decide,/schema,/explain,/source) work anonymously, so the SDK no longer forces a key on instantiation. Whenapi_keyis omitted, thex-api-keyheader is simply not sent. Authoring endpoints will still return 401 without a key. Existing callers passingapi_key="..."are unaffected.- README quickstart now shows
Aethis()(no key) as the primary form, targetsaethis/uk-fsm/child-eligibility(a live public ruleset) instead of the datedeng_lang:20250912-ec5d7c23, and prints the audit fields (inputs_hash,decision_id,decision_time,engine_version) added in 0.3.2. Configuration table updated:api_keyis now documented as optional during the developer beta. examples/oneshot.pyrefreshed to match: no key required by default,AETHIS_BUNDLE_IDenv var renamed toAETHIS_RULESET_ID(catching the 0.3.0bundle → rulesetrename it had missed), targets the live UK Free School Meals ruleset, prints the audit fields.
Notes
- Backwards-compatible:
Aethis(api_key="ak_live_...")continues to work exactly as before. - This pairs with the public-surface positioning that evaluation is free during the developer beta — see
docs.aethis.ai.
Added
DecideResponse.decision_id— per-call audit identifier returned by the engine.DecideResponse.inputs_hash— canonical SHA-256 fingerprint of the input set.DecideResponse.decision_time— ISO-8601 timestamp of the decision.DecideResponse.engine_version—aethis-core@<semver>string identifying the engine that produced the decision.
Fixed
DecideResponsepreviously declaredruleset_idtwice; Pydantic silently overrode the first declaration with the second. Deduplicated.- The four audit fields above were already returned by
/api/v1/public/decidebut were silently dropped by Pydantic because the model didn’t declare them. Callers can now read them directly off the typed response — no need to reach for the raw JSON. This is the audit-trail fingerprint that the docs and homepage prominently advertise (inputs_hash,decision_id); shipping an SDK that hid it was a defect.
Notes
- Backwards-compatible. All four new fields default to
None, so older engines that don’t emit them still parse cleanly.
Fixed
aethis_sdk.__version__now resolves from installed package metadata viaimportlib.metadatainstead of a hardcoded constant. Previously reported"0.1.0"on every install regardless of the actual package version. Falls back to"0.0.0+unknown"only if the package is imported without being installed (editable dev or zip-on-PYTHONPATH).- Package description on PyPI:
"…and bundle schemas"→"…and ruleset schemas"to match the v0.3.0 public-surface rename.
Added
- README PyPI / Python-version / License shields.
- docs: restructure README as dev MCP docs — Install / Quick start / Tools / Setup leads, positioning sections (Problem, Accuracy, When to use this, How it works, Example walkthrough) removed; their content belongs in docs.aethis.ai or the benchmarks repo
- docs: trim narrative paragraphs across Quick start, Conversational eligibility, and Authoring; collapse repeated Tips into terse callouts
- docs: header tagline rewritten to a single factual line; link bar updated to the new structure
- docs: normalise tone to documentation register — replace argumentative Proof section with one-liner accuracy claim, neutralise example framing, trim sales-y bullets in When to use this
- docs: add private-beta callout for authoring tools (decision tools remain public, no key required)
- docs: align README with positioning bible — promote 225-scenario accuracy framing
- docs: add aethis-bible: markers to derived copy blocks
- docs: fix latency claim to <1ms (was <5ms)
- fix: replace deprecated “rule bundle” terminology with “ruleset”
- docs: remove Why Aethis section — package README is a reference surface (per aethis.os/positioning/surface-types.md); install / quick start / authentication is the right lead, not a problem statement
- docs: add private-beta callout for authoring tools (decision tools remain public, no key required)
- docs: clarify in Authentication that aethis login requires an invite during the beta
- docs: align README with positioning bible — add Why Aethis section, solution framing, TDD methodology beat
- docs: add aethis-bible: markers to derived copy blocks
- fix: replace deprecated “rule bundle” terminology with “ruleset” in pyproject.toml description
Changed (Breaking)
- Renamed the public bundle concept to ruleset throughout the SDK to match the
aethis-core 0.10.0API contract. Everybundle_idparameter and JSON key is nowruleset_id. URL paths inside the client moved from/api/v1/public/bundles/...to/api/v1/public/rulesets/.... TheSessionconstructor now takesruleset_idand exposessession.ruleset_idinstead ofsession.bundle_id. Class names:BundleSummary→RulesetSummary.
Required
- Engine
aethis-core 0.10.0or newer. Older engines respond at the legacy/bundles/*paths and this client will 404. Pinaethis-sdk==0.2.0to keep working against an older engine.
- Breaking: renamed the public bundle concept to ruleset throughout the MCP tool set, to match the
aethis-core 0.10.0API contract. The compiled rule artefact is now called a ruleset in every tool name, parameter, and prose description. Specifically:- Tools:
aethis_create_bundle→aethis_create_ruleset,aethis_list_bundles→aethis_list_rulesets,aethis_archive_bundle→aethis_archive_ruleset - Parameters: every
bundle_id→ruleset_id - JSON keys returned to the agent:
bundle_id/latest_bundle_id/bundle_version/deprecated_bundles/result_bundle_id/bundle_refs→ruleset_idetc. - URL paths inside the client:
/bundles/...→/rulesets/...
- Tools:
- This release requires
aethis-core 0.10.0or newer. Older engines respond at the legacy/bundles/*paths withbundle_idJSON keys; this client expects/rulesets/*and will 404. Pinaethis-mcp@0.2.6if you need to keep working against an older engine until you can deploy. - MCP tool renames are part of the public LLM-facing contract. Coding agents that have learnt the old tool names (
aethis_list_bundlesetc.) from training data will get “no such tool” errors and need to retry against the new names. Tool descriptions explicitly call out the new naming so the LLM picks it up on first read.
- Breaking: renamed the public bundle concept to ruleset throughout the CLI to match the
aethis-core 0.10.0API contract. The compiled rule artefact is now called a ruleset everywhere — in command names, in flag names, in JSON keys, and in prose. Specifically:aethis bundles list/archive→aethis rulesets list/archive--bundle-idflag →--ruleset-idclient.list_bundles()/archive_bundle()/get_bundle_schema()/explain_bundle()/get_bundle_source()/set_bundle_visibility()SDK methods →*_ruleset- JSON keys
bundle_id/latest_bundle_id/bundle_version/bundle_refs→ruleset_idetc. - Default scope strings
bundles:read/explain/write→rulesets:*(validated against the engine’s permission registry)
- This release requires aethis-core 0.10.0 or newer. Older engines return
bundles:*scopes and the CLI will reject them as invalid. Pin toaethis-cli==0.7.2if you need to keep working against an older engine until you can deploy.
- Docs: replaced two stale
aethis.ai/sign-uprequest-access pointers in the README authoring section withaethis.ai/developer-access. After the Clerk cutover,/sign-upserves the Clerk SignUp form for invitees rather than the Notion request-access form. No code or behaviour changes.
- Docs: replaced the stale
aethis.ai/sign-uprequest-access link withaethis.ai/developer-accessin the README “Author your own rules” section and in theaethis whoamihint shown when the active key has no authoring scope. After the Clerk cutover,/sign-upserves the Clerk SignUp form for invitees rather than the Notion request-access form, so external “Request access” pointers were broken. No code path changes.
- Docs: README Quick start now leads with
aethis mcp install --target all(via aethis-cli v0.5.0+). The manualclaude mcp addand per-client JSON tabs are demoted to “Manual install” beneath. Setup section gains a Keys & security subsection coveringAETHIS_API_KEYvsANTHROPIC_API_KEYplacement (MCP client config, not shell), rotation workflow (aethis account generate+aethis account revoke), and multi-machine guidance. - Discoverability:
package.jsonkeywordsextended withregulation,policy,eligibility-check,deterministic-decision— matches the highest-intent search terms used by developers in regulated domains. Existing keywords retained. - CLAUDE.md updated to note the
aethis mcp installinstall path so future contributors don’t re-document the manual JSON as primary.
- Docs: README gains a dedicated Authentication section explaining the three modes (
aethis loginfor explicit setup, lazy auth for inline mid-command sign-in,--no-promptfor CI). Authoring quickstart leads withaethis init(the v0.7.0 wizard prompts for a name and runs sign-in itself, soaethis loginas a separate step is no longer needed). Environment-variable table expanded to coverAETHIS_BASE_URLandANTHROPIC_API_KEY. Troubleshooting entry forAuth errornow mentions the lazy-auth prompt and--no-prompt. CLAUDE.md updated to document theaethis mcp installpath, lazy-auth helper, and--no-promptflag for future agents working on the CLI. No behaviour change.
- New:
aethis initfirst-run wizard. With no args, prompts for the project name (default = current directory name); a positionalaethis init <name>keeps working unchanged. If no API key is cached, triggers the same OAuth flow asaethis loginbefore any filesystem writes — Ctrl-C during browser sign-in no longer leaves a half-scaffolded project on disk. After scaffolding, prints the next-step ladder (aethis sections discover→fields discover→generate --poll) so new users have a clear path forward. New--no-promptflag for scripted use; with that flag, missing required values fail fast and missing auth surfaces a cleanAuthRequirederror instead of opening a browser. 10 new tests covering prompted, non-prompted, no-auth + interactive, no-auth +--no-prompt, and name-validation paths. Closes #15.
- New: lazy auth. Authenticated commands (
aethis projects list,generate,publish, etc.) now detect missing credentials or 401 responses and offer an inline browser sign-in prompt:"No API key. Open browser to sign in? [Y/n]". On accept, the same OAuth flow asaethis loginruns, the key is cached, and the original command retries — exactly once, no infinite loops. Non-TTY stdin/stdout (CI, pipes) and the new--no-promptglobal flag skip the prompt and surface a cleanAuthRequirederror.--api-key <key>still bypasses the helper entirely. New helper moduleaethis_cli/auth_helpers.py; the OAuth flow insidecommands/login_cmd.pywas factored into a reusablerun_browser_login(). 17 new tests intests/test_lazy_auth.py. Closes #12.
- New:
aethis mcp install --target <client>writes the MCP server entry into your editor’s config in one shot. Supportsclaude-code(project-level.mcp.json),cursor(~/.cursor/mcp.json),claude-desktop(~/Library/Application Support/Claude/claude_desktop_config.jsonon macOS,~/.config/Claude/...on Linux),windsurf(~/.codeium/windsurf/mcp_config.json), and--target allfor everything at once. Idempotent, preserves any other configured MCP servers.aethis mcp uninstall --target <client>reverses the install. Closes #16.
- UX:
aethis login --helpnow reads “Sign in and store an API key locally. First-time setup — this is all you need.”aethis account generate --helpclarifies it’s for additional keys (rotation, multi-machine, scoped access). After successfulaethis login, a tip line points ataethis status/aethis account keys. README quickstart collapses any “first login then generate” sequence into a singleaethis loginstep. No behaviour change. Closes #13.
- Docs: README install section now leads with
uv tool install aethis-cli(recommended) andpipx install aethis-cli, withpip installin a venv as the third option. Pairs with Aethis-ai/docs#12. Closes #14.
First version published to npm since 0.2.2. The
v0.2.3 tag exists in git but predates the publish workflow — it never reached npm. This release rulesets all work since 0.2.2.Registry
- MCP Registry submission ready. Added
mcpName: io.github.aethis-ai/aethis-mcptopackage.jsonand a top-levelserver.jsondeclaring the npm package, transport, and environment variables. Submit viamcp-publisherafternpm publish.
Breaking Changes
openai_keyparameter renamed toanthropic_keyonaethis_generate,aethis_generate_and_test, andaethis_refine. The old parameter name is still accepted for backwards compatibility but will be removed in a future release.
Improvements
- Better error messages on generation failure. Failed jobs now surface classified error details (invalid key, rate limit, connection failure) instead of “unknown error”.
- Sends both
X-Anthropic-KeyandX-OpenAI-Keyheaders for backwards compatibility with older API versions. aethis_explain_failureclarification. Tool docs now note thatruleset_idmust be the concrete ID from a/decideenvelope; slugs are not yet resolved on this endpoint (tracked in aethis-core#51).
Docs
- Proof section updated to cite the Simpson et al. 2026 benchmark paper. Replaced the pre-paper 11-scenario table (GPT-5.4-mini 82%, GPT-5.3 27%) with paper-backed figures from Table 8b of the published benchmark. Removed the 27% GPT-5.3 claim — the paper identifies that figure as a harness-configuration bug; the corrected value is 63.6%.
- Proof section: add §6.10 LegalBench external-validation paragraph. v3.8 of the paper adds external validation across 9 LegalBench tasks (949 held-out cases). Combined paired-binomial McNemar’s: p < 0.001 vs Sonnet 4.6, p = 0.003 vs Opus 4.7, p < 0.001 vs GPT-5.4. Linked to the public LegalBench harness at
confidently-wrong-benchmark/legalbench/. - Proof section: replaced 11-scenario subset table with v3.8 adversarial extension (§6.4.1). The v3.7 11-scenario exception-chain table no longer differentiates current frontier models from the engine (GPT-5.4 default and low both 11/11, Opus 4.7 11/11). The Proof section now leads with the v3.8 adversarial extension (20 newly-authored scenarios; engine 20/20; Opus 4.7 18/20; GPT-5.4 default 19/20 with 0 reasoning tokens; Sonnet 4.6 19/20) and the shifting-ground argument from paper §6.5 Finding 6.
- Use
aethis/construction-all-risksslug in CAR proof example for stable URL across ruleset regenerations. - Invite-only beta messaging replaces “rolling out now” framing throughout README — explicit approval-gated framing aligned with current onboarding.
docs.aethis.aibadge added to README.
Internal
- Added
.github/workflows/publish.yml(provenance via OIDC +NPM_TOKEN) so future tag pushes auto-publish. - Added Claude PR review workflow (dry-run mode).
- Added internal
CLAUDE.mdfor agent onboarding.
Two bug fixes that block the documented quickstart against public bundles.
Bug fixes
aethis decide -b <slug>/explain -b <slug>/bundles archive -b <slug>now accept slugs. The classifier in_id_utils.classify_idpreviously returned"unknown"for slugs (e.g.aethis/uk-fsm/universal-infant), andrequire_bundle_idrejected them with"is not a valid Bundle ID". The public API resolves both bundle IDs and slugs on/decide,/schema, and/explain, so the CLI now passes both through. Error message updated to mention slugs and link toaethis bundles list.aethis fields -b <bundle>no longer requires anaethis.yaml. It now uses the sameload_client_or_fallback()helper asdecide,explain,bundles, andprojects— read-only commands work from any directory. Previously this command errored out with"No aethis.yaml found"even when called with a concrete bundle reference.
Added
DecideResponse.slug— stable, human-readable handle for the ruleset (e.g.aethis/uk-fsm/child-eligibility). Set when the resolved ruleset was published under a slug;Noneotherwise. Prefer this overruleset_idfor any reference that should survive ruleset regeneration.SchemaResponse.slug— same handle, surfaced fromGET /rulesets/{id}/schema.
Notes
- Backwards-compatible. Existing code that reads
ruleset_idkeeps working unchanged;slugis purely additive. - Requires the
aethis-coreengine release that surfaces the field in/decideand/rulesets/{id}/schemaresponses (rolling out 2026-04). Older engines will simply leaveslug=None.
aethis status output polish
- Server line now shows just the URL when it’s the default (
https://api.aethis.ai) — the(default — no override)suffix was noise in the common case. Overrides (AETHIS_BASE_URL,aethis.yaml) still show source with a green marker. - Identity line now says
✗ API key rejected (run \aethis login` to re-authenticate)when/mereturns 401/403/404, instead of the raw✗ 404 from /me (Not Found)` HTTP message. Other HTTP errors keep a contextual message.
This release ships the rich-status and read-only-from-anywhere work that the 0.2.0 notes already described but which hadn’t actually been merged into a published release yet. (The code was sitting in a local branch; the prior 0.2.x/0.3.x wheels still had the minimal status command.)
aethis status — context-aware summary
aethis statuswith no args now prints CLI version, resolved server URL (with source — env / yaml / default), loadedaethis.yaml, bundle id from.aethis/state.json, andwhoamiidentity (key id, tenant, tier, scopes,can_author). Helps answer “what will my next command actually hit?” before running it.aethis status -p <project_id>(or from inside a project dir) still shows generation progress, appended after the global summary.
Read-only commands usable from anywhere
aethis explain,decide,bundles list,bundles archive,projects list,projects show,projects archiveno longer require anaethis.yamlin the current directory — they fall back toAETHIS_BASE_URL(or the defaulthttps://api.aethis.ai).aethis explain/decidenow reject Project IDs (proj_*) passed to-b/--bundle-idwith a one-line hint pointing at the Bundle column ofaethis projects list, instead of silently 404’ing.
Internals
- New
resolve_base_url_with_source()/load_client_or_fallback()helpers inaethis_cli/config.pythat the above commands share. - New
aethis_cli/commands/_id_utils.py+ test coverage for bundle-id validation. - New tests for
explain,status, and_id_utils.
Docs cleanup
- README and docs.aethis.ai/interfaces/cli no longer document
AETHIS_BASE_URLor showbase_url:in theaethis.yamlexample — public users always hithttps://api.aethis.ai, and the documented values were just duplicating the default. The env var still works as an override for devs and CI; it’s intentionally undocumented. - Dropped the
AETHIS_CLERK_DOMAINenv var from the README (marked “development only” and confusing for public users). The override still works in code.
Trim public CLI to the developer API surface
The public CLI now only ships commands every developer can use againsthttps://api.aethis.ai. Privileged and staff-only commands have been removed and will live in a separate internal plugin package.Breaking changes:- Removed
aethis source— internal-only DSL viewer; moved to theaethis-cli-internalplugin. - Removed
aethis account permissions— IAM permission registry; internal-only. - Removed the
aethis guidance domain …group (and the deprecatedaethis domain guidance …alias) — domain-level guidance is staff-managed. - Removed the global
--base-urlflag (plus the per-command--base-urlonlogin,account generate,account keys,account revoke). TheAETHIS_BASE_URLenv var still overrides the default. The flag had no meaning for the public API target and cluttered--help.
- The CLI now discovers plugins via Python entry points under the
aethis_cli.pluginsgroup. A plugin exposes one callableregister(app: typer.Typer) -> Noneand attaches extra commands to the root app. Plugin load failures print a single warning to stderr and never crash the CLI. - The staff-facing
aethis-cli-internalpackage uses this hook to re-attachsource,domain guidance,permissions, and the--base-urlflag.
Consolidated guidance command tree
aethis domain guidance ...moved underaethis guidance domain ...— thedomaingroup exists only to hostguidance, so having two top-level trees for the same concept was confusing. All four subcommands (add,list,import,export) behave identically on the new path.- The old
aethis domain guidance ...path still works as a hidden deprecated alias: invocations continue to succeed and emit a one-line deprecation notice to stderr. It is no longer shown inaethis --help. Planned removal in a future release.
aethis status — global CLI context
- New behaviour:
aethis status(no args) now prints a one-screen summary of the current CLI context: CLI version, resolved server URL (with source —--base-url/ env / yaml / default), loadedaethis.yaml+ project, bundle id from.aethis/state.json, and whoami identity (key id, tenant, tier, scopes,can_author). Answers “what will the next command hit?” — the usual cause of “why is my project missing?” is talking to the wrong server. - Backward compatible:
aethis status -p <project_id>(or invoked from a project dir) still shows generation progress, now appended after the global summary.
UX improvements for read-only commands
aethis explain,decide,bundles list,bundles archive,projects list,projects show, andprojects archiveno longer require anaethis.yamlin the current directory — they fall back toAETHIS_BASE_URL(or the defaulthttps://api.aethis.ai) when invoked from anywhere.aethis explainanddecidenow reject Project IDs (proj_*) passed to-b/--bundle-idwith a one-line hint pointing at theBundlecolumn ofaethis projects list, instead of silently proceeding to a 404.aethis --base-url <url>is now a top-level flag, equivalent to settingAETHIS_BASE_URLfor one invocation. Lets you hit staging or a self-hosted instance without editingaethis.yaml.aethis projects listprints a short tip after the table showing how to copy a Bundle value intoaethis explain -b ….- Configuration and authentication errors now render as a single red line via the existing
cli()handler, not a Rich traceback panel.pretty_exceptions_enable=Falseis set on every Typer app.
Better --help
- Top-level
aethis --helpnow shows common flows (status, list, explain, decide), authoring flow, and how to target a different server. explain,decide,bundles list,projects list, andstatusall have “Examples:” blocks in their per-command help.
New Tools
aethis_add_domain_guidance— Add cross-section guidance hints at domain level (e.g.uk_citizenship). Applies automatically to all projects in the domain during generation.aethis_list_domain_guidance— List all active domain-level guidance hints.aethis_list_guidance— List all guidance hints accumulated for a project. Use before adding new guidance to avoid duplicates.aethis_explain_failure— Diagnose a failing test case. Returns criterion statuses with DSL metadata and a targeted fix hint.
Improvements
aethis_add_guidancenow acceptsprocess_type("rule_generation"|"field_extraction"). Usefield_extractionfor field design principles (solicitor navigation, raw-facts principle). Defaults to"rule_generation".aethis_add_domain_guidanceacceptsnotes— SME commentary or legislation provenance stored on the hint. Never sent to the LLM.- Two-level hint retrieval: generation now fetches domain-level hints (cross-section) alongside project-level hints in a single pass.
New Tools
aethis_discover_fields— Discover input fields from source text. Returns field names, types, and completeness assessment. Call before writing test cases.aethis_refine_fields— Iterate on field discovery with targeted feedback.
Improvements
- Added
aethis-authorandaethis-decideMCP prompts for compatible clients (Claude Desktop, Cursor, VS Code Copilot).
Initial release.
Features
- Decision tools:
aethis_schema,aethis_decide,aethis_next_question,aethis_explain - Discovery tools:
aethis_list_projects - Authoring tools (TDD workflow):
aethis_create_ruleset,aethis_generate_and_test,aethis_add_guidance,aethis_refine,aethis_publish,aethis_archive_project,aethis_archive_ruleset - HTTPS enforcement for remote hosts
- Exponential backoff with retry on 429/502/503/504
- Works with Claude Desktop, Claude Code, Cursor, and Windsurf
Note: v0.1.0 usedaethis_create_ruleset(renamed toaethis_create_ruleset) andaethis_project_status(replaced byaethis_list_projects). These tools were removed in v0.2.x.
Initial release.
Features
- Account management:
aethis account generate(browser OAuth),aethis account keys,aethis account revoke - Project authoring:
aethis init,aethis generate --poll,aethis test,aethis publish - Decision tools:
aethis decide,aethis fields,aethis explain - Project management:
aethis projects list,aethis bundles list,aethis bundles archive - Security: HTTPS enforcement, OS keychain storage, PKCE OAuth flow
- Example: Spacecraft Crew Certification Act 2049 with 5 golden test cases