Skip to content
This repository was archived by the owner on Apr 18, 2026. It is now read-only.

AlexProgrammerDE/vibelang

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

36 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

vibelang

vibelang is a Python-shaped interpreted language where user-defined function bodies are plain language. The interpreter is written in Go, and every function call is executed by a local or remote LLM running through Ollama, llama.cpp, or an OpenAI-compatible endpoint.

What It Does

  • Uses indentation-sensitive, Python-like syntax for variables, expressions, loops, and conditionals.
  • Treats every def body as natural-language instructions instead of imperative code.
  • Supports AI macros with macro definitions and @macro(...) expansion syntax, so the model can synthesize real vibelang expressions on the fly.
  • Supports module loading with import "./module.vibe" as module and from "./module.vibe" import helper.
  • Supports Python-style default parameter values and keyword arguments for user-defined functions and builtins.
  • Supports Python-style unpacking targets in assignments and for loops.
  • Supports inline * prompt expressions in assignments, conditions, loops, and standalone statements.
  • Supports leading AI directives such as @temperature, @max_tokens, @max_steps, @cache, @system, @tools, and @deny_tools inside function and macro bodies.
  • Evaluates ${...} prompt placeholders as real vibelang expressions, including indexing and prompt-safe builtins such as len, basename, or join_path.
  • Supports Python-style list and dict comprehensions with optional trailing if filters.
  • Supports structural match / case branching with wildcard, list, dict, and capture patterns plus optional if guards.
  • Supports Python-style with blocks backed by native managed resources such as temporary files, temporary directories, mutex guards, and OpenTelemetry span scopes.
  • Supports structured AI return types such as dict{city: string, alerts: optional[list[string]]} and tuple[string, int], and turns them into tighter JSON schemas for model backends.
  • Constrains helper calls with per-helper JSON schemas, so models see the exact argument names, required fields, and types for each callable tool.
  • Supports deterministic try / except / finally blocks for builtin, tool, and model-call failures.
  • Supports Python-style assert statements for deterministic guards and self-checking programs.
  • Supports block-scoped defer expressions for LIFO cleanup on normal exit, break, continue, and errors.
  • Supports Python-like member access for imported modules and dict-shaped values, so shared.helper() works naturally.
  • Supports Python-style negative indexing and slicing for lists and strings, plus operand-returning and/or short-circuit behavior.
  • Lets AI functions call other AI functions through a strict JSON tool-call loop, supports explicit JSON call_many batches, and also understands provider-native tool_calls responses from Ollama, llama.cpp, and OpenAI-compatible backends, including multi-call batches.
  • Rejects direct and indirect recursive AI helper re-entry before it spirals into repeated depth exhaustion, feeds the rejection back into the next model step, and fails fast if the model keeps retrying a rejected helper.
  • Adds opt-in AI result caching, CSV/YAML/TOML helpers, first-class sets, richer dict and list helpers, Python-inspired collection helpers such as all, any, reversed, flatten, and batched, Markdown rendering, route construction, time parsing and formatting, UUID generation, numeric reducers, structured logging, and OpenTelemetry trace export.
  • Captures surrounding non-function values by value when an AI function is defined, so later mutations do not silently change prompt inputs.
  • Exposes a broader standard library for AI execution, including filesystem, JSON, YAML, path, string, cookies, environment, globbing, HTTP, WebSocket and TCP clients, TCP listeners, time, math, local process helpers, async tasks, channels, channel selection, mutexes, wait groups, route matching, and runtime metrics.
  • Lets one AI body route itself to a different backend with @provider, @model, @endpoint, @api_key_env, and @timeout_ms, so local Gemma can coexist with remote OpenAI-compatible calls in one program.
  • Starts AI-backed HTTP servers, including ordered route tables, WebSocket upgrades, and Server-Sent Event streaming backed by native channel handles.
  • Serves deterministic static frontend assets, including HTML, JS, CSS, JSON, SVG, and .wasm, with correct HTTP content types through http_static_response and mime_type.
  • Exposes deterministic tool introspection so programs can inspect the live helper catalog through tool_catalog and tool_describe.
  • Resolves modules from relative paths, VIBE_PATH, working-directory std/ modules, direct URLs, and github.com/owner/repo/path@ref imports.
  • Runs against local or remote model servers, with first-class support for Ollama, llama.cpp, OpenAI, Groq, and other OpenAI-compatible gateways.
  • Sends chat-style structured JSON requests to local backends, which works better with modern Gemma 4 model servers.
  • Caches parsed prompt templates so repeated ${...} interpolation work does not keep reparsing the same expressions.
  • Ships standard-library modules written in vibelang itself, including std/web, std/react, std/config, std/telemetry, std/runtime, and std/ai, with std/web able to render wasm-oriented HTML shells that expose initial state and optional host model endpoints, std/react able to generate React-like shells over the same host-model pattern, and std/config able to summarize TOML-driven configuration with AI.

Quick Start

Build the interpreter:

go build -o bin/vibelang ./cmd/vibelang

Run the included example with Ollama:

ollama serve
ollama pull gemma4
./bin/vibelang --provider ollama --model gemma4 examples/hello.vibe

For smaller local runs, Ollama also exposes lighter Gemma 4 tags such as gemma4:e4b.

Run the same program with llama.cpp:

llama-server -m /models/gemma4.gguf --port 8080
./bin/vibelang --provider llamacpp --endpoint http://127.0.0.1:8080 --model gemma4 examples/hello.vibe

If your local model tag or GGUF filename uses a different name, pass that exact value with --model.

Run against a remote OpenAI-compatible endpoint:

export OPENAI_API_KEY=...
./bin/vibelang --provider openai --model gpt-4.1-mini examples/hello.vibe
export GROQ_API_KEY=...
./bin/vibelang --provider groq --model openai/gpt-oss-20b examples/hello.vibe

Validate a program without hitting the model:

./bin/vibelang --check examples/modules/main.vibe

Print the interpreter version:

./bin/vibelang --version

Example

def summarize_weather(city: string, tone: string = "crisp") -> string:
    Write one ${tone} sentence about the weather in ${city}.

city = "Berlin"
forecast = summarize_weather(city=city)
print(forecast)

Inline prompts work anywhere a full expression makes sense in statement position:

workspace = join_path([cwd(), "tmp"])
make_dir(workspace)
path = join_path([workspace, "pi.txt"])
digits = * return the first 5 digits of pi as a string without explanation.

if * check whether ${path} already exists:
    * delete the file at ${path}.
else:
    * write ${digits} to the file at ${path}.

Prompt interpolation is expression-aware, not just name-aware:

def explain_file(path: string, digits: string, tone: string = "matter-of-fact") -> string:
    Write one ${tone} sentence about ${basename(path)} inside ${dirname(path)}.
    Mention that ${digits} has ${len(digits)} characters.

AI bodies can also declare execution controls up front:

def slugify(title: string) -> string:
    @temperature 0
    @max_steps 4
    @cache true
    @system You are a literal slugging assistant. Return only the slug text.
    @tools lower, trim, replace, regex_replace
    Convert ${title} into a lowercase URL slug.
    Replace whitespace runs with "-".
    Remove punctuation and collapse repeated "-" runs.

Bodies can also pin themselves to a different model route when one program needs both local and remote execution:

def summarize_release(changes: list[string]) -> string:
    @provider openai-compatible
    @endpoint https://models.example.com/v1
    @model hosted-gemma
    @api_key_env VIBE_REMOTE_API_KEY
    @timeout_ms 10000
    Summarize ${json_pretty(changes)} in one crisp paragraph.

Slices are first-class expressions:

digits = "31415926535"
items = ["alpha", "beta", "gamma", "delta"]

print(digits[:5])
print(digits[-3:])
print(items[1:3])
print(items[::-1])

Comprehensions work the way Python users expect:

names = [upper(name) for name in ["ada", "grace", "linus"] if "a" in name]
lengths = {name: len(name) for name in names if len(name) > 3}

print(json(names))
print(json(lengths))

Python-shaped collection helpers keep deterministic reshaping out of the model loop:

values = [1, 2, 3, 4, 5]
groups = batched(values, 2)

print(all([true, 1, "Ada"]))
print(any([false, "", "vibe"]))
print(json(reversed(values)))
print(json(flatten(groups)))
print(json(dict_delete({"name": "Ada", "role": "builder"}, "role")))
print(json(set_values(set_symmetric_difference(set([1, 2, 3]), set([3, 4])))))

Unpacking works in plain assignments and loop headers:

