One binary.
Your entire
ops stack.

Weft is a scripting language that ships everything you need to build agent tools, HTTP services, and devops scripts. No interpreters to install. No dependencies to manage.

Read the tutorial View source
install + run
# one-line install (no Go required)
$ curl -fsSL https://weftproject.dev/install.sh | sh

$ weft doctor
$ cat agent.weft

fn lookup(name) {
    db := db.open("sqlite:app.db")?
    db.query("SELECT * FROM users WHERE name = ?", [name])?
}

fn main -> Result {
    // the model calls your functions
    reply := llm.ask("Find user Alice", [
        llm.tool("lookup", lookup),
    ])?
    say(reply)
}
65+ stdlib packages
0 runtime dependencies
1 binary to deploy
4 LLM providers built in

Why another language?

We kept writing the same glue in Python and Bash: call an LLM, hit an API, check a server, parse some config. Each time we needed a virtualenv, a Dockerfile, or three layers of error handling. Weft is the language we wanted for those scripts.

Errors are values

Result and ? instead of try/catch. You see every failure path in the code. No surprises at 3am.

Concurrency is default

map and filter fan out automatically. spawn, channels, race, timeout. No async/await coloring.

Ship the binary

go build once. Copy to your server. No Go on the target, no interpreter, no package manager at deploy time.


LLM tool calling in 10 lines

Give a model your functions. It decides when to call them, you handle the results. Works with OpenAI, Anthropic, Ollama, and vLLM — swap providers with an env var.

Multi-step agents loop automatically: the model calls tools, reads results, and calls more tools until it has an answer. Set max_steps to cap iterations.

llm.ask llm.tool llm.agent llm.chat
fn get_weather(city) { http.get_json("https://wttr.in/$city?format=j1")? } fn search_docs(query) { db := db.open("sqlite:docs.db")? db.query("SELECT * FROM docs WHERE body LIKE ?", ["%$query%"])? } fn main -> Result { reply := llm.ask("What's the weather where our SF office is?", [ llm.tool("get_weather", get_weather), llm.tool("search_docs", search_docs), ], {"max_steps": 5})? say(reply) }
// multi-turn conversation fn main -> Result { msgs := [ {"role": "system", "content": "You are a code reviewer."}, {"role": "user", "content": "Review this: fn add(a,b) { a + b }"}, ] review := llm.chat(msgs)? say(review) // streaming for long responses text := llm.stream_text("Write a haiku about error handling")? say(text) // structured extraction data := llm.extract("Paris is the capital of France", { "city": "string", "country": "string", })? say(data.city) // "Paris" }

Chat, stream, extract

Simple chat for quick prompts. Multi-turn with message arrays. Stream long responses token by token. Extract structured data from free text.

Run locally with Ollama or vLLM — same code, same API, no vendor lock-in. Just set WEFT_PROVIDER=ollama.

llm.chat llm.stream_text llm.extract llm.client ollama vllm

Structured output with mold

LLMs return free text. mold pours it into the shape you need — define a schema, validate the output, generate JSON Schema or tool params for the model to follow.

Pairs with ml for embeddings and RAG, and tokensave for context management. The full agent stack, installable as packages.

