AGENTS.md - Beamtalk Development Guidelines (Expanded)

July 31, 2026 Β· View on GitHub

This document provides guidance for AI coding agents working on the beamtalk compiler and ecosystem.

Repository Information

Always use these values for GitHub API calls:

PropertyValue
Ownerjamesc
Repositorybeamtalk
Full namejamesc/beamtalk
URLhttps://github.com/jamesc/beamtalk

Example gh CLI usage:

gh api repos/jamesc/beamtalk/pulls
gh pr create --repo jamesc/beamtalk

Project Overview

Beamtalk is a Smalltalk/Newspeak-inspired programming language that compiles to the BEAM virtual machine. The compiler is written in Rust and generates Core Erlang, which is then compiled to BEAM bytecode via erlc.

Key principle: Beamtalk is an interactive-first language. The live environment and hot code reloading are core to the design, not afterthoughts.

Important: While heavily inspired by Smalltalk, Beamtalk makes pragmatic departures from Smalltalk syntax and semantics to work well with BEAM and modern development practices. We're "Smalltalk-like," not Smalltalk-compatible. See beamtalk-syntax-rationale.md for specific differences, beamtalk-principles.md for design philosophy, and beamtalk-language-features.md for full language specification.


Syntax Verification - Preventing Hallucinations 🚨

CRITICAL: AI agents must verify all Beamtalk syntax before using it in code, tests, or examples. Do not invent or assume syntax patterns exist.

Common Hallucination Patterns

PatternExampleReality
Inline class syntaxCounter := Actor [...]Only Actor subclass: Counter works
Ruby/Python-stylePoint.new(x: 3)Beamtalk uses Point new: #{x => 3}
Compound assignmentself.value += 1Use self.value := self.value + 1 (ADR 0001)
Missing constraintsCounter new on ActorError β€” actors use spawn, not new
Sealed subclassingInteger subclass: MyIntPrimitives are sealed, cannot subclass
Smalltalk verbatimObject subclass: #CounterBeamtalk uses identifiers, not symbols
Load syntax confusion@load in REPLUse :load in REPL, @load only in test files
Pragma confusion@primitive blockValueStructural intrinsics use @intrinsic; @primitive is for quoted selectors only

Test File Rules