first, second = ["Ada", "Lovelace"]
print(first)
print(second)

for index, label in zip([1, 2, 3], ["a", "b", "c"]):
    print(index)
    print(label)

Assertions make deterministic invariants explicit:

snapshot = runtime_metrics()
assert snapshot["go.goroutine.count"] >= 1, "expected at least one goroutine"

with blocks make temporary resources and scope-based cleanup explicit:

with temp_dir(prefix="vibelang-demo-") as workspace:
    note = join_path([workspace, "note.txt"])
    write_file(note, "hello")
    print(file_exists(note))

print(file_exists(workspace))

Pattern matching lets deterministic code branch on data shape before handing the rest to AI:

packet = {"type": "message", "payload": ["alpha", "beta"], "meta": {"city": "Berlin"}}

match packet:
    case {"type": "ping"}:
        print("pong")
    case {"type": "message", "payload": [head, tail]} if head == tail:
        print("duplicate payload")
    case {"type": "message", "payload": [head, tail], "meta": {"city": city}}:
        print(head)
        print(tail)
        print(city)
    case _:
        print("fallback")

AI macros expand into real expressions before evaluation:

macro even_numbers(limit: int) -> list[int]:
    Return one valid vibelang expression that builds the even numbers below ${limit} * 2.
    Prefer using range with explicit named arguments.

numbers = @even_numbers(5)
print(numbers)

Structured outputs can stay Python-shaped while still giving the model a precise target:

def describe_weather(city: string) -> dict{city: string, summary: string, alerts: optional[list[string]], stats: dict{temp_c: int, wind_kph: int}, focus: tuple[string, int]}:
    Return a compact weather object for ${city}.

print(json_pretty(describe_weather("Berlin")))

Modules are ordinary .vibe files:

# shared.vibe
prefix = "Dr."

def format_name(name: string) -> string:
    Return exactly: ${prefix} ${name}
# main.vibe
from "./shared.vibe" import prefix, format_name
import "./shared.vibe" as shared

print(prefix)
print(format_name("Ada"))
print(shared.format_name("Grace"))

Concurrent work uses native Go-backed primitives:

ch = channel(1)
channel_send(ch, "tasks queued")

wg = wait_group()
wait_group_add(wg, 2)

first = spawn(str, args=[42], wait_group=wg)
second = spawn(join, args=[["vibe", "lang"], "-"], wait_group=wg)

notice = channel_recv(ch)
wait_group_wait(wg)

print(notice["value"])
print(await_task(first))
print(await_task(second))

Channel selection adds a more Go-like coordination primitive:

fast = channel(1)
slow = channel(1)
channel_send(slow, "background")

packet = channel_select([fast, slow], timeout_ms=10)
print(packet["channel"] == slow)
print(packet["value"])

Programs can inspect the helper surface deterministically:

json_tools = tool_catalog(prefix="json_")
print(json_tools[0]["name"])
print(tool_describe("http_request")["params"][0]["name"])

Explicit AI caching is useful for expensive deterministic helpers:

def normalize_city(city: string) -> string:
    @temperature 0
    @cache true
    Return ${city} in uppercase letters.

print(normalize_city("berlin"))
print(normalize_city("berlin"))
print(cache_stats()["entries"])

HTTP handlers can also be AI-backed:

import "std/web" as web

def handle(request: dict) -> dict:
    Call web.render_app_shell with the title "vibelang demo", the route ${request["path"]}, and initial state {"path": request["path"]}.
    Return a dict with html set to that app shell.

server = http_serve("127.0.0.1:0", handle)
response = http_request("http://" + server["address"] + "/hello")
print(response["status"])
http_server_stop(server["handle"])

Static frontend bundles can stay deterministic and still plug into the same HTTP surface:

site = join_path([cwd(), "site"])
make_dir(join_path([site, "pkg"]))
write_file(join_path([site, "index.html"]), "<h1>vibelang</h1>")
write_file(join_path([site, "pkg", "app.wasm"]), "\u0000asm")

home = http_static_response(site, {"path": "/"}, cache_control="public, max-age=60")
wasm = http_static_response(site, {"path": "/pkg/app.wasm"})

print(home["headers"]["Content-Type"])
print(wasm["headers"]["Content-Type"])

std/web can also render wasm-first shells for AI-backed routes:

