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

Specification

§2 · Lexical structure

Status: DRAFT. The keyword set (§2.3) is probed. Literals and operators are UNVERIFIED and marked so.

2.1 Source text

An Osy# source file is Unicode text with the extension .osy.

A generated migration is NOT Osy# and does not use this extension. Migrations are <name>.migration, in their own grammar, and are specified in §13. The extension is deliberate: a migration carried the .osy extension until 2026-08-26 and was swept up by every *.osy glob — including globs outside this project, which is why the fix is an extension and not a filter.

2.2 Comments

// line comment, to end of line
/* block comment */
/// documentation comment, attached to the following declaration

Documentation comments are not decoration: they are carried into the model and served by osy docs and the language server. UNVERIFIED: whether a /// in a position with no following declaration is an error.

2.3 Keywords

Osy# has a SMALL reserved set and a LARGE contextual vocabulary, and this is the first thing that surprises a reader. Exactly 22 words are reserved. Every other word that looks like a keyword — entity, component, render, workflow, state, event, subscribe, security, allow, deny, policy, terminal, live — is contextual: recognised by the grammar at the position where it means something, and an ordinary identifier everywhere else.

2.3.1 Reserved words

These MUST NOT be used as identifiers:

async     await     break     catch     continue  else      false     finally
foreach   if        in        is        new       not       null      return
throw     true      try       var       void      while

Probe (established, and RUN BY CI). A reserved word used as an identifier is refused. Source of the set: the lexer's Keywords table, OsySharpLexer.cs:94.

int Probe() { var new = 1; return new; }

2.3.2 Contextual words

A word that introduces a declaration is not reserved, and MAY be used as an identifier.

Probe (established, and RUN BY CI). Every one of these is a declaration keyword somewhere in the grammar, and an ordinary identifier here:

int Probe() {
  var entity    = 1;
  var component = 2;
  var render    = 3;
  var state     = 4;
  return entity + component + render + state;
}

Informative. This follows C#, which reserves a fixed set and makes later additions contextual so that adding a word to the language cannot break existing programs. Osy# takes the same position, and takes it further: the words that introduce its most distinctive constructs are all contextual, so a program that used workflow as a variable name before workflows existed still compiles.

Consequence for tooling, and for readers: you cannot decide what a word means by looking it up in a list. state is a declaration keyword inside a workflow body and a variable name in a function body. Syntax highlighting that colours it unconditionally is wrong, and so is a mental model that treats these as reserved.

2.3.3 Words that are NOT in the language

UNVERIFIED as a complete list, but recorded because each has been mistakenly assumed to exist:

assumedactual
out, refrefused — a parameter passes a value in, and only the return comes back out. A call can suspend and resume elsewhere, so no caller frame is guaranteed to still be waiting for a write-back. See §11.
[Display]replaced by [Label]
Nowremoved entirely; use DateTime.UtcNow, which the compiler lowers to the ambient clock

2.4 Identifiers

UNVERIFIED. Expected to follow C# — a letter or _ followed by letters, digits or _, compared ordinally. The probe that would establish this (a Unicode identifier, a leading digit, @-escaping) has not been written.

Members are PascalCase and parameters camelCase by convention; this is style, not grammar, and the linter — not the compiler — enforces it.

2.5 Literals

UNVERIFIED. The forms below are observed in real source but their exact grammar has not been probed:

kindobserved
integer1, 0
decimal2m — a suffixed decimal literal
string"…" and interpolated $"…{expr}…"
booleantrue, false
nullnull

2.6 Operators and punctuation

UNVERIFIED as a complete table. Two facts are established elsewhere and belong here when this section is written properly:

  • ?? over numeric operands WIDENS to the wider type, rather than dropping to the narrower one. The previous rule produced wrong numbers, not merely a dropped cast (fixed 2026-08-26, f15275418).
  • xs[i] on a collection is not a nullable read — the resolver briefly said it was, and a MUST-tier lint believed it (fixed 2026-08-26, 8ed45bfbd).

Next: §3 Types — the type system, nullability, and the required-by-default rule (R12). Not started.