Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Specification

§11 · Deliberate non-features, and why

Status: DRAFT. Every refusal below is probed and run by CI (SpecProbeGateTests). The reasons are informative; the refusals are normative.

Most specifications list what a language has. This section lists what Osy# refuses, and why — because in every case here the reason is a property of the execution model rather than a matter of taste, and a reader who knows the reason can predict the next refusal instead of discovering it.

These are not "not yet". Each is a decision. Where a construct might arrive later, this section says so explicitly; where it cannot, it says that too.


11.1 out and ref PARAMETERS

Normative. A parameter declaration MUST NOT carry out or ref.

int TryIt(string s, out int value) { value = 1; return 0; }

Why, and it is the model rather than the taste. A parameter passes a value in; only the return comes back out. An Osy# call can suspend and resume in a different process (§9), so there is no caller frame guaranteed to still be waiting for a write-back when the callee finishes. out is not merely unidiomatic here — its meaning depends on a stack frame the execution model does not promise to keep.

A declaration cannot arrive later. Its signature is the thing the durability model forbids, so this half is excluded by §9 rather than awaiting demand. Recorded as an owner decision, 2026-08-26.

11.1a ⛔ CORRECTION — a TryParse CALL SITE compiles, and this section said it could not

Normative. TryTTT(…, out var x) on a stdlib parser IS accepted.

decimal Parsed(string s) {
  if (decimal.TryParse(s, out var v)) { return v; }
  return 0m;
}

Until 2026-08-29 this section asserted three things that are now false, and they are written out rather than edited away because a spec is read as authority:

it saidthe truth
"A call site MUST NOT pass one"a stdlib TryParse call site may
"there is no TryParse"there is
the Convert.ToInt / int.Parse table was "the two real forms"there are three

AND MY OWN GATE PASSED THROUGH THE CHANGE. SpecProbeGateTests is built to fail in both directions — including on "a refusal the language no longer makes", which the plan calls the more dangerous kind of stale text. It did not fire, because the probe was narrower than the claim: the claim covered a declaration AND a call site; the probe only ever exercised a declaration. A gate is only as wide as the narrowest thing it actually runs, and a normative sentence with no probe under it is prose.

The lesson for the rest of this document, and it is a rule now: every clause of a normative sentence needs its own probe. Two clauses joined by "and" are two claims, and pinning one of them proves nothing about the other.

11.1b How it works, given out is refused

out is never accepted — the call is REWRITTEN before resolution. decimal.TryParse(s, out var v) becomes, hoisted ahead of its statement:

decimal? v = null; try { v = decimal.Parse(s); } catch { }

with v != null left in the call's place. So no out reaches the resolved tree: there is no write-back, no caller frame to survive a suspension, and nothing for §9 to forbid. The nullable local is the did-it-work channel — the shape Osy# already uses everywhere — and TryParse is C# spelling the compiler translates into it.

Informative — why the two halves differ. A DECLARATION is a signature the author writes and the durability model cannot honour. A CALL SITE is a shape the compiler owns end to end, so it can be rewritten out of existence. The durability argument bites on the first and has nothing to say about the second.

And it declines by POSITION, which is the tell that this is a rewrite rather than support. The hoist needs somewhere to go, so a loop CONDITION or a lambda BODY is refused — there the parse would have to happen per iteration or per element rather than once before. The diagnostic prints the whole mechanism and the remedy for each position, which is why this section can describe it without reading the resolver.


11.2 await on an ordinary call

Normative. await MUST NOT be applied to an ordinary function call.

int Inner() { return 1; }
int Outer() { var v = await Inner(); return v; }

Why. Effects run in place. There is no async/await colouring in Osy#, because there is nothing for it to distinguish: a call that performs I/O and a call that does not are written and read the same way, and the runtime decides how to execute them.

The one exception is await Workflow.Run(...) — a Saga-style durable wait on a child workflow (§9). That is not the C# meaning of await. It does not yield a thread and resume shortly after; it parks the run durably, possibly for days, and resumes it in whatever process picks it up. The keyword is reused because the reading is right — "continue when that finishes" — but the mechanism is the durable engine, not a task scheduler.

Informative. This is why §11.1 and §11.2 belong together: both follow from a call not owning a stack frame for its own lifetime.


11.3 Now

Normative. The identifier Now MUST NOT resolve. It is not merely deprecated — the name is absent from app-source resolution entirely, and answers exactly what any undefined name answers.

DateTime When() { return Now.Utc; }

Write DateTime.UtcNow. The compiler lowers it to the ambient clock, so it is testable (TestClock.*) and correct inside a live var, a render slot, and a durable body alike.

Why the name is hidden rather than deprecated. A retirement message was tried first and made things worse: Osyrin.Now is a real class in the model, so with the refusal removed Now.Utc resolved far enough to answer "'Osyrin.Now.Utc' is an instance member" — which still tells the reader the class is there. Worse, a typo'd Nowt produced "Did you mean 'Osyrin.Now'?": the compiler proactively recommending a name it would then refuse. Hiding the name from resolution is what makes the removal complete.


11.4 A retired spelling is deleted, not aliased

Normative. A renamed surface MUST NOT keep its old spelling as an alias.

retiredcurrent
[Display][Label]
Modes(light:, dark:)Modes.Of(light:, dark:)
<hash>.migration.osy<hash>.migration

Informative. Osy# has no released versions and no deployed programs outside this repository, so an alias would preserve compatibility with nothing while doubling the surface a reader must learn and a diagnostic must consider. The rule is expected to change when the language ships a stable release; §13 will say how.


11.5 What is NOT in this section

Two things are frequently assumed to be deliberate refusals and are not:

  • [Server] / [Client] annotations. These exist. They are a forcing hatch, and reaching for one is almost always the wrong instinct — the side is inferred (§6). They were de-advertised across 174 files on 2026-08-27 precisely because their presence in examples taught readers to write them. Absent from your source is the normal case, not a refusal.
  • A scalar array on an entity (string[] Tags;). Refused today, with a diagnostic naming the two real options — but this is a gap, not a decision, and it is tracked as such. UNVERIFIED here: no probe written.

Next: §6 The execution-side model and §9 Durability semantics — the two rules §11.1 and §11.2 both depend on. Both drafted.