Two execution tiers

Ecko runs on two engines that share one global environment.

tierruns
Bytecode VMthe whole language, including functions with boolean contracts
AST evaluatorai 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, @tool functions 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.