In test files (stdlib/bootstrap-test/*.btscript and tests/repl-protocol/cases/*.btscript):

  • Every expression MUST have a // => assertion (even // => _ for wildcard)
  • No assertion = no execution β€” expressions are silently skipped
  • Missing assertions fail CI (BT-249)
  • Pattern matching in assertions: _ within a pattern is a wildcard segment (e.g., Actor(Counter, _))
  • @load is a test directive; :load is a REPL command β€” don't mix them up

⚠️ Dangerous pattern β€” this looks fine but DOESN'T RUN:

x := 0
3 timesRepeat: [x := x + 1]   // No assertion β†’ SKIPPED, never executes!
x
// => 3                        // Fails! x was never set

Safe pattern β€” always add assertions:

x := 0
// => _
3 timesRepeat: [x := x + 1]
// => nil
x
// => 3

Verification Before Using Syntax

Before using ANY Beamtalk syntax, verify it exists in at least one of:

  1. Language spec: docs/beamtalk-language-features.md
  2. Examples: examples/*.bt
  3. Tests: stdlib/bootstrap-test/*.btscript, tests/repl-protocol/cases/*.btscript
  4. Parser tests: crates/beamtalk-core/src/source_analysis/parser/

If it doesn't appear in any of these β†’ it's likely hallucinated. Search the codebase or ask.

Pattern Lookup Protocol β€” Required Before Any New Implementation

Before writing new .bt code or Erlang FFI, run these searches and review 3+ hits:

# Find existing examples of a class/method pattern in Beamtalk source
grep -r "subclass:" stdlib/test/ stdlib/src/ examples/ --include="*.bt" -l
grep -r "state:.*::" stdlib/src/ --include="*.bt" | head -10   # type annotations
grep -r "ifTrue:\|ifFalse:\|whileTrue:" stdlib/test/ --include="*.bt" | head -10

# Find Erlang FFI primitives for a domain area
grep -r "beamtalk_\|@primitive\|(Erlang " stdlib/src/ --include="*.bt" | head -10
grep -r "beamtalk_http\|beamtalk_system\|beamtalk_file" runtime/apps/ --include="*.erl" -l

# Confirm type annotation syntax (always ::, never :)
grep -r " :: " stdlib/src/ --include="*.bt" | head -5

# Confirm ^ usage (early return only, never on last expression)
grep -rn "\^" stdlib/src/ stdlib/test/ --include="*.bt" | grep -v "// =>" | head -10

Do this lookup before proposing an approach. Show the examples found, identify the pattern they follow, then implement consistently with them. If no examples exist, check the language spec before writing any code.


Domain-Driven Design (DDD)

The beamtalk architecture follows Domain-Driven Design. Use domain terms consistently (e.g., CompletionProvider not completions). The codebase has four bounded contexts: Language Service (IDE features), Compilation (lexer→codegen), Runtime (actors, OTP), and REPL (interactive dev).

All new modules should include a DDD Context header near the top of the file. Use the correct comment style for the language:

  • Rust: //! **DDD Context:** ContextName
  • Erlang: %%% **DDD Context:** ContextName (triple % β€” module-level doc style)

See docs/beamtalk-ddd-model.md for the full domain model.

Erlang Documentation β€” EEP-59 Attributes

All Erlang modules use EEP-59 structured doc attributes (not EDoc comments):

-moduledoc "Module summary here.".

-doc "Function purpose here.".
-spec my_function(Arg) -> Result.
my_function(Arg) -> ....
  • -moduledoc "..." replaces %%% @doc at module level
  • -doc "..." before each exported function replaces %%% @doc before functions
  • Do not use -doc false for internal helpers β€” keep docs visible for developers and tooling
  • DDD Context stays as a regular %%% comment (not part of EEP-59)
  • These attributes compile directly into EEP-48 doc chunks in .beam files (OTP 27+), making docs available via :h Erlang <module>

See erlang-guidelines.md for full details.


Development Architecture Principles

The beamtalk codebase follows strict architectural principles for code organization, error handling, testing, security, and dependencies. Full details in docs/development/architecture-principles.md.

Core principles:

  1. Layered Architecture - Dependencies flow down only (core never depends on CLI)
  2. Error Recovery - Return partial results + diagnostics (don't stop at first error)
  3. Testing Pyramid - Unit 60-70%, Integration 20-30%, E2E 10%
  4. Security-First - Input validation at boundaries, no unsafe without justification
  5. Minimal Dependencies - Prefer std library, document why each dependency exists

Critical rules:

❌ NEVER:

  • beamtalk-core importing beamtalk-cli or beamtalk-lsp
  • Panic on user input (malformed source, invalid args, missing files)
  • Add dependencies without security review and commit message justification
  • Use unwrap() on user input
  • Use bare tuple errors in user-facing code - Public API errors MUST use #beamtalk_error{} records (internal runtime helpers may use {ok, Value} | {error, Reason} if translated at boundaries)

βœ… ALWAYS:

  • Return (Result, Vec<Diagnostic>) or equivalent for user-facing operations
  • Validate file paths and buffer boundaries
  • Document unsafe code with // SAFETY: comment explaining invariants
  • Run cargo audit before releases
  • Use structured errors at public API boundaries - beamtalk_error:new/3 + with_hint/with_details for all user-facing error paths

See full guide: docs/development/architecture-principles.md


Error Handling - CRITICAL RULES

All user-facing/public API errors MUST use the structured #beamtalk_error{} system. Internal runtime helpers (in runtime/**/*.erl) may use bare {ok, Value} | {error, Reason} tuples, but these MUST be translated to #beamtalk_error{} at public API boundaries before reaching user code.

Structured Error System

