getting-started — Agent Guide
Introductory Beamtalk examples covering core language concepts.
Workspace Structure
getting-started/
├── beamtalk.toml # Package manifest
├── src/
│ ├── hello.bt # Minimal class
│ ├── counter.bt # Stateful actor
│ ├── hanoi.bt # Towers of Hanoi (recursion)
│ ├── logging_counter.bt # Inheritance and super
│ ├── protoobject_proxy.bt # Transparent proxy pattern
│ └── stream.bt # Lazy pipelines
├── test/
│ ├── counter_test.bt # BUnit tests for Counter
│ ├── hello_test.bt # BUnit tests for Hello
│ ├── hanoi_test.bt # BUnit tests for Hanoi
│ └── logging_counter_test.bt # BUnit tests for LoggingCounter
├── AGENTS.md # This file
├── .mcp.json # MCP server config
├── .github/
│ └── copilot-instructions.md
└── .gitignore
Starting the REPL
cd examples/getting-started
beamtalk repl
All source files in src/ load automatically when you start the REPL.
To run the tests: :load "test/"
Beamtalk Syntax Basics
// Variables
x := 42
name := "hello"
// Message sends
x factorial // unary
3 + 4 // binary
list at: 1 put: "value" // keyword
// Blocks (closures)
square := [:x | x * x]
square value: 5 // => 25
// Classes
Object subclass: Counter
state: count = 0
increment => self.count := self.count + 1
count => self.count
Development Workflow
The .mcp.json MCP server provides a persistent REPL session. Use it as
your primary development environment — not CLI commands.
Session startup:
- Call
describeto discover available operations - Call
load_projectwithinclude_tests: trueto load all source + tests - On a new codebase, read the language guide at https://www.beamtalk.dev/docs/language-features
Edit → Reload → Test → Debug loop:
- Edit a
.btsource file evaluate: 'Workspace load: "path"'orevaluate: "ClassName reload"— orload_projectagain after multi-file editstestwith class name or file path — fast, no recompileevaluateto debug failures — bindings preserved from prior calls- Only use CLI
beamtalk testas a final full-suite check before committing
Live Workspace (MCP)
The .mcp.json in this project configures the beamtalk MCP server, which gives
you live access to a running REPL. Claude Code starts it automatically via
beamtalk-mcp --start — no manual beamtalk repl required.
Prefer MCP tools over guessing. If you're uncertain what a method returns or whether code is correct, evaluate it directly rather than inferring from source.
| Tool | When to use |
|---|---|
describe | First call — discover operations and protocol version |
load_project | Session startup — load all source + test files |
evaluate | Test expressions, debug, call Workspace/Beamtalk APIs |
test | Run tests by class name or file path |
complete | Autocompletion suggestions |
search_examples | Find patterns and working code (offline) |
show_codegen | Inspect generated Core Erlang |
inspect | Examine a live actor's state |
Essential Patterns
Class Hierarchy
// Immutable data — auto-generates getters, withX: setters, keyword constructor, equality
Value subclass: Point
state: x = 0
state: y = 0
// Mutable state — manual getters/setters, self.field := works
Object subclass: Config
state: raw = nil
// Concurrent process — gen_server backed, async casts with !
Actor subclass: Server
state: count = 0
// OTP supervision tree — for long-running services
Supervisor subclass: MyApp
class strategy => #oneForOne
class children => #(DatabasePool, HttpServer, Worker)
Rules:
- Pure data →
Value - Mutable but not concurrent →
Object - Concurrent process →
Actor - Long-running service with child processes →
Supervisorwithbeamtalk run
String Escaping
| Syntax | Result |
|---|---|
"hello {name}" | String interpolation |
"literal \{ brace \}" | Escaped braces |
"She said ""hello""" | Escaped double-quote |
Destructuring and match:
// Tuple destructuring (critical for Erlang FFI)
{#ok, content} := Erlang file read_file: "path"
// Array destructuring
#[a, b] := #[10, 20]
// Map destructuring
#{#x => x, #y => y} := someDict
// match: with clauses
value match: [
#ok -> "success";
#error -> "failure";
_ -> "unknown"
]
Key Stdlib Classes
| Class | Purpose |
|---|---|
System | getEnv:, osPlatform, pid |
Subprocess | Sync subprocess with stdin/stdout |
ReactiveSubprocess | Push-mode subprocess with delegate callbacks |
Supervisor | OTP supervision trees for service applications |
HTTPClient / HTTPServer | HTTP client and server |
File | Filesystem operations |
Json / Yaml | Serialization |
Critical Gotcha — Block Mutations
// WRONG on Value/Object — assignment inside block doesn't propagate
count := 0
items do: [:x | count := count + 1] // count is still 0!
// CORRECT — use inject:into:
count := items inject: 0 into: [:acc :x | acc + 1]
Not Smalltalk — Common Pitfalls
Beamtalk looks like Smalltalk but has important differences. The compiler will catch most of these, but they waste time:
| Smalltalk habit | Beamtalk equivalent | Notes |
|---|---|---|
| temp | temp var declarations | Just use := directly | No declaration syntax |
Trailing . on every statement | Newline is the separator | . is optional; use it only to disambiguate cascades |
"this is a comment" | // this is a comment | Double-quoted strings are data, not comments |
^value on last expression | Just write value | ^ is early-return only; last expr is implicitly returned |
Left-to-right binary (2+3*4=20) | Standard math precedence (2+3*4=14) | * binds tighter than + |
'hello', name concatenation | "hello {name}" interpolation | ++ also works: "hello" ++ name |
[:x | |temp| temp := x] block locals | [:x | temp := x] | No block-local declarations |
: for type annotations | :: (double-colon) | state: x :: Integer = 0, param :: Type -> ReturnType => |
| Unknown message raises error | Same — DNU raises does_not_understand error | Use respondsTo: to check before sending |
Implicit return rule: the last expression of a method body is always its
return value. Never write ^ on the last line — only use it for early exits
inside the method:
// Wrong — redundant ^
max: other =>
^(self > other ifTrue: [self] ifFalse: [other])
// Correct
max: other =>
self > other ifTrue: [self] ifFalse: [other]
// Correct use of ^ for early return
safeDiv: other =>
other = 0 ifTrue: [^0].
self / other
Language Documentation
- Full language reference: https://www.beamtalk.dev/docs/language-features — read this when starting work on a new Beamtalk codebase
- Syntax rationale: https://www.beamtalk.dev/docs/syntax-rationale
- Examples: see files in this directory