Osy#betaa language · its runtime Osyrin · a hosted platform
Why Osy#Built for agentsAgents as declarationsWorkflows that waitRuns exactly onceSecure by defaultNothing to mockThe editor is the compilerUI in the languageDocuments are dataOne program

Reference / Agent

turning AI off (the runtime switch)

app-detail → AI: Text generation ▢ · Embedding ▢

An operator can switch an application's AI off while it runs — no recompile, no redeploy. Text generation and embedding are two independent switches, because they do different things and send different data. A refused call fails with a 403 naming which capability was off; it never silently returns nothing.

stable1 example compiled by CIagentaioperationssecurity

Summary#

Every AI call an application makes passes through one place, and an operator can close it. Two switches, not one: text generation and embedding are turned off independently. Neither needs a recompile, a redeploy or a restart — the change reaches every process within about fifteen seconds.

A call made while its capability is off does not quietly return nothing. It fails, with a message naming which capability was switched off and noting that the other is switched separately.

Signature#

app-detail  →  AI
   ▢ Text generation — models may write for this app
   ▢ Embedding — semantic search, and every indexed row is sent to the model host

There is no Osy# declaration for this. It is deliberately not something an application can write about itself — see Who can change it.

Description#

Why two switches and not one#

They differ on both axes that matter to whoever is turning AI off.

What they do. Generation samples text that influences something a person reads or acts on. An embedding is a single forward pass producing a fixed-size vector: it ranks, it does not decide or write prose. It is still inference — calling it "not AI" would not survive a compliance reading — but it is not what a rule about AI in decisions is aimed at.

What they send. This is the one that catches people out. A generation call sends what somebody typed. Embedding sends every indexed row to whoever hosts the model — much the larger flow of application data leaving the platform. So a policy about generated output covers only the first, while a policy about where data may go covers both.

Because only you know which policy you are applying, the platform models the two and composes neither. If you want one "AI off" control, turn both off.

"AI is off" is false while either one runs. Turning generation off and reporting "we disabled AI" is a statement about data egress that is not true if embedding is still running. Both the admin page and the refusal message say which capability is which for exactly this reason.

Who can change it#

The application's own Owner or Admin, its organization's Owner or Admin, or a platform admin. It is stored in the control plane, not in the application's model, and that placement is the point: an operator's decision must not be something the application's own source can withdraw at its next compile.

It is also the only field pair on an application record that an app's own Owner may write — everything else there stays with the organization tier. The reasoning: this is not administration of the app record, it is an operational pull on what the app does, and the person accountable for that must be able to stop it without escalating.

What a refused call looks like#

The call fails. Over HTTP the response is 403 with the message:

{ "error": "Text generation is switched off for this application — an operator disabled the Generative AI capability, so no model call was made. This is not a budget limit and will not clear on its own. Embedding (semantic search) is switched separately and may still be running." }

403 rather than 500, because this is a decision somebody made rather than a fault; and rather than 429, because a budget refusal clears at midnight and invites a retry while this one does not clear at all.

When it takes effect#

Within about fifteen seconds, in every process. The switch is read on every AI call, so it is cached briefly rather than re-read from the control plane each time; the cache is time-bounded, so a change propagates everywhere with nothing to deliver and nothing to go wrong in delivering it.

So "it is off" and "it will be off shortly" are different claims for a few seconds after you flip it. The admin page says so.

What it does NOT do#

Two things worth knowing before you design around this.

An application CAN ask whether AI is onAi.GenerationEnabled and Ai.EmbeddingEnabled, two booleans read on the server:

using Osysharp.Ui;

[Page("/assistant")]
[Render(CSR)]
[AllowAnonymous]
component Assistant() {
  live var aiOn = Ai.GenerationEnabled;

  render {
    if (aiOn) { Text("Ask the assistant anything."); }
    else      { Text("The assistant is switched off for this application."); }
  }
}

This is advisory, never a gate. The authority is the refusal above: the read has crossed to a client and can be up to fifteen seconds stale, so a page that hides a button is being polite, not enforcing anything. What makes the pair trustworthy is that both read the same switch — the button and the refusal cannot disagree about the answer, only about how fresh it is.

⚠ And it is read when the page loads, not pushed. A page already open when the switch is flipped keeps showing the button until it reloads; pressing it then gives the refusal. If you need the page to react live, that is a different mechanism.

The refusal is not a catchable Osy# exception. It is not a member of the language's closed exception set, so catch (…) in Osy# cannot name it specifically. It surfaces as a call failure carrying the message above.

Rows already embedded were already sent. Turning embedding off stops future calls; it does not recall anything. If the concern is where data has gone rather than where it is going, this switch is not the whole answer — see embedding provider (app.Embedding) for running the model somewhere you choose instead.

Examples#

An operator turns generation off for one application and leaves search working:

app-detail → AI
   ▢ Text generation      ← unticked
   ☑ Embedding
   "Generation is off. Embedding still runs, so indexed rows are still sent to whoever hosts that model."

The application's next model call fails with the 403 above. Its semantic search keeps answering.

The dial beside the switch#

The switch is binary. The budget is the same control plane's dial: hard daily limits in tokens and dollars, per organisation, per application and per user, enforced before a call leaves and fail-closed. An application that handles the switch's refusal handles the budget's the same way — both are the model not answering, and the degraded path is one path.

See also#

Related

LLM budgets (hard daily limits per organisation, app and user)

Every model call an application makes is metered, and the platform refuses a call before it leaves when the day's…

default LLM model (app.DefaultModel)

Declares the app's default LLM — the provider, model, API-key secret, and optional endpoint — as a single app-level…

embedding provider (app.Embedding)

`app.Embedding` declares the embedding model the app uses to turn text into vectors for semantic search over…

Agents (calling a model like anything else you declared)

An `agent` is a declaration — instructions, tools, model — and calling it is calling a name. The first-day mistake is…