import "std/web" as web

def handle(request: dict) -> dict:
    Call web.respond_wasm_shell with the title "vibelang wasm demo", the route ${request["path"]}, state {"path": request["path"]}, brief "A dashboard shell that boots a wasm bundle.", wasm_path "/pkg/app.wasm", js_path "/pkg/app.js", and model_endpoint "/api/llm".

std/react adds a React-like route-shell prompt module for AI-authored frontend scaffolding:

import "std/react" as react

def handle(request: dict) -> dict:
    Call react.respond_react_shell with the title "vibelang react shell", route ${request["path"]}, state {"path": request["path"], "user": {"name": "Ada"}}, brief "A React-like dashboard shell with one interactive root.", model_endpoint "/api/llm".

SSE handlers can stream channel-backed event feeds:

updates = channel(2)
channel_send(updates, sse_event("booting", event="status", id="evt-1"))
channel_send(updates, "done")
channel_close(updates)

def handle(request: dict) -> dict:
    Return exactly {"status": 200, "sse_channel": updates}.

server = http_serve("127.0.0.1:0", handle)
response = http_request("http://" + server["address"] + "/events")
print(response["headers"]["Content-Type"])
http_server_stop(server["handle"])

HTTP handlers can also upgrade into AI-backed WebSocket sessions:

def upgrade(request: dict) -> dict:
    Return exactly {"websocket": "chat"}.

def chat(session: dict) -> none:
    Receive one websocket message from session["handle"].
    Reply with the uppercased text.
    Close the websocket.

server = http_serve("127.0.0.1:0", upgrade)
client = websocket_dial("ws://" + server["address"] + "/chat")
websocket_send(client, "ping")
packet = websocket_recv(client, timeout_ms=1000)
print(packet["data"])
websocket_close(client)
http_server_stop(server["handle"])

Ordered route tables keep larger AI-backed services predictable:

def home(request: dict) -> dict:
    Return a dict with status 200, json {"route": "home"}.

def profile(request: dict) -> dict:
    Return a dict with status 200, json {"route": "profile", "id": request["params"]["id"]}.

routes = [{"pattern": "/", "handler": home}, {"pattern": "/users/:id", "methods": ["GET"], "handler": profile}]
server = http_serve_routes("127.0.0.1:0", routes)

TCP listener handles let deterministic code accept sockets while AI stays focused on the protocol logic:

listener = socket_listen("127.0.0.1:0")
accept_task = spawn(socket_accept, args=[listener["handle"]])
client = socket_open(listener["address"])
accepted = await_task(accept_task)

socket_write(client, "ping")
print(socket_read(accepted["handle"]))
socket_listener_close(listener["handle"])

Deterministic code can recover from runtime errors without dropping back to the host shell:

try:
    fail("simulated failure")
except err:
    print(err)
finally:
    print("cleanup complete")

Block-scoped cleanup is available without wrapping everything in try / finally:

for name in ["alpha", "beta"]:
    path = join_path([cwd(), name + ".tmp"])
    defer delete_file(path)
    write_file(path, name)
    print("created " + basename(path))

URL helpers and JSON-first HTTP helpers keep API plumbing deterministic:

parsed = url_parse("https://ada.example:8443/products/view?tag=lang&tag=ai&sort=desc#hero")
rebuilt = url_build({"scheme": parsed["scheme"], "host": parsed["host"], "path": parsed["path"], "query": parsed["query"], "fragment": parsed["fragment"]})
response = http_request_json("https://example.com/api", method="POST", body={"name": "Ada"})

print(parsed["hostname"])
print(rebuilt)
print(response["json"])

Structured data helpers also cover CSV, durations, and timestamp math:

rows = csv_parse("name,role\nAda,builder\nGrace,scientist\n")
print(rows[1]["role"])
print(time_format("2026-04-17T12:34:56Z", layout="date"))
print(time_add("2026-04-17T12:34:56Z", "90m"))
print(duration_parse("1h30m"))
print(uuid_v7())

Route matching is built in for AI-backed HTTP handlers and plain deterministic code:

user = route_match("/users/:id", "/users/42")
assets = route_match("/assets/*path", "/assets/css/app.css")

print(user["params"]["id"])
print(assets["params"]["path"])

TOML and Markdown helpers make config-heavy web flows less prompt-dependent:

config = toml_parse("title = \"vibelang\"\n[server]\nport = 8080\n")
route = route_build("/users/:id/files/*path", {"id": "42", "path": "docs/readme.md"}, {"tab": "preview"})
html = markdown_to_html("# Release Notes\n\n- shipped\n")

print(config["server"]["port"])
print(route)
print(contains(html, "<li>shipped</li>"))

Project Layout

  • cmd/vibelang: CLI entrypoint.
  • internal/lexer: indentation-aware line lexer and tokenizer.
  • internal/parser: AST builder for statements, expressions, and raw AI function bodies.
  • internal/runtime: evaluator, builtins, type coercion, prompt construction, and AI tool-call loop.
  • internal/model: Ollama, llama.cpp, and OpenAI-compatible HTTP clients plus native tool-call transport.
  • std: bundled vibelang modules that ship prompt-native library helpers.
  • examples: runnable sample programs.
  • docs: tutorial, how-to, reference, and explanation documents.

Expanded Standard Library

The deterministic runtime now covers more of the boring work that AI functions should not hallucinate:

  • Filesystem: read_file, write_file, append_file, copy_file, move_file, glob, temp_dir, temp_file, read_json, write_json, read_yaml, write_yaml, read_toml, write_toml
  • Data: json_parse, json_pretty, yaml_parse, yaml_stringify, toml_parse, toml_stringify, csv_parse, csv_stringify, set, set_add, set_remove, set_has, set_values, set_union, set_intersection, set_difference, set_symmetric_difference, dict_has, dict_get, dict_set, dict_merge, dict_delete, all, any, reversed, flatten, batched, sorted, unique, sum, min, max
  • Paths and strings: join_path, abs_path, dirname, basename, split, join, replace, contains, base64_encode, base64_decode, url_encode, url_decode, query_encode, query_decode, url_parse, url_build, html_escape, markdown_to_html, template_render, sha256, regex_match, regex_find_all, regex_replace, cookie_parse, cookie_build
  • System: run_process, env, cwd, now, unix_time, time_parse, time_format, time_add, time_diff, duration_parse, uuid_v4, uuid_v7, sleep
  • Math: sqrt, pow, abs, floor, ceil, plus pi and e
  • Network: http_request, http_request_json, websocket_dial, websocket_send, websocket_recv, websocket_close, sse_event, socket_listen, socket_accept, socket_open, socket_write, socket_read, socket_local_addr, socket_remote_addr, socket_listener_close, socket_close
  • Concurrency: spawn, await_task, task_status, channel, channel_send, channel_recv, channel_select, channel_close, mutex, mutex_guard, mutex_lock, mutex_unlock, wait_group, wait_group_add, wait_group_done, wait_group_wait
  • Services: route_match, route_build, mime_type, http_static_response, http_serve, http_serve_routes, http_server_stop, plus HTTP response-mode WebSocket upgrades through {"websocket": ...}
  • Runtime introspection: tool_catalog, tool_describe
  • Observability: log, otel_init_stdout, otel_span_start, otel_span_scope, otel_span_event, otel_span_end, otel_flush, metrics_snapshot, runtime_metrics, runtime_metric

Bundled std modules currently include:

  • std/web: AI helpers for HTML page rendering, component fragments, app shells, wasm shells, typed HTML responses, JSON response construction, and SSE wrappers via respond_app_shell, respond_wasm_shell, respond_json, respond_sse, and respond_sse_channel
  • std/react: AI helpers for React-like component fragments and route shells via render_react_component, render_react_shell, and respond_react_shell
  • std/config: AI helpers for summarizing config objects and TOML text or files
  • std/telemetry: AI helpers for summarizing runtime metrics
  • std/runtime: AI helpers for summarizing live Go runtime metrics
  • std/ai: reusable AI helpers for rewriting, payload summaries, and release note drafting

Documentation

Status

The interpreter is production-shaped, but the runtime behavior still depends on how well the selected local model follows the JSON protocol. Lower temperatures and smaller helper-call limits generally make execution more predictable. run_process, network access, and file-mutating helpers are intentionally powerful, so treat .vibe programs the way you would treat any other local code execution surface.

About

🍜 PoC coding language where you write code as words and is evaluated by AI on the fly.

Resources

License

Stars

Watchers

Forks

Contributors

Languages