mold ml tokensave warp
use mold fn main -> Result { schema := mold.model({ "name": mold.string({"min": 1}), "email": mold.string({"format": "email"}), "age": mold.integer({"min": 0, "max": 150}), "role": mold.one_of(["admin", "user", "guest"]), }) // ask the model to fill the schema spec := mold.tool_params(schema) reply := llm.ask("Create a user for Alice, age 30", [ llm.tool("create_user", create_user, spec), ])? say(reply) }

Train your own Weft writer

Teach a model to emit Weft instead of Python. Private by default — training data never leaves your machines unless you explicitly upload it.

Prepare data

Build a supervised fine-tune dataset from the gold corpus, your own domain examples, or tokensave's clarified memory. All local.

Train locally

LoRA fine-tune on open models (Qwen, Llama, Gemma) on your GPU. Presets handle the config. Air-gapped mode for secure environments.

Serve and test

Host your adapter with vLLM or TGI. Point WEFT_API_BASE at it. Evaluate accuracy against gold prompts.

# 1. build dataset (merge your private domain data) $ weft train prepare -o weft-sft --expand \ --from /secure/my-domain.jsonl # 2. fine-tune locally (data never uploaded) $ weft train presets qwen-1.5b Qwen2.5-1.5B ~4 GB qwen-7b Qwen2.5-7B ~16 GB llama-8b Llama-3.1-8B ~16 GB $ weft train finetune --private --preset qwen-7b # 3. serve + evaluate $ export WEFT_API_BASE=http://localhost:8000/v1 $ weft train eval --live --limit 20 $ weft train chat "write a health check script" # generate Weft from English $ weft gen "agent with weather tool" -o weather.weft --run

From prompt to fine-tune

weft train handles the full pipeline: prepare data, run LoRA on open frontier models, evaluate accuracy, then serve via any OpenAI-compatible host.

Your private domain data stays on your machine. Use --from to merge confidential examples. WEFT_TRAIN_PRIVATE=1 blocks any accidental cloud upload.

weft gen turns English into Weft scripts — with your fine-tuned model or any provider.

weft train prepare weft train finetune weft train eval weft gen

Any provider, same code

Switch between OpenAI, Anthropic, Ollama, and vLLM with one env var. The same llm.chat, llm.ask, and llm.stream calls work everywhere — including your fine-tuned model.

Run fully offline with Ollama on your laptop, or hit a private vLLM cluster in your VPC. No SDK lock-in, no vendor-specific code paths.

OpenAI Anthropic Ollama vLLM
# OpenAI (default) $ export OPENAI_API_KEY=sk-... $ weft run agent.weft # Anthropic $ export WEFT_PROVIDER=anthropic $ export ANTHROPIC_API_KEY=sk-ant-... $ weft run agent.weft # same code, different model # Local Ollama (offline) $ export WEFT_PROVIDER=ollama $ export OLLAMA_MODEL=llama3.2 $ weft run agent.weft # same code, no internet # Your fine-tuned model on vLLM $ export WEFT_PROVIDER=vllm $ export VLLM_BASE_URL=http://gpu-box:8000/v1 $ export WEFT_MODEL=weft-writer $ weft run agent.weft # your model, your infra
# air-gapped training (no internet on GPU box) $ weft train offline -o weft-airgap --expand \ --from /secure/domain.jsonl # copy weft-airgap/ via USB to GPU machine # on the air-gapped box: $ export HF_HUB_OFFLINE=1 $ export TRANSFORMERS_OFFLINE=1 $ weft train finetune --private --skip-prepare \ --data weft-airgap \ --model /models/Qwen2.5-7B-Instruct \ --out weft-lora

Air-gapped environments

For classified or regulated data: bundle the dataset and config on an internet-connected machine, copy to the GPU box via USB or internal storage, train with no network access.

Set WEFT_TRAIN_PRIVATE=1 to block any accidental cloud upload across the whole pipeline. The model weights, training data, and LoRA adapters never touch a public network.

weft train offline air-gapped WEFT_TRAIN_PRIVATE

fn main -> Result { mem := sysinfo.memory()? disk := sysinfo.disk("/")? up := sysinfo.uptime()? say("up ${up.human}, ram ${mem.percent}%") for port in [80, 443, 5432] { p := netutil.tcp_ping("localhost", port)? if p.open { say(" :$port ${p.latency_ms}ms") } else { log.error(" :$port down") } } nginx := proc.find("nginx")? say("nginx: ${len(nginx)} processes") }

DevOps without the glue

System metrics, process management, port scanning, DNS lookups — all in the stdlib. No shelling out to free -m and parsing the output.

Write your runbooks in a language that handles errors properly and runs the same on every machine.

sysinfo proc netutil sh fs secrets signal log

Packages with guardrails

Third-party packages install to vendor/ with a lockfile. Every publish is ed25519-signed.

The capability system restricts what packages can touch — a JSON validation library can't read your filesystem or call home unless you explicitly grant it.

// $ weft registry install mold // $ weft registry install ml // capabilities enforced per-package: // mold → pure (no fs, no http) // ml → fs only use mold use ml fn main -> Result { // mold can't touch the network // ml can read files but can't call home schema := mold.model({"q": mold.string()}) vec := ml.embed("search query")? say(len(vec)) }

stdlib — everything in the binary

httpwebjsondbllmfsshclisysinfoprocnetutilcsvyamltomlxmlinicryptoretimemathenvlogpcapsocketredismongonatsamqpgraphqlemailarchivesecretssignalollamavllmviztablestripuuidrandomdecimalbase64urlmimehtmlitercollectionspipefunctoolscopyheapbisectshlexdifflibbinstructplatformtracebackratelimitmigratemetricstokenizerdatasetwswebrtciojsonlpickletest