All errors use #beamtalk_error{} records defined in runtime/include/beamtalk.hrl:

-record(beamtalk_error, {
    kind    :: atom(),              % does_not_understand | immutable_value | type_error | instantiation_error | ...
    class   :: atom(),              % 'Integer', 'Counter', 'Actor'  
    selector:: atom() | undefined,  % method that failed
    message :: binary(),            % human-readable explanation
    hint    :: binary() | undefined,% actionable suggestion
    details :: map()                % additional context
}).

In Runtime Erlang Code

Use beamtalk_error module helpers:

Error0 = beamtalk_error:new(does_not_understand, 'Integer', 'foo'),
Error1 = beamtalk_error:with_hint(Error0, <<"Check spelling">>),
error(Error1)

In Generated Core Erlang Code

Codegen MUST use beamtalk_error calls:

%% ❌ WRONG - bare tuple (never do this!)
call 'erlang':'error'({'some_error', 'message'})

%% βœ… RIGHT - structured error
let Error0 = call 'beamtalk_error':'new'('instantiation_error', 'Actor', 'new') in
let Error1 = call 'beamtalk_error':'with_hint'(Error0, <<"Use spawn instead">>) in
call 'erlang':'error'(Error1)

Error Kinds

KindWhenExample
does_not_understandUnknown method42 foo
immutable_valueMutation on primitive42 fieldAt:put:
type_errorWrong argument type"hello" + 42
arity_mismatchWrong argument countMissing/extra args
instantiation_errorWrong instantiationActor new (use spawn)
future_not_awaitedMessage to Future(future) size
timeoutOperation timeoutAwait exceeds deadline

Benefits of Structured Errors

  1. Consistent tooling - Pattern match on kind/class/selector
  2. Better UX - Actionable hints guide users
  3. Rich context - Details map for debugging
  4. Future-proof - Easy to add metadata without breaking changes

See full error taxonomy: docs/internal/design-self-as-object.md


Logging - CRITICAL RULES

Erlang runtime: Use OTP logger macros (?LOG_ERROR, ?LOG_WARNING, ?LOG_INFO, ?LOG_DEBUG), never io:format or logger:error() function calls. Macros auto-include caller MFA metadata. Every .erl file that logs must include -include_lib("kernel/include/logger.hrl"). Always use structured metadata maps, not format strings.

Rust CLI: Use the tracing crate (trace!, debug!, info!, warn!, error!). Use println! for user-facing output, tracing for diagnostics. Enable with RUST_LOG=beamtalk_cli=debug.

See docs/development/erlang-guidelines.md for full logging guidelines.


User Experience & Developer Experience (DevEx) First

A feature is not done until the user can interact with it in the REPL. Building internal infrastructure is necessary but insufficient β€” always complete the loop to user-facing integration.

Before marking any feature complete, verify:

  • Feature works in the REPL β€” can demonstrate in 1-2 lines
  • Error messages are user-friendly and actionable (use self not Self)
  • Examples in examples/ or docs show real usage
  • Failure modes tested and produce helpful output

🚩 Red flags: "Infrastructure is done" but no user sees it. "Tests pass" but no examples. "Error handling works" but messages are raw tuples.


Scope Control

Fix inline: formatting/typos in files you're modifying, test failures from your changes, clippy warnings in your code, docs for features you just added.

Create follow-up issue: bugs in unrelated code, performance optimization opportunities, missing tests in other modules, refactoring not required for current issue.

🚩 Scope creep red flags: PR grows beyond 10 files, "just one more thing", acceptance criteria met but still coding.


Debugging

See docs/development/debugging.md for step-by-step debugging guides covering compiler crashes, runtime errors, test failures, E2E failures, codegen debugging, and performance profiling.

If stuck for >30 minutes: Stop and ask. Summarize what you tried, share the error, ask for direction.


Work Tracking

We use Linear for task management. Project prefix: BT

Referencing Issues

Include the Linear issue ID in commits and PR titles:

