Skip to content
LogoLogo
A new programming language for Agents

VibeLang

TypeScript-shaped code with the compiler-level semantics for building reliable tools and agents.

npm install --save-dev vibelang
npx vibelang init
stageprealpha
licenseMIT

TypeScript compatible

Import TypeScript packages, use both languages side by side, and compile VibeLang to TypeScript.

Native when ready

Optionally compile to native code—the easiest path from a TypeScript codebase to native performance.

Comptime

Run deterministic code, generate types, and derive validators during compilation.

Durable execution

Compile selected Actions and Flows into typed, resumable execution plans.

Overview

VibeLang starts with TypeScript, then makes the minimal number of intentional changes needed to supercharge it for building reliable programs in the AI era.

99% TypeScript

VibeLang is almost TypeScript. The vast majority of its syntax is indistinguishable from TypeScript. You can import .ts files directly and use any TypeScript or JavaScript dependency. On the TypeScript target, VibeLang compiles to ordinary TypeScript that fits into the ecosystem you already use.

import { clamp } from "lodash-es"
 
export function normalizeConfidence(score: number): number {
  return clamp(score, 0, 1)
}

For ordinary code, there is nothing to learn or rewrite—the three versions are identical.

Typesafe Errors as Values

One of VibeLang's only changes to TypeScript is that errors are treated as strongly typed values that must be handled.

class NotFound extends Error {
  constructor(readonly id: string) {
    super(`User not found: ${id}`)
  }
}
 
function getUser(
  users: Map<string, User>,
  id: string,
): Result<User, NotFound> {
  const user = users.get(id)
  if (user === undefined) throw new NotFound(id)
  return user
}
 
const user = getUser(users, id).match({
  ok: user => user,
  error: error => error.match({
    NotFound: () => User.guest(),
  }),
})

The compiler sees Result<User, NotFound> and lowers the plain return and Error throw into its two variants. The caller must propagate or handle that Result. error.match is exhaustive over the known Error union.

Result is an ordinary must-use value. There is no generator ceremony or universal effect interpreter; functions remain eager with ordinary stacks.

Dependencies in the function signature

VibeLang uses an Effect-inspired context package instead of adding dependency-injection syntax. Its API looks like an ordinary library, but the compiler tracks every accessed service in the function's type signature.

import { Context } from "vibelang/context"
 
abstract class Clock extends Context {
  abstract now(): Date
}
 
abstract class AuditLog extends Context {
  abstract write(event: AuditEvent): Result<void, AuditError>
}
 
function issueToken(user: User): Result<Token, AuditError> {
  const clock = Clock.context()
  const audit = AuditLog.context()
  const token = Token.create(user, clock.now())
  audit.write({ type: "token.issued", userId: user.id }).unwrap()
  return token
}
 
// Inferred context: Clock | AuditLog

The inherited context() method is compiler-aware. It adds the capability class to the function's inferred context channel, and that requirement propagates through ordinary calls until a context or layer provides it. The function stays eager and callers never pass a context argument by hand.

Comptime can produce real types

VibeLang can run deterministic code during compilation—and the result can be a real TypeScript type. Here, a project-defined comptime function turns a tracked model file into a type the rest of the program uses normally.

import { comptime } from "vibelang:comptime"
 
const model = comptime(JSON.parse(embed("./account.model.json")))
const Account = comptime(deriveType(model))
 
function displayName(account: Account) {
  return `${account.name} (${account.plan})`
}

Effect can derive a type from an Effect schema, but the schema must become the source of truth. VibeLang comptime can execute an ordinary, project-defined derivation and return a type. The compiler tracks every input, caches the result, and reports a compile error if evaluation depends on runtime state.

Files become typed modules

Compiler-known files participate in the same type system and incremental build graph. VibeLang takes the direct-import ergonomics of Bun's loaders and extends them with compile-time types and dependency tracking.

JSON that keeps its literals

Ordinary TypeScript can import JSON, but values such as strings and numbers are widened. VibeLang's const form preserves the file as a deeply readonly literal value.

import config from "./agent.config.json" with {
  type: "json",
  mode: "const",
}
 
// "reasoning" — not string
type Model = typeof config.model
// readonly ["search", "write"] — not string[]
type Tools = typeof config.tools

Markdown is a prompt module

A Markdown file imports as a string at compile time. It can go straight into a model request without runtime filesystem access, a raw-loader suffix, or a declaration file.

