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

Guides

The editor

The thing answering your editor is the compiler, not an approximation of it. A margin that marks which lines leave the machine, tests in a tree, and a debugger that stops on the line you wrote — with one call stack across the client/server seam, and a parked run you can rewind.

01

The thing answering your editor is the compiler

Not a grammar that approximates it, and not a second implementation that drifts. The language server runs the same front-end osy validate runs, over the whole workspace — so the squiggle under your cursor and the error from the build are the same sentence, and they cannot disagree.

What it answers, today

Diagnostics as you type
whole workspace
Cross-file references resolve, so a rename in one file is an error in the other before you save either.
Completion · signature help · hover
from the model
Completion over what is actually in scope, and hover that knows an entity's security posture and a function's effects — because it is asking the resolved model, not an index.
Go to definition · find references · rename
semantic
Rename with a prepare pass, so the editor refuses the position rather than performing a half-rename.
Semantic tokens · inlay hints · code lens · folding · document colour
yes
The colour on an identifier is its EXECUTION SIDE — see §2. A theme token's colour is drawn in the margin as a swatch you can click.
Call hierarchy · document + workspace symbols · code actions
yes
Incoming and outgoing calls across the whole app, and a quick fix where a diagnostic knows one.
Test discovery
from the server
The Test Explorer's tree comes from the same discovery pass the runner uses, so a streamed result binds to a tree node even after edits have moved every span — see §3.
VS Code showing an Osy# page: semantic colouring, an inlay hint, a code lens, and the Osy# project view
A real page from the chat app, in a real editor. The colour on an identifier is semantic — it comes from the resolved model, not a regex. var want: string is an inlay hint; 0 references is a code lens; the status bar says Osy#, 0 errors.

02

The editor knows which half of the program each line is

Osy# makes the client/server seam invisible on purpose — you write ordinary code across it, and the compiler decides where each body runs. That is right for correctness and it hides the one thing you still have to know: this line is a network round trip. So the editor draws it.

Ask the editor about a file and it answers for every call in it — the side that call runs on, and the side of the body it sits in. Here is an ordinary game loop. Nothing in it declares a side; the compiler worked all of this out.

component Game() {
  int score = 0;

  on every (TimeSpan.FromMilliseconds(16)) {
1    if (Keyboard.Down(Space)) { score = score + 1; }
    SaveScore(score);
  }

  render { Box(keys: [Space]) { Text($"{score}"); } }
}
what the editor receives for that file
line  16  Keyboard.Down    side=client   body=server   crosses=true
line  17  SaveScore        side=server   body=server   crosses=false

Captured from the real language server. The mark is on the line you would never suspect — and NOT on the one that looks expensive.

Read that the other way round and it is the whole argument for the margin. SaveScore writes to the database and does not cross: the compiler put the whole tick body on the server, so by the time that call runs it is already there. Keyboard.Down reads the browser's held keys — a browser object — so it hands off and comes back. Sixty times a second. Nothing in the source says either of those things, and no amount of reading the file tells you.

03

Tests in the tree, and one keystroke to stop inside one

The Test Explorer is FILE → FIXTURE → TEST, discovered by the language server. A [TestFixture] is a group rather than a test — it seeds the branch the tests under it fork from. A [Skip("reason")] test appears with its reason, because a parked test is a visible reminder that a gap exists.

From the editor, without a terminal

Run one test · a fixture's worth · the file
Results stream back into the tree as each finishes.
Debug this test
breakpoint
Set one and start. It runs on a throwaway clone with your security enforced, exactly as an ordinary run does — debugging is not a weaker mode.
Compile · Launch · Launch the page at the cursor · Tail logs
a command
Against the local platform, or a deployed one, from the same palette.
Attach to a running flow
a picker
Lists the live and parked durable runs and attaches to the one you choose — §5.
The VS Code Test Explorer showing an Osy# app's tests grouped by file and fixture
FILE → FIXTURE → TEST, discovered by the language server rather than by a second parser in the extension. The ids are the same ones osy test --test takes.

