When you call Cantrip.new/1, you are constructing a cantrip: a reusable
value that binds an LLM, an identity, and a circle. Cast it with
Cantrip.cast/3 and one entity is summoned into the circle for one episode;
summon it with Cantrip.summon/1 and the entity stays alive across many
sends. In the default port code sandbox, a code-medium inhabitant can use the
same new/cast/cast_batch calls to construct and run child cantrips;
Dune circles use injected host closures instead. The shape is shared by
humans and inhabitants, with sandbox-specific affordances.
Public API for building and running Cantrip programs.
A cantrip combines an LLM, an identity, a circle, optional loom storage,
retry configuration, and folding options into a reusable runtime program.
Cantrip.new/1 validates that configuration, and Cantrip.cast/3 runs one
entity episode against an intent.
The usual entry points are:
new/1to construct a reusable cantrip.cast/3to run one episode and return{result, next_cantrip, loom, meta}.cast_batch/2to fan out work to child cantrips while preserving request order.summon/2andsend/3to keep an entity process alive across multiple intents.Cantrip.Loom.fork/4to replay a loom prefix and branch from an earlier turn.
Composition deliberately uses this same public API. Code-medium entities
create children with Cantrip.new/1, run them with Cantrip.cast/3 or
Cantrip.cast_batch/2, and return compact summaries upward.
Summary
Functions
Runs one entity episode for intent.
Cast multiple cantrips and return their results in request order.
Runs one entity episode while exposing streaming events.
Deprecated compatibility wrapper for Cantrip.Loom.fork/4.
Builds a reusable cantrip from keyword or map attributes.
Sends a new intent to a persistent entity.
Sends a new intent with per-call options, for example stream_to: pid.
Creates a persistent entity without running an intent.
Creates a persistent entity and immediately runs the first intent.
Types
@type t() :: %Cantrip{ child_llm: {module(), term()} | nil, circle: Cantrip.Circle.t(), folding: map(), id: String.t(), identity: Cantrip.Identity.t(), llm_module: module(), llm_state: term(), loom_storage: term(), node: node() | nil, retry: map(), schema_version: pos_integer() }
Functions
@spec cast(t(), String.t() | nil) :: {:ok, term(), t(), Cantrip.Loom.t(), map()} | {:error, String.t(), t()}
Runs one entity episode for intent.
The returned cantrip carries updated reusable runtime configuration. The loom
contains the durable turn record for the episode, and meta includes
termination information such as truncation.
@spec cast_batch( [map()], keyword() ) :: {:ok, [term()], [t()], [Cantrip.Loom.t()], map()} | {:error, term()}
Cast multiple cantrips and return their results in request order.
When called from inside a parent code-medium turn, this uses the same explicit
parent context as cast/2, records one cast_batch observation on the
parent loom, and grafts all child turns under that parent turn.
@spec cast_stream(t(), String.t()) :: {Enumerable.t(), Task.t()}
Runs one entity episode while exposing streaming events.
Returns {stream, task} where:
streamis anEnumerableof{:cantrip_event, event}tuplestaskis aTaskthat resolves to the final{:ok, result, cantrip, loom, meta}or error
Events follow the runtime hierarchy: :step_start, :message_start,
:text, :tool_call, :tool_result, :usage, :message_complete,
:step_complete, :final_response.
@spec fork(t(), Cantrip.Loom.t(), non_neg_integer(), map()) :: {:ok, term(), t(), Cantrip.Loom.t(), map()} | {:error, term(), t()}
Deprecated compatibility wrapper for Cantrip.Loom.fork/4.
Builds a reusable cantrip from keyword or map attributes.
Required attributes are:
:llmas{module, state}implementingCantrip.LLM.:circlewith exactly one medium declaration, gates, and wards.
Optional attributes include :identity, :child_llm, :loom_storage,
:retry, and :folding.
Sends a new intent to a persistent entity.
State owned by the entity process, including loom, code-medium bindings, and message history, accumulates across all sends.
Sends a new intent with per-call options, for example stream_to: pid.
Creates a persistent entity without running an intent.
Returns {:ok, pid}. Use send/2 or send/3 to run intents against the
same process. Medium state, message history, and the loom accumulate across
those episodes.
@spec summon(t(), String.t(), keyword()) :: {:ok, pid(), term(), t(), Cantrip.Loom.t(), map()} | {:error, term(), t()}
Creates a persistent entity and immediately runs the first intent.
This is equivalent to summon/1 followed by send/2. Options such as
:stream_to are passed to the entity process.