Skip to content

BeamTalk: Where actors don't just run—they evolve.

License

Notifications You must be signed in to change notification settings

jamesc/beamtalk

Beamtalk

CI API Docs Rust coverage Erlang coverage

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}

Why Beamtalk?

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

Key Features

Actors as Objects

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

Async Message Passing

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

Pattern Matching

Match expressions with pattern arms:

status match: [
  #ok -> "success"
  #error -> "failure"
  _ -> "unknown"
]

// Variable binding in patterns
42 match: [n -> n + 1]  // => 43

Collections

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

Live Code Reloading

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

Designed for AI Agents

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.


Getting Started

Prerequisites

  • Rust (latest stable)
  • Erlang/OTP 26+ with erlc on PATH
  • Node.js LTS (optional) — for building the VS Code extension

Build & Run

Using Just (Recommended)

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

Using Cargo Directly

# 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 devcontainer

Installation

You 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/.local

The 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.

VS Code Extension (Local Dev)

For local extension development (debug LSP, no .vsix packaging):

just build-vscode

Then 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.

REPL Usage

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 Files

> :load examples/counter.bt
Loaded Counter

Development Setup

Prerequisites

Required Dependencies

  • Docker Desktop — For devcontainer support
  • VS Code — With the Dev Containers extension
  • Rust (latest stable) — For building the compiler
  • Erlang/OTP 26+ — With erlc on PATH

Optional Dependencies

  • Node.js LTS + npm — For building the VS Code extension (editors/vscode/)

Required Environment Variables

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

Optional: Devcontainer CLI

For worktree-based parallel development, install the devcontainer CLI:

npm install -g @devcontainers/cli

Note: The worktree-new scripts will auto-install this if missing.

Quick Start: Basic Development

VS Code (Recommended):

  1. Clone the repository
  2. Open in VS Code: File → Open Folder
  3. When prompted, click Reopen in Container
  4. Wait for container build (~5 minutes first time)
  5. Open a terminal and run: just build
  6. 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

Advanced: Parallel Development with Worktrees

Git worktrees enable multiple Copilot agents working in parallel on different branches, each in its own isolated devcontainer.

Why Worktrees?

  • 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

Directory Structure

~/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 .env overrides

Environment Variables

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 runtime

Note: Linux/Mac instructions will be added once the scripts are tested.

Creating a Worktree

# From main repo directory
.\scripts\worktree-new.ps1 BT-99-feature

# Create new branch from main
.\scripts\worktree-new.ps1 -Branch BT-99 -BaseBranch main

What happens:

  1. Creates worktree as sibling directory to main repo
  2. Checks out the branch (creates from base if new)
  3. Starts devcontainer with all services
  4. Opens bash shell inside container

Port assignment:

  • REPL ports are OS-assigned (ephemeral) by default — no port conflicts
  • Override with --port flag or BEAMTALK_REPL_PORT env var

Removing a Worktree

.\scripts\worktree-rm.ps1 BT-99-feature

# Force remove (discard uncommitted changes)
.\scripts\worktree-rm.ps1 -Branch BT-99 -Force

What happens:

  1. Stops and removes the devcontainer
  2. Removes the Docker volume (target cache)
  3. Fixes .git file if modified by container
  4. Removes the worktree directory
  5. Optionally deletes the branch (prompts for confirmation)

Cleaning Up Orphaned Containers

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-confirm

See scripts/README.md for detailed documentation.

GitHub Integration

Authentication

The devcontainer uses GH_TOKEN for GitHub CLI authentication:

# Verify authentication (inside container)
gh auth status

# Login manually if needed
gh auth login

Priority order:

  1. GH_TOKEN environment variable (from host)
  2. VS Code credential helper (auto-configured)
  3. Manual gh auth login

Git Configuration

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.

Commit Signing

SSH Signing (Recommended)

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_signing

2. 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 true

4. Add private key to SSH agent:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519_signing

Note: For worktrees, the worktree-new.ps1 script copies the signing key into the container and configures git if GIT_SIGNING_KEY environment variable is set. It does not automatically start or configure ssh-agent.

Installed Tools & Extensions

Command Line Tools

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

Cargo Extensions

Pre-installed via cargo-binstall:

  • cargo-watch — Auto-rebuild on file changes
  • cargo-nextest — Faster test runner
  • cargo-insta — Snapshot testing
  • cargo-llvm-cov — Code coverage reports

VS Code Extensions

Automatically installed in devcontainer:

  • rust-lang.rust-analyzer — Rust language server
  • tamasfe.even-better-toml — TOML file support
  • vadimcn.vscode-lldb — Debugger for Rust
  • usernamehw.errorlens — Inline error display
  • erlang-ls.erlang-ls — Erlang language server
  • github.copilot — AI pair programming
  • github.vscode-github-actions — GitHub Actions integration
  • github.vscode-pull-request-github — PR management
  • linear.linear — Linear issue tracking

Troubleshooting

cargo clean fails with "Device or resource busy"

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 {} +

Devcontainer won't start

# Rebuild container from scratch
# In VS Code: Ctrl+Shift+P → Dev Containers: Rebuild Container

Git authentication fails

# Inside container, check gh authentication
gh auth status

# If failed, login manually
gh auth login

Worktree .git file corruption

# If `.git` file points to container paths, fix it:
.\scripts\worktree-rm.ps1 BT-99 -Force  # Will auto-fix before removal

Port conflicts

If you need a fixed REPL port, set an env var before launching REPL:

BEAMTALK_REPL_PORT=9999

Project Status

Active development — the compiler core is working with an interactive REPL.

What Works Now

  • 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 matchingmatch: 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 TestCase framework (beamtalk test)

Planned

  • 📋 Supervision trees — OTP supervision as language-level constructs
  • 📋 Live browser — Smalltalk-style class browser (Phoenix LiveView)

Documentation

📚 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

Core Documents

Architecture

Tooling & Vision


Examples & Standard Library

Examples (examples/)

Simple programs demonstrating language features:

cargo run -- repl
> :load examples/hello.bt

Standard 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.


Repository Structure

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.


Inspiration

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

Contributing

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).


License

Licensed under the Apache License, Version 2.0. See LICENSE for details.

About

BeamTalk: Where actors don't just run—they evolve.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors