The ask operator
ask is a unary prefix operator with await’s precedence. It resolves any askable to its value. Because it binds like await, a method chain on the operand needs parentheses: ask (..`x`<string>).withRetry(2).
What ask accepts
Section titled “What ask accepts”An askable is one of three things: an extractor, a call intent, or the Intent returned by calling an infer function.
export infer function getUserById(.name: string) { return ask ..`the user record for the named person`<{ name: string; id: string }>;}import { getUserById } from "./users.tsi";
type User = { name: string };
export infer function report(.text: string) { const user = ask ..`the user named in the text`<User>; // extractor const record = ask getUserById(user.name); // another infer function return record;}Call intents are the third kind — see Call intents.
ask vs await
Section titled “ask vs await”| You are in… | Use | On |
|---|---|---|
| an infer function body | ask |
any askable |
| an infer function body | await |
ordinary promises (fetch, a database, any library) |
| plain TypeScript | await |
the intent an infer function returned — this opens an invocation |
A raw extractor or call intent is never bare-awaited. It carries no context of its own; it borrows the frame of the ask that resolves it, so awaiting one directly throws NOLA3010 at run time:
// not-checked — WRONG: nothing supplies the inference contextimport { nameIntent } from "./person.tsi";const name = await nameIntent;export const nameIntent = ..`the user's full name`<string>;
// RIGHT — resolve it with `ask` inside an infer functionexport infer function whoIsIt(.text: string) { return ask nameIntent;}// RIGHT — from plain TS, await the INFER FUNCTION's resultimport { whoIsIt } from "./person.tsi";
const name = await whoIsIt("Alice Smith, 32, works at Acme Corp.");console.log(name);ask with <name>
Section titled “ask with <name>”ask with <name> resolves one ask through a named provider from nola.config.ts:
export infer function summarize(.text: string) { const draft = ask with fast ..`a rough summary`<string>; const final = ask with careful ..`a polished summary of: ${draft}`<string>; return final;}import { anthropic, openai } from "@nola-lang/providers";import { defineConfig } from "@nola-lang/runtime";
export default defineConfig({ providers: { default: openai({ model: "gpt-5-mini" }), fast: openai({ model: "gpt-5-nano" }), careful: anthropic({ model: "claude-sonnet-4-5" }), },});<name>must be a static identifier naming a key of theprovidersmap. A string literal, a member expression or a parenthesized expression afterwithisNOLA1009.- The name is matched against the config at ask time, not compile time; an unknown name fails at run time with
NOLA3004, listing the configured names. - For a dynamic choice use the intent method instead:
ask (..`a rough summary`<string>).withProvider(useFast ? "fast" : "careful")— see Intent methods. ask withoutis a plain ask of an identifier namedwithout, not a pin.
Routing precedence, highest first: forceProvider in the config → the ask-site pin (ask with / .withProvider) → providers.default. See Providers.
Where ask is legal
Section titled “Where ask is legal”Directly inside an infer function body — not at module level, and not inside a nested closure, even one written inside the infer function (NOLA2001). ask is a reserved word in .tsi files (still legal as a member or property name, obj.ask); using it as an identifier is NOLA1003.
Next: Call intents