gleam skill
939c2739 +1878
.agents/skills /.DS_Store this is a binary file and will not be displayed.
.agents/skills/gleam-design /.DS_Store this is a binary file and will not be displayed.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234---name: gleam-designdescription: Design, implement, refactor, and review idiomatic Gleam code using source-backed patterns for modules, custom and opaque types, explicit dependencies, state ownership, and concurrency. Use for .gleam projects, Gleam architecture reviews, OTP actors and supervision, ETS or Cell sharing, database handles, Lustre state, event-driven services, or deciding between message passing and ordinary state/value passing. Distinguish Erlang and JavaScript targets and avoid unnecessary actors, unsafe sharing, and generic abstraction sprawl.---
# Gleam Design
Prefer ordinary values and functions. Introduce processes for a real concurrency,ordering, resource-lifetime, or failure boundary. Introduce shared mutable storagefor a demonstrated access pattern with an explicit consistency contract.Do not treat "Gleam runs on the BEAM" as a reason to actorify every module.
## 1. Inspect before designing
Load only the supporting references relevant to the task, not the entire corpus.
Read the project's `gleam.toml`, `manifest.toml`, entrypoint, relevant modules,FFI, and tests. Establish the compiler/library versions, supported targets, andexisting public contracts. Follow coherent local conventions rather than imposinga new architecture because a reference repository uses it.
Trace one representative path end to end: input decoding, domain operation,state access, effects, response, and recovery. Read the actual dependency APIbefore using version-sensitive OTP, selector, database, or framework functions.Consult source/documentation when local dependency code does not answer amaterial question. Do not guess APIs from old examples.
For a small pure helper, implement and test the helper; do not produce anapplication architecture document. For stateful changes, identify:
- The authoritative state and any derived snapshots or caches.- The owner, readers, writers, and lifetime of each state/resource.- The invariant, atomic operation, readiness condition, and failure behavior.
Use a compact ownership ledger when several resources interact:
```textresource | authority/owner | readers/writers | consistency | restart/close```
## 2. Choose the smallest mechanism
| Need | Default choice | Check before escalating || --- | --- | --- || Local transformation or immutable config | Function arguments and returned values | Is any independent lifecycle needed? || Evolving data used by one execution flow | A record/Dict threaded through functions | Is there actually a second writer? || A safely shareable dependency | Explicit handle or captured context | Does this particular handle permit concurrent use? || Ordered decisions or asynchronous lifecycle | One actor owning the relevant state | What invariant or resource does it own? || Many readers of an occasionally replaced value | Published snapshot, possibly ETS-backed | Publication atomicity, writer discipline, readiness, lifetime || Concurrent independent updates | Documented atomic store operation | Does the guarantee cover the whole operation? || Durable or multi-record invariants | Suitable database transaction/authority | Which connection and isolation boundary apply? || Very rarely changed VM-wide data | Consider `persistent_term` only deliberately | Are its replacement and GC costs acceptable? |
Distinguish **sharing a value**, **sharing a resource handle**, and **sharingmutable storage**. A record containing a cell or socket does not make thereferenced resource immutable. A typed handle does not prove liveness,thread-safety, uniqueness, or correct lifetime.
For a tree or graph owned by one process, start with ordinary recursive valuesor `Dict(Id, Node)`. Do not create an actor or a cell for every node or field.Local value passing is not the same cost model as sending the whole structurebetween processes.
Read [state and concurrency](references/state-and-concurrency.md) before addingshared access, changing ownership, or relying on restart behavior.
## 3. Put types at the right boundaries
Use variants for meaningful alternatives, with the data needed by each variant.Prefer `Idle | Running(run_id) | Stopping(run_id)` to correlated booleans andoptional fields. Keep independent dimensions independent; do not build a giantsum type merely to avoid every optional field.
Use public records when callers should assemble data. Use opaque types when aconstructor must enforce an invariant, callers must not depend on representation,or a handle should expose only selected operations. State the invariant ratherthan adding wrappers by habit. A type alias is not a distinct nominal type.
Preserve relationships through type parameters: request/body, query/decoded row,runtime/message, or snapshot/status. Use phantom states only when they eliminatereal invalid combinations; they do not enforce resource ownership by themselves.
Decode JSON, database rows, and foreign-runtime values at explicit boundaries.Keep `Dynamic`, unsafe casts, and wire-specific shapes out of ordinary domainlogic. Preserve one canonical native model; do not invent JSON mirrors just tomove typed data between modules or local processes.
Keep expected failures in `Result` with useful error variants. Reserve assertionsfor programmer invariants or intentionally fatal startup conditions. Distinguishbad input, dependency failure, transport failure, and corrupted internal state.
Read [types and modules](references/types-and-modules.md) for examples from HTTP,Pog, Squirrel, Birdie, and the standard library.
## 4. Separate module boundaries from process boundaries
Keep related types and operations in the module that owns their meaning. Extractan `internal` module, adapter, or protocol module when there is a real boundary.Avoid a catch-all `types.gleam`, duplicate model definitions, and one-module-per-function fragmentation. Do not impose arbitrary file-size limits.
Compose dependencies at startup or the request boundary. Capture an immutablecontext in a handler closure when appropriate; give lower-level functions thenarrow values or handles they actually need. Do not add a dependency-containeractor just to avoid passing a record.
For a complex event-driven service, consider separating a pure transition fromits executor. Keep business transitions, protocol decoding, worker supervision,and persistence distinguishable. Do not require an effect framework for a simplehandler that can directly call a few functions.
Use pipelines for transformations, `case` for meaningful alternatives, and`use` when callback composition improves readability. Inspect the calledfunction: `use` is callback syntax, not automatic async execution or `finally`.A function-valued field can still do IO.
## 5. Design the process protocol, not just its messages
Keep private runtime state separate from the handle or startup data returned tocallers. Hide message constructors behind ordinary functions unless callerslegitimately need the protocol for integration.
Send operations that protect whole invariants. Prefer `Reserve(amount, reply)`to a client-side `Get` followed by `Set` when interleaving would be incorrect.Use plain snapshot reads where snapshot semantics are sufficient.
Separate application events from transport/lifecycle events. Use explicitvariants for user input, scheduled wakeups, worker results, stream events,monitor notifications, and shutdown. Do not forge user messages for internalwakes merely because an external API has a narrower message format.
Keep the owner responsive to the events it must service. Offload long jobs whennecessary, with bounded admission and appropriate supervision/monitoring.Return compact work results and correlate them with run/request identity.An `Effect` value does not itself mean work runs in another process.
Treat a reply containing `Result` separately from a fallible call transport. Inthe inspected `gleam_erlang` API, `process.call` can panic rather than return atimeout error. Verify the installed version and choose its supported failureboundary; do not invent a `try_call` function.
Define what an acknowledgment means: accepted, applied, published, or durable.A timeout does not prove work stopped. Cancellation needs a real protocol, andlate results need identity/generation checks. Include an owner-incarnation tokenwhen identity could be reused after restart. Add idempotency or reconciliationwhere retrying external effects could duplicate them.
Define overload behavior at the ingress, not only inside the actor handler.Rejecting `Busy` after dequeue does not bound an already growing mailbox.Specify admission limits, batching, coalescing, or demand as appropriate.
## 6. Share state without inventing guarantees
For a writer-actor/direct-reader hybrid, identify the authoritative state,publish a complete snapshot in one appropriate operation, and let each logicalreader capture it once. Decide whether a successful write acknowledgment followspublication. Define initialization, stale-read, and unavailable-owner behavior.
Do not assume a writer actor makes several ETS writes atomic to direct readers.Do not implement concurrent read-modify-write as separate `cell.read` and`cell.write` operations. Use a suitable atomic operation, an explicit retryprotocol, or owner serialization covering the full invariant.
Distinguish protected ETS from Cell's inspected **public** ETS implementation.Single-writer discipline in an application is not a guarantee enforced by Cell.Do not claim a snapshot is consistent when it contains independently mutablehandles, or when a logical read spans unrelated database/store operations.
Distinguish pool handles from checked-out transaction connections. Keep atransaction's work on the supplied connection. Do not leak it beyond its scopeor assume queries through the original pool join the transaction. A failed rowdecoder does not imply the SQL write did not happen.
Create resources in their intended owner. Passing an ETS ID to an actor doesnot transfer table ownership. A parent-owned table may be correct when theparent's lifetime is intentional; an actor-owned table belongs in its initializeror a deliberate ownership-transfer protocol.
Design recovery for **handles as well as processes**. A stale PID subject or ETSID does not become valid when a child restarts. Choose refresh/lookup, groupedrestarts, ownership transfer, or another explicit recovery boundary. Choosesupervision strategy from dependencies, not habit.
Allocate a finite set of process names at application composition time, not perrequest or inside repeatedly restarted initialization. Avoid unbounded atomcreation. Use direct subjects or a suitable dynamic registry where required.
Keep these BEAM-specific rules out of JavaScript-only designs. Encapsulated localmutation in a foreign implementation can preserve an immutable API; it does notjustify exposing shared mutable objects to arbitrary callers.
## 7. Verify the behavior that matters
Run the project's supported equivalents of:
```shgleam format --check src testgleam checkgleam test```
Adjust paths to the project; do not create nonexistent test directories merelyto fit this command. Run each supported target when relevant. Check the localCLI for version-specific options. Report missing tools or unrun checks honestly.
Test pure transitions and boundary decoders without infrastructure first. Thenexercise the applicable concurrency cases: initialization failure, owner death,which children restart, stale handles, duplicate/late results, cancellation thathas not completed, timeout after an effect may have occurred, concurrent writers,snapshot readiness, and overload. Use acknowledgments or monitors to controlordering rather than treating a sleep as proof of synchronization.
Review FFI contracts and cleanup explicitly. Supervision, a typed signature, acallback, and passing unit tests are not interchangeable guarantees.
Use [review and test cases](references/review-and-tests.md). For long-livedsession/event loops, read [the worked example](references/session-example.md)and its original [core](assets/examples/session_core.gleam) and[test cases](assets/examples/session_core_test.gleam). The bundled Gleam fileswere source-reviewed but **not compiler- or runtime-verified** in this study.
## 8. Deliver a small, justified change
State the chosen ownership/consistency boundary, the important alternative notchosen, and the tests actually run. For a review, prioritize correctness,lifetime, and observable failure semantics before cosmetic style. Do not rewritean entire architecture or add a cache, actor, registry, or persistence layerwithout a requirement that justifies it.
Consult [repository studies](references/repository-studies.md) for the 16-repocomparison and counterexamples. Use [sources](references/sources.md) or the[machine-readable ledger](references/source-ledger.json) for exact revisions andreading scope. Treat upstream code as evidence, not infallible authority; checklocal versions before borrowing an API or a behavior.1234interface: display_name: "Gleam Design" short_description: "Types, ownership, and concurrency without actor sprawl" default_prompt: "Use $gleam-design to inspect this Gleam project, choose appropriate type and state-ownership boundaries, and implement or review the smallest correct change."123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166# Repository studies
## Contents
- Synthesis- Applications: Packages and Gloogle- Runtime libraries: OTP, Erlang, Glisten, Mist- Function-first interfaces: Wisp, HTTP, Pog, SQLight- UI and portable libraries: Lustre, stdlib, JSON- Tooling: Squirrel and Birdie- Small shared-state primitive: Cell- Limits of the evidence
Read this as a comparison of selected implementation paths, not a ranking of repositories. The [source ledger](sources.md) records 36 files across 16 repositories, reading ranges, revisions, and five inspected test files.
## Synthesis
The strongest common lesson is not "use actors" or "avoid actors." It is to distinguish **ordinary data, a handle to a resource, and the authority that changes a resource**. The studied designs put these at different boundaries for different reasons.
Use a module to own an API and representation. Use a process to own time, asynchronous interaction, failure, or ordered decisions. Pass values and appropriate handles explicitly. Reach for shared state when its access pattern and consistency contract justify it. These recommendations are a synthesis; no single repository establishes a universal rule.
## Applications: Packages and Gloogle
### 1. gleam-lang/packages
**Observed:** Startup constructs storage and a `TextSearchIndex`, passes dependencies into request handling, and starts supervised periodic work. The opaque index contains an ETS bag handle, a typed cell holding known words, and a stemmer. Search combines store access with ordinary list/dictionary processing. The periodic actor stores a work callback and interval, then schedules its next run after the previous one completes. [S01-S05](sources.md#s01)
**Use:** Keep a background lifecycle in a process without routing all queries through that process. A domain module can expose straightforward functions even when it contains shared-store access.
**Limit:** The index is not a multi-operation transaction: package insertion and known-word updates are separate. The cell update reads a set, computes a new set, and writes it. Serialization assumptions and acceptable partial visibility need separate justification. Do not copy this as an atomic authoritative inventory design.
### 2. ghivert/gloogle
**Observed:** `jupiter.gleam` composes a PostgreSQL pool, HTTP server, periodic workers, and a search worker. Context initialization creates the cell table; the search worker initializes and updates its value. `Add` travels through the actor, while search requests read the published `TypeSearch` value directly. The index itself is a recursive structure of records, dictionaries, options, and lists. Lookup can also consult PostgreSQL. [S24-S27](sources.md#s24)
**Use:** Separate write coordination from query execution. A tree does not need a process or mutable cell for every node. Request-specific context updates can stay ordinary record updates.
**Limit:** Cell permits public writes, so this is not runtime-enforced single-writer access. HTTP starts before the search worker, making initial readiness a real design question. The code also uses unsafe name coercions; prefer supported typed naming APIs in new code. A cell read is not a transaction spanning the subsequent database queries.
## Runtime libraries: OTP, Erlang, Glisten, Mist
### 3. gleam-lang/otp
**Observed:** An actor builder takes state, a callback, and initialization behavior. `Started(data)` distinguishes the supervised process PID from data returned to the caller. An initializer can choose its selector and returned data. The loop carries ordinary state forward through `Next`. Tests exercise initialization failures, timeouts, selector replacement, and distinct supervision restart strategies. [S09-S11](sources.md#s09)
**Use:** Design the process API separately from its private state. Initialize owned resources in the intended owner. Test the dependency/restart graph through observable lifecycle events.
**Limit:** A subject is only one possible startup result, and supervision alone does not preserve old resource handles or durable facts. Do not infer that every stateful module belongs in a supervisor. Do not imitate tiny test timeouts without considering the execution environment.
### 4. gleam-lang/erlang
**Observed:** Subjects encode an owner/tag or a name. Selectors map different message sources into one typed payload. Names use atoms and are documented for creation at startup. `call` returns the reply directly; timeout or missing/failed callee can panic. Monitors and timers are explicit mechanisms. [S12](sources.md#s12)
**Use:** Keep typed message protocols at process boundaries. Map foreign or heterogeneous events once, then pattern-match on your own event type. Distinguish a reply's domain error from transport failure.
**Limit:** A typed subject is not a liveness guarantee. A name is not a durable mailbox. New names on every request or child restart are not a harmless allocation pattern. Selectors and monitors do not eliminate the need to reason about cleanup and late messages.
### 5. rawhat/glisten
**Observed:** An acceptor accepts a socket, starts a connection child, transfers the controlling process, and sends `Ready`. The connection actor keeps transport state and user state together while giving callbacks ordinary values. Internal socket events and custom user messages have distinct variants. Connection children use a temporary restart policy. [S13-S14](sources.md#s13)
**Use:** Make a process boundary follow a real resource lifecycle. Keep protocol machinery separate from user-domain messages. Make ownership handoff explicit before enabling event delivery.
**Limit:** This is transport infrastructure, not a template for putting all application functions behind actors. The inspected handler catches callback crashes and continues with old state, and some returned selector values are ignored. Do not assume those behaviors are appropriate for transactional application logic or infer guarantees from type names alone.
### 6. rawhat/mist
**Observed:** A public facade exposes response variants, body-reading operations, and an opaque next-step type. The internal handler uses `Http1` and `Http2` variants containing the state relevant to each protocol. It captures dependencies in a callback and dispatches between protocol-specific modules. Body reading changes `Request(Connection)` into `Request(BitArray)`. [S17-S18](sources.md#s17)
**Use:** Express mutually exclusive protocol states with variants, and preserve type relationships across transformations. Hide transport-specific details behind a useful public boundary without forcing every helper into a process.
**Limit:** A consumption callback can operate on a live socket and is not necessarily pure or replayable. The independently inspected Mist and Glisten revisions do not imply that their current public APIs are mutually compatible; verify the consuming project's manifest.
## Function-first interfaces: Wisp, HTTP, Pog, SQLight
### 7. gleam-wisp/wisp
**Observed:** Responses are ordinary typed values. The database example creates a connection, puts it in an application context, and partially applies the request handler with that context. It does not introduce a new application actor just to distribute the context. [S15-S16](sources.md#s15)
**Use:** Make dependency passing boring. Let handlers call domain functions with the required handles, and keep request/response transformations straightforward.
**Limit:** This example establishes the shape of dependency injection, not the concurrency guarantees of every possible database connection or the behavior of `tiny_database` internals. It is not evidence that all handles can be shared without qualification.
### 8. gleam-lang/http
**Observed:** `Request(body)` is a public record. `set_body` and `map` can change the body type while preserving request metadata. Header and URI helpers use ordinary transformations. Tests construct values directly and assert conversion behavior, including absent and malformed query data. [S28](sources.md#s28), [S35](sources.md#s35)
**Use:** Make valid relationships generic rather than introducing a family of disconnected DTOs. Use public constructors when callers are meant to assemble records. Test pure behavior without starting infrastructure.
**Limit:** A public record also permits callers to bypass helper conventions. Choose an opaque constructor when a domain invariant really requires it; public data in a protocol library is not a universal argument against opacity.
### 9. lpil/pog
**Observed:** The opaque connection type represents either a pool name or a checked-out connection. A query carries typed parameters and a row decoder. Transactions check out one connection, defer its check-in, and roll back on callback failure or panic. Tests check commit, error rollback, panic rollback, decoder mismatch, and timeout behavior. [S19](sources.md#s19), [S36](sources.md#s36)
**Use:** Pass shareable pool handles without adding another serializing actor by default. Carry a result decoder in a query builder. Keep transaction work on the supplied connection and clean up deliberately.
**Limit:** Do not leak or concurrently reuse a checked-out connection merely because its public type is also `Connection`. Calling the original pool inside the callback does not automatically use that transaction. A decode failure occurs after the SQL ran and is not proof the mutation did not happen.
### 10. lpil/sqlight
**Observed:** One typed public API bridges Erlang and JavaScript implementations. Query results pass through a `Decoder(t)`. The inspected `with_connection` implementation opens a connection, calls a callback, then closes on the normal return path. [S20](sources.md#s20)
**Use:** Keep external representations behind a small typed adapter. Read both the Gleam declaration and foreign implementation when concurrency or failure guarantees matter.
**Limit:** `use` syntax does not itself ensure cleanup if a callback crashes. The inspected helper is not equivalent to Pog's explicit crash cleanup. No claim about arbitrary concurrent access to a SQLight connection is established by this source sample.
## UI and portable libraries: Lustre, stdlib, JSON
### 11. lustre-labs/lustre
**Observed:** The public architecture links model, message, update, view, and effect types. The Erlang server runtime stores ordinary application state within an actor, while its own protocol distinguishes client events, effect-produced domain messages, subscriptions, monitors, and shutdown. Subscriber bookkeeping is ordinary dictionary/set state. Effects contain callbacks; the runtime invokes the effect executor. [S21-S23](sources.md#s21)
**Use:** Separate domain transitions from runtime transport and lifecycle events. Use a pure transition boundary where it makes testing useful. Keep collections of subscribers or pending work as data unless each needs a separate lifecycle.
**Limit:** An effect is not inherently an independently scheduled job. The inspected server runtime is Erlang-specific; do not assume those implementation details describe the JavaScript runtime. Nor does a returned effect guarantee durable, exactly-once execution.
### 12. gleam-lang/stdlib
**Observed:** The dictionary module presents an immutable API. Its JavaScript implementation path uses private transient dictionaries with explicit single-use discipline. Dynamic decoding composes typed field readers and reports accumulated errors through the final `run` boundary. [S29-S30](sources.md#s29)
**Use:** Distinguish a stable public semantic contract from internal implementation technique. Keep mutation tightly scoped where it is justified, and keep dynamic inspection at a clear typed boundary.
**Limit:** The transient discipline is documented internally, not a general linear-type guarantee offered to application code. Do not expose those operations or use a native mutable object as shared state without a separate ownership design. Cross-target decoding behavior deserves tests.
### 13. gleam-lang/json
**Observed:** `parse` accepts a decoder and returns typed output. Syntax errors and inability to decode the desired shape have distinct variants. Erlang and JavaScript details are handled behind target-specific functions and FFI. [S31](sources.md#s31)
**Use:** Turn boundary data into the intended type once and keep downstream code typed. Make error categories correspond to actions a caller can take.
**Limit:** Successful JSON syntax parsing is not domain validation. Do not serialize already typed local values merely to move them across a module boundary, and do not assume a type signature establishes foreign-runtime behavior without inspection.
## Tooling: Squirrel and Birdie
### 14. giacomocavalieri/squirrel
**Observed:** Value identifiers and type identifiers are distinct opaque types with checked constructors. Structured type variants replace loosely organized strings. The PostgreSQL inference path carries a single connection and ordinary dictionary caches in a context, then threads that context through its computation abstraction. [S32-S33](sources.md#s32)
**Use:** Validate representations at construction boundaries and make illegal combinations harder to express. Keep local cache evolution as local data rather than inventing an actor per cache.
**Limit:** The inspected identifier constructor checks particular lexical rules; do not assume that proves every conceivable code-generation constraint. A state-threading abstraction is not a recommendation to add that dependency for every small function. Network-facing code here still performs IO.
### 15. giacomocavalieri/birdie
**Observed:** Internal snapshots use a phantom status parameter, so new and accepted snapshots appear distinctly in comparisons and outcomes. Snapshot IO, content comparison, serialization, and diagnostics remain identifiable responsibilities. The implementation deliberately defers expensive source analysis to review rather than doing it for every snapshot assertion. [S34](sources.md#s34)
**Use:** Use types to distinguish values with identical storage but different valid roles. Sometimes a better phase for expensive work is simpler than adding a concurrent cache.
**Limit:** The root file is large; file length alone is not an architecture verdict. Cached global-helper calls appear in the code, but their implementation was not audited here. Neither parallel-test safety nor global cache semantics is inferred from those calls.
## Small shared-state primitive: Cell
### 16. lpil/cell
**Observed:** A generic opaque cell couples a table with a reference. The FFI uses a public ETS set and turns missing/destroyed-table operations into errors. The inspected test covers empty cells, replacement, deletion, differently typed cells, and dropping the whole table. [S06-S08](sources.md#s06)
**Use:** A compact typed handle can expose intentionally shared, occasionally replaced values without a bespoke message protocol for every read.
**Limit:** Tests of sequential operations do not establish atomic read-modify-write. This implementation supplies individual operations, not transactions or compare-and-swap. The table lifetime remains tied to its ETS owner. For local algorithmic state, prefer ordinary immutable structures unless a measured requirement justifies mutation.
## Limits of the evidence
No repository was audited in its entirety. No benchmarks, deployments, upstream test suites, or compiler checks of the bundled original example were run. Concrete conclusions above come from inspected source paths; recommendations additionally use the official Erlang and Gleam semantics in [E01-E05](sources.md#e01).
Do not convert every observed choice into a rule. This study intentionally retains counterexamples: public records beside opaque types, large facades beside focused modules, direct shared-store reads beside actor messages, and local mutation behind immutable interfaces. Use the smallest design that meets the actual contract.123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118# Review and test cases
## Contents
- Review a change- Select tests by the changed guarantee- Evaluate an agent using this skill- Record verification accurately
## Review a change
Trace the new behavior through input, state, side effects, and recovery. Look for an invariant that the implementation does not actually protect. Treat style preferences as secondary unless they obstruct that reasoning.
Ask these questions in order:
1. **State:** Is this a value, a resource handle, actor-owned state, a shared store, or durable authority? Are multiple supposed sources of truth kept consistent?2. **Type boundary:** Can callers represent an invalid phase or swap unrelated IDs? Do decoders check the actual boundary, including domain constraints?3. **Operation:** Is the claimed atomic unit a single operation, several calls, several tables, or several systems? Is the guarantee documented at that scope?4. **Lifecycle:** Who owns resources, who closes them, what is restartable, and who still holds obsolete handles afterward?5. **Execution:** Does the owner remain responsive? Does an effect callback really offload work? What prevents unbounded queued work?6. **Failure:** What is acknowledged, what may already have happened after timeout, and what makes retry safe?7. **Evidence:** Which tests demonstrate these claims? Which remain assumptions?
Use the inspected tests as styles of evidence, not complete coverage: Cell checks sequential table behavior; HTTP checks pure transformations and invalid input; OTP checks initialization/selectors and restart membership; Pog checks commit and error/panic rollback. [S08, S10-S11, S35-S36 in the ledger](sources.md)
## Select tests by the changed guarantee
| Change | Important test | Do not accept as equivalent || --- | --- | --- || Pure domain transition | Expected new state and effect descriptions for each meaningful event | Only a full integration smoke test || New decoder or smart constructor | Missing, malformed, boundary, and domain-invalid values | One valid example || Actor protocol | Request/reply identity, domain rejection, transport failure, selector reachability | Compilation of the message type || Shared counter/update | Force two writers to observe the same old value; verify required result | Repeating one sequential write many times || Snapshot publication | Read before initialization, during update, after acknowledgment, and during rebuild | Only reading after startup settles || Supervision change | Kill the relevant child; assert exactly the dependent set restarts and handles recover | Asserting a supervisor PID exists || Worker cancellation | Cancel while work is running; send late completion; confirm state policy and worker termination | Sending a cancel message and immediately assuming success || External write retry | Simulate timeout/response loss after commit; check idempotency or reconciliation | Treating timeout or decode error as rollback || Database transaction | Verify commit, callback error rollback, panic cleanup, and correct connection use | Only checking the callback's returned value || Admission/backpressure | Drive input beyond service rate and check queue/rejection/coalescing behavior | A `Busy` branch after dequeue || Cross-target FFI | Exercise representation and cleanup on each supported target | Passing Erlang tests for a JavaScript deployment |
Use barriers, typed acknowledgments, and monitors for controlled interleavings. Keep deadlines bounded and reasonably tolerant of CI scheduling. Ensure test processes, tables, connections, and files are cleaned up. Where termination can bypass ordinary cleanup, test the actual owner/OS/resource behavior rather than assuming callback cleanup runs.
For property-oriented tests, consider: old generation results do not change current state; terminal results do not apply twice; a rejected request does not mutate authority; snapshot metadata and payload refer to one generation; pure transformations preserve the original value. These are suggested properties, not claims that every upstream project already tests them.
## Evaluate an agent using this skill
Use these prompts as **unexecuted evaluation cases** for a future agent or reviewer. They are not a benchmark score and no separate agent evaluation was run for this package.
### Case A: The actor-per-module request
**Prompt:** "Split this Gleam HTTP app into ConfigActor, ValidatorActor, JsonActor, and UserRepositoryActor so every module owns its state."
**Expected:** Inspect existing code and requirements. Keep configuration and validation as ordinary data/functions. Pass an appropriate database handle. Introduce a process only for a real coordination or lifecycle need. Explain the smaller design without refusing useful refactoring.
**Failure:** Generate the four actors because the language targets BEAM.
### Case B: The apparently safe cell increment
**Prompt:** "Each request reads an Int cell, adds one, and writes it back. The cell is typed and ETS is concurrent, so approve this."
**Expected:** Show the lost-update interleaving. Select an atomic increment, owner operation, or other correctly scoped mechanism. Distinguish an individual cell operation from a multi-step update.
**Failure:** Add a sleep, a concurrency flag, or a type annotation and call it fixed.
### Case C: The read-heavy derived index
**Prompt:** "A background worker rebuilds an index occasionally; thousands of handlers query it. Put every query through the worker to avoid all shared state."
**Expected:** Consider ordinary published snapshots/direct reads, with writer discipline, readiness, publication scope, copying cost, lifetime, and tolerated freshness specified. Compare owner queries if the index is huge or stronger semantics are needed. Do not insist on either mechanism without the contract.
**Failure:** Universal actor-only dogma, or unqualified global mutable state.
### Case D: The restarted table owner
**Prompt:** "The supervised actor recreated its ETS table, so all existing request-handler contexts still have a valid table handle."
**Expected:** Trace the obsolete ID and the restart dependency graph. Propose appropriate refresh/lookup, grouped restarts, or ownership/lifetime changes and test them.
**Failure:** Claim that retaining a typed handle keeps the original table alive or redirects it to the replacement.
### Case E: The hidden execution context
**Prompt:** "I moved a thirty-second function into an Effect callback. Now my coordinator can process cancellation concurrently."
**Expected:** Inspect the executor. Offload to a suitable bounded worker only if needed. Track worker identity and results; do not infer concurrency from a wrapper type.
**Failure:** Claim the callback or `use` expression automatically runs asynchronously.
### Case F: The SQL write with a bad row decoder
**Prompt:** "The INSERT returned a decoding error, so retrying the INSERT cannot duplicate anything."
**Expected:** Separate query execution from row decoding. Determine whether a transaction rolled back, whether the write committed, and whether a stable operation key or reconciliation is required.
**Failure:** Treat all `Error` values as proof no external effect occurred.
### Case G: The scope illusion
**Prompt:** "This `use conn <- with_connection(...)` guarantees closure on all failures, and I can send conn to a detached worker for later use."
**Expected:** Read the helper implementation and resource contract. Check exception paths and lifetime; do not leak scoped connections. Explain that callback syntax provides neither automatic cleanup nor ownership transfer.
**Failure:** Promise RAII/finally semantics from syntax alone.
### Case H: The small JavaScript utility
**Prompt:** "Refactor this JavaScript-target Gleam list transformation using the skill."
**Expected:** Improve the function/types/tests as needed without ETS, OTP, an ownership ledger in the user-facing answer, or new framework scaffolding.
**Failure:** Apply BEAM runtime architecture to a pure helper.
## Record verification accurately
In a real implementation response, report which format, compile, unit, integration, and target checks actually ran. Distinguish a reviewed test source from an executed test, a package-structure validator from a Gleam compiler, and an inferred risk from a reproduced bug.
The current research inspected upstream test source but did not execute upstream suites. The bundled example is original educational code, not an audited production runtime. Compile and run it against the receiving project's toolchain before adopting it.123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293# Worked example: one owner, ordinary state, explicit work
## Contents
- Purpose and files- Guarantees and deliberate policy choices- Integrate with a runtime- Add snapshots only when needed- Extend without changing the model into a framework- Verification status
## Purpose and files
Use this example to teach separation of domain transitions from process mechanics. It is original educational code inspired by the patterns studied, not copied from an upstream application and not a complete agent harness.
Read [session_core.gleam](../assets/examples/session_core.gleam) and [session_core_test.gleam](../assets/examples/session_core_test.gleam). The core uses only built-in types and returns `#(Model, List(Effect))`. Its tests call transitions directly; no actor or database is needed to reason about them.
The domain distinguishes `UserRequested(prompt)` from `ScheduledWake(tag)`. Both can request work without pretending a timer is a human message. The runtime may later translate these into a provider-specific representation, but the native event keeps its actual meaning.
## Guarantees and deliberate policy choices
The model tracks one active run, a monotonically increasing sequence within that owner incarnation, and three phases: `Idle`, `Running`, and `Stopping`.
A request while running or stopping is rejected as busy. This is a **domain admission policy**, not a bound on a BEAM mailbox. A real ingress still needs a queue/rate/admission policy before arbitrarily many messages accumulate.
Canceling enters `Stopping` and emits `AskJobToStop`. It does not immediately return to `Idle`, because asking a worker to stop is not proof it stopped. Another run is admitted only after a matching `WorkEnded` event. Repeated cancellation does not emit repeated stop effects.
A matching terminal event while stopping produces `ReportAbandoned`. This means the session no longer uses the result, not that external work was rolled back. The worker may have completed just before cancellation. Unknown, old, and duplicate terminal events leave the model unchanged.
The run ID combines an incarnation identifier with the local sequence. The runtime must supply a fresh incarnation identifier when an owner is replaced. A constant string reused on restart breaks this guarantee. This is an injected contract, not uniqueness magically supplied by the core.
This example makes the model opaque to protect transition construction, while exposing phase and protocol values for inspection and integration. It does not make the whole type system into an authorization boundary.
## Integrate with a runtime
Keep the runtime state small: the core model, the resources it really owns, and a dictionary from work IDs to worker/monitor information. Keep a single canonical model; do not synchronize a second JSON mirror after every event.
Use the following executor contract. It is deliberately architectural pseudocode rather than an invented Gleam library API:
```texton a decoded domain event: next_model, effects = transition(current_model, event) adopt next_model interpret effects in their defined order
StartJob(id, reason): admit/start a bounded, appropriately supervised worker record its identity and monitor translate start failure into WorkEnded(id, Failed(...)) arrange a terminal event even when the worker crashes
AskJobToStop(id): request cancellation through the worker's actual protocol apply the project's timeout/escalation policy if it cannot stop do not manufacture WorkEnded until the worker ended or another deliberately defined terminal condition has been established
worker completion or monitor-down: correlate to its work ID and incarnation clean up worker/monitor bookkeeping deliver WorkEnded(id, outcome) to the core
ReportFinished / ReportAbandoned / RejectBusy: notify the appropriate requester or observer```
Resolve duplicate terminal signals from a normal result and a monitor notification. The core tolerates duplicates, but the adapter must also avoid leaked monitors and stale worker entries. Keep notifications correlated to requesters in the real protocol; `RejectBusy` is intentionally simplified here and does not itself identify a recipient.
Do not run a long provider call inside the coordinator merely because it has been wrapped as an effect. Use the installed OTP APIs and actual worker lifecycle. Avoid unlinked fire-and-forget work when the coordinator's recovery depends on knowing whether it is still running.
Treat model adoption and effect execution as non-transactional unless a durable design explicitly joins them. A crash after choosing a run but before starting its worker needs a recovery policy. Durable accepted jobs may require persisted work records, idempotency, or an outbox; a disposable interactive session may instead fail and restart. Do not add that machinery without the requirement.
## Add snapshots only when needed
Start with a status request to the owner or pass a snapshot to the one caller that needs it. Introduce direct shared reads when measured access patterns justify them.
A read-side projection can expose only current phase, run ID, progress, and revision rather than every worker handle or the full transcript. Publish it after the model transition at a documented point. Decide what readers see during initialization or owner replacement. A status snapshot should not accidentally grant write access or expose secrets.
Do not let the read-side projection become a second authority. A direct reader cannot reserve a new run simply by reading `Idle`; the authoritative operation must still decide whether to accept it.
## Extend without changing the model into a framework
For streaming, add chunk events with the active run ID and define bounds on retained data. For parallel tools, use a dictionary of pending call IDs and typed outcomes rather than an actor for each field. For timers, preserve timer identity/generation so superseded wakeups are distinguishable. Keep heterogeneous transport inputs mapped to explicit domain/runtime variants.
Choose a queue, replacement, or coalescing policy instead of busy rejection only when product semantics require it. Cancellation of external effects, completion of all workers, and user-visible abandonment are separate concepts; keep that separation when extending the state machine.
Do not claim replayability makes arbitrary function callbacks pure. The provided core is pure because it only manipulates values and returns effect descriptions. The executor is where resources, time, and failure belong.
## Verification status
The files were written and manually source-reviewed for the intended cases, but no Gleam compiler or Erlang runtime was available in the research environment. The nine test functions have **not been run**. They are examples to execute in the receiving project's toolchain, not evidence of a passing build.
Copy the core to an appropriate `src` module and the tests to the project's test tree, adjust the import if renamed, and use the project's existing test runner. Run formatting, compilation, and tests before adopting the example. No dependency manifest is supplied because inventing versions without a compiler would create a misleading runnable-project claim.