Your first program

This is the ten-minute path from an empty file to a typed, verified AI call. No API key is needed for any of it.

1. Hello

# hello.ecko
print("Hello, AI World!")
ecko hello.ecko

2. Values and functions

name = "Ada"                  # a binding
mut count = 0                 # explicitly mutable
count = count + 1

fn greet(who) = "Hello, {who}!"   # single-expression function

print(greet(name))            # Hello, Ada!

Strings interpolate with {expr} directly. Functions can be one expression with =, or a block with { }.

3. Collections and pipelines

scores = [3, 9, 4, 1]

top = scores
    |> filter(fn(s) s > 2)
    |> sort()
    |> reverse()

print(top)      # [9, 4, 3]

|> feeds the value on the left into the call on the right. It binds loosest of all operators, so a pipeline reads top to bottom.

4. Your first ai call

ai is a keyword. There is nothing to import and nothing to configure:

answer = ai "Name a colour"
print(answer)

Run it. With no API key you get [AI Mock] Name a colour - deterministic mock mode, which is what makes the rest of this page testable offline.

5. Typed output

An untyped call returns text. Ask for a type and you get that type:

count = ai[Int] "How many days in a week?"
print(count + 1)              # arithmetic on the result, not on a string

The type is enforced by coercion through a schema. ai[Int] is an Int or the call fails - it is never the string "seven". Structs, enums and lists work the same way:

type Sentiment = Positive | Negative | Neutral

mood = ai[Sentiment] "The service was wonderful"

See ai and typed output for the full set.

6. A contract

Types catch type errors. Contracts catch the rest:

@ensures(len(result) <= 80)
fn headline(article) = ai "Headline for: {article}"

print(headline("A cat was rescued from a tree."))

If the model returns something too long, Ecko feeds the failure back and retries rather than handing you bad output. Contracts can also be written in natural language, checked by a model. See Contracts.

7. Run it as a test

Rename the checks into a test file and ecko test runs them in forced mock mode - deterministic, offline, no key:

# hello_test.ecko
import std.test

test.case("greeting", fn() {
    test.eq(greet("Ada"), "Hello, Ada!")
})
ecko test

Where to go next