en un clic
gleam-otp-actors
gleam-otp-actors
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
gleam-otp-actors
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Basé sur la classification professionnelle SOC
Diagnose Azedarach command latency and daemon/TUI performance with OpenTelemetry traces exported to a local Jaeger backend. Use when investigating slow `az` commands, TUI stalls, daemon RPC latency, missing spans, local Jaeger setup, or trace-driven performance regressions in this repository.
Review and integrate Azedarach issues that are in_review. Use when Codex is asked to run a reviewer/integrator session, sweep active issues that are ready for review, inspect worker evidence and diffs, return actionable findings to live or paused issue sessions, close accepted work, or get in_review issues integrated.
Review, consolidate, test, and harden Azedarach SQLite migrations before integration. Use for any migration, schema ensure/repair logic, trigger or index change, persistence-authority change, migration failure, or pre-merge review of a branch containing database changes. Also use when validating upgrades against real root-user or registered project databases.
Run an iterative code review and fix cycle until findings stabilize. Use when the user asks Codex to review code, review and fix, loop review/fix, address review findings, harden a change before handoff, or keep reviewing until repeated passes find no new actionable issues. Also use proactively before Codex declares coding work done, marks an issue in_review, says a change is ready, or hands off implementation that modified code, tests, build scripts, migrations, config, or developer tooling.
Go Testing Skill
Build and review production-grade Go logging and observability with `log/slog` and OpenTelemetry. Use when adding or refactoring logs, instrumenting request paths, defining event schemas, reducing log cost/cardinality, enforcing redaction/PII policy, correlating logs with traces, or reviewing Go services for observability gaps.
| name | gleam-otp-actors |
| description | gleam-otp-actors |
Essential patterns for building correct, message-passing actors in Gleam using gleam_otp.
In Gleam OTP, a Subject(Msg) is a typed mailbox. Messages sent to a subject are received by whoever is blocking on process.receive(subject, timeout).
Critical insight: process.new_subject() creates a brand new, independent mailbox that is NOT connected to any actor's message handler. If nobody calls receive on it, messages are silently discarded.
// ❌ WRONG: Orphan subject - nobody listens!
fn handle_message(state, msg) {
case msg {
DoAsyncWork -> {
let reply_to = process.new_subject() // Creates orphan!
spawn_worker(reply_to) // Worker sends result to orphan
actor.continue(state) // Result is LOST
}
}
}
The spawned worker sends its result to reply_to, but:
Symptoms:
Actors don't have built-in access to their own subject. Pass it explicitly:
// Define message with self parameter
pub type Msg {
Initialize(self: Subject(Msg))
PeriodicTick
WorkerResult(data: String)
}
// Caller passes subject when initializing
pub fn initialize(subject: Subject(Msg)) -> Nil {
process.send(subject, Initialize(subject))
}
// Handler stores and uses self reference
fn handle_message(state, msg) {
case msg {
Initialize(self) -> {
// Store self for later use
schedule_tick(self)
actor.continue(State(..state, self_subject: Some(self)))
}
PeriodicTick -> {
do_periodic_work()
// Use stored self to schedule next tick
case state.self_subject {
Some(self) -> schedule_tick(self)
None -> Nil
}
actor.continue(state)
}
}
}
// Timer sends back to actor's real subject
fn schedule_tick(self: Subject(Msg)) -> Nil {
let _ = process.spawn(fn() {
process.sleep(1000)
process.send(self, PeriodicTick) // Goes to actor's handler!
})
Nil
}
For spawning async work that returns results to the actor:
pub type Msg {
StartWork(id: String)
WorkComplete(id: String, result: Result(Data, Error))
}
fn handle_message(state, msg) {
case msg {
StartWork(id) -> {
// Use self_subject to receive result
case state.self_subject {
Some(self) -> spawn_async_worker(self, id)
None -> Nil // Not initialized, skip
}
actor.continue(state)
}
WorkComplete(id, result) -> {
// Process result and update state
let new_state = apply_result(state, id, result)
actor.continue(new_state)
}
}
}
fn spawn_async_worker(reply_to: Subject(Msg), id: String) -> Nil {
let _ = process.spawn(fn() {
let result = do_expensive_work(id)
process.send(reply_to, WorkComplete(id, result))
})
Nil
}
When forwarding messages between actors with different message types:
// Source actor sends: SourceMsg
// Target actor expects: TargetMsg
/// Create a bridge that converts SourceMsg -> TargetMsg
fn create_bridge(
target: Subject(TargetMsg),
) -> Subject(SourceMsg) {
let bridge_subject = process.new_subject()
// Spawn process that listens on bridge and forwards to target
let _ = process.spawn(fn() {
forward_loop(bridge_subject, target)
})
bridge_subject // Give this to the source actor
}
fn forward_loop(
from: Subject(SourceMsg),
to: Subject(TargetMsg),
) -> Nil {
case process.receive(from, 60_000) {
Ok(SourceEvent(id, data)) -> {
// Convert and forward
process.send(to, TargetEvent(id, data))
forward_loop(from, to) // Continue listening
}
Ok(SourceError(id, reason)) -> {
process.send(to, TargetError(id, reason))
forward_loop(from, to)
}
Error(_) -> {
// Timeout - continue listening
forward_loop(from, to)
}
}
}
Key insight: The bridge subject is NOT orphan because we spawn a process that immediately starts receiving on it.
For synchronous calls where caller blocks waiting for response:
// In the API module
pub fn get_monitors(supervisor: Subject(Msg)) -> List(String) {
let reply_subject = process.new_subject() // OK here!
process.send(supervisor, ListMonitors(reply_subject))
// Caller blocks until reply received
case process.receive(reply_subject, 5000) {
Ok(monitors) -> monitors
Error(_) -> [] // Timeout
}
}
// In the actor's message handler
fn handle_message(state, msg) {
case msg {
ListMonitors(reply_to) -> {
let monitors = dict.keys(state.monitors)
process.send(reply_to, monitors) // Reply to waiting caller
actor.continue(state)
}
}
}
Why this works: The caller immediately calls process.receive() on the subject, so someone IS listening. The subject isn't orphaned.
When actors depend on each other's subjects, start in dependency order:
// ❌ WRONG: Start order doesn't respect dependencies
fn start_children() {
let callback = process.new_subject() // Orphan!
start_worker(callback) // Worker will send to orphan
start_coordinator() // Coordinator should receive, but started too late
}
// ✅ CORRECT: Start receiver first, then sender
fn start_children() {
// Start coordinator first - it will receive updates
case coordinator.start() {
Ok(coord) -> {
// Create bridge that forwards to coordinator
let callback = create_bridge(coord)
// Now start worker with valid callback
start_worker(callback)
}
Error(_) -> // Handle error
}
}
Before using process.new_subject(), verify:
Is someone calling receive() on it?
For async results, am I using the actor's self?
process.new_subject() for results: WRONGstate.self_subject for results: CORRECTFor timers/schedulers, where do messages go?
process.send(new_subject, Tick): Lostprocess.send(self_subject, Tick): HandledFor bridges, is there a receiver process?
Symptom: "Actor discarding unexpected message"
Symptom: State never updates after async operation
self_subject is None when spawningSymptom: Periodic tasks stop after first run
// Get actor's self: Pass via Initialize message, store in state
// Schedule timer: spawn + sleep + send to self
// Async with reply: spawn + work + send result to self
// Sync request-reply: new_subject + send request + receive reply
// Bridge pattern: new_subject + spawn receiver loop + return subject
// NEVER: new_subject for fire-and-forget results