Effects
How Qosm tracks every side effect in the type system.
In most languages, you can't tell by looking at a function's type whether it makes network requests, writes to a database, or calls an LLM. In Qosm, you can — because every side effect is part of the type.
Effects in types
The ! annotation on a function type lists its effects:
// Pure — no effects
let add x y = x + y;
// add : Int -> Int -> Int
// One effect
let fetch url = api_get {url: url, headers: []};
// fetch : String -> Result<String, QosmError> ! {Http.Get}
// Two effects
let classify_page url =
match fetch url with (
| Ok{value: body} -> my_claude @{topic: String} `Topic of: {{ body }}`
| Err{value: e} -> Err{value: e});
// classify_page : String -> Result<{topic: String}, QosmError> ! {Http.Get, LLM.Call}
When you compose functions, their effects merge automatically. The compiler does this — you never annotate effects by hand.
Why this matters
Effects give you three things:
1. Visibility. Look at any function's type and know exactly what it does. If classify_page shows ! {Http.Get, LLM.Call}, you know it makes HTTP requests and LLM calls. Nothing else. No hidden database writes, no secret logging.
2. Cost awareness. In the workspace, the inference panel shows effects for every binding. If a function has LLM.Call, running it consumes credits. If it has no effects, it's pure computation — free and instant.
3. Composability. Higher-order functions like map and filter are effect-polymorphic. You can pass them effectful callbacks and the effects propagate correctly:
// classify has effect LLM.Call
let results = map (λ text -> classify text) texts;
// results : Array<Result<{topic: String}, QosmError>> ! {LLM.Call}
The type system knows that mapping with an effectful function produces an effectful result. No special annotation needed.
Set semantics
Effects use set semantics — each effect appears at most once. Even if your code calls http_get a hundred times, the effect is just {Http.Get}. The type tracks which effects, not how many times.
Effects and capabilities are two sides of the same coin
Every capability declares an effect. Every effect traces back to a capability. The effect set in your function's type is exactly the set of capabilities it uses — no more, no less.
This is why the capability model works: the compiler can see every side effect in the code, and it can guarantee that the only effects present are the ones you authorized by providing capabilities.
Next: Code Mode — how Qosm handles MCP tools and OpenAPI endpoints differently from other agent frameworks.