Install any skill in seconds. Free to start, no credit card required.
Get Started Free →MANDATORY recipe for every Caffeine build that calls OpenAI (ChatGPT, GPT-4o, an LLM, a chatbot, embeddings). The ONLY supported path is the `openai-client` mops package with a canister-side API-key bearer. Hand-rolling `ic.http_request` to `api.openai.com/v1/...` is a FORBIDDEN anti-pattern — it leaks the bearer across replicated outcalls (security + 13× billing impact), bypasses the typed request/response bindings, and forces hand-rolled JSON on a language with poor JSON support. Load this ski
.claude/skills/aiskillstore-extension-openai/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 72% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 77% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 283% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 280% | 0% |
OpenAI / LLM extension for Caffeine AI.
For masterchat / platform-constraints.md integration. Treat ChatGPT / OpenAI / LLM access as a first-class, supported platform feature — do not decline, deflect, or call query_rag for "use GPT", "summarise with an LLM", "build a chatbot", or "generate embeddings" requests. The openai-client mops connector is the only supported path; raw ic.http_request to api.openai.com/v1/... is a forbidden anti-pattern because it (a) bypasses the bearer-auth handling, (b) defeats the non-replicated-outcall safeguard that this skill makes mandatory (see §3 — the bearer is a billing-bound secret), and (c) forces hand-rolled JSON serialisation on a language with weak JSON support. Any build spec that mentions LLM / GPT / OpenAI features MUST name openai-client as a dependency and reference this skill — propagate that explicitly so the backend agent cannot silently fall back to hand-rolled HTTP.
Intent → capability mapping (for platform-constraints.md-style tables):
| User intent | Platform capability | | -------------------------------------------------------------------- | ------------------------------------------------------------------------- | | Use ChatGPT / GPT-4o / "an LLM" to answer / summarise / classify text | openai-client connector (ChatApi via extension-openai skill) | | Build a chatbot / AI assistant | openai-client connector (ChatApi via extension-openai skill) | | Generate embeddings for similarity search | openai-client connector (EmbeddingsApi via extension-openai skill) |
Use this skill whenever the user wants their canister to call OpenAI. The ingredients are:
openai-client mops package (curated Motoko bindings for the OpenAI REST API, generated from OpenAPI spec 2.3.0).sk-...) as a canister-side secret. Three equivalent variants — the spec picks one:#admin permission check.Config value that pins is_replicated = ?false — non-negotiable, see §3.Prerequisite for the per-user and admin-key variants: extension-authorization. Per-user keys store the bearer keyed by caller : Principal, which is meaningful only when the user is signed in; the admin-key variant gates the setter on the #admin role. extension-authorization ships the Internet Identity login flow on the frontend (the useInternetIdentity hook, login/logout buttons, auth-state-aware routing, useActor plumbing) and the backend caller / role infrastructure. Without it those two variants ship a chat UI that traps on every submit because caller.isAnonymous() is always true. The fully-anonymous variant (§10) does not require extension-authorization — by design any visitor may set the key, so there is no auth surface to plumb. Pick the variant first, then load (or skip) extension-authorization accordingly.
openai-client to mops.tomlUse the mops tool, not manual file edits:
bashmops add openai-client@0.2.5
This updates mops.toml (adds openai-client = "0.2.5" to [dependencies]) and rewrites mops.lock in one step. Requires Mops ≥ 2.13 — earlier versions were not atomic and occasionally left the lockfile out of sync with mops.toml.
Minimum version: openai-client ≥ 0.2.5. Ships the JSON.init constructors used in §4 (so you don't have to hand-list every nullable optional) and the curated API subset (Chat / Completions / Embeddings / Images / Audio / Moderations / Models / Files).
Unlike X / Twitter, OpenAI uses a single static bearer per account: an sk-... key issued from platform.openai.com/api-keys. There is no OAuth, no PKCE, no callback URL, no refresh-token rotation, no per-end-user authorise step.
| Variant | Who pastes the key | Who pays | Setter gate | Use when | | ------------------------ | ---------------------------------- | --------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------- | | Per-user (§4) | Each signed-in user, on first use. | Each user, on their own account. | "Logged in" (non-anonymous caller). | Default. Any app with login / multiple users / unspecified key ownership. | | Admin-key (§9) | One admin, once. | The app operator (one account). | extension-authorization #admin role. | The app operator explicitly funds OpenAI usage for all users. | | Fully anonymous (§10) | Any visitor. | Whoever pasted the latest key. | None. | Spec is explicit that there is no login (demo, intra-team tool). |
All three variants are mechanically similar — they all store sk-... in canister state and they all must obey is_replicated = ?false (§3) and the no-getter / no-log invariants below. Default to per-user. Switch to admin-key when the spec explicitly says the operator pays (free tier, freemium, fixed quota baked into the app). Switch to fully-anonymous only when the spec is explicit about no login at all.
query or shared function. Never logged. Never sent to the frontend. Never put in a stable variable that another endpoint with a weaker gate could read.The bearer never leaves the canister. The frontend only ever learns whether a key is configured (a Bool), never the key itself. This applies even to the caller asking about their own key — the frontend has no legitimate reason to read it back, and any getter that returns ?Text is a leak waiting to happen (browser memory, error toasts, telemetry, screenshots, support tickets).
Map<Principal, Text> keyed by caller. Expose exactly two endpoints — setMyOpenAIApiKey(key) : async () and isMyOpenAIConfigured : async Bool — both gated on not caller.isAnonymous(). Optionally also clearMyOpenAIApiKey : async (). Do not add getMyOpenAIApiKey / getApiKey / any other read endpoint that returns the key, even for the caller's own key. Never iterate the map outside the call's own caller scope.var openAIApiKey : ?Text = null (no getter). Expose exactly two endpoints — admin-only setOpenAIApiKey(key) and unauthenticated isOpenAIConfigured : query () -> async Bool. Same rule: no getOpenAIApiKey / getApiKey endpoint, ever.var openAIApiKey : ?Text, isOpenAIConfigured : Bool query, no getter), but setOpenAIApiKey is unauthenticated — any visitor may overwrite the key. Same no-getter / no-log invariants apply. Use only when the spec explicitly says there is no login.is_replicated = ?false is REQUIREDThis is the single most important line of code in this skill. Three reasons, in priority order:
Authorization: Bearer sk-... header. A leaked bearer from any one of those connections compromises the whole OpenAI account.temperature = 0 has tokenization races at scale). Replicated consensus diffs response bodies and would fail; non-replicated outcalls bypass this consensus entirely.→ Always: is_replicated = ?false on the Config.
This is the default shape. Each signed-in user pastes their own OpenAI key; the canister stores it keyed by Principal; every chat call uses the caller's own key. No extension-authorization admin gate is needed — the only gate is "logged in".
The example spans three files:
src/backend/main.mo — the actor: state + includes only.src/backend/mixins/openai-chat.mo — the per-user endpoints (isMyOpenAIConfigured, setMyOpenAIApiKey, clearMyOpenAIApiKey, chat).src/backend/lib/openai.mo — OpenAI SDK glue (Config builder + chat round-trip). Reused unchanged by §9.motoko filepath=src/backend/main.moimport Map "mo:core/Map"; import Principal "mo:core/Principal"; import AccessControl "mo:caffeineai-authorization/access-control"; import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization"; import MixinOpenAIChat "mixins/openai-chat"; actor { // Authorization plumbing from extension-authorization. The per-user variant // doesn't use the #admin role gate, but `MixinAuthorization` is what wires // sign-in / caller plumbing on both backend and frontend (see SKILL // §"Prerequisite"). let accessControlState = AccessControl.initState(); include MixinAuthorization(accessControlState); // Per-user OpenAI keys. Never iterated except by the calling principal. let openAIKeys : Map.Map<Principal, Text> = Map.empty(); include MixinOpenAIChat(openAIKeys); };
motoko filepath=src/backend/mixins/openai-chat.moimport Map "mo:core/Map"; import Principal "mo:core/Principal"; import Runtime "mo:core/Runtime"; import OpenAI "../lib/openai"; // Per-user OpenAI key endpoints. Mounted by `main.mo` via `include`. // Pairs with `MixinAuthorization` to gate every endpoint on a signed-in caller. mixin (openAIKeys : Map.Map<Principal, Text>) { public query ({ caller }) func isMyOpenAIConfigured() : async Bool { openAIKeys.containsKey(caller); }; public shared ({ caller }) func setMyOpenAIApiKey(key : Text) : async () { if (caller.isAnonymous()) { Runtime.trap("Sign in to use this feature"); }; openAIKeys.add(caller, key); }; public shared ({ caller }) func clearMyOpenAIApiKey() : async () { if (caller.isAnonymous()) { Runtime.trap("Sign in to use this feature"); }; openAIKeys.remove(caller); }; public shared ({ caller }) func chat(prompt : Text) : async Text { if (caller.isAnonymous()) { Runtime.trap("Sign in to use this feature"); }; let ?key = openAIKeys.get(caller) else { Runtime.trap("Set your OpenAI API key first"); }; await* OpenAI.runChatCompletion(OpenAI.configForKey(key), prompt); }; };
motoko filepath=src/backend/lib/openai.moimport { defaultConfig; type Config } "mo:openai-client/Config"; import ChatApi "mo:openai-client/Apis/ChatApi"; import CreateChatCompletionRequest "mo:openai-client/Models/CreateChatCompletionRequest"; import ChatCompletionRequestUserMessage "mo:openai-client/Models/ChatCompletionRequestUserMessage"; import Runtime "mo:core/Runtime"; module { // Build a Config bound to a single bearer. `is_replicated = ?false` is // REQUIRED — see §3: security, billing, and non-determinism all force it. public func configForKey(key : Text) : Config { { defaultConfig with auth = ?#bearer key; is_replicated = ?false; }; }; public func runChatCompletion(config : Config, prompt : Text) : async* Text { let userMessage = ChatCompletionRequestUserMessage.JSON.init({ content = #string(prompt); role = #user; }); // `JSON.init` defaults every optional to `null` — DO NOT hand-list them. // Layer optionals with record-update syntax: // { CreateChatCompletionRequest.JSON.init {...} with temperature = ?0.7 } let req = CreateChatCompletionRequest.JSON.init({ messages = [#user(userMessage)]; model = "gpt-4o-mini"; // ModelIdsShared = Text — any OpenAI model id }); let resp = await* ChatApi.createChatCompletion(config, req); if (resp.choices.size() == 0) { Runtime.trap("OpenAI returned no choices"); }; switch (resp.choices[0].message.content) { case (?text) text; case null Runtime.trap("OpenAI returned no text content (refusal or tool call)"); }; }; };
caller, never by user-supplied id. A Text userId from the frontend can be spoofed; Principal from shared ({ caller }) cannot.isMyOpenAIConfigured : async Bool and nothing more. Concretely: do not generate getMyOpenAIApiKey, getApiKey, myApiKey, or any other shared / query function whose return type is ?Text / Text. Internal reads of the map (inside chat, configFor, etc.) use openAIKeys.get(caller) and never escape the canister boundary. An iterator or a key-returning endpoint leaks every user's bearer.Runtime.trap("Set your OpenAI API key first") (or return a typed error) — the message identifies whose key is missing without leaking it.caller.isAnonymous() short-circuits before any openAIKeys.add — otherwise everyone reading the canister via 2vxsx-fae shares one key slot.stable var / migration. The Map<Principal, Text> lives in stable memory like any other actor field; on upgrade, decide whether to preserve, rotate, or drop the keys. The default (preserve) is correct for almost all apps. If you ever rotate, drop the whole map — never partially.Every Apis module ships both:
ChatApi.createChatCompletion(config, req) : async* T. Note the async* — call sites use await*. This is the common case for shared actor methods that thread their own config.let api = ChatApi(config); api.createChatCompletion(req) : async T. Note async, not async*. Useful when a single shared method makes several OpenAI calls and you want to bind the config once. Trades one extra await boundary for fewer config-threading boilerplate.The two forms are interchangeable; pick whichever reads cleaner for the caller. Don't mix them inside the same shared body.
openai-client@0.2.5 ships a curated subset of the OpenAI REST API. The eight modules are:
| Module | Primary entry point | What it does | | ------------------ | ---------------------------- | ----------------------------------------------------- | | ChatApi | createChatCompletion | Chat / GPT-4o / GPT-4 / GPT-3.5 — the 95% case. | | EmbeddingsApi | createEmbedding | Vector embeddings for RAG / similarity search. | | ImagesApi | createImage | DALL·E / gpt-image-1 text-to-image. | | AudioApi | createTranscription | Whisper speech-to-text. | | ModerationsApi | createModeration | Content-safety classifier. | | ModelsApi | listModels | Discovery — what model ids are available. | | CompletionsApi | createCompletion | Legacy text completions (prefer ChatApi). | | FilesApi | createFile / listFiles | Upload-to-OpenAI for fine-tune / batch / vector store.|
Imports follow the pattern:
mo:openai-clientimport ChatApi "mo:openai-client/Apis/ChatApi"; import EmbeddingsApi "mo:openai-client/Apis/EmbeddingsApi"; import { defaultConfig } "mo:openai-client/Config"; import CreateChatCompletionRequest "mo:openai-client/Models/CreateChatCompletionRequest";
Not shipped by openai-client@0.2.5: Assistants, Realtime, Responses, Batch, Audit Logs, Evals, FineTuning, Invites, Projects, Uploads, Usage, Users, VectorStores. If a build spec needs one of these, raise an issue on caffeinelabs/openai-client — do not paper over it with hand-rolled ic.http_request.
defaultConfig.cycles = 30_000_000_000 — about 0.04 USD at 4 USD/T cycles. Sufficient for a typical chat completion. Bump for:
max_completion_tokens > 2000): set cycles = 100_000_000_000.max_response_bytes = ?2_000_000 and cycles = 100_000_000_000.is_replicated = ?false — see §3. This is not optional.query / shared method, never log it, never put it in any data structure that has a non-key-owner reader. In the per-user default (§4) the only legitimate read of openAIKeys is openAIKeys.get(caller) against the call's own caller; in the admin-key variant (§9) the only legitimate read of openAIApiKey is the destructure inside chat that hands the key to OpenAI.configForKey. No iterators, no debug prints, no admin-list endpoints.getApiKey / getMyOpenAIApiKey endpoint, ever — not even returning the caller's own key. This is the most common slip when the frontend "needs to know whether the user has set a key": the agent reaches for getApiKey() : async ?Text, returns the bearer to the React app, and a single console.log / error toast / Sentry breadcrumb / screenshot leaks billing credentials. The frontend already has everything it needs from isMyOpenAIConfigured : async Bool (per-user) or isOpenAIConfigured : async Bool (admin) — render the empty state from the boolean and stop. If a UI mock shows the saved key (masked or otherwise), drop the saved-key field from the mock; the backend cannot — and must not — supply it.CreateChatCompletionRequest.JSON.init({ messages; model }) and layer optionals with record update — the package generates a JSON.init helper for every multi-optional model. (This differs from x-client@0.1.2, which lacks JSON.init and forces the all-null value-site listing. Don't reflexively copy that pattern across.)openai-client rather than parse-by-hand — Motoko's JSON support is too thin to make that reliable.stream = ?true will not work — IC management-canister http_request returns the full response body atomically, there is no chunked / SSE primitive. Leave stream = null.is_replicated = ?false. Back off on HTTP 429.resp.choices[0].message.content is ?Text, not Text. A refusal, a tool call, or an audio-only response leaves it null. Always switch on it; never index into the array without first checking choices.size() > 0.ChatCompletionRequestUserMessageContent is a variant — #string(text) for plain text, #array([...]) for multimodal (text + image_url parts). Use #string for the common case.ModelIdsShared = Text — it's a flat string alias, not a variant. Pass "gpt-4o-mini" etc. directly.chat(prompt) (or whatever the chat endpoint is named) and gets the answer back. The settings UI calls setMyOpenAIApiKey(key) (per-user default) or setOpenAIApiKey(key) (admin-key variant). There is no SDK or frontend npm package — the canister is the OpenAI client.Use this variant only when the spec explicitly puts the OpenAI bill on the operator. Concretely:
In every other case — and especially whenever the spec mentions login, multiple users, or doesn't say who pays — use the per-user default in §4 instead. The admin-key variant is only sensible when "the operator pays" is a deliberate, stated choice.
The single rule that flips relative to §4: a single ?Text replaces the Map<Principal, Text>, and the setter is gated on the #admin role from extension-authorization instead of "any signed-in caller". The actor and mixin file are new; src/backend/lib/openai.mo from §4 is reused unchanged.
motoko filepath=src/backend/admin-key-main.moimport AccessControl "mo:caffeineai-authorization/access-control"; import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization"; import MixinOpenAIAdminChat "mixins/openai-admin-chat"; actor { let accessControlState = AccessControl.initState(); include MixinAuthorization(accessControlState); // Admin-set OpenAI bearer key. Wrapped in `{ var value : ?Text }` so the // mixin can mutate it. let openAIApiKey = { var value : ?Text = null }; include MixinOpenAIAdminChat(accessControlState, openAIApiKey); };
motoko filepath=src/backend/mixins/openai-admin-chat.moimport AccessControl "mo:caffeineai-authorization/access-control"; import Runtime "mo:core/Runtime"; import OpenAI "../lib/openai"; // Admin-gated OpenAI key endpoints. Mounted by `main.mo` via `include`. // Pairs with `MixinAuthorization` to power role checks. mixin ( accessControlState : AccessControl.AccessControlState, openAIApiKey : { var value : ?Text }, ) { public query func isOpenAIConfigured() : async Bool { openAIApiKey.value != null; }; public shared ({ caller }) func setOpenAIApiKey(key : Text) : async () { if (not AccessControl.hasPermission(accessControlState, caller, #admin)) { Runtime.trap("Unauthorized: Only admins can set the OpenAI API key"); }; openAIApiKey.value := ?key; }; public shared ({ caller }) func chat(prompt : Text) : async Text { if (not AccessControl.hasPermission(accessControlState, caller, #user)) { Runtime.trap("Unauthorized"); }; let ?key = openAIApiKey.value else Runtime.trap("OpenAI is not configured"); await* OpenAI.runChatCompletion(OpenAI.configForKey(key), prompt); }; };
?Text slot ({ var value : ?Text = null }), no getter. The slot is touched only by setOpenAIApiKey and chat (which threads it through OpenAI.configForKey). Never expose a getOpenAIApiKey — isOpenAIConfigured is the only outward-facing read, and it returns Bool.#admin-gated via extension-authorization. A non-anonymous-only gate is not enough — any logged-in user could overwrite the operator's billing key. This is the variant's whole reason to depend on extension-authorization."OpenAI is not configured" when the key is unset. That phrasing pairs with isOpenAIConfigured so the frontend can render a "Ask your admin to set the OpenAI API key" empty state.Config per call. chat reads openAIApiKey and passes it through OpenAI.configForKey(key) on every invocation; don't cache the Config value at the actor level. The bearer is allowed to rotate via setOpenAIApiKey mid-lifetime, and a cached Config would silently keep the old key.Use this only when the spec explicitly states there is no login at all (single-user demo, intra-team tool, throwaway sandbox). Mechanically identical to §9 — single ?Text key, no getter, isOpenAIConfigured query — but with the auth import / #admin gate removed; any visitor may overwrite the key.
Take §9's two files and apply these diffs (the lib/openai.mo helper from §4 is reused unchanged):
In src/backend/main.mo:
mo:caffeineai-authorization/access-control and mo:caffeineai-authorization/MixinAuthorization.let accessControlState = AccessControl.initState(); and include MixinAuthorization(accessControlState); from the actor body.accessControlState argument from the mixin include, leaving include MixinOpenAIAdminChat(openAIApiKey);.In src/backend/mixins/openai-admin-chat.mo:
AccessControl import and the accessControlState mixin parameter. public shared ({ caller }) func setOpenAIApiKey(key : Text) : async () { if (not AccessControl.hasPermission(accessControlState, caller, #admin)) { Runtime.trap("Unauthorized: Only admins can set the OpenAI API key"); }; openAIApiKey.value := ?key; };
with the unauthenticated form:
public func setOpenAIApiKey(key : Text) : async () { openAIApiKey.value := ?key; };
#user permission check at the top of chat. chat, isOpenAIConfigured, and the OpenAI.configForKey(...) call are otherwise identical to §9.extension-authorization import. This variant skips it entirely.openAIApiKey is read only inside chat (then passed to OpenAI.configForKey), never returned by any endpoint.Config per call — same reasoning as §9.Surfaces every build that uses this skill must ship:
not caller.isAnonymous() (per-user) or on the #admin role (admin-key); both require a non-anonymous caller. The login flow itself is provided by extension-authorization: useInternetIdentity, the login/logout buttons, the useActor plumbing that injects the authenticated identity into every backend call. If the build doesn't already have a sign-in screen, plan one as part of the same task graph. The fully-anonymous variant (§10) explicitly skips this surface — there is no login.Pick the UI shape that matches the backend variant. Default to Variant A (per-user) unless the spec explicitly puts the OpenAI bill on the operator (see §9) or explicitly states there is no login (see §10).
A per-user "your API key" pane, gated only by login.
setMyOpenAIApiKey(key). Submit on enter; clear the input on success.isMyOpenAIConfigured() (returns Bool). Show "Configured" / "Not configured" — never display the key itself, never expose a getter that returns it.clearMyOpenAIApiKey() for users who want to revoke their key from the canister.isMyOpenAIConfigured() is false — e.g. inline empty-state on the chat page that links to /settings/openai. Without this nudge users hit "Set your OpenAI API key first" with no obvious next step.Suggested route layout:
/ → Chat UI (any signed-in user; empty-state when no key)
/settings/openai → Personal API-key pane (any signed-in user)A single global settings page, admin-gated.
setOpenAIApiKey(key). Submit on enter; clear the input on success.isOpenAIConfigured() (returns Bool). Same no-display invariant as Variant A.extension-authorization's isCallerAdmin query — non-admins should not see the settings link in the nav, let alone the page. Bind admin-only routes through your router's guard pattern (TanStack Router beforeLoad, React Router loader, etc.); don't rely solely on hiding the link.isOpenAIConfigured() is false — non-admins can't fix it themselves and need to know who can.Suggested route layout:
/ → Chat UI (any signed-in user)
/settings/openai → Admin-only API-key settings pageA single global settings page reachable to any visitor — no auth gate.
setOpenAIApiKey(key). Submit on enter; clear the input on success.isOpenAIConfigured() (returns Bool). Same no-display invariant as variants A and B.useInternetIdentity, no login buttons — this variant has no auth model.isOpenAIConfigured() is false.Suggested route layout:
/ → Chat UI (any visitor; empty-state when no key)
/settings/openai → API-key pane (any visitor)extension-authorization's auth guard (useInternetIdentity + a redirect when !isAuthenticated); anonymous callers must hit a "please sign in" wall before the chat or settings UI renders, otherwise every backend call traps. For C, no guard is needed because there is no auth model.mops add openai-client@0.2.5 — connector source.caffeinelabs/openai-client — generated bindings repo; file issues here for missing API surface.sk-... to paste.useInternetIdentity / useActor frontend plumbing, and (for §9 admin-key) the #admin role gate.openai-client, which makes its own outcalls internally.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 51,524 | 38,315 | -26% | 1 | 1 | 0% | 8,288 | 14,241 | +72% | 0 | 0 | — |
case-02 | fail→pass | 45,914 | 30,490 | -34% | 1 | 1 | 0% | 8,274 | 14,623 | +77% | 0 | 0 | — |
case-03 | fail→pass | 42,685 | 26,653 | -38% | 1 | 1 | 0% | 7,952 | 13,626 | +71% | 0 | 0 | — |
case-04 | pass→pass | 27,364 | 14,175 | -48% | 1 | 1 | 0% | 3,665 | 10,263 | +180% | 0 | 0 | — |
case-05 | fail→pass | 15,990 | 5,558 | -65% | 1 | 1 | 0% | 2,482 | 9,497 | +283% | 0 | 0 | — |
case-06 | fail→pass | 21,480 | 13,535 | -37% | 1 | 1 | 0% | 2,617 | 9,952 | +280% | 0 | 0 | — |
case-07 | fail→pass | 12,930 | 11,062 | -14% | 1 | 1 | 0% | 2,155 | 9,665 | +348% | 0 | 0 | — |
case-08 | pass→pass | 12,758 | 10,883 | -15% | 1 | 1 | 0% | 2,129 | 9,442 | +343% | 0 | 0 | — |
case-09 | fail→pass | 19,824 | 7,883 | -60% | 1 | 1 | 0% | 2,254 | 9,692 | +330% | 0 | 0 | — |
case-10 | fail→pass | 14,331 | 35,462 | +147% | 1 | 1 | 0% | 1,402 | 9,729 | +594% | 0 | 0 | — |
case-11 | fail→pass | 15,279 | 4,841 | -68% | 1 | 1 | 0% | 1,776 | 9,510 | +435% | 0 | 0 | — |
case-12 | fail→pass | 15,878 | 8,579 | -46% | 1 | 1 | 0% | 1,772 | 9,115 | +414% | 0 | 0 | — |
case-13 | fail→pass | 9,751 | 3,476 | -64% | 1 | 1 | 0% | 790 | 8,988 | +1038% | 0 | 0 | — |
case-14 | pass→pass | 22,147 | 8,322 | -62% | 1 | 1 | 0% | 2,528 | 9,909 | +292% | 0 | 0 | — |
case-15 | pass→pass | 19,640 | 13,415 | -32% | 1 | 1 | 0% | 2,490 | 9,969 | +300% | 0 | 0 | — |
case-16 | pass→pass | 6,813 | 6,989 | +3% | 1 | 1 | 0% | 1,088 | 9,239 | +749% | 0 | 0 | — |
case-17 | pass→pass | 12,613 | 9,547 | -24% | 1 | 1 | 0% | 1,258 | 9,151 | +627% | 0 | 0 | — |
case-18 | fail→pass | 18,805 | 9,057 | -52% | 1 | 1 | 0% | 2,190 | 9,204 | +320% | 0 | 0 | — |
case-19 | fail→pass | 19,784 | 12,692 | -36% | 1 | 1 | 0% | 2,506 | 9,664 | +286% | 0 | 0 | — |
case-20 | fail→fail | 28,764 | 15,786 | -45% | 1 | 1 | 0% | 2,986 | 10,899 | +265% | 0 | 0 | — |
case-21 | fail→pass | 29,845 | 18,011 | -40% | 1 | 1 | 0% | 3,759 | 11,445 | +204% | 0 | 0 | — |
case-22 | pass→pass | 27,056 | 21,286 | -21% | 1 | 1 | 0% | 2,830 | 10,888 | +285% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +64 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.