Language note 01 For working developers

Deterministic programs.
Fuzzy decisions.
Evidence for every call.

MagicELISP is Emacs Lisp with one addition: LLM calls as first-class, contracted, traceable language citizens.

  • 01 Prompts are data
  • 02 Contracts are mechanical
  • 03 Traces are evidence
sentiment.magic.el MAGIC-EL
(magic-atom sentiment (text)
  "Classify the sentiment of TEXT."
  :ensure (strip-fences line-out
           (expect-enum positive
                        negative neutral))
  (magic-call
   <<<
   Answer with exactly one word:
   {{ text }}
   >>>))
event 7f3acomplete
renderhashed ensurerepaired resultpositive

“Positive.” → positive recorded

THE KERNEL One bounded fuzzy judgment
01Host builds
the closed world
02Atom makes
the judgment
03Host validates
and acts
01

What it is

Keep the program.
Bound the uncertainty.

MagicELISP is the Elisp binding of Magic, a small language family—MagicPython, MagicC, MagicJS, and MagicELISP share one kernel specification—built around a simple observation.

Most programs that use LLMs don’t need an “agent.” They need ordinary, deterministic code with a few fuzzy decision points in the middle.

Classify this. Name that. Extract these. Judge whether this paragraph is about X. Everything around those points—control flow, effects, error handling, composition—is exactly the kind of thing classical code does better than any model.

Magic inverts the usual framing. Instead of a chat loop that occasionally runs code, you write a normal program that occasionally consults a model—through a declared, validated, recorded operation called an atom.

λ

Atom

One contracted semantic event.

Molecule

Host code composing several events.

Ensure

A mechanical output contract.

Trace

The evidence left by every call.

02

The problem it actually solves

An API call is easy.
A reliable boundary is not.

If you’ve wired an LLM API into a program, you know the failure modes. The call returns prose when you wanted JSON. It returns JSON wrapped in markdown fences. It returns the right shape with a hallucinated value. It works in testing and drifts in production.

Then someone asks: “What exactly did we send, and what came back?” The answer is a shrug, because the prompt was assembled from string concatenation across three functions and nothing was logged.

The usual response is either a heavyweight framework or fifty lines of defensive parsing per call site. MagicELISP makes the call site itself carry the contract.

sentiment.magic.el
(magic-atom sentiment (text)
  "Classify the sentiment of TEXT."
  :ensure (strip-fences line-out
           (expect-enum positive negative neutral))
  (magic-call
   <<<
   Classify the sentiment of the following text.
   Answer with exactly one word: positive, negative, or neutral.

   {{ text }}
   >>>))

This defines an ordinary Elisp function, sentiment. Calling (sentiment "I love this") sends the rendered prompt to the model your environment routes to. Before your code sees the result, the runtime strips markdown fences, demands a single line, and requires one of three tokens.

Model outputContract resultYour code receives
Positive.repair recordedpositive
neutralpassneutral
Here is my analysis…rejectedstructured error
03

How it works, mechanically

Templates become data.
Data gains identity.

You write .magic.el files: Emacs Lisp plus template islands, the <<<>>> blocks. A small pre-reader lowers those islands into plain Elisp data and places an ordinary .el file in __magic_cache__/, which is what Emacs loads.

sourcescratch.magic.elElisp + islands
pre-readerlower + hashtemplates become data
cachescratch.elordinary Elisp
runtimeevent + attemptsvalidated evidence

During development, M-x magic-eval-buffer compiles and loads in one step. For scripts, magic-run-file invokes a magic-main entrypoint. The cache invalidates automatically when either the compiler or source changes.

Why tolerate compilation in a language famous for having none? Because islands aren’t strings—they’re templates with identity. Every template, rendered prompt, and call-options set has a content hash. Months later, a trace can identify the exact template version, rendered text, model route, and gateway request behind an answer.

String concatenation cannot give you that. It also cannot give you portability: the same template travels unchanged between MagicELISP and MagicPython because templates belong to the kernel, not the host.

04

Templating in two minutes

Visible bytes.
Composable values.

Inside <<<>>>, text is literal and {{ name }} is a hole filled from a lexical variable. Holes take text or template fragments. If you need a number, render it explicitly with (number-to-string n).

That deliberate law keeps the bytes sent over the wire visible and hashable. No locale-dependent or binding-dependent implicit formatting gets to decide your prompt behind your back.

reviewer.magic.el
(defun reviewer-persona ()
  <<<
  You are a terse senior code reviewer. You only flag genuine
  problems: bugs, unhandled errors, security risks, data loss.
  >>>)

(magic-atom review-hunk (hunk)
  "Judge one diff hunk."
  :ensure (strip-fences json-out (expect-keys verdict))
  (magic-call
   <<<
   Review this diff hunk:

   {{ hunk }}
   >>>
   :system (reviewer-persona)))

:system is a prompt component. It ships as a genuine system message where supported, its rendered text contributes to call identity, and it never mixes with per-call task data. Stable “who you are and how you answer” text lives there; volatile payload stays in the user template.

05

The ensure gradient

No model ever
validates a model.

:ensure chains run mechanically. Each operation can pass, repair trivial noise such as casing or fences, or fail with a structured error. Repairs and failures are both recorded in the trace.

strip-fences

Remove markdown wrappers.

line-out

Demand a single line.

json-out

Parse JSON into host values.

expect-enum

Close the set of legal labels.

expect-keys

Require object fields.

regexp-compiles

Prove a regexp loads.

elisp-expr-compiles

Prove returned Elisp reads.

Close the world at runtime

Build legal answers from live program state. The model then cannot file a note under a heading you don’t have.

