Cantrip (Cantrip v1.3.3)

Copy Markdown View Source

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/1 to construct a reusable cantrip.
  • cast/3 to run one episode and return {result, next_cantrip, loom, meta}.
  • cast_batch/2 to fan out work to child cantrips while preserving request order.
  • summon/2 and send/3 to keep an entity process alive across multiple intents.
  • Cantrip.Loom.fork/4 to 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

t()

@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

cast(cantrip, intent)

@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.

cast(cantrip, intent, opts)

@spec cast(t(), String.t() | nil, keyword()) ::
  {:ok, term(), t(), Cantrip.Loom.t(), map()} | {:error, String.t(), t()}

cast_batch(items, opts \\ [])

@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.

cast_stream(cantrip, intent)

@spec cast_stream(t(), String.t()) :: {Enumerable.t(), Task.t()}

Runs one entity episode while exposing streaming events.

Returns {stream, task} where:

  • stream is an Enumerable of {:cantrip_event, event} tuples
  • task is a Task that 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.

fork(cantrip, loom, from_turn, opts)

This function is deprecated. Use Cantrip.Loom.fork/4.
@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.

new(attrs)

@spec new(keyword() | map()) :: {:ok, t()} | {:error, String.t()}

Builds a reusable cantrip from keyword or map attributes.

Required attributes are:

  • :llm as {module, state} implementing Cantrip.LLM.
  • :circle with exactly one medium declaration, gates, and wards.

Optional attributes include :identity, :child_llm, :loom_storage, :retry, and :folding.

send(pid, intent)

@spec send(pid(), String.t()) ::
  {:ok, term(), t(), Cantrip.Loom.t(), map()} | {:error, term()}

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.

send(pid, intent, opts)

Sends a new intent with per-call options, for example stream_to: pid.

summon(cantrip)

@spec summon(t()) :: {:ok, pid()} | {:error, term()}

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.

summon(cantrip, intent, opts \\ [])

@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.