Summary#
There is no async in Osy#, and no Task<T>. A function that calls out to the world — an HTTP request, an LLM
completion — is written exactly like one that adds two numbers. You call the thing; the platform takes care of the
waiting.
await exists in one place in the whole language: await Workflow.Run("step", …).
If you are coming from C#, this is the first thing to unlearn, so it is worth a page of its own.
Signature#
// No `async`. No `Task<T>`. No `.Result`, no `ConfigureAwait`, no `Task.WhenAll`.
<ReturnType> <Name>(<params>) {
var r = Http.Get(url); // an effect — just call it
…
}
await Workflow.Run("step", <workflow>); // the ONE await: wait for another long-running thing to finishDescription#
Just call the effect#
An effect is anything that reaches outside the database: an HTTP call, a model completion, a file read. You call it. That is all:
app Shop {
model "model/**/*.osy";
use Osyrin.Http;
}
entity Order {
[Required] string Code;
bool Notified;
}
void Notify(string code, string url) {
var order = Order.Single(o => o.Code == code);
var r = Http.Get(url); // the function pauses here — but you did not have to say so
order.Notified = r.IsSuccess;
}The function does not return a Task. Its caller does not await it. Nothing about its signature says it might take
a while.
What happens when a function hits an effect?#
When a function reaches an effect, the engine suspends it, performs the effect, and resumes it at the very next line — with every local still in place. That suspension is durable: if the process is restarted, redeployed or killed while the HTTP call is in flight, the function still resumes where it left off. It is not a thread parked in memory; it is a continuation the platform persisted and will pick up again.
That is a stronger promise than async makes. A C# async method whose process dies mid-await is simply gone.
Why is there no async colour to propagate?#
In C#, async is a colour. A method that awaits must be async, so its callers must await it, so they must
be async, and it spreads outward until it has reached Main. You end up marking a hundred functions to describe a
property of one — and then a library forces you into .Result and you deadlock a thread pool at 4am.
The colour exists so that a caller knows the callee might yield. Here, that is the engine's business rather than the signature's: any function can suspend, so no function has to advertise it. There is nothing to spread, so there is nothing to mark.
The practical consequence: the ability to call out to the world costs you nothing in your function signatures. You can add an HTTP call to a function three layers down and not touch a single caller.
The one place await is legal — Workflow.Run#
await Workflow.Run("step", …) is the exception, and it earns it. There, waiting is the thing you are actually saying: start
this other long-running process, and do not continue until it is finished. It may be finished in ten seconds or in
three weeks; the await is what says you are content to wait either way.
That is a decision about your business process, not a detail of your threading — which is exactly why it is the one place the word appears.
If you write async or await anyway#
The compiler stops you. async is a parse error — "Osy# has no async — effects run in place, so a function or
method is never marked async." await is an error everywhere except the one place it means something
(await Workflow.Run("step", …)) — "Osy# has no await — effects run in place, so await is never needed." Both messages
point you straight back to this rule. There is no tolerated middle ground: the word suggests a distinction that does
not exist, so the compiler removes the temptation rather than letting it read as meaningful.