04

A debugger that pauses inside your own language

Not a JavaScript debugger, and not a .NET one showing you the interpreter's frames. It stops on the line you wrote, in the file you wrote it in, with your own values in the pane.

a real session — what the editor sends, and what comes back
 initialize
{ "supportsConfigurationDoneRequest": true, "supportsEvaluateForHovers": true,
  "supportsSetVariable": true, "supportsSetExpressionRequest": true,
  "supportsExceptionInfoRequest": true, "supportsConditionalBreakpoints": true,
  "supportsHitConditionalBreakpoints": true, "supportsLogPoints": true,
  "exceptionBreakpointFilters": [ "All Exceptions", "Uncaught Exceptions" ] }

 setBreakpoints  model/orders.osy:9
{ "breakpoints": [ { "verified": true, "line": 9 } ] }

 launch          tests/orders.test.osy::a_big_order_gets_its_discount
event stopped     { "reason": "breakpoint", "threadId": 1 }

 stackTrace
  Discount                        orders.osy:9
  call:Discount                   orders.osy:14
  Place                           orders.osy:14
  call:Place                      a_big_order_gets_its_discount:4
  a_big_order_gets_its_discount   a_big_order_gets_its_discount:4

 variables
  total = 200

 evaluate  total * 2
{ "result": "400", "type": "Decimal" }

Captured by driving osy debug-test the way the extension drives it. The stack is Osy# frames — your function, the call site that reached it, and the test that started the whole thing.

VS Code paused on a breakpoint in an Osy# file, with the call stack, locals and inline values
The same session, in the editor. Paused on breakpoint, the stack in Osy# frames, total = 200 in Locals — and the same value rendered inline beside the code, on the lines that have actually run.

05

One call stack, across the network — and a run you can rewind

This is the part that has no equivalent elsewhere, because nowhere else is one program. A durable flow IS the debuggee: the same handle addresses a run that is executing right now and one that parked two days ago waiting for a person.

What attaching to a flow gives you

A stack that spans the seam
one stack
The server's frames and the browser's frames are stitched into a single call stack, innermost first. Not two debuggers side by side — one, because it is one program.
One breakpoint set, pushed to both
both sides
You set a breakpoint on an editor LINE. It is resolved separately for each side, which is exactly why it survives the hand-off: the run crosses the wire and stops where you asked.
Attach to a PARKED run
days later
A flow waiting on a person is not gone — it is parked, and it reads identically to a live one: the same position, the same stack, the same answer from an evaluate.
Step BACKWARDS
time travel
A parked flow rewinds to the step boundary before this one and re-materialises there. State-only today: a step that already committed data is not unwound with it.
Conditional breakpoints · hit counts · logpoints
yes
Stop on the hundredth row, or on the one whose total is wrong, or do not stop at all and just print.

06

What happens when you change it

Editor changes, and what they cost

Rename a field the whole app uses
compiles
Rename from the editor. It is semantic, and the diagnostics that follow come from the compiler, so the list you get is the list the build would give you.
Move a function to another file
compiles
Nothing to update. The workspace is one model — the gutter, the tokens and the diagnostics all see across files.
Work without the extension
compiles
Everything here has a CLI twin: osy check, osy test, osy debug-test, osy model. The editor is a front end for the same answers, which is why an agent and a person get the same ones.
Use an editor that is not VS Code
compiles
The server speaks LSP over stdio and the debugger speaks DAP. Neither knows what is on the other end — the extension itself contains no protocol logic.
Point the extension at a different compiler
refused
There is nothing to point. The extension ships the server, so the editor and the build cannot be two versions.

Where to go next

Testing

The tests the tree in §3 is running.

One program

Why §2's gutter has anything to mark.

Testing reference

Every flag the editor is calling behind the buttons.