Part III. Living with others · this page as markdown

Limits, errors and refusals

Every run has limits, and every limit has a message. This chapter goes through them, so that when a plan or a toy stops, you know what the message means and what to change. The figures are in the limits appendix, made from the code that enforces them; this chapter shows each one at work.

Three things every refusal says #

Before it runs #

A plan is read and checked whole before any of it runs. These are refused at that point.

A word that isn't in the table, with the nearest words that are (chapter 1). A form the language doesn't read, even with words it has:

Refused#err-form
(if (> (coins) 10) (chimney))
It answers
That plan writes if in a form Plazmi Lisp doesn't take, so nothing in it ran. Nothing was changed.

At the top of a plan, an if that does something needs both branches; (when (> (coins) 10) (chimney)) says this one.

A value the language doesn't read:

Refused#err-ratio
1/2
It answers
That plan has a value Plazmi Lisp doesn't take, like a character (\a) or a fraction written out (1/2), so nothing in it ran. Nothing was changed.

A step's value outside what it takes. The reply names the step by its place in the plan, and says what it takes:

Refused#err-field
(roof {:pitch 80})
It answers
…pitch…5 to 70…Nothing was changed.
Refused#err-times
(dotimes [i 101] (inc i))
It answers
Step 1's times is a whole number from 0 to 100. Nothing was changed.
Refused#err-params
(defn seven [a b c d e f g] a)
It answers
Step 1's params is a list of up to 6 different names. Nothing was changed.

Text written in a plan is any text up to 400 characters; a key or a link meant for a person isn't taken, because it never goes into the world.

While it runs #

These stop a plan partway, and nothing it did is kept.

Steps. A run has 100,000 steps of fuel: each call, each turn of a loop, each item made, costs one, and working through text costs one for every 100 characters. A loop with no way out runs out:

Refused#err-fuel
(loop [i 0] (recur (inc i)))
It answers
Out of fuel: the script stopped at 100000 steps. Nothing was changed.

Almost anything that runs away runs out of fuel first, a loop or a list that's far too long alike. Behind the fuel are two more limits, of time (3 seconds) and memory, which almost nothing reaches.

Calls. A function that calls itself too many times without returning stops before its fuel does (chapter 6): "That recursed too deeply."

Values. Some things can't be worked out:

Refused#err-divide
(/ 1 0)
It answers
Divide by zero. Nothing was changed.
Refused#err-index
(nth ["rose" "sage"] 5)
It answers
That index is past the end of the list. Nothing was changed.
Refused#err-nil
(+ 1 (get {:rings 4} :chimes))
It answers
Something there was nil (nothing) where a value was needed. Nothing was changed.

The last is the most common, and the plainest to prevent: (get m :k 0) or (or (get m :k) 0) gives a value when the key is missing.

A case with no match. A case with no last value, given a value none of its constants match:

Refused#err-case
(case :snow :sunny "Bright." :rain "Wet.")
It answers
None of that case's values matched, and it has no last value for when none does. Nothing was changed.

In a toy #

A toy's run has the same fuel, and the same stops. When a verb stops, the player is told, and nothing the verb did is kept:

#err-toy
(defobject abacus
  {:name "Abacus"
   :state {:counts []}
   :verbs {:count (fn [self who]
                    (say self (str "The third count was " (nth (:counts self) 2) ".")))}})
It answers
The abacus is ready.
Played
> count abacus
The Abacus's script stopped: That index is past the end of the list.

A toy that stops on words it overheard tells the speaker once, and its maker hears of it; the same stop isn't told again until the toy is made anew. A toy that stops at dawn says why in its maker's digest.

A toy's own limits: its state is plain data, up to 4,000 characters (chapter 12); it says up to three lines a play, 280 characters each (chapter 9); it answers to up to 24 verbs; and a resident can have up to 20 kinds of toy.

Over an hour #

The worker that runs plans is shared by everyone, so each resident's runs share a budget of steps over a rolling hour: 2,000,000, the same as twenty runs that use all their fuel, or two hundred light ones. A run that used less than a tenth of its fuel counts as a tenth. Past the budget, a plan is refused before it runs, with nothing changed, and the reply says to try again later in the day. You will rarely meet it; a resident sending the same failing plan over and over will.

Reading a refusal #

Read the refusal for what it names, change that, and send again. Don't send the same plan twice: it will be refused the same way. A few habits help:

Try it #

  1. Send (nth [] 0), read the refusal, and change the plan so it answers "none" instead.
  2. Write a cond that would stop with a nil, then fix it with or.
  3. Make a toy whose verb divides by its count, and play it before the count has ever gone up. Then fix it.
  4. Which limit would (vec (range 200000)) meet first? Send it and see.

Recap #

Next: style, or how to write a toy that lasts.

Written by Claude, Plazmi's writer, for Plazmi, a service of Manatee Media LLC. Text © 2026 Manatee Media LLC. The examples are free to copy, change and run.