git commit -m "Implement lexer tokens BT-123"

Issue Lifecycle

StateMeaning
BacklogIdea captured, not yet specified
ReadyFully specified with acceptance criteria; agent can pick up
In ProgressActively being worked on
In ReviewCode complete, needs human verification
DoneMerged and verified

Labels

We use several label groups to categorize issues:

Agent State

Tracks workflow status:

  • agent-ready - Task is fully specified, agent can start immediately
  • needs-spec - Requires human to clarify requirements before work begins
  • blocked - Waiting on external dependency or another issue
  • human-review - Agent completed work, needs human verification
  • done - Issue is complete and closed

Item Area

Identifies which component of the codebase the issue affects:

LabelDescriptionKey Directories
class-systemClass definition, parsing, codegen, and runtimecrates/beamtalk-core/src/ast.rs, crates/beamtalk-core/src/source_analysis/
stdlibStandard library: collections, primitives, strings (compiled from stdlib/src/*.bt via pragmas β€” see ADR 0007)stdlib/src/
replREPL backend and CLI interactionruntime/src/beamtalk_repl.erl, crates/beamtalk-cli/src/repl/
cliCommand-line interface and build toolingcrates/beamtalk-cli/
codegenCode generation to Core Erlang/BEAMcrates/beamtalk-core/src/erlang.rs
runtimeErlang runtime: actors, futures, OTP integrationruntime/src/
parserLexer, parser, ASTcrates/beamtalk-core/src/source_analysis/, crates/beamtalk-core/src/ast.rs

Issue Type

Categorizes the kind of work:

  • Epic - Large initiatives that group related issues (5+ child issues)
  • Feature - A chunk of customer visible work
  • Bug - Bugs, broken tests, broken code
  • Improvement - Incremental work on top of a feature
  • Documentation - Words that explain things
  • Infra - Tools, CI, dev environment configuration
  • Language Feature - New Beamtalk language syntax/semantics
  • Refactor - Code cleanups, tech debt
  • Research - Research projects, code spikes
  • Samples - Code, examples, things to help devs get started

Item Size

T-shirt sizing for estimates: S, M, L, XL

When creating issues: Always set:

  1. An Agent State label (agent-ready or needs-spec)
  2. An Item Area label (what part of codebase)
  3. An Issue Type label (what kind of work)
  4. An Item Size label (how big)

Epics

Epics group 5+ related issues for large initiatives. Use Epic: title prefix, Epic label, and size XL or L. Link child issues with "blocks" relationships. Include overview, goals, status summary, and child issue list in description.

Query Linear for epic status with the Linear MCP tools (e.g. list_issues filtered to the Epic label and In Progress state).

Writing Agent-Ready Issues

For an issue to be agent-ready, include:

  1. Context - Why this work matters, background info
  2. Acceptance Criteria - Specific, testable requirements (checkboxes)
  3. Files to Modify - Explicit paths to relevant files
  4. Dependencies - Other issues that must complete first
  5. References - Links to specs, examples, or related code
  6. Blocking Relationships - Use Linear's "blocks" relationship for dependencies

Example:

Title: Implement basic lexer token types

Context:
The lexer is the first phase of compilation. It needs to tokenize
Smalltalk-style message syntax including identifiers, numbers, and keywords.

Acceptance Criteria:
- [ ] Tokenize identifiers (letters, digits, underscores)
- [ ] Tokenize integers and floats
- [ ] Tokenize single and double quoted strings
- [ ] Tokenize message keywords ending in `:`
- [ ] Tokenize block delimiters `[` `]`
- [ ] All tokens include source span

Files to Modify:
- crates/beamtalk-core/src/source_analysis/token.rs
- crates/beamtalk-core/src/source_analysis/lexer.rs

Dependencies: None

References:
- See Gleam lexer: github.com/gleam-lang/gleam/blob/main/compiler-core/src/parse/lexer.rs

Creating Issue Blocking Relationships

When creating issues with dependencies, always set up Linear's "blocks" relationships:

// After creating issues BT-X and BT-Y, where BT-X blocks BT-Y:
mutation {
  issueRelationCreate(input: {
    issueId: "<BT-X issue ID>"
    relatedIssueId: "<BT-Y issue ID>"
    type: blocks
  }) {
    success
  }
}

Rules:

  • If issue A must be completed before issue B can start, then A "blocks" B
  • Always create blocking relationships when dependencies are mentioned
  • Linear automatically shows blocked/blocking status in the UI
  • Use GraphQL to create relationships after issue creation
  • Set agent-state label when creating issues:
    • agent-ready if fully specified with all acceptance criteria
    • needs-spec if human clarification needed first

Example: For stdlib implementation issues:

  • BT-21 (API definitions) blocks BT-32, BT-33, BT-34, BT-35, BT-36, BT-37
  • BT-32 (block evaluation) blocks BT-35 (iteration uses blocks) and BT-37 (collections use blocks)
  • All new issues marked with agent-ready label

Architecture Decision Records (ADRs)

Significant design decisions are documented as ADRs in docs/ADR/. Create an ADR when a decision affects language design, core architecture, interoperability, or user-facing behavior.

Format: docs/ADR/NNNN-kebab-case-title.md with Status, Context, Decision, Consequences, References sections. Number sequentially, update docs/ADR/README.md index. See existing ADRs for format examples.


Agent Skills

Agent skills are defined as personal skills in ~/.claude/skills/ (Claude Code) or ~/.copilot/skills/ (Copilot). They are not stored in the repository.

Available skills include: pick-issue, done, resolve-pr, resolve-merge, create-issue, refresh-issue, update-issues, whats-next, draft-adr, plan-adr, plan-refactor, do-refactor, review-code, review-adr.


Repository Structure

runtime/
β”œβ”€β”€ rebar.config            # Umbrella project config (ADR 0007, 0009)
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ beamtalk_runtime/   # Core runtime OTP application (ADR 0009)
β”‚   β”‚   β”œβ”€β”€ src/            # Runtime source (primitives, object system, actors, futures)
β”‚   β”‚   β”œβ”€β”€ include/        # Header files (beamtalk.hrl)
β”‚   β”‚   β”œβ”€β”€ test/           # Runtime unit tests (*.erl)
β”‚   β”‚   β”œβ”€β”€ config/         # OTP sys.config
β”‚   β”‚   └── test_fixtures/  # Fixtures for runtime tests (BT-239)
β”‚   β”‚       β”œβ”€β”€ logging_counter.bt
β”‚   β”‚       β”œβ”€β”€ compile_fixtures.escript  # Auto-compiles fixtures via rebar3 pre-hook
β”‚   β”‚       └── README.md
β”‚   β”œβ”€β”€ beamtalk_workspace/  # Workspace and interactive development OTP application (ADR 0009) β€” NEW
β”‚   β”‚   β”œβ”€β”€ src/            # REPL source (repl eval, server, workspace supervision, session management)
β”‚   β”‚   └── test/           # REPL unit tests (*.erl)
β”‚   └── beamtalk_stdlib/    # Standard library OTP application (ADR 0007)
β”‚       β”œβ”€β”€ src/            # Application metadata (beamtalk_stdlib.app.src)
β”‚       └── ebin/           # Generated .beam files (Actor, Block, Integer, etc.) created by build-stdlib

stdlib/
β”œβ”€β”€ bootstrap-test/        # Bootstrap expression tests (ADR 0014)
β”‚   β”œβ”€β”€ arithmetic.btscript  # 12 test files β€” run via `just test-stdlib`
β”‚   β”œβ”€β”€ booleans.btscript
β”‚   └── ...
└── test/                  # BUnit TestCase tests
    β”œβ”€β”€ actor_test.bt        # 151 test files β€” run via `just test-bunit`
    β”œβ”€β”€ array_test.bt
    └── ...

tests/
└── e2e/
    β”œβ”€β”€ cases/             # REPL integration tests (54 .btscript files)
    └── fixtures/          # Shared test fixtures
        └── counter.bt     # CANONICAL counter implementation (BT-239)

examples/
β”œβ”€β”€ getting-started/       # Beginner tutorial project
β”œβ”€β”€ bank/                  # Banking example with actors
β”œβ”€β”€ chat-room/             # Multi-actor chat example
β”œβ”€β”€ otp-tree/              # Supervision tree example
└── repl-tutorial.md       # REPL tutorial (BT-239)

Dependency direction (ADR 0009):

beamtalk_workspace (interactive dev)
    ↓ depends on
beamtalk_runtime (core language runtime)
    ↓ depends on
beamtalk_stdlib (compiled stdlib)

Test Organization - CRITICAL DISTINCTION

IMPORTANT: The test suite has multiple layers. Be precise about which tests you're referring to:

1. Runtime Unit Tests

Location: runtime/apps/beamtalk_runtime/test/*_tests.erl (e.g., beamtalk_actor_tests.erl)

  • Tests individual runtime modules in isolation
  • Uses hand-written test fixtures (e.g., test_counter.erl)
  • Calls gen_server protocol directly with raw pids
  • Appropriate for testing low-level runtime behavior

2. REPL Unit Tests

Location: runtime/apps/beamtalk_workspace/test/*_tests.erl (ADR 0009)

  • Tests REPL evaluation, protocol, server, workspace management
  • Includes session supervision and idle monitoring tests
  • Moved from beamtalk_runtime/test/ in BT-351

3. Codegen Simulation Tests

Location: runtime/apps/beamtalk_runtime/test/beamtalk_codegen_simulation_tests.erl

  • Tests using real compiled Beamtalk code from tests/repl-protocol/fixtures/counter.bt (unified fixture - BT-239)
  • The spawn/0 and spawn/1 tests use counter:spawn() from compiled module
  • Other tests use simulated state structures for complex scenarios
  • Test fixtures compile automatically via rebar3 pre-hook (no manual step needed)
  • Fixtures: logging_counter.bt stored in runtime/apps/beamtalk_runtime/test_fixtures/, counter.bt sourced from REPL-protocol fixtures
  • Compiled by runtime/apps/beamtalk_runtime/test_fixtures/compile_fixtures.escript (rebar3 pre-hook)
  • See docs/development/testing-strategy.md for compilation workflow details

4. Stdlib Tests (Compiled Expression Tests) β€” ADR 0014

Location: stdlib/bootstrap-test/*.btscript (12 files)

  • Pure language feature tests compiled directly to EUnit (no REPL needed)
  • Uses same // => assertion format as REPL-protocol tests
  • Runs via just test-stdlib (fast, ~14s vs ~50s for REPL-protocol)
  • Tests arithmetic, strings, blocks, closures, collections, object protocol, etc.
  • Supports @load directives for fixture-dependent tests (actors, sealed classes)
  • Use this for any new test that doesn't need REPL/workspace features

5. BUnit Tests (TestCase Classes) β€” ADR 0014 Phase 2

Location: stdlib/test/*.bt (project test directory)

  • SUnit-style test classes that subclass TestCase
  • Methods starting with test are auto-discovered and run with fresh instances
  • setUp/tearDown lifecycle methods for test fixtures
  • Assertion methods: assert:, assert:equals:, deny:, should:raise:, fail:
  • Runs via beamtalk test (compiles to EUnit, fast)
  • Can also run interactively in REPL: CounterTest runAll or CounterTest run: #testName
  • Use this for stateful tests, complex actor interactions, multi-assertion scenarios

6. REPL-Protocol Tests (REPL Integration)

Location: tests/repl-protocol/cases/*.btscript (54 files)

  • Require a running REPL daemon (started automatically by test harness)
  • Test workspace bindings, REPL commands, variable persistence, auto-await
  • Test ERROR: assertion patterns and @load-error directives
  • Runs via just test-repl-protocol (~50s)
  • Only use for tests that genuinely need the REPL

When choosing between stdlib, BUnit, and REPL-protocol tests:

Test needs...Where
Pure language features (arithmetic, strings, blocks)stdlib/bootstrap-test/
Collections (List, Dictionary, Set, Tuple)stdlib/bootstrap-test/
Actor spawn + messaging (with @load)stdlib/bootstrap-test/
Stateful tests with setUp/tearDownstdlib/test/*.bt (BUnit)
Complex actor interactions, multiple assertionsstdlib/test/*.bt (BUnit)
Workspace bindings (Transcript, Beamtalk)tests/repl-protocol/cases/
REPL commands, variable persistencetests/repl-protocol/cases/
Auto-await, ERROR: assertionstests/repl-protocol/cases/

Test Fixture Organization (BT-239)

Runtime fixtures: runtime/apps/beamtalk_runtime/test_fixtures/

  • Colocated with runtime tests for better locality
  • Compiled by apps/beamtalk_runtime/test_fixtures/compile_fixtures.escript (rebar3 pre-hook)
  • Currently: logging_counter.bt (super keyword tests)
  • Note: counter.bt consolidated to REPL-protocol fixture

REPL-protocol fixtures: tests/repl-protocol/fixtures/

  • Used by REPL-protocol test cases
  • counter.bt is the canonical Counter implementation
  • Also used by runtime tests (unified fixture)

Development Guidelines

Detailed coding standards are in docs/development/:

GuideDescription
Rust GuidelinesNaming, traits, error handling, testing, compiler patterns
Erlang GuidelinesCode generation, OTP patterns, BEAM interop
Common TasksAdding AST nodes, CLI commands, stdlib features
DebuggingStep-by-step debugging for all failure types
Language FeaturesFull Beamtalk syntax specification

CI Commands

just ci                      # Run all CI checks (build, lint, test, test-integration, test-mcp, test-parity, test-repl-protocol, check-corpus, check-surface-drift)
just build                   # Build Rust + Erlang runtime
just test                    # Rust + stdlib + BUnit + runtime tests
just test-stdlib             # Compiled language feature tests (~14s)
just test-bunit              # BUnit TestCase tests
just test-repl-protocol      # Run REPL TCP-protocol tests (~50s)
just fmt                     # Format all code
just clippy                  # Lints (warnings = errors)
just dialyzer                # Erlang type checking

License Headers

All source code files (.rs, .erl, .bt, .hrl) must include:

// Copyright 2026 James Casey
// SPDX-License-Identifier: Apache-2.0

Do not add license headers to .md files. The repo LICENSE file covers all documentation content.

Beamtalk Style

  • Implicit returns: Use ^ ONLY for early returns, never on last expression
  • No periods: Newlines separate statements, not .
  • Comments: Use // and /* */, not Smalltalk's "..."

Blocks Passed Into Class Methods

A class method runs in its class object's gen_server process, so a block passed into one runs there, not where it was written. Values and non-local returns (^) cross that boundary fine; process-local side effects do not β€” a block that writes the process dictionary or reads self() affects the class process, and a block that messages the same class back raises dispatch_error rather than deadlocking (BT-3022).

Practical rule: a Collection subclass may implement do: via a class-side helper (the inherited protocol works), but that helper must not reach back into its own class. See Passing Blocks Through Class Methods.

Code Generation β€” Document API Only

All Core Erlang codegen MUST use Document / docvec! API. Never use format!() or string concatenation. If you see existing string-based patterns, convert them.

Clippy Discipline

Never suppress clippy warnings without a comment explaining why. Split long functions, remove dead code, fix return types. Goal: under 30 suppressions codebase-wide.


File Conventions

ExtensionDescription
.btBeamtalk source files
.coreGenerated Core Erlang
.beamCompiled BEAM bytecode
.erlErlang source (helpers, tests)
.hrlErlang header files

Resources

Beamtalk Design

Rust Guidelines

BEAM/Erlang

Reference Implementations