Skip to content

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).

An askable is one of three things: an extractor, a call intent, or the Intent returned by calling an infer function.

users.tsi
export infer function getUserById(.name: string) {
return ask ..`the user record for the named person`<{ name: string; id: string }>;
}
report.tsi
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.

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 context
import { nameIntent } from "./person.tsi";
const name = await nameIntent;
person.tsi
export const nameIntent = ..`the user's full name`<string>;
// RIGHT — resolve it with `ask` inside an infer function
export infer function whoIsIt(.text: string) {
return ask nameIntent;
}
main.ts
// RIGHT — from plain TS, await the INFER FUNCTION's result
import { whoIsIt } from "./person.tsi";
const name = await whoIsIt("Alice Smith, 32, works at Acme Corp.");
console.log(name);

ask with <name> resolves one ask through a named provider from nola.config.ts:

summarize.tsi
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;
}
nola.config.ts
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 the providers map. A string literal, a member expression or a parenthesized expression after with is NOLA1009.
  • 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 without is a plain ask of an identifier named without, not a pin.

Routing precedence, highest first: forceProvider in the config → the ask-site pin (ask with / .withProvider) → providers.default. See Providers.

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