Nola 0.1.14: context statements
- release
Welcome to Nola 0.1.14. Here are the new features and improvements we think you’ll like most:
- Context statements. Give the model instructions anywhere in your code, and show it live values from your program.
- Colon types. Type an ask the same way you type a variable:
ask `the ticket id`: string. - Simpler function calls. Let the model fill in a function’s arguments without the
..prefix. - A prompt you can read. The model gets your own words in four plain tags, and nothing else.
- A new starter.
npm create nolanow includes a small agent in a single file. - Better debugging. Breakpoints stop where you put them.
A few older forms were removed along the way. If you are coming from 0.1.13, read Upgrading before you update.
Language
Context statements
Until now, a function or a file could carry a single instruction for the model. Real programs need more: different guidance at different points, and a way to show the model what the program has worked out so far.
In this release, any template literal on a line of its own, in an infer function or at the top level of a file, is a context statement. Every ask below it sees the text. Add a value after the text, and the model sees that value as it is at the moment of the ask.
`You triage a support inbox. Suggest one step at a time.`
const .ticket = "Since yesterday our nightly export job fails with \"disk quota exceeded\".";const steps: string[] = [];`Steps taken so far:` steps;
while (steps.length < 3) { `Answer "done" when nothing is left to do.`
const next = ask `the next step`: string;
if (next === "done") break;
steps.push(next);}
console.log(steps);Each time the loop asks, the model sees the current list of steps. There is no prompt string to rebuild and no history to manage. You update the array, and the next ask sees it.
A few things worth knowing:
- Scoped like a
const. A context statement applies to the asks below it, inside the same braces. The “done” line above reaches only the asks inside the loop. - Never repeated. Every ask reads each statement once, fresh. Three passes through the loop do not put three copies in front of the model.
- File-wide at the top. A statement at the top of a file also applies to the infer functions declared below it.
- Your words, not data. The model follows a context statement as an instruction, so keep it to text you control. Anything that comes from a user or a document belongs in a
.paramor aconst .xbinding, like.ticketabove. The model is told to treat those as data.
Prefer ${…}? It now works in every instruction. `Page ${oncall} first.` and `Page` oncall `first.` mean the same thing.
Read more in Context statements.
Type an ask with a colon
You can now write the type of an ask after a colon, the way you annotate a variable:
const .message = "Hi, I'm Ada Lovelace. I'm 36 and I work as a mathematician.";
const name = ask `the person's name`: string;const age = ask `the person's age`: number;
console.log(name, age);The angle-bracket form, ask `the person's name`<string>, still works and means exactly the same, so your existing code needs no change. The docs, the examples and the editor snippets use the colon from now on.
Keep the angle brackets when you chain a method straight off the ask, as in ask `the person's name`<string>.withRetry(2).
Function calls without the dots
When you ask the model to fill in a function’s arguments, you no longer need the .. prefix on each one. Write the prompt and its type right where the value would go:
const .request = "Checkout shows a blank page after paying, and I was charged twice.";
function createTicket(title: string, priority: number) { console.log(`[p${priority}] ${title}`);}
ask createTicket( `a short ticket title`: string, `priority 1-5, where 1 is most urgent`: number,);The model fills in both arguments, and then createTicket runs with them. You can pass ordinary values too: ask createTicket(`a short ticket title`: string, 2) asks the model for the title only. Code that uses the dots keeps working.
A prompt you can read
Nola no longer adds wording of its own to your prompt. The model receives the text you wrote, wrapped in four tags that say what each part is:
| Tag | What it holds |
|---|---|
<context> |
One function or file: its context statements, then its inputs |
<input> |
One .param or const .x value, by name |
<task> |
The ask itself |
<correction> |
What was wrong with a rejected reply, when Nola retries |
Here is what the third ask of the triage loop above sends:
<context module="src/triage.tsi">You triage a support inbox. Suggest one step at a time.Steps taken so far: ["check the disk quota on the export host","clear last month's export archives"]Answer "done" when nothing is left to do.<input name="ticket">Since yesterday our nightly export job fails with "disk quota exceeded".</input></context>
<task>the next step</task>That is the whole message. The answer’s type is not in it, because it travels through the provider’s structured output instead. The system message is four short sentences, and you can replace them with system.message in nola.config.ts.
If your server cannot enforce a schema, set structuredOutputs: false on the provider, and Nola writes the schema into the system message.
The prompt has more examples.
Tooling
A new starter: the agent loop
npm create nola now opens with five ready-made examples and an empty project. The new one, agent-loop, is a small agent in a single file: a while loop that keeps asking until the model answers null, with a context statement that shows the model what it has found so far. It runs offline on a mock provider, so you can try it without an API key.
Two smaller changes: the empty template now starts you in src/main.tsi, and example templates are downloaded from GitHub when you pick one, so you need to be online to scaffold them.
nola run exits like node
nola run now ends when your program is done, the way node does. A script that starts a server keeps running, and the last traces reach the console before the process exits.
Debugging
- A breakpoint on an
askline could be skipped when a multi-line instruction sat above it. It now stops where you put it. - You can put a breakpoint on a context statement. Inside a loop, it pauses once on every pass.
- Stepping over an ask with F10 takes you to the next line of your program.
Editor
Completion lists and unused-variable warnings no longer show the __nola names that Nola generates. The VS Code extension highlights colon types, and its snippets write the new forms.
Custom providers
If you maintain your own provider, there is now one method to implement: infer. It receives the ask as data. Call renderPrompt for the default prompt, send it to your backend, and return the reply:
import type { LanguageModel } from "@nola-lang/runtime";import { renderPrompt } from "@nola-lang/providers";
export const myProvider: LanguageModel = { name: "my-provider", async infer(req) { const sent = renderPrompt(req.intent); // { system, messages } const text = await callMyBackend(sent, req.intent.output, req.signal); return { text, sent }; },};Here callMyBackend stands for your own request code, and req.intent.output carries the schema of the answer. Return sent, and the receipt shows exactly what went out. Providers API has a complete example.
Upgrading
Update every Nola package to 0.1.14 together, then rebuild your project. Code built with 0.1.13 does not run on the new runtime.
Then look for these, which were removed in this release:
- An instruction after a function’s name,
infer function f`…`(…). Move the text into the body as its first line. The compiler reports each one asNOLA1019. ${.member}in an instruction. Delete it. The value already reaches the model as an<input>block...choice,..scaleand..prob. Write the type instead:ask `Which team?`: Choice<{ billing: "Payments"; sales: "Pricing" }>. See Decision types.completein a custom provider. Rename it toinferand get the text fromrenderPrompt(req.intent). In amockProviderfunction, readreq.promptinstead ofreq.payload.- Replay ledgers recorded with 0.1.13. Record them again. The request format changed, so the old answers no longer match and replay fails with
NOLA3008.
Everything else keeps working, including <T> types and .. in call arguments.
Questions or feedback? Join us on Discord or open an issue on GitHub.