A live, interactive Smalltalk-like language for the BEAM VM
Beamtalk brings Smalltalk's legendary live programming experience to Erlang's battle-tested runtime. While inspired by Smalltalk's syntax and philosophy, Beamtalk makes pragmatic choices for modern development (see Syntax Rationale). Write code in a running system, hot-reload modules without restarts, and scale to millions of concurrent actors.
// Spawn an actor with state
counter := Counter spawn
// Send messages (async by default)
counter increment
counter increment
value := counter getValue await // => 2
// Cascades - multiple messages to same receiver
Transcript show: "Hello"; cr; show: "World"
// Map literals
config := #{#host => "localhost", #port => 8080}
| Feature | Benefit |
|---|---|
| Interactive-first | REPL and live workspace, not batch compilation |
| Hot code reload | Edit and reload modules in running systems |
| Actor model | Actors are BEAM processes with independent fault isolation |
| Async by default | Actor message sends return futures; blocking only via await |
| Reflection | Inspect any actor's state and methods at runtime |
| Runs on BEAM | Compiles to Core Erlang; deploy to existing OTP infrastructure |
| Testing built-in | SUnit-style TestCase framework with beamtalk test |
Every Beamtalk actor is a BEAM process with its own state and mailbox:
Actor subclass: Counter
state: value = 0
increment => self.value := self.value + 1
decrement => self.value := self.value - 1
getValue => ^self.value
Messages are asynchronous by default, returning futures:
// Returns immediately with a future
result := agent analyze: data
// Wait when you need the value
value := result await
Match expressions with pattern arms:
status match: [
#ok -> "success"
#error -> "failure"
_ -> "unknown"
]
// Variable binding in patterns
42 match: [n -> n + 1] // => 43
Rich collection types written in Beamtalk itself:
list := #(1, 2, 3)
list collect: [:x | x * 2] // => #(2, 4, 6)
dict := #{#name => "Alice", #age => 30}
dict at: #name // => Alice
Redefine methods on running actors — state is preserved:
// In the REPL: redefine a method on a running class
Counter >> increment => self.value := self.value + 10
// Existing actors immediately use the new code
c increment // now adds 10 instead of 1
Beamtalk is purpose-built for multi-agent AI systems:
- Every actor is a BEAM process — millions of concurrent isolated agents
- Live inspection — query actor class, methods, and capabilities at runtime
- Hot-reload — edit agent behavior while they run, no restart needed
- Fault tolerance — actors crash independently; isolation via BEAM processes
- BEAM foundation — inherits BEAM's distributed runtime; Beamtalk-level distribution API planned
// Each agent is its own BEAM process — isolated, supervised, inspectable
researcher := Researcher spawn
critic := Critic spawn
// Async messaging — returns futures
analysis := researcher analyze: codeRepo
findings := analysis await
// Live introspection while running
researcher class // => Researcher
researcher respondsTo: #plan: // => true
Researcher methods // => #(analyze:, plan:, query:, ...)
// Hot-reload: redefine behavior mid-run, takes effect on next message
Researcher >> plan: prompt =>
Transcript show: "Planning: ", prompt.
^super plan: prompt
See Agent-Native Development for the full vision.
- Rust (latest stable)
- Erlang/OTP 26+ with
erlcon PATH - Node.js LTS (optional) — for building the VS Code extension
The project includes a Justfile with common tasks:
# Clone and setup
git clone https://github.com/jamesc/beamtalk.git
cd beamtalk
# Install Just (if not already installed)
cargo install just
# See all available tasks
just --list
# Run local CI checks (build, lint, unit & E2E tests)
# Note: runtime integration tests run only in GitHub Actions CI
just ci
# Start the REPL
just repl
# Run tests
beamtalk test # Run BUnit TestCase tests
just test-stdlib # Run compiled expression tests
just test-e2e # Run REPL integration tests
# Clean build artifacts (works with Docker volumes)
just clean# Build
cargo build
# Start the REPL
cargo run -p beamtalk-cli --bin beamtalk -- repl
# Clean (note: fails in devcontainer due to volume mount)
cargo clean # Use `just clean` instead in devcontainerYou can install beamtalk to a system prefix for use outside the development checkout:
# Install to /usr/local (may need sudo)
just install
# Install to a custom prefix
just install PREFIX=$HOME/.local
# Create a local distribution in dist/
just dist
# Uninstall
just uninstall PREFIX=$HOME/.localThe install layout follows the OTP convention (PREFIX/lib/beamtalk/lib/<app>/ebin/), so beamtalk repl and beamtalk build work correctly from any directory when the binary is on PATH.
For local extension development (debug LSP, no .vsix packaging):
just build-vscodeThen in VS Code, run Developer: Install Extension from Location... and select editors/vscode.
If your stdlib source files are outside the project root, set:
{
"beamtalk.stdlib.sourceDir": "/opt/beamtalk/stdlib/lib"
}beamtalk.stdlib.sourceDir can be absolute (including outside the project) or relative to the Beamtalk project root (directory containing beamtalk.toml). See editors/vscode/README.md for full extension configuration.
New to Beamtalk? See the REPL Tutorial for a complete beginner's guide!
Beamtalk v0.1.0
Type :help for available commands, :exit to quit.
> message := "Hello, Beamtalk!"
"Hello, Beamtalk!"
> 2 + 3 * 4
14
> :load examples/hello.bt
Loaded Hello
> Hello new
{__class__: Hello}
> :load examples/counter.bt
Loaded Counter
- Docker Desktop — For devcontainer support
- VS Code — With the Dev Containers extension
- Rust (latest stable) — For building the compiler
- Erlang/OTP 26+ — With
erlcon PATH
- Node.js LTS + npm — For building the VS Code extension (
editors/vscode/)
| Variable | Purpose | How to Set |
|---|---|---|
GH_TOKEN |
GitHub authentication for devcontainers | gh auth login, then $env:GH_TOKEN = (gh auth token) |
LINEAR_API_TOKEN |
Linear issue tracking | Get from Linear API settings |
For worktree-based parallel development, install the devcontainer CLI:
npm install -g @devcontainers/cliNote: The worktree-new scripts will auto-install this if missing.
VS Code (Recommended):
- Clone the repository
- Open in VS Code: File → Open Folder
- When prompted, click Reopen in Container
- Wait for container build (~5 minutes first time)
- Open a terminal and run:
just build - Enable the pre-push lint hook:
git config core.hooksPath .githooks
The devcontainer includes all dependencies pre-configured:
- Rust toolchain with
clippy,rustfmt,rust-analyzer - Erlang/OTP 26+ and
rebar3 - Node.js LTS for build tooling
- GitHub CLI (
gh) with authentication - GitHub Copilot CLI for AI assistance
- VS Code extensions for Rust, TOML, Erlang, GitHub, Linear
Git worktrees enable multiple Copilot agents working in parallel on different branches, each in its own isolated devcontainer.
- Parallel work — Multiple agents on different issues simultaneously
- Isolation — Each worktree has its own build artifacts, IDE state, and port
- No stashing — Switch context without stashing uncommitted changes
- Container per branch — Each worktree runs in its own devcontainer
~/source/
├── beamtalk/ # Main repository (origin)
│ ├── .git/ # Main Git directory
│ ├── .devcontainer/
│ ├── crates/
│ └── ...
│
├── BT-99-feature/ # Worktree #1 (sibling directory)
│ ├── .git # File pointing to ../beamtalk/.git/worktrees/BT-99-feature
│ ├── crates/
│ └── ...
│
└── BT-123-another/ # Worktree #2 (sibling directory)
├── .git # File pointing to ../beamtalk/.git/worktrees/BT-123-another
├── crates/
└── ...
Key points:
- Worktrees are created as sibling directories to the main repo
- Worktree scripts do not write per-worktree
.envoverrides
Required for worktree devcontainers:
| Variable | Purpose | How to Set |
|---|---|---|
GH_TOKEN |
GitHub authentication | gh auth login, then $env:GH_TOKEN = (gh auth token) |
Optional for Linear integration:
| Variable | Purpose | Where to Set |
|---|---|---|
LINEAR_API_TOKEN |
Linear issue tracking | Host environment |
Setting environment variables permanently (Windows):
# GitHub: authenticate first, then set GH_TOKEN
gh auth login
$env:GH_TOKEN = (gh auth token) # Set for current session
# Or add to your PowerShell profile for persistence
# Linear: store securely, retrieve at runtimeNote: Linux/Mac instructions will be added once the scripts are tested.
# From main repo directory
.\scripts\worktree-new.ps1 BT-99-feature
# Create new branch from main
.\scripts\worktree-new.ps1 -Branch BT-99 -BaseBranch mainWhat happens:
- Creates worktree as sibling directory to main repo
- Checks out the branch (creates from base if new)
- Starts devcontainer with all services
- Opens bash shell inside container
Port assignment:
- REPL ports are OS-assigned (ephemeral) by default — no port conflicts
- Override with
--portflag orBEAMTALK_REPL_PORTenv var
.\scripts\worktree-rm.ps1 BT-99-feature
# Force remove (discard uncommitted changes)
.\scripts\worktree-rm.ps1 -Branch BT-99 -ForceWhat happens:
- Stops and removes the devcontainer
- Removes the Docker volume (target cache)
- Fixes
.gitfile if modified by container - Removes the worktree directory
- Optionally deletes the branch (prompts for confirmation)
If you deleted worktree directories manually, clean up leftover containers:
.\scripts\worktree-cleanup.ps1 # Interactive
.\scripts\worktree-cleanup.ps1 -DryRun # Preview only
.\scripts\worktree-cleanup.ps1 -NoConfirm # Auto-confirmSee scripts/README.md for detailed documentation.
The devcontainer uses GH_TOKEN for GitHub CLI authentication:
# Verify authentication (inside container)
gh auth status
# Login manually if needed
gh auth loginPriority order:
GH_TOKENenvironment variable (from host)- VS Code credential helper (auto-configured)
- Manual
gh auth login
Set your identity for commits:
Option 1: Environment variables (recommended for worktrees)
export GIT_USER_NAME="Your Name"
export GIT_USER_EMAIL="you@example.com"Option 2: Global git config (inside container)
git config --global user.name "Your Name"
git config --global user.email "you@example.com"The postStartCommand in devcontainer.json automatically configures git from environment variables.
Configure SSH signing for verified commits:
1. Generate SSH signing key (on host):
ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519_signing2. Add public key to GitHub:
cat ~/.ssh/id_ed25519_signing.pub
# Copy output to GitHub Settings → SSH and GPG keys → New SSH key (select "Signing Key")3. Configure git (inside container):
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519_signing
git config --global commit.gpgsign true4. Add private key to SSH agent:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519_signingNote: For worktrees, the
worktree-new.ps1script copies the signing key into the container and configures git ifGIT_SIGNING_KEYenvironment variable is set. It does not automatically start or configuressh-agent.
| Tool | Version | Purpose |
|---|---|---|
rustc |
Latest stable | Rust compiler |
cargo |
Latest | Rust package manager |
clippy |
Latest | Rust linter |
rustfmt |
Latest | Rust code formatter |
just |
Latest | Task runner (alternative to Make) |
erlc |
OTP 26+ | Erlang compiler |
erl |
OTP 26+ | Erlang runtime |
rebar3 |
Latest | Erlang build tool |
gh |
Latest | GitHub CLI |
copilot |
Latest | GitHub Copilot CLI (AI assistant) |
node |
LTS | Node.js runtime |
npm |
Latest | Node package manager |
Pre-installed via cargo-binstall:
cargo-watch— Auto-rebuild on file changescargo-nextest— Faster test runnercargo-insta— Snapshot testingcargo-llvm-cov— Code coverage reports
Automatically installed in devcontainer:
rust-lang.rust-analyzer— Rust language servertamasfe.even-better-toml— TOML file supportvadimcn.vscode-lldb— Debugger for Rustusernamehw.errorlens— Inline error displayerlang-ls.erlang-ls— Erlang language servergithub.copilot— AI pair programminggithub.vscode-github-actions— GitHub Actions integrationgithub.vscode-pull-request-github— PR managementlinear.linear— Linear issue tracking
The target/ directory is mounted as a Docker volume for performance. Use just clean instead:
# ❌ Fails in devcontainer
cargo clean
# ✅ Works in devcontainer
just clean
# Or manually (safe pattern that avoids .. expansion)
find target -mindepth 1 -maxdepth 1 -exec rm -rf {} +# Rebuild container from scratch
# In VS Code: Ctrl+Shift+P → Dev Containers: Rebuild Container# Inside container, check gh authentication
gh auth status
# If failed, login manually
gh auth login# If `.git` file points to container paths, fix it:
.\scripts\worktree-rm.ps1 BT-99 -Force # Will auto-fix before removalIf you need a fixed REPL port, set an env var before launching REPL:
BEAMTALK_REPL_PORT=9999Active development — the compiler core is working with an interactive REPL.
- ✅ REPL — Interactive evaluation with variable persistence
- ✅ Lexer & Parser — Full expression parsing with error recovery
- ✅ Core Erlang codegen — Compiles to BEAM bytecode via
erlc - ✅ Actors — Spawn actors with state, send async messages, futures with
await - ✅ Field assignments — Actor state mutations via
:= - ✅ Method dispatch — Full message routing (unary, binary, keyword)
- ✅ Pattern matching —
match:expressions with literal and variable patterns - ✅ Hot code reloading — Redefine classes/methods on running actors via
>> - ✅ Standard library — Boolean, Block, Integer, Float, String, Character, Collections
- ✅ Collections — List, Dictionary, Set, Tuple, Association
- ✅ Class system — Inheritance,
super,sealed, class-side methods, abstract classes - ✅ Cascades — Multiple messages to same receiver
- ✅ Map literals —
#{key => value}syntax with Dictionary codegen - ✅ LSP — Language server with completions, hover, go-to-definition, diagnostics
- ✅ Testing — SUnit-style
TestCaseframework (beamtalk test)
- 📋 Supervision trees — OTP supervision as language-level constructs
- 📋 Live browser — Smalltalk-style class browser (Phoenix
LiveView)
📚 Documentation Index — Start here for a guided tour 🌐 API Reference — Standard library API docs (auto-generated) 📖 Documentation Site — Full docs including language features, principles, and architecture
- Design Principles — 13 core principles guiding all decisions
- Language Features — Syntax, semantics, and examples
- Syntax Rationale — Why we keep/change Smalltalk conventions
- Object Model — How Smalltalk objects map to BEAM
- Known Limitations — What's not yet supported in v0.1
- Architecture — Compiler pipeline, runtime, hot reload
- Testing Strategy — How we verify compiler correctness
- Agent-Native Development — AI agents as developers and live actor systems
Examples (examples/)
Simple programs demonstrating language features:
cargo run -- repl
> :load examples/hello.btStandard Library (stdlib/src/)
Foundational classes implementing "everything is a message":
| Class | Description |
|---|---|
Actor |
Base class for all actors |
Block |
First-class closures |
True / False |
Boolean control flow |
Integer / Float |
Numeric types |
String / Character |
UTF-8 text and characters |
List / Tuple |
Ordered collections |
Set / Dictionary |
Unordered collections |
Nil |
Null object pattern |
TestCase |
SUnit-style test framework |
See stdlib/src/README.md for full documentation.
beamtalk/
├── crates/
│ ├── beamtalk-core/ # Lexer, parser, AST, codegen
│ ├── beamtalk-cli/ # Command-line interface & REPL
│ └── beamtalk-lsp/ # Language server (LSP)
├── stdlib/src/ # Standard library (.bt files)
├── runtime/ # Erlang runtime (actors, REPL backend)
├── stdlib/test/ # BUnit test cases (TestCase classes)
├── tests/ # Stdlib & E2E tests
├── docs/ # Design documents
├── examples/ # Example programs
└── editors/vscode/ # VS Code extension
The compiler is written in Rust and generates Core Erlang, which compiles to BEAM bytecode via erlc.
Beamtalk combines ideas from:
- Smalltalk/Newspeak — Live programming, message-based syntax, reflection (inspiration, not strict compatibility)
- Erlang/BEAM — Actors, fault tolerance, hot code reload, distribution
- Elixir — Protocols, comprehensions, with blocks
- Gleam — Result types, exhaustive pattern matching
- Dylan — Sealing, conditions/restarts, method combinations
- TypeScript — Compiler-as-language-service architecture
We welcome contributions! See CONTRIBUTING.md for how to get started — covering dev setup, running tests, PR guidelines, and where to help.
For AI agent contributors, see AGENTS.md for detailed development guidelines.
We use Linear for issue tracking (project prefix: BT).
Licensed under the Apache License, Version 2.0. See LICENSE for details.