x-lang

AGENTS.md — x-lang for coding agents

This is the briefing. It exists because the rest of the documentation is written for a person reading in order, and an agent arrives in the middle, needs six facts, and will otherwise guess them wrong — the same six, every time. Read this file first; it is short on purpose and every claim in it is checkable with one command.

CLAUDE.md is a symlink to this file.

What x-lang is, in three sentences

A minimal, type-agnostic engine provides atoms, pairs, an adaptive runtime type system, and fexpr-based evaluation. Everything else — the semantics, the standard library, the object system, the numeric tower, the JIT, the toolchain — is written in x-lang on top of it, in layers, none of which modify the layer below. s-expressions are the initial syntax, not the only one: the reader is extensible, and whole surface languages (Scheme, C, Python, awk) load on top as langs.

The engine lives in a separate repository and arrives as a pinned, verified artifact behind a published contract. This repository is the language. The engine symlink is the only path to it.

Run it

sh x.sh -q -c '(write (+ 1 2))'      # evaluate an expression and exit
sh x.sh -q -f program.x              # evaluate a file and exit
echo '(write (+ 1 2))' | sh x.sh -q  # stdin is program text
sh x.sh                              # REPL (needs a terminal)

-c/--eval is repeatable and expressions run in order, so a definition and its use can share one command:

sh x.sh -q -l xe -c '(def sq (fn (_ n) (* n n)))' -c '(write (sq 12))'

Errors go to stderr and exit non-zero. -q suppresses the banner. There is no implicit printing — an expression’s value is not shown unless you print it; use (write x) for the machine-readable form, (display x) for the human one, (newline) between them.

Never invoke the engine directly

Do not run ./x-bin (or x-bin-asan, x-bin-cov, …). Run bare, it has no allocation ceiling and a runaway program takes the machine down — nine documented OOM incidents. A PreToolUse hook (.claude/hooks/x-guard.sh) blocks it, but the hook is a backstop, not the rule. The safe routes arm limits first: x.sh (pipes the library onto stdin, which arms conservative limits) and tests/x/spec-runner.sh (arms X_ALLOC_LIMIT_OBJS per spec).

It is not Scheme

It looks like a Lisp and it is not one. These are the guesses that fail, and they fail on the first line you write:

You will reach for It is actually Note
car / cdr first / rest car is unbound
print write / display print is unbound
(string-split s ",") ("a,b" split ",") methods dispatch subject-last
(lambda (x) …) (fn (self x) …) every closure gets itself as argument 0
(define …) (def …)  
(quote x) / 'x 'x or (lit x) both work; lit is the primitive
#t / #f same falsy is exactly {nil, #f}0 and "" are true

Three more that have no Scheme analogue at all:

Finding out what exists

This is the part worth internalising, because it replaces guessing. The library documents itself, and three calls reach all of it:

sh x.sh -q -c '(apropos "split")'    # search every documented name
sh x.sh -q -c '(help Str8/split)'    # signature, arg types, return, example
sh x.sh -q -c '(help x/core/math)'   # a module: its exports
sh x.sh -q -c '(modules)'            # every module, with [loaded] markers
sh x.sh -q -c '(help)'               # the overview

(help Name/method) is the high-value one. It answers with the real signature, every argument’s type and meaning, the return type, and a runnable example:

Str8/split: Split s into a list of pieces around each occurrence of sep.
  sep : STRING -- Separator to split on
  s : STRING -- String to split
  => LIST -- List of substrings of s between separators
  > (Str8 split "," "a,b,c") => ("a" "b" "c")

Reach for apropos before grepping the library, and before assuming a function does not exist. Names are not the ones you would guess (Str8 upcase, not upper), but they are all in there.

A missed method suggests near matches — no such method splt -- did you mean split? — so a typo self-corrects, but a wrong concept will not.

The generated API reference covers every module, built from the (doc …) forms in the source: https://jonruttan.github.io/x-lang/ — the whole documentation set is published there, both generated references included. Offline, make doc-x writes it to docs/ref/x/ (it is generated in CI, not committed, so a fresh clone will not have it until you build it).

Dialects: pick the right one or things will be “missing”

The library composes into three dialects, and helium is the default. Most “that function doesn’t exist” confusion is a dialect mismatch.

Flag Dialect Has
(none) helium (lib/x.xlib/he.x) 40+ modules: combinators, lists, sort, strings, vectors, promises, quasiquote, REPL. No numeric tower.
-l xe xenon helium + POSIX, hash tables, JIT, the full numeric tower (bigint, float, rational, complex, decimal)
-l rn radon xenon + raw syscalls, char/IO constants; file I/O and sockets on demand. Experimental.
sh x.sh -q -c '(write (+ 1/3 1/6))'        # helium: fails, no tower
sh x.sh -q -l xe -c '(write (+ 1/3 1/6))'  # xenon: 1/2

Load a module explicitly with (import x/type/regex). Modules are auto-discovered and deduplicated.

Where things are

Path What
lib/x/** the library, ~130 modules, all x-lang. The (doc …) forms here generate the API reference.
lib/x.x, lib/xe.x, lib/rn.x dialect entry points
engine symlink to the engine checkout or unpacked release. Not this repo.
tests/x/specs/** 142 spec files: executable examples with expected output. The best corpus for “how is this actually used”.
tests/x/conformance/** what judges an engine implementation
docs/ 28 hand-written documents; docs/index.md is the front door
tools/ self-hosted linter, formatter, coverage, profiler, doc generator
apps/, examples/ worked programs

Reading tests/x/specs/** is usually faster than reading prose. Each test is a heading, a fenced x-lang block, a --- line, and the expected last line of output — shown here indented, since the real thing is markdown:

### detects undefined symbol reference

```x
(display (lint-has? "x" %undef))
```
---
    #t

The format is tests/spec-format.md. x-lang’s own specs are tagged x, not scheme — a reader who trusts a scheme tag guesses a language that is not this one.

Conventions that will bite

Full style rules: docs/contributing.md and CONVENTIONS.md.

Testing and gates

make test-x       # the x-lang spec suite (2,800+ cases), each job booted
                  # from a state image; IMG=0 boots every library from source
make test-c       # the engine's C unit tests (delegated; a fetched
                  # release ships no C and says it skipped)
make test         # everything
make lint         # the self-hosted linter

Run make test-x after touching anything under lib/. CI runs the full suite on macOS and Linux plus a hard AddressSanitizer gate.

Documentation map

Start at docs/index.md. When you need one specific thing:

Question Document
What does this word mean? glossary.md
Is this behaviour normative? spec.md
How does evaluation work? architecture.md
How do types/dispatch work? type-system.md
How do classes work? object-system.md
What can I write? syntax.md, primitives.md
What’s in the library? standard-library.md, or apropos
How do modules/pinning work? modules.md
What must an engine provide? engine-contract.md
How do I build a surface language? crafting-a-lang.md

If you change something