You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: spacecraft-gleam-guidelines/SKILL.md
+20-1Lines changed: 20 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,9 @@
1
1
---
2
2
name: spacecraft-gleam-guidelines
3
3
description: Use for writing type-safe fault-tolerant highly-concurrent Gleam code on the BEAM following Spacecraft Software standards. Triggers on any request involving Gleam, .gleam files, gleam.toml, the gleam CLI (build, check, test, format, add, publish, export), gleam_stdlib, typed OTP — gleam_otp actors, static_supervisor, factory_supervisor, supervision child specs — gleam_erlang processes, Subjects, selectors, named processes, Result/Option error handling, use expressions, exhaustive case, opaque types, @external FFI, the JavaScript target, or gleeunit/qcheck/birdie testing. Trigger even when implicit, e.g. "write a typed actor", "port this GenServer to Gleam", "supervise this Gleam process", or "make this Gleam code faster". Do NOT trigger for Erlang (spacecraft-erlang-guidelines) or Elixir (spacecraft-elixir-guidelines) — Gleam's typed actor and supervision APIs differ sharply from raw OTP, and generic BEAM advice gets them wrong. By Mohamed Hammad and Spacecraft Software.
@@ -13,12 +16,28 @@ description: Use for writing type-safe fault-tolerant highly-concurrent Gleam co
13
16
14
17
## Core Philosophy
15
18
-**Stability first (Standard §3 Priority 1).** Gleam layers a sound static type system on the BEAM's fault tolerance — two complementary safety nets. Model *expected* failures as `Result` values the compiler forces callers to handle; let *unexpected* faults crash the process and let its supervisor restart it. Never blur the two lanes.
16
-
-**Then Performance (Priority 2).** Concurrency is the default lever — cheap isolated BEAM processes, one scheduler per core, share-nothing message passing through typed `Subject`s. No locks, because nothing is shared.
19
+
-**Then Performance (Priority 2).** Concurrency is the default lever — cheap isolated BEAM processes, one scheduler per core, share-nothing message passing through typed `Subject`s. Avoid process bottlenecks; keep stateless operations synchronous.
17
20
-**Types are the design tool.** No nulls, no exceptions, exhaustive `case`. Make invalid states unrepresentable with custom types and `opaque` constructors instead of validating at every call site.
18
21
-**Immutable, functional core.** All data is immutable; transform with pure functions, pattern matching, and pipelines. Keep side effects at the process boundary.
19
22
-**Target discipline.** The Erlang target is the Spacecraft default for services — OTP, supervision, and `gleam_otp` exist only there. The JavaScript target has **no supervision**: a panic kills the whole runtime, so "let it crash" is Erlang-target advice only.
20
23
-**Current APIs only.**`gleam_otp` and `gleam_erlang` had breaking 1.0 redesigns (2025-06-12). Verify against hexdocs for the pinned version — most older tutorials (and model memory) show APIs that no longer exist.
21
24
25
+
## Memory Safety & Type Guarantees
26
+
-**Compile-Time Soundness:** Gleam's compiler enforces type-safe boundaries. It contains no `null` or `nil` values (use `Option` or `Result`), requires exhaustive matching on `case` patterns (no unhandled paths), and prevents mutable state bugs.
27
+
-**Process Heap Isolation:** On the BEAM, every process has its own private heap. Garbage collection runs per-process and does not block the entire VM ("stop-the-world"). A crash or memory leak in one process is isolated and does not affect the rest of the application.
28
+
-**FFI Protection:** FFI allows unsafe operations to escape compiler checks. Always wrap foreign calls in thin, safe wrappers, decode untyped data immediately using `gleam/dynamic/decode`, and convert Erlang/JS exceptions/throws into Gleam `Result`s.
29
+
30
+
## Concurrency vs. Performance Tradeoffs
31
+
-**When Concurrency Helps (Do Spawn):**
32
+
- Stateful services that require serialized updates (e.g. database connections, user sessions).
- Isolating failure domains so a crash in one connection does not take down others.
35
+
-**When Concurrency Hurts (Do NOT Spawn):**
36
+
- Stateless calculations: Wrapping stateless logic in an actor serializes requests, introducing a CPU bottleneck and overhead from message passing.
37
+
- Trivial tasks: The overhead of spawning a process and copying data exceeds execution time for small computations.
38
+
- Large data transfer: BEAM copies message payloads between process heaps (except ref-counted binaries >64 bytes). For large terms, keep data process-local or use read-optimized ETS only if benchmarked.
39
+
40
+
22
41
## Mandatory Abstraction Choice
23
42
Always match the abstraction to the workload — and **prefer a plain module of pure functions when no state or isolation is needed** (do not wrap pure logic in a process):
and gleam_stdlib 1.0.3. `gleam_otp`/`gleam_erlang` broke compatibility at 1.0
13
14
(2025-06-12) — reject any pattern from pre-1.0 tutorials.
14
15
16
+
## Concurrency vs. Performance: When it Helps vs. When it Hurts
17
+
18
+
BEAM processes are extremely cheap, but they are not free. Concurrency is an architectural decision that must be guided by the workload shape:
19
+
20
+
1.**When Concurrency Helps (Spawn Processes):**
21
+
-**State Isolation:** When you need a serialized service managing state that changes over time (e.g., an actor maintaining a pool connection or session state).
-**Failure Isolation:** Running independent tasks (like client connection handlers) in isolated processes so that a crash in one task does not crash other sessions.
24
+
2.**When Concurrency Hurts (Keep it Serial / Synchronous):**
25
+
-**Stateless Calculations:** Wrapping pure algorithms (e.g., parsing, string transformations, math) in processes serializes requests. This forces caller processes to block on a single actor's mailbox, turning the actor into a serialized bottleneck.
26
+
-**Trivial/Small Tasks:** The overhead of spawning a process and scheduling it is larger than executing simple synchronous code.
27
+
-**Data Copying Overhead:** BEAM processes share nothing; sending a message copies the payload from the sender's heap to the receiver's heap. While ref-counted binaries >64 bytes are shared via a global heap, large nested terms (large dicts, custom records) suffer significant copying overhead. Keep large data structures in the process that processes them, or use a read-optimized ETS table (via thin FFI modules) only if benchmarking proves a performance gain.
28
+
29
+
## Memory Safety & Runtime Isolation
30
+
31
+
Gleam delivers memory safety at two levels: the compile-time type system and the BEAM runtime's process model.
32
+
33
+
1.**Compile-Time Safety:**
34
+
-**No Nulls/Exceptions:** Gleam eliminates null pointer exceptions by using `Option(Type)` and `Result(Type, Error)` types.
35
+
-**Exhaustive Matching:** The compiler guarantees that all pattern matches (`case` expressions) are total. You cannot compile a program with unhandled variants or missing cases.
36
+
-**Immutability:** All data structures are immutable. This eliminates class-level bugs like data races on shared memory, pointer aliasing, or accidental mutation.
37
+
2.**Runtime Isolation (BEAM Heap Model):**
38
+
-**Per-Process Heaps:** Unlike traditional VMs (JVM, V8) that use a single shared heap with a global garbage collector, the BEAM allocates a small private heap for each process.
39
+
-**Non-Blocking GC:** Garbage collection runs per-process. A process only collects its own heap when idle or when its heap is full. This prevents "stop-the-world" pauses from impacting other processes.
40
+
-**Crash Containment:** A process crash terminates only the offending process and its linked children. Memory is reclaimed instantly when the process dies, avoiding VM-wide leaks.
41
+
3.**Safe FFI Boundaries:**
42
+
- Since FFI targets (Erlang/JavaScript) bypass Gleam's compiler safety checks, foreign functions must be isolated behind a thin wrapper module.
43
+
- Always catch exceptions thrown in foreign code (using the `exception` package on Erlang or standard try-catch on JavaScript) and map them to Gleam `Result` types.
44
+
- External raw terms must be validated at the boundary using decoders (`gleam/dynamic/decode`) before passing them to the rest of the application.
45
+
46
+
15
47
## 1. Program shape: `main` + root supervisor (the backbone)
16
48
17
49
Create process names up front, start one root supervisor, park the main
0 commit comments