dynamic-enum.magic.el
(magic-call prompt
  :ensure (list 'strip-fences 'line-out
                (cons 'expect-enum
                      (mapcar #'intern my-headings))))

Dereference, don’t hallucinate

Number the options, ask for a number, enum over 0..N-1, and dereference host-side. Hallucinated targets stop being prompt engineering and become a type error.

For executable artifacts, ask for the artifact plus test examples, then let the host run them. Shape is enforced by ensure; meaning is enforced by you.

06

Molecules, concurrency, failures

Orchestration is
ordinary Elisp.

A molecule is an ordinary function that happens to trigger several semantic events. Magic classifies by observation: zero events is host code, one is an atom, several is a molecule. Declare intent with magic-molecule and mismatches become visible in traces.

magic-awaitwait for one
magic-allrun many
magic-map-boundedlimit concurrency
magic-thenchain work

The idiomatic fan-out converts each failure into a row instead of aborting the entire batch:

bounded-fanout.magic.el
(magic-await
 (magic-map-bounded
  (lambda (chunk) (soccur-judge question chunk))
  chunks :max-in-flight 3 :failure-mode 'data))

One provider hiccup becomes one error row; the other nineteen answers survive. The host decides what failure means—retry, skip, surface—because the host owns control flow, always.

07

Defaults versus the metal

Declare intent.
Reach the full API.

DEFAULTSwhere you should usually live
(magic-atom summarize (text)
  "One-sentence summary of TEXT."
  (magic-call
   <<<
   Summarize in one sentence:
   {{ text }}
   >>>))
ON THE METALwhen you know what you need
(magic-atom summarize (text)
  :model thorough
  :temperature 0
  :max-tokens 60
  :top-p 0.9
  :ensure (strip-fences line-out)
  (magic-call prompt
    :system persona
    :slot (buffer-name)))

Without a named model or knobs, the call routes through the current environment: an env.toml mapping semantic tiers to concrete provider routes.

env.toml
[tier]
quick    = "deepseek-intl-openai:deepseek-v4-flash"
balanced = "deepseek-intl-openai:deepseek-v4-flash"
thorough = "deepseek-intl-openai:deepseek-v4-pro"
default  = "deepseek-intl-openai:deepseek-v4-flash"

Swap environments with (setq magic-current-env "free") and every atom reroutes without code changes. :model quick means “cheap and fast,” not a vendor SKU.

Every unknown generation option passes through to the provider, respelled where necessary—:max-tokens becomes max_tokens. Identity and routing options such as :model, :ensure, :system, :tools, and :slot are partitioned out and never leak into generation.

08

Evidence

When it behaves oddly,
you don’t reconstruct. You read.

Every call leaves a record. The event holds the definition, template hashes, system hash, optional rendered content, and slot. Its attempts hold the route, latency, token usage, each ensure outcome, structured failure details, and the gateway request id.

trace / event 7f3a9c
14:32:08.418
ATOMsentimentslot: buffer/messages.org
template
sha256:a91e…4fd2matched
route
quick → deepseek-v4-flash284 ms
ensure[0]
strip-fencespass
ensure[1]
line-outrepair
ensure[2]
expect-enumpass
gateway
req_01JQ…N3Tjoinable

Traces become the debugging story, the regression corpus, and the honest answer to “is the cheap model actually good enough for this atom?” That is an empirical question your own evidence can settle.

09

Idiomatic Magic—and free jazz

Recommendations with reasons.
Not walls.

  • 01

    Keep atoms narrow. Closed outputs—enums and strict JSON—beat prose.

  • 02

    Keep stable text in :system. Let volatile payload remain in the user template.

  • 03

    Let the host own every effect. Atoms judge; hosts write. Edits and files go behind explicit confirmation.

  • 04

    Give concurrent calls a :slot. Make failures data so partial success survives.

  • 05

    Never trust shape as meaning. Pair mechanical ensures with host-side semantic tests.

None of this is enforced. You can call the biggest model at temperature 1.2 with no ensure chain from a hook that rewrites your buffer on save. The language will let you, trace it faithfully, and say nothing.

What Magic insists on is small and non-negotiable: prompts have identity, declared contracts run mechanically, correlation is per transaction, and evidence is recorded. Within that, whatever works for you works.

10

When to reach for it

Use a model last,
not first.

REACH FOR MAGIC

Easy to state.
Miserable to enumerate.

  • Triaging notes into your actual Org headings
  • Judging paragraphs by meaning, not regexp
  • Extracting constrained action items
  • Reviewing diffs for genuine hazards
  • Generating Elisp that provably compiles
DON’T REACH FOR MAGIC

Already deterministic.
Or openly conversational.

  • A lookup table already solves it
  • A regexp expresses the rule honestly
  • Twenty lines of Elisp will do
  • The task is open-ended conversation
  • You cannot validate the consequence

The pattern is always the same: deterministic host builds a closed world, one bounded fuzzy judgment happens inside it, deterministic host acts on the result. Magic is for programs, not chats.

11

Getting started

Your first trace
in four moves.

  1. 01

    Write an atom

    Put it in scratch.magic.el.

  2. 02

    Compile and load

    Run M-x magic-eval-buffer.

  3. 03

    Call it

    Open M-x ielm and await the result.

  4. 04

    Read the evidence

    Check routing with M-x magic-env-show, then inspect M-x magic-trace-show.

Before touching a live API

Script the model.
Test the contract.

The runtime works offline. Bind a scripted provider and your tests become deterministic and free.

sentiment-test.el
(magic-use-provider
    (magic-scripted-provider '("neutral"))
  (should
   (equal (magic-await
           (sentiment "meh"))
          "neutral")))

The model is the least trusted component in every Magic program. The language’s whole job is to make that distrust cheap, declared, and recorded—so you can spend your attention on the program.