import systemPrompt from "./system.md" with { type: "text" }
 
const response = (await model.generate({
  system: systemPrompt,
  prompt: question,
})).unwrap()

Rust and Zig import like source modules

Existing Node-API packages built with napi-rs remain ordinary npm dependencies. Local Rust and Zig sources can also be imported directly; VibeLang drives the foreign compiler and generates checked bindings for the selected TypeScript, native, or Wasm target.

import { createCanvas } from "@napi-rs/canvas"
import { rank } from "./rank.rs" with { type: "rust" }
import { tokenize } from "./tokenize.zig" with { type: "zig" }
 
const canvas = createCanvas(512, 512)
const ranked = rank(tokenize(source))

This follows Bun's proven loader model: text files can import as strings, .node files use its N-API loader, and Node-API is its stable native boundary. VibeLang adds generated source bindings and records source locations, toolchain versions, and dependency edges so diagnostics, watch mode, and remote caching stay correct across every format.

Concurrency keeps standard call syntax

Async functions remain ordinary async functions. Authored VibeLang consumes Promises only with await; a static or library combinator starts concurrent work, and Result.all combines expected outcomes.

const results = await Promise.all([
  loadProfile(userId),
  loadActivity(userId),
])
const [profile, activity] = Result.all(results).unwrap()

VibeLang bans Promise instance .then(), .catch(), and .finally() in authored source. Structured cancellation and child ownership belong in an explicit library combinator rather than custom parser syntax. Effect provides those guarantees through Effect values and its fiber runtime.

Make execution durable where it matters

Durability is explicit. Actions define persistence boundaries, while the compiler lowers functions passed to durable(...) into typed, resumable execution plans.

import { durable } from "vibelang:flows"
 
abstract class Research extends Action<
  (input: ResearchInput) => Result<Report, ResearchFailed>
> {}
 
abstract class Publish extends Action<
  (input: PublishInput) => Result<Publication, PublishFailed>
> {}
 
const Release = durable(function Release(
  input: ReleaseInput,
): Result<Publication, ResearchFailed | PublishFailed> {
  const report = Research.run({ topic: input.topic }).unwrap()
  return Publish.run({ report })
})

The compiler derives success/Error schemas, plan edges, and worker contracts from each Action's Result signature. Completed Actions can be replayed after interruption without rerunning work that already succeeded. Effect adds typed composition and retry policies, but an Effect program is not durably journaled across process restarts by itself.

TypeScript today, native when ready

VibeLang can emit TypeScript for immediate compatibility, then optionally compile portable parts of the same program to native code or Wasm. The compiler checks the complete dependency graph and explains which call, package, or capability still requires the TypeScript runtime.

This gives teams a gradual path to native deployment: start inside an existing TypeScript codebase, move boundaries when they become valuable, and avoid a ground-up rewrite.

Features

VibeLang is designed to provide these capabilities out of the box:

  • TypeScript compatible — import TypeScript packages, adopt VibeLang incrementally, and compile back to TypeScript
  • Optional native compilation — move from TypeScript to native code gradually instead of committing to a rewrite
  • Comptime type generation — derive validators, codecs, and real types without maintaining a parallel schema language
  • Durable programs — turn explicit Action and Flow boundaries into persistent, distributed execution plans
  • Typed Results — use must-use Result<A, E> values and exhaustive Error matching across synchronous and asynchronous functions
  • Capability-based dependencies — make databases, clocks, filesystems, and other services visible in function types
  • Typed asset imports — load JSON, Markdown, MDX, SQL, GraphQL, Zig, and Rust through compiler-tracked loaders
  • Concurrency without Promise chains — use typed workers, static/library joins, await, and cancellation without a fiber runtime

Built for tools and agents

Reliable agents need more than a tool-call protocol. They need explicit authority, recoverable failures, runtime validation, deterministic compilation, and durable work that can resume after interruption.

VibeLang makes those concerns part of one language model. Generated TypeScript runs with an explicit function surface, platform access is capability-based, and Actions or Flows add durability only where the program asks for it.

Read the Agent Library guide or start with Why VibeLang?.

Help shape the language

VibeLang is in active design. The syntax, compiler architecture, and core semantics are being specified before the production toolchain is built. This is the stage where concrete feedback can change the language at the lowest cost.

Review the specification or join the project on GitHub.