Putting language in a loop can make it come alive. You say words, the words change the room, the room changes you, you say different words. We call it chanting, and it is one of the oldest tools of magic.
An agent is the same shape. A language model predicts the next token; put it in a loop with an environment, and something emerges that wasn’t in the instructions.
Cantrip names the parts:
- Circle — the environment the entity is given to act within
- Medium — what the entity thinks in: conversation, Elixir, or a shell
- Gates — boundary crossings such as file reads, tools, and child entities
- Wards — enforced limits on turns, recursion, code, and access
- Loom — the record of every turn, including forks and annotations
- Entity — what arises when the loop runs
A cantrip is an Elixir value that binds a language model, an identity, and a circle. Cantrip.cast runs it once. Cantrip.summon puts it under OTP supervision so the entity can receive another intent without losing its process-owned state. Its loom can live in JSONL or Mnesia, and a prior turn can be replayed and forked into another thread.
The medium changes the kind of work the entity can do. A conversation cantrip calls typed tools. A code cantrip writes Elixir with gates injected as functions and normally evaluates inside a child BEAM process. A bash cantrip runs commands in fresh sandboxed subprocesses with network and writable paths off unless the circle grants them. The action space is not “whatever the model wants.” It is the medium plus the gates, minus the wards.
The packaged Familiar is the codebase-facing version. It observes a workspace, reasons in Elixir, delegates to child cantrips, and keeps a durable loom. It can run from a Mix task or over ACP, which means another application can operate the same entity without pretending to be its runtime. Haven already launches the Familiar this way and records the streamed turn in its own durable run timeline.
The current source is an unpublished 1.4.0 release candidate; Hex still carries 1.3.3. The package now includes a small ordinary Mix consumer that keeps a scripted entity alive for two intents, stops it, and rehydrates the same versioned JSONL loom to three turns in a fresh process. The package gate copies that consumer outside the artifact, performs a normal dependency install, then compiles and runs it offline. This is deliberately narrower than a model demo: it proves that another application can install the package and rely on its process and persistence boundaries without making a provider call. It does not prove autonomous behavior or model quality.
The code came before the vocabulary. I built the first prototype from the Recursive Language Models paper, then tried publishing the idea as a ghost library: one large spec, behavioral tests, and reference implementations in several languages. It was a useful way to make the architecture legible, but it created a three-body problem between the spec, the implementations, and what running the system taught me. The current project keeps the spellbook’s language and commits to one real implementation on the BEAM.
That change made the original point sharper. You do not get an entity by declaring an Agent class. You decide what kind of world it can perceive, what kind of thought it can perform there, which boundaries it may cross, and which constraints are real. Then you run the loop and watch what happens.
Read the code | Read the original announcement | Read the RLM essay