Two execution tiers
Ecko runs on two engines that share one global environment.
| tier | runs |
|---|---|
| Bytecode VM | the whole language, including functions with boolean contracts |
| AST evaluator | ai internals, functions with a string contract or an @ensures on a typed ai body, @tool functions, async functions, py(), finding and loading an imported module (whose code then runs on the VM) |
Why two
The VM is faster and is the shipped engine. But some of the AI machinery is easier to get right as a tree walk over the source: a contract retry loop re-runs a function body with a failure fed back, the ai tool loop suspends and resumes around external calls, and both need the declaration's structure rather than a flattened instruction stream.
Rather than complicate the VM for constructs that are dominated by network latency anyway, those stay on the evaluator. A contract retry costs a model call; the interpretation overhead is not measurable next to it.
A boolean contract is not one of those. @requires(n >= 0) and @ensures(result > 0) compile into the function they guard: each condition is ordinary bytecode run before the body and before every return, so a contracted function runs on the VM at close to the speed of the same function without its contract. A function moves only when every contract on it is a boolean expression; one string contract, an @ensures on a typed ai body (the retry case), @tool, or async keeps it on the evaluator.
The bridge
The two directions both work, which is what makes the split invisible:
- Functions the evaluator runs are evaluator values, dispatched through a shared apply path that handles string contracts, the retry loop and task spawning.
- The evaluator calls VM closures back through the same path, so higher-order built-ins,
@toolfunctions and spawned tasks all work with either kind of function.
So you can pass a plain lambda to a contracted function, use an async fn as a map callback, or hand a VM closure to a tool - and none of it needs to know which engine produced the other.
Why it is worth telling you
Mostly it is not: the tiers are an implementation detail and the language behaves uniformly. Two places it becomes visible:
- Functions the evaluator runs have a different internal representation from VM ones. They are still first-class values.
- The evaluator is the parity oracle. The test suite runs programs on both engines and asserts identical results, which is how a VM optimization is kept from quietly changing semantics.
That second point is the real reason the AST tier still exists at full fidelity: a second independent implementation of the same semantics is the most effective correctness check the project has.