<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dirk Mattig</title>
    <description>The latest articles on DEV Community by Dirk Mattig (@newadventuresinit).</description>
    <link>https://dev.to/newadventuresinit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3937640%2F60dcd699-87e8-4614-957f-60a6ea2af6ac.png</url>
      <title>DEV Community: Dirk Mattig</title>
      <link>https://dev.to/newadventuresinit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/newadventuresinit"/>
    <language>en</language>
    <item>
      <title>By Shape, Not by Name: Types and Numbers in Neander</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Fri, 31 Jul 2026 05:33:01 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/by-shape-not-by-name-types-and-numbers-in-neander-39eh</link>
      <guid>https://dev.to/newadventuresinit/by-shape-not-by-name-types-and-numbers-in-neander-39eh</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/newadventuresinit/without-exception-how-neander-programs-fail-1i4o"&gt;Last time&lt;/a&gt; covered failure, in the program and in the envelope. This is the second encore and the last entry in the series, and it takes that closer look at the type system: structural, recursive, and reaching down to integers and decimals of arbitrary precision.&lt;/p&gt;

&lt;p&gt;As we saw, every type error in Neander is a Flaw. There is no such thing as a type error at runtime, because by the time a program runs there is no question left about the type of anything in it. The interpreter still has runtime errors to report, division by zero and an index past the end of a list, but none of them are about types.&lt;/p&gt;

&lt;p&gt;That is the arrangement the whole type system is built around, and most of what follows is a consequence of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole type universe
&lt;/h2&gt;

&lt;p&gt;Neander has five base types: &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;decimal(S, R)&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, and &lt;code&gt;null&lt;/code&gt;. There are three composites: records, lists &lt;code&gt;[T]&lt;/code&gt;, and maps &lt;code&gt;map&amp;lt;V&amp;gt;&lt;/code&gt; with string keys. And any type can carry two wrappers: &lt;code&gt;T?&lt;/code&gt; for a value that may be absent, and &lt;code&gt;T!&lt;/code&gt; for one that may be an error. That is the complete inventory.&lt;/p&gt;

&lt;p&gt;Absent are classes, interfaces, inheritance, generics beyond a list's element type and a map's value type, enums, user-defined unions, a void type, and functions as values.&lt;/p&gt;

&lt;p&gt;The size of the inventory matters for a reason specific to this language beyond general simplicity. The Reference the runtime hands back on a &lt;a href="https://dev.to/newadventuresinit/the-cold-start-learning-the-language-by-submitting-a-program-39il"&gt;cold start&lt;/a&gt; has to describe all of it in-band, next to the actual task, inside a context window. A type system that needs a book is a type system the agent will read a summary of.&lt;/p&gt;

&lt;h2&gt;
  
  
  A word on values
&lt;/h2&gt;

&lt;p&gt;Every value in a Neander program is immutable. A list cannot grow, a map cannot take a new entry, a record field cannot be reassigned, and a name cannot be rebound. Values are constructed and then read. The cost of this approach is real: combining two lists means building a third. That is what the spread operator is for, in list and map literals alike, and &lt;code&gt;map { ..defaults, ..overrides }&lt;/code&gt; is the shape that motivated it.&lt;/p&gt;

&lt;p&gt;The cost is worth paying, and the null rules below are where the return shows up. The type checker also gets to stay small, because it never has to track who else is holding a reference to what.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shape, not name
&lt;/h2&gt;

&lt;p&gt;Record compatibility in Neander is structural. A record value fits a target record type if it has all the fields the target declares, with compatible types. Extra fields are ignored. The names of the two types are not consulted at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;FullOrder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;half_away&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;OrderSummary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;half_away&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;OrderSummary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FullOrder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;9.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"new"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One rule, applied at every boundary where a record meets a target type: &lt;code&gt;let&lt;/code&gt; bindings, &lt;code&gt;call&lt;/code&gt; parameters, &lt;code&gt;call&lt;/code&gt; return values, &lt;code&gt;return&lt;/code&gt;, &lt;code&gt;yield&lt;/code&gt;, and record field assignment.&lt;/p&gt;

&lt;p&gt;Two things follow immediately. The agent declares only the fields it actually intends to use, so a program that needs an order's total does not have to restate the eleven other fields the API returns. And a program does not break when the API adds a twelfth.&lt;/p&gt;

&lt;p&gt;The deeper reason is about what an agent can verify. Field shapes come back from &lt;code&gt;discover&lt;/code&gt;, and the validator checks the program against the manifest before anything runs, so a wrong shape is caught with a line and a column. A type &lt;em&gt;name&lt;/em&gt; the agent invented has no authority behind it whatsoever. Under nominal typing that invented name still has to match something; under structural typing there is no site left where a name has to be right at all. The error class is not reduced, it is deleted. That is a bet about where agent-written code goes wrong, and it is a cheap bet, because nothing is lost if it turns out to be the wrong one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recursive by coinduction
&lt;/h2&gt;

&lt;p&gt;A record type may reference itself, directly or through a cycle. During a single compatibility check, encountering the same ordered pair of source and target record types again satisfies that recursive obligation. The check fails only if it finds a concrete mismatch in a field, a wrapper, or a base type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Comment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;CommentText&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CommentText&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checking a &lt;code&gt;Comment&lt;/code&gt; against &lt;code&gt;CommentText&lt;/code&gt; starts with &lt;code&gt;body&lt;/code&gt;, which matches, and then reaches &lt;code&gt;replies&lt;/code&gt;, which sends the check into &lt;code&gt;Comment&lt;/code&gt; against &lt;code&gt;CommentText&lt;/code&gt; a second time. That pair is already open, so the obligation is discharged there and the check succeeds. Without the rule it would descend forever, on two types small enough to verify at a glance.&lt;/p&gt;

&lt;p&gt;That is a coinductive definition: assume the types are compatible and look for a contradiction, rather than assemble compatibility out of proven parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Literals have no names either
&lt;/h2&gt;

&lt;p&gt;Record, list, and map literals are anonymous. No literal names a type. Each one is typed entirely by its context, meaning the declared type at the position where it appears.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"prod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"us-east-1"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;unitPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;9.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inStock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An empty &lt;code&gt;[]&lt;/code&gt; or &lt;code&gt;map {}&lt;/code&gt; takes its element type from that context too. Because &lt;code&gt;let&lt;/code&gt; requires an annotation, and parameters and fields are always typed, every position where a literal can appear has a declared type, so a literal never lacks a context to be typed by.&lt;/p&gt;

&lt;p&gt;Names do not appear in compatibility checks, and now they do not appear at construction sites either. A program can be wrong about a type's shape, and the validator will say so precisely. It cannot be wrong about a type's name, because it never writes one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The types with no shape
&lt;/h2&gt;

&lt;p&gt;Three types complement this design: &lt;code&gt;Namespace&lt;/code&gt;, &lt;code&gt;Function&lt;/code&gt;, and &lt;code&gt;Document&lt;/code&gt;, the handles &lt;a href="https://dev.to/newadventuresinit/discovery-how-an-agent-finds-your-apis-hh7"&gt;discovery&lt;/a&gt; produces. They are a peer category to records rather than a special kind of record, and they have no program-observable structure at all. A program cannot build one with a literal, read a field off one, or convert one to a string. It can bind one, store it, pass it back to &lt;code&gt;discover&lt;/code&gt;, and return it.&lt;/p&gt;

&lt;p&gt;So the structural question never arises for them. Their compatibility is by type identity, because there is nothing to match structurally in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparability is a property of types
&lt;/h2&gt;

&lt;p&gt;A type is comparable if it is a base type, a nullable of a comparable type, or a composite whose parts are all comparable. Failable types are not comparable, discovery handles are not comparable, and neither is any composite that transitively contains one. Applying &lt;code&gt;==&lt;/code&gt; to those is a type error, caught at validation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;==&lt;/code&gt; and &lt;code&gt;!=&lt;/code&gt; extend structurally to composites. Lists compare element-wise and by length, maps compare by key set and by value, and nullables compare equal when both are null. Map iteration order is not observable, so equality does not consider it. Records compare field-wise, with one condition: both sides must have the same declared record type. That is the one site in the language where a type's identity is consulted, and a mismatch there is a type error rather than a silent &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What follows is that &lt;code&gt;==&lt;/code&gt; never has to do anything at runtime beyond what the validator already proved safe. There is no case where a structural comparison meets a value it does not know how to compare, so there is no silent &lt;code&gt;false&lt;/code&gt; for incomparable operands and no runtime error code for the situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manifest types, by name
&lt;/h2&gt;

&lt;p&gt;Program-declared types and manifest-declared types live in separate scopes, bridged structurally at each &lt;code&gt;call&lt;/code&gt; site. Sometimes redeclaring is not worth the lines, so a program can name a manifest type directly with a qualified type name: &lt;code&gt;orders.Order&lt;/code&gt; references the type exactly as the manifest declares it, with every field, and no &lt;code&gt;types&lt;/code&gt; entry of its own.&lt;/p&gt;

&lt;p&gt;The naming convention keeps this unambiguous. Type names are PascalCase and everything else is camelCase, so &lt;code&gt;orders.Order&lt;/code&gt; and &lt;code&gt;orders.getOrder&lt;/code&gt; are told apart by one character, and no parser has to guess whether the second segment is a function or a type.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard wall around null
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;T?&lt;/code&gt; and &lt;code&gt;T&lt;/code&gt; are strictly separated. A value whose type is &lt;code&gt;string&lt;/code&gt; is never null: there is no widening, no cast, and no runtime path that puts a null there.&lt;/p&gt;

&lt;p&gt;Conversion runs in one direction only. A &lt;code&gt;T&lt;/code&gt; is assignable wherever a &lt;code&gt;T?&lt;/code&gt; is expected, which the spec calls nullable widening, and it applies deep: &lt;code&gt;[int]&lt;/code&gt; goes where &lt;code&gt;[int?]&lt;/code&gt; is expected, and a record with an &lt;code&gt;int&lt;/code&gt; field goes where the target's field is &lt;code&gt;int?&lt;/code&gt;. That is sound purely because nothing is mutable. Nothing can reach the widened value through the wider view and write a null into it, because nothing can write at all. The reverse direction stays closed. A &lt;code&gt;T?&lt;/code&gt; is never assignable to a &lt;code&gt;T&lt;/code&gt; without &lt;code&gt;??&lt;/code&gt; or &lt;code&gt;=?&lt;/code&gt;. In particular there is no flow-sensitive type narrowing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="py"&gt;.customerName&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"anonymous"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// name is still string? here&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"anonymous"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checking a value against &lt;code&gt;null&lt;/code&gt; does not change its type in the other branch. Flow-sensitive narrowing is some of the most intricate machinery in the Kotlin and TypeScript compilers, and Neander's entire type checker is a single validation pass. &lt;code&gt;??&lt;/code&gt; covers the vast majority of null handling in one expression and &lt;code&gt;=?&lt;/code&gt; covers the rest.&lt;/p&gt;

&lt;p&gt;There is a second argument for that beyond implementation cost. An agent that has to model the checker's flow analysis in order to predict whether its program validates is an agent that will occasionally predict wrong, and it only finds out one submission later.&lt;/p&gt;

&lt;p&gt;There is no failable counterpart for widening. A &lt;code&gt;T&lt;/code&gt; is not assignable to a &lt;code&gt;T!&lt;/code&gt;, nor a &lt;code&gt;T?&lt;/code&gt; to a &lt;code&gt;T?!&lt;/code&gt;. Failable values originate only from a &lt;code&gt;call&lt;/code&gt;, as the last post laid out, and a widening rule that could manufacture a &lt;code&gt;!&lt;/code&gt; would quietly undo that guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two axes, one operator
&lt;/h2&gt;

&lt;p&gt;A value can carry both wrappers. &lt;code&gt;T?!&lt;/code&gt; is a value that may be an error, and may otherwise be null. The &lt;code&gt;!&lt;/code&gt; always comes last, and &lt;code&gt;T!?&lt;/code&gt; is not a type.&lt;/p&gt;

&lt;p&gt;Both &lt;code&gt;=?&lt;/code&gt; and &lt;code&gt;??&lt;/code&gt; are target-driven. The declared type of the binding decides which wrappers come off, and the operation is always a single step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Order?! to OrderSummary?, in one =?&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;maybeSummary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OrderSummary&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=?&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="nf"&gt;.findMaybe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two independent things happen in that line. On the union-layer axis, the binding keeps the &lt;code&gt;?&lt;/code&gt; and drops the &lt;code&gt;!&lt;/code&gt;, so an error throws out of the enclosing block while a null flows through untouched. On the structural axis, and underneath the &lt;code&gt;?&lt;/code&gt; that survived, &lt;code&gt;Order&lt;/code&gt; narrows to &lt;code&gt;OrderSummary&lt;/code&gt; by the ordinary structural rule.&lt;/p&gt;

&lt;p&gt;The axes compose, and the composition is still one &lt;code&gt;=?&lt;/code&gt;. Peeling &lt;code&gt;T?!&lt;/code&gt; down to &lt;code&gt;T&lt;/code&gt; is one target type that happens to strip both layers, not two operations written in sequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers, exactly
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;int&lt;/code&gt; type is arbitrary-precision. There is no 32-bit or 64-bit variant, no maximum, and no overflow.&lt;/p&gt;

&lt;p&gt;Fixed-width integers behave differently across platforms, and they do it silently. In API orchestration an &lt;code&gt;int&lt;/code&gt; is an identifier, a count, or a monetary amount in minor units, and silently wrapping any of those does not produce an approximation, it produces a wrong answer that looks like a right one.&lt;/p&gt;

&lt;p&gt;The cost objection answers itself. A Neander program spends its time in API calls measured in milliseconds, and a few hundred bignum operations are microseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scale belongs to the binding
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;decimal&lt;/code&gt; is an arbitrary-precision finite decimal number, and its declared type carries two things: a scale &lt;code&gt;S&lt;/code&gt;, the number of fractional digits, and a rounding mode &lt;code&gt;R&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;half_away&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// commercial rounding&lt;/span&gt;
&lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;half_even&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// banker's rounding&lt;/span&gt;
&lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// integer-like, toward negative infinity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decision that shapes everything else is that scale and rounding mode attach to the &lt;em&gt;binding&lt;/em&gt;, not to the value. Once a value exists, only its mathematical magnitude is part of its identity.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;1.50&lt;/code&gt; and &lt;code&gt;1.5000&lt;/code&gt; are the same number, and &lt;code&gt;==&lt;/code&gt; says so. The pitfall where two spellings of the same amount compare unequal, familiar from every decimal library that makes scale part of the value, does not exist here, because scale was never in the value to begin with.&lt;/p&gt;

&lt;p&gt;Scale and mode are per binding rather than per program because one program legitimately needs several. Commercial rounding on an invoice total, banker's rounding on accrued interest, floor on an amount withheld. Five modes exist: &lt;code&gt;half_away&lt;/code&gt;, &lt;code&gt;half_to_zero&lt;/code&gt;, &lt;code&gt;half_even&lt;/code&gt;, &lt;code&gt;floor&lt;/code&gt;, and &lt;code&gt;ceiling&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Arithmetic is exact. &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt; produce a result at the larger of the two natural scales, &lt;code&gt;*&lt;/code&gt; at the sum of them, and no rounding happens in either case. Mixing scales is always allowed, and intermediate results carry their own natural scale and no rounding mode at all.&lt;/p&gt;

&lt;p&gt;Integer division truncates toward zero and is unremarkable. Decimal division is the interesting case. &lt;code&gt;1.0 / 3.0&lt;/code&gt; has no finite decimal expansion, so the language refuses to divide until it knows where to stop. A decimal &lt;code&gt;/&lt;/code&gt; must appear in a position whose target type is a known &lt;code&gt;decimal(S, R)&lt;/code&gt;: the right-hand side of an annotated &lt;code&gt;let&lt;/code&gt;, an argument to a typed parameter, the expression of a &lt;code&gt;return&lt;/code&gt; when &lt;code&gt;main&lt;/code&gt; returns a decimal, or the value expression of a &lt;code&gt;yield&lt;/code&gt; under a map form with a decimal element type. Anywhere else it is a &lt;code&gt;type_error&lt;/code&gt; Flaw, with the hint &lt;em&gt;"decimal division requires a target scale; bind the result to a typed &lt;code&gt;decimal(S, R)&lt;/code&gt;"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Which is the rule this post opened with, reaching arithmetic. An operation whose result is undefined until someone makes a policy decision is not a runtime condition to detect and report. It is a program the validator declines to run.&lt;/p&gt;

&lt;p&gt;Rounding therefore happens in exactly two places: at a decimal &lt;code&gt;/&lt;/code&gt;, to the target's scale and mode, and at assignment to a typed binding whose scale is smaller than the value's natural scale. Everywhere else the arithmetic is exact. Cross-implementation determinism is nothing more than that fact, because every rounding step in a valid program has a statically known scale and a statically known mode, and two conforming runtimes have nothing left to disagree about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;half_away&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;29.9900&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;half_away&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0725&lt;/span&gt;

&lt;span class="c1"&gt;// exact intermediate at natural scale 8, rounded to 2 at the binding&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;half_away&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;taxRate&lt;/span&gt;    &lt;span class="c1"&gt;// 2.17427500 becomes 2.17&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no implicit conversion between &lt;code&gt;int&lt;/code&gt; and &lt;code&gt;decimal&lt;/code&gt;. A numeric literal containing a dot is a decimal literal and its natural scale is the number of digits after the dot; a literal without one is an &lt;code&gt;int&lt;/code&gt;; mixing the two in a single arithmetic expression is a type error. &lt;code&gt;toDec()&lt;/code&gt; exists to parse a string of unknown provenance or to convert an &lt;code&gt;int&lt;/code&gt; value, not to write down a constant. A known constant is written as a literal, so &lt;code&gt;order.total &amp;gt; 1000.00&lt;/code&gt; rather than &lt;code&gt;order.total &amp;gt; toDec("1000", 2, half_away)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One last rule holds the rest together. A decimal leaves in the response envelope as a JSON string, &lt;code&gt;"10.50"&lt;/code&gt;, never as the JSON number &lt;code&gt;10.5&lt;/code&gt;. The exactness above would be undone at the last step by a parser on the other side that reads numbers into a &lt;code&gt;double&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grotto's take on numbers
&lt;/h2&gt;

&lt;p&gt;Grotto represents an &lt;code&gt;int&lt;/code&gt; as a JavaScript &lt;code&gt;bigint&lt;/code&gt;, and a &lt;code&gt;decimal&lt;/code&gt; as a bigint coefficient plus a natural scale, where the value is the coefficient times ten to the negative scale. Rounding decisions are made by comparing remainders as integers. No Neander number is ever routed through a JavaScript float, not even in the places where doing so would have been convenient.&lt;/p&gt;

&lt;p&gt;The choice reaches the response envelope as well. The platform's JSON serializer refuses a &lt;code&gt;bigint&lt;/code&gt; outright, so Grotto writes envelopes with its own, which emits integers as raw digit runs. A forty-digit &lt;code&gt;int&lt;/code&gt; arrives with forty digits.&lt;/p&gt;

&lt;p&gt;Arbitrary precision has a second consequence, and this one lands on the &lt;a href="https://dev.to/newadventuresinit/the-budget-system-thalers-bytes-and-milliseconds-47fo"&gt;budget system&lt;/a&gt;. Everywhere else in the interpreter, computation cost is a reasonable proxy for memory cost. Work is charged in Thalers, and the values that work produces are bounded by operands that were themselves already paid for. Exact decimal arithmetic breaks that relationship. Squaring a decimal doubles its digit count and costs one Thaler. Do it repeatedly and the Thaler count rises by one per step while the memory rises by a factor of two, which is a program that is computationally trivial and enormous at the same time.&lt;/p&gt;

&lt;p&gt;So Grotto charges memory in proportion to digit count rather than a flat cost per value. And for the handful of operations whose result size is named by a scale parameter in the source rather than bounded by their operands, meaning &lt;code&gt;toDec&lt;/code&gt; to a large scale, a division with a large target scale, and a rounding step that scales upward, it computes the digit count the coefficient is about to have and checks that against the memory budget &lt;em&gt;before&lt;/em&gt; the bignum is built. An over-budget scale raises a cooperative memory Abort instead of allocating first and noticing afterward.&lt;/p&gt;

&lt;p&gt;That is a budget check and not a precision cap. There is no maximum scale in the language and none in the runtime. Arbitrary precision stays arbitrary, and the memory budget remains the only ceiling, the same one that binds every other allocation the program makes. Thalers are portable and kilobytes are not, and this is what that distinction looks like once it reaches actual code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field Notes from the Grotto
&lt;/h2&gt;

&lt;p&gt;The series started with a runtime and ends with rounding modes, which is roughly the right order. The arguments that decide whether an idea is interesting live at the top, and the ones that decide whether it is usable live at the bottom.&lt;/p&gt;

&lt;p&gt;Every entry turned out to be about something that is not there. Neander cannot recurse, cannot loop without a bound, and cannot reach a socket, not because a guard turns the attempt away but because there is no way to write the attempt down. Spending and isolation are the two places where something is genuinely enforced, and they are the smaller half of the story. The type system is the cheapest version of the same move, because none of it costs anything at runtime: a name that never has to match, a null that cannot appear where it was not declared, a division that will not happen until someone has said where to round it.&lt;/p&gt;

&lt;p&gt;That is the tour. The series ends here, but there is still time to read the &lt;a href="https://newadventuresinit.github.io/neander/" rel="noopener noreferrer"&gt;Neander&lt;/a&gt; spec, embed &lt;a href="https://github.com/newadventuresinit/grotto" rel="noopener noreferrer"&gt;Grotto&lt;/a&gt; in your own app, and let me know what shape it comes back in.&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Without Exception: How Neander Programs Fail</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Tue, 28 Jul 2026 03:50:02 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/without-exception-how-neander-programs-fail-1i4o</link>
      <guid>https://dev.to/newadventuresinit/without-exception-how-neander-programs-fail-1i4o</guid>
      <description>&lt;p&gt;Neander has no exceptions. No &lt;code&gt;try&lt;/code&gt;, no &lt;code&gt;catch&lt;/code&gt;, no &lt;code&gt;finally&lt;/code&gt;. A call to one of the host application's APIs returns something closer to Rust's &lt;code&gt;Result&lt;/code&gt;: either the answer, or the reason there is no answer. In place of a catch block there is one type marker, three operators, and a guarantee that every submission comes back in the same shape no matter what happened.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/newadventuresinit/inside-grotto-isolation-and-the-api-boundary-4pbp"&gt;Last time&lt;/a&gt; the foundational series closed with isolation. This is the first of two encores, and it takes the subject that came up in nearly every entry without ever being laid out in full: what happens when something goes wrong.&lt;/p&gt;

&lt;p&gt;There are two answers, because there are two audiences. An error is a &lt;em&gt;value&lt;/em&gt; while the program runs, and a &lt;em&gt;verdict&lt;/em&gt; once it has stopped. The two are made of the same parts, on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failable type
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;call&lt;/code&gt; returns a failable type, written &lt;code&gt;T!&lt;/code&gt;. It carries either a value of type &lt;code&gt;T&lt;/code&gt; or an error with a code, a message, and the name of the function that produced it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;T!&lt;/code&gt; is the mirror of the nullable type &lt;code&gt;T?&lt;/code&gt;. Same shape, different question: one asks whether a value is there at all, the other asks whether obtaining it worked. The mirroring runs deeper than the notation, because the same three operators serve both types. A failure gets no unwrapping vocabulary of its own.&lt;/p&gt;

&lt;p&gt;Those three are &lt;code&gt;=?&lt;/code&gt;, &lt;code&gt;??&lt;/code&gt; and &lt;code&gt;is&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// narrow, or throw the error out of the enclosing block&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;=?&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// or substitute a default&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="n"&gt;emptyOrder&lt;/span&gt;

&lt;span class="c1"&gt;// or inspect it and decide&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;Order!&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;emptyOrder&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A standalone &lt;code&gt;call&lt;/code&gt; statement, one without a &lt;code&gt;let&lt;/code&gt;, narrows implicitly: the error is thrown and the success value is discarded.&lt;/p&gt;

&lt;p&gt;One property does the heavy lifting throughout the rest of this post: &lt;code&gt;T!&lt;/code&gt; originates &lt;em&gt;only&lt;/em&gt; from a &lt;code&gt;call&lt;/code&gt;. No expression picks up a &lt;code&gt;!&lt;/code&gt; along the way, and no widening rule introduces one. The marker means exactly one thing, namely that the value came from outside the program.&lt;/p&gt;

&lt;p&gt;Which is also why it appears nowhere else. Division by zero, an index out of bounds, a missing map key: none of them change an expression's type. &lt;code&gt;total / count&lt;/code&gt; is an &lt;code&gt;int&lt;/code&gt; and never an &lt;code&gt;int!&lt;/code&gt;, and the agent guards it with &lt;code&gt;if count != 0&lt;/code&gt; rather than with the type system. That is inconsistent, and it is deliberate. Making runtime errors failable would put a &lt;code&gt;!&lt;/code&gt; on very nearly every expression in the language, where it would come to mean "something could go wrong here", which is true of all code everywhere and therefore worth nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  There is no catch
&lt;/h2&gt;

&lt;p&gt;Errors propagate by throwing, and throwing is control flow, not a value. &lt;code&gt;=?&lt;/code&gt; throws from the immediately enclosing block. Inside an &lt;code&gt;each&lt;/code&gt; or a &lt;code&gt;repeat&lt;/code&gt;, that ends the iteration and hands the error to the scope above.&lt;/p&gt;

&lt;p&gt;Nothing catches it, because the language has no construct that catches. A thrown error cannot be bound to a failable type and recovered, since failable values come from calls and never from throws. An error that no branch handled keeps rising until it leaves &lt;code&gt;main&lt;/code&gt;, and at that point the program is over.&lt;/p&gt;

&lt;p&gt;Which is why &lt;code&gt;main -&amp;gt; Order!&lt;/code&gt; is a type error, rejected before the program runs. An error does not leave &lt;code&gt;main&lt;/code&gt; through the return type. It leaves by being thrown.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;main -&amp;gt; [Order!]&lt;/code&gt; is perfectly valid, though. The return type there is a list, and a list is not failable. Only its elements are. That distinction looks like a technicality, and most of the design rests on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errors are data
&lt;/h2&gt;

&lt;p&gt;The type checker refuses to let a &lt;code&gt;T!&lt;/code&gt; reach a position that expects a &lt;code&gt;T&lt;/code&gt;. It does not, however, insist that every &lt;code&gt;T!&lt;/code&gt; be resolved. A failable value can be stored in a record field, held in a list, checked with &lt;code&gt;is&lt;/code&gt;, or returned as part of a composite result, all without ever being narrowed. The rule binds at boundaries; it is not ceremony on every line.&lt;/p&gt;

&lt;p&gt;That is what turns partial failure into something a program can express. Consider fifty payment initiations, written the strict way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PaymentRef&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=?&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;invoices&lt;/span&gt;&lt;span class="nf"&gt;.list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"approved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PaymentRef&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PaymentRef&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PaymentRef&lt;/span&gt; &lt;span class="o"&gt;=?&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="nf"&gt;.initiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="py"&gt;.id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="py"&gt;.total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;refs&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first failed initiation throws out of the &lt;code&gt;each&lt;/code&gt;, then out of &lt;code&gt;main&lt;/code&gt;, and the other forty-nine outcomes are gone. Whether the eleventh invoice was paid is not in the response.&lt;/p&gt;

&lt;p&gt;Now remove one operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nd"&gt;PaymentRef!&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=?&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;invoices&lt;/span&gt;&lt;span class="nf"&gt;.list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"approved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nd"&gt;PaymentRef!&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="n"&gt;invoices&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nd"&gt;PaymentRef!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="nf"&gt;.initiate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="py"&gt;.id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="py"&gt;.total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;refs&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same program, one &lt;code&gt;=?&lt;/code&gt; apart. This one runs to the end and returns all fifty outcomes, successes and failures side by side. Each element serializes with its own discriminator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8801&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"invoiceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"150.00"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vendor account frozen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments.initiate"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is deliberately &lt;code&gt;ok&lt;/code&gt; rather than &lt;code&gt;success&lt;/code&gt;, to keep it distinct from the top-level field of the response envelope. More on that field in a moment.&lt;/p&gt;

&lt;p&gt;This is the case that exceptions make genuinely awkward. An exception unwinds, so producing a partial-failure report out of one takes a &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; around every iteration plus a hand-rolled accumulator, written correctly every time. Here it is the &lt;em&gt;absence&lt;/em&gt; of an operator. &lt;code&gt;[PaymentRef!]&lt;/code&gt; serializes into the response envelope with one &lt;code&gt;ok&lt;/code&gt; flag per element, so the agent receives a report rather than a stack trace, and knows what to retry without submitting a second program to find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  One envelope, every time
&lt;/h2&gt;

&lt;p&gt;Once an error leaves &lt;code&gt;main&lt;/code&gt;, it stops being a value. It becomes the runtime's verdict on the submission.&lt;/p&gt;

&lt;p&gt;Every submission produces the same response envelope that already carried &lt;a href="https://dev.to/newadventuresinit/discovery-how-an-agent-finds-your-apis-hh7"&gt;discovery results&lt;/a&gt; back to the agent: &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;result&lt;/code&gt;, &lt;code&gt;meta&lt;/code&gt;. When &lt;code&gt;success&lt;/code&gt; is false, the response carries a &lt;em&gt;Failure&lt;/em&gt;, and every Failure falls into one of three categories, divided by when it happened and whose fault it was, not by how bad it was.&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;Flaw&lt;/em&gt; is a defect in the program, found before execution. The program never ran. There are seven kinds, from &lt;code&gt;parse_error&lt;/code&gt; and &lt;code&gt;type_error&lt;/code&gt; through &lt;code&gt;validation_error&lt;/code&gt; to the static caps of the &lt;a href="https://dev.to/newadventuresinit/the-budget-system-thalers-bytes-and-milliseconds-47fo"&gt;budget system&lt;/a&gt; such as &lt;code&gt;program_too_large&lt;/code&gt;. A Flaw reports line, column, a remediation hint, and the limit that was exceeded where one applies. Its &lt;code&gt;meta&lt;/code&gt; block is empty, because nothing executed.&lt;/p&gt;

&lt;p&gt;An &lt;em&gt;Error&lt;/em&gt; is a failure during execution, and it means the program ran and something got out. It reports a code, a message, and a source, and the source alone tells the three origins apart: a qualified function name for a failed API call, &lt;code&gt;"runtime"&lt;/code&gt; for one of the twelve runtime error codes such as division by zero or index out of bounds, and &lt;code&gt;"main"&lt;/code&gt; for an error the program threw deliberately.&lt;/p&gt;

&lt;p&gt;An &lt;em&gt;Abort&lt;/em&gt; is imposed from outside. Either a budget was exhausted, in which case the envelope names which one along with the limit and the amount consumed, or the runtime hit a condition it could not recover from. Nothing is wrong with the program. Its execution was simply ended.&lt;/p&gt;

&lt;p&gt;The split earns its keep because each category answers the agent's next question. A Flaw means rewrite the program, and line, column and hint make that close to mechanical. An Error means the program was right and the world was not. An Abort means do not resubmit the same thing, ask for less. The taxonomy tells the agent what to do next. It is not a severity scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seam
&lt;/h2&gt;

&lt;p&gt;An &lt;em&gt;Error&lt;/em&gt; envelope carries &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt; and &lt;code&gt;source&lt;/code&gt;. Those are precisely the values &lt;code&gt;errorCode()&lt;/code&gt;, &lt;code&gt;errorMessage()&lt;/code&gt; and &lt;code&gt;errorSource()&lt;/code&gt; would have returned had the program handled the error itself. Same error, one altitude up. The two halves of this post are one object seen from two positions.&lt;/p&gt;

&lt;p&gt;Exactly one thing crosses in the other direction. The per-call timeout, which the budget post left open, is a runtime error delivered &lt;em&gt;inside&lt;/em&gt; a &lt;code&gt;T!&lt;/code&gt;. That does not contradict the rule that runtime errors never add a &lt;code&gt;!&lt;/code&gt; to a type: the &lt;code&gt;!&lt;/code&gt; was already there, because &lt;code&gt;call&lt;/code&gt; is failable to begin with. A timed-out call is one more failure that call can carry, handled with &lt;code&gt;=?&lt;/code&gt; or &lt;code&gt;??&lt;/code&gt; like any other. Only &lt;code&gt;errorSource()&lt;/code&gt; returning &lt;code&gt;"runtime"&lt;/code&gt; reveals that the runtime produced it rather than the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grotto's take on failure
&lt;/h2&gt;

&lt;p&gt;Each category is produced at a different point in Grotto's dispatcher-worker split.&lt;/p&gt;

&lt;p&gt;Flaws come from the parser and the validator, before &lt;code&gt;main&lt;/code&gt; is invoked at all. Errors come from the interpreter. Aborts come from two places: the cooperative budget checks inside the worker, and the dispatcher's own clock when it has to terminate a worker that stopped checking in. Provider contract violations surface as infrastructure aborts, which is how a bug in host-supplied code stays out of the program's failure model entirely.&lt;/p&gt;

&lt;p&gt;The guarantee that matters is the dispatcher's: exactly one envelope per submission, including for a worker it had to kill mid-instruction. A submission never produces silence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next from the Grotto
&lt;/h2&gt;

&lt;p&gt;One encore left. The type system deserves a closer look on its own terms: structural rather than nominal, recursive, and reaching all the way down to integers and decimals of arbitrary precision.&lt;/p&gt;

&lt;p&gt;In the meantime, read the &lt;a href="https://newadventuresinit.github.io/neander/" rel="noopener noreferrer"&gt;Neander&lt;/a&gt; spec, embed &lt;a href="https://github.com/newadventuresinit/grotto" rel="noopener noreferrer"&gt;Grotto&lt;/a&gt; in your own app, and let me know how it failed.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Inside Grotto: Isolation and the API Boundary</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Tue, 21 Jul 2026 04:15:13 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/inside-grotto-isolation-and-the-api-boundary-4pbp</link>
      <guid>https://dev.to/newadventuresinit/inside-grotto-isolation-and-the-api-boundary-4pbp</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/newadventuresinit/the-budget-system-thalers-bytes-and-milliseconds-47fo"&gt;Last time&lt;/a&gt; I started looking at some of Grotto's implementation details while going through the budget system.&lt;/p&gt;

&lt;p&gt;This time I want to take a step back and look at the overall picture. How does Grotto make it safe to embed the runtime in a host application and execute untrusted code inside it? And how does the host get its own APIs into the runtime for the code to call in the first place?&lt;/p&gt;

&lt;h2&gt;
  
  
  The language is the sandbox
&lt;/h2&gt;

&lt;p&gt;Half the work is already done by the language itself. Neander follows the guiding design principle that the language &lt;em&gt;is&lt;/em&gt; the sandbox.&lt;/p&gt;

&lt;p&gt;A Neander program has no file I/O, no sockets, no system access. It can only reach the APIs the embedding application registered, and even those only by going through &lt;code&gt;discover&lt;/code&gt; and &lt;code&gt;call&lt;/code&gt;. On top of that, every valid program is guaranteed to terminate, and the budget system stops it before it can drain the host's resources.&lt;/p&gt;

&lt;p&gt;All that is left for Grotto is to make this design a reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The grotto is the fortress
&lt;/h2&gt;

&lt;p&gt;The language being safe by omission handles the program's &lt;em&gt;intentions&lt;/em&gt;. The runtime architecture handles everything else: runaway resource use, one submission interfering with another, a wedged program that will not stop.&lt;/p&gt;

&lt;p&gt;Grotto's architecture is based on the dispatcher-worker pattern:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83rslfzr9ntujwsh3szl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83rslfzr9ntujwsh3szl.png" alt="Grotto architecture" width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only the dispatcher runs on the host's thread and it does not touch the code itself. Every non-empty, in-range program submission runs in its own fresh worker thread, spawned for that one program and terminated once it returns the response envelope. Two programs cannot observe or influence each other, because they never share a thread and never share memory. The only channel that exists at all is a message pipe to the dispatcher, and it carries copies, not references. The isolation sits on an OS thread boundary.&lt;/p&gt;

&lt;p&gt;Because the program runs on a thread separate from the dispatcher, the dispatcher can always kill it. As we already saw last time, that is what makes the duration budget enforceable even against a program stuck in a tight synchronous loop with no yield point: the dispatcher keeps the clock on its own thread and, on overrun, terminates the worker outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  API providers: the one door in
&lt;/h2&gt;

&lt;p&gt;The whole safety story rests on a program being able to reach nothing except the APIs the host registered. Which raises the question the series has quietly deferred until now: how do those APIs get in, and why is that the one door that does not undo everything else?&lt;/p&gt;

&lt;p&gt;The host registers its APIs as &lt;em&gt;provider modules&lt;/em&gt;. Each module is one namespace, and it exports two things: a &lt;em&gt;manifest&lt;/em&gt; that declares what the namespace offers (its functions, their parameter and return types, its named types and documents) and a &lt;em&gt;handlers&lt;/em&gt; map that supplies the actual code behind each declared function. The manifest is what an agent sees through &lt;code&gt;discover&lt;/code&gt;. The handlers are what a &lt;code&gt;call&lt;/code&gt; eventually runs.&lt;/p&gt;

&lt;p&gt;The runtime checks these once, at startup, not on every submission. When the embedding application starts the runtime, a provider validator imports every module and verifies the contract: every function the manifest declares has a matching handler and vice versa, no two modules claim the same namespace, nobody grabs the reserved &lt;code&gt;runtime&lt;/code&gt; or &lt;code&gt;main&lt;/code&gt; names, and every declared type actually parses and resolves. A malformed provider fails loudly, once, at start. By the time the runtime accepts a single submission, the whole API surface is known to be well-formed.&lt;/p&gt;

&lt;p&gt;At execution time the handler runs in-thread, inside the worker, as an ordinary async function call. There is no per-call IPC and no serialization across a process boundary. What there is, is a marshalling boundary: values are converted between their Neander representation and plain JavaScript as they cross into and out of the handler, and every conversion is checked against the type the manifest declared.&lt;/p&gt;

&lt;p&gt;That boundary is where the runtime hands control to code it did not write, so it is worth being precise about what can happen there. A handler that throws a conforming &lt;code&gt;{code, message}&lt;/code&gt; error comes back to the program as an ordinary error, which it can handle like any other &lt;code&gt;call&lt;/code&gt; failure: inspect it, substitute a default, or re-throw it. A handler that misbehaves, by throwing something malformed or returning a value that does not match its declared type, is not the program's fault and is not handed to the program. It is a provider contract violation: the execution aborts, the blame lands on the named provider, and the agent's program never sees it. Provider bugs stay provider bugs. They do not leak into the language's failure model and they do not compromise the isolation around them.&lt;/p&gt;

&lt;p&gt;So the one capability the language grants is narrow by construction: reachable only through &lt;code&gt;discover&lt;/code&gt; and &lt;code&gt;call&lt;/code&gt;, backed by a contract verified before anything runs, and marshalled across a typed, checked boundary every time it is used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defense in depth, by category
&lt;/h2&gt;

&lt;p&gt;Put it together and the guarantees line up in layers, each catching a different class of problem at a different moment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before the program runs&lt;/strong&gt;: validation. Anything malformed, ill-typed, or referring to APIs that do not exist is a &lt;em&gt;Flaw&lt;/em&gt;, and the program never executes. Forging a discovery handle, mixing up types, calling a function that is not there: caught here, statically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;While it runs&lt;/strong&gt;: budgets. Too much computation, memory, or time is an &lt;em&gt;Abort&lt;/em&gt;. The program is stopped, the host is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Around the whole thing&lt;/strong&gt;: isolation. No I/O to misuse, no sibling submission to spy on, a worker the host can terminate at will, and a provider boundary that blames the provider rather than corrupting the run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these is the load-bearing one. That is the point of layering them. A program that passes validation still cannot outspend its budget. One that stays inside its budget still cannot reach the filesystem. One that somehow wedged its own thread still cannot survive the dispatcher's clock. Nothing here requires trusting the agent. It requires trusting the runtime, and the runtime is a small, dependency-free dispatcher plus a throwaway worker, open for anyone to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field Notes from the Grotto
&lt;/h2&gt;

&lt;p&gt;Across the series we have covered a lot of ground: how an agent finds the host's APIs, how it learns the language cold from an empty program, why its programs always stop, what they are allowed to spend, why it is safe to run them at all, and how the host's own APIs get in. The argument stands on its own now. Put the safety in the language and the runtime, by construction, and no sandbox is needed, because the things a sandbox exists to contain were never built.&lt;/p&gt;

&lt;p&gt;For the embedder, this was the answer to the question that arrives the moment someone says "let an agent write code and run it in production": &lt;em&gt;why is that not insane?&lt;/em&gt; Because the code the agent writes cannot do anything the host did not hand it, cannot outlast or outspend its allowance, cannot touch the request running next to it, and reaches the host's own code only across a boundary the host verified first.&lt;/p&gt;

&lt;p&gt;This is the end of the foundational series. Thank you for taking the tour. There will be a couple of encores, standalone entries on parts that reward a closer look on their own terms: the full failure taxonomy, from in-program failable values to the one top-level verdict every submission returns, and the type system, structural and recursive, down to the arbitrary-precision numbers.&lt;/p&gt;

&lt;p&gt;There is still time to read the &lt;a href="https://newadventuresinit.github.io/neander/" rel="noopener noreferrer"&gt;Neander&lt;/a&gt; spec, embed &lt;a href="https://github.com/newadventuresinit/grotto" rel="noopener noreferrer"&gt;Grotto&lt;/a&gt; in your own app, and let me know if your APIs got called.&lt;/p&gt;

&lt;p&gt;Mind your head on the way out.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>neander</category>
    </item>
    <item>
      <title>The Budget System: Thalers, Bytes, and Milliseconds</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Fri, 17 Jul 2026 03:50:20 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/the-budget-system-thalers-bytes-and-milliseconds-47fo</link>
      <guid>https://dev.to/newadventuresinit/the-budget-system-thalers-bytes-and-milliseconds-47fo</guid>
      <description>&lt;p&gt;A Neander program is guaranteed to stop, as we saw &lt;a href="https://dev.to/newadventuresinit/sub-turing-all-good-programs-must-come-to-an-end-1kkf"&gt;last time&lt;/a&gt;. But "stops eventually" is not good enough in a real-world scenario, where the program runs inside the host application and consumes its resources while it does.&lt;/p&gt;

&lt;p&gt;So on top of a sub-Turing language, Neander features a &lt;strong&gt;budget system&lt;/strong&gt; which places hard upper bounds on a program in six different dimensions. Three of them are budgets in the literal sense: they bound what a running program is allowed to consume. The other three are static caps on the shape of the program itself, checked before it is allowed to run at all. All six are set and enforced by the runtime, never declared, requested or negotiated by an agent or a program. The agent can obtain the current budget limits from the runtime by submitting the empty program (what it would do anyway during a &lt;a href="https://dev.to/newadventuresinit/the-cold-start-learning-the-language-by-submitting-a-program-39il"&gt;cold start&lt;/a&gt;). The &lt;code&gt;meta&lt;/code&gt; block of the Reference Response contains the runtime's budget configuration.&lt;/p&gt;

&lt;p&gt;It is worth stressing that the budget system is an integral part of the &lt;em&gt;language definition&lt;/em&gt;, not merely a bolted-on feature of one particular runtime implementation.&lt;/p&gt;

&lt;p&gt;What is runtime-specific are the details of how and in what amount these upper bounds are assigned to a submitted program. The Grotto reference implementation, for example, currently requires a static budget configuration at startup time and applies it to every program submission.&lt;/p&gt;

&lt;p&gt;What is normative, however, is the consequence of exceeding one. Either way the program produces a Failure result, but the two kinds of bound fail differently. Overrunning a static cap is a &lt;em&gt;Flaw&lt;/em&gt;: the program is rejected outright, before execution. Overspending a budget is an &lt;em&gt;Abort&lt;/em&gt;: execution stops immediately. There is exactly one exception to this rule, and we will get to it in a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Computation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Computation&lt;/strong&gt; is measured in &lt;strong&gt;Thalers&lt;/strong&gt;, Neander's unit of computational work, named after an &lt;a href="https://en.wikipedia.org/wiki/Thaler" rel="noopener noreferrer"&gt;old silver coin&lt;/a&gt;. Every operation that actually computes something costs one Thaler. Things that do not compute are free, e.g. binding a name, returning a value, a literal. The Neander specification contains a complete &lt;em&gt;Thaler Cost Table&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Arithmetic op (&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comparison (&lt;code&gt;==&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1 Thaler per base-type comparison&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logical op (&lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;not&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;is&lt;/code&gt; type check&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;??&lt;/code&gt; null coalescing&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field access (&lt;code&gt;.field&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List index (&lt;code&gt;[i]&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Map access (&lt;code&gt;["key"]&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List projection (&lt;code&gt;[*].field&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1 Thaler per element&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;contains()&lt;/code&gt; / &lt;code&gt;indexOf()&lt;/code&gt; on list&lt;/td&gt;
&lt;td&gt;1 Thaler per element checked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;replace()&lt;/code&gt; / &lt;code&gt;split()&lt;/code&gt; on string&lt;/td&gt;
&lt;td&gt;1 Thaler per occurrence found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other built-in function call&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;call&lt;/code&gt; (API function)&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;each&lt;/code&gt; iteration&lt;/td&gt;
&lt;td&gt;1 Thaler per iteration (body costs are additional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;repeat&lt;/code&gt; iteration&lt;/td&gt;
&lt;td&gt;1 Thaler per iteration (body costs are additional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;discover&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1 Thaler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;if&lt;/code&gt; evaluation&lt;/td&gt;
&lt;td&gt;0 (the condition's operations already cost Thalers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;let&lt;/code&gt; binding&lt;/td&gt;
&lt;td&gt;0 (the expression's operations already cost Thalers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;return&lt;/code&gt; / &lt;code&gt;throw&lt;/code&gt; / &lt;code&gt;yield&lt;/code&gt; / &lt;code&gt;skip&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0 (the expression's operations already cost Thalers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Literal value&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spread (&lt;code&gt;..&lt;/code&gt;) in list or map literal&lt;/td&gt;
&lt;td&gt;1 Thaler per element or entry spread&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Memory
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; is measured in whole kilobytes, and the memory budget is a ceiling on peak allocation. API return values, large lists and maps, deep record structures: all of it is charged, and an allocation that would cross the ceiling is refused &lt;em&gt;before&lt;/em&gt; it happens.&lt;/p&gt;

&lt;p&gt;In contrast to computation costs, which are standardized by the language, memory costs are heavily implementation-dependent. The Neander design gives each runtime implementation the breathing space it needs to account for the memory allocation specifics of its underlying platform. What is standardized is the contract around the number: that the ceiling binds peak allocation, that crossing it stops the program before the allocation happens, and that the figures reported back are whole kilobytes. Thalers are portable across conforming runtimes. Kilobytes are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Time&lt;/strong&gt; is measured in wall-clock milliseconds, and there exist two separate duration budgets. The first limits the overall program execution duration, including in particular all API calls. The second limits the duration of each API call individually, so one slow API cannot quietly eat the whole duration budget on its own.&lt;/p&gt;

&lt;p&gt;The per-call timeout is the exception promised above. Exceeding it does not stop the program. Instead, it returns a recoverable runtime error from the API call, and execution continues. The reasoning is that the program's other budgets may well still be within limits. So the runtime hands it back as an ordinary failed call and lets the program decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Size
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Size&lt;/strong&gt; is measured in bytes and limits the length of the submitted program as UTF-8-encoded source. The standard mandates that this limit is checked before any source code pre-processing (lexing, parsing, etc.) starts. That pre-processing costs the runtime real work, but it is not covered by the budgets for computation, memory, and time. These budgets only apply to program execution. The size cap is what bounds everything that happens before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Depth
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Depth&lt;/strong&gt; is a unitless positive integer and limits the longest root-to-leaf path in the program's &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree" rel="noopener noreferrer"&gt;abstract syntax tree&lt;/a&gt;. It protects against stack overflows during parsing, type checking, and execution, which is a separate attack vector independent of program size: a short program can nest very deeply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeat
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repeat&lt;/strong&gt; is a unitless positive integer and caps the &lt;code&gt;limit&lt;/code&gt; literal that every &lt;code&gt;repeat&lt;/code&gt; loop must declare. There are two separate limits at play here, and it is important to distinguish between them. The first is part of the &lt;code&gt;repeat&lt;/code&gt; syntax itself and acts as a &lt;em&gt;runtime&lt;/em&gt; precondition on the actual repeat count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;repeat&lt;/span&gt; &lt;span class="n"&gt;pageCount&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="nf"&gt;.list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;pageCount&lt;/code&gt; turned out to be larger than &lt;code&gt;20&lt;/code&gt; during execution, then a runtime error would prevent the loop from even starting. This construct gives an agent the opportunity to express an expectation and ensure that the loop does not execute if the expectation is not met.&lt;/p&gt;

&lt;p&gt;The budget-system repeat limit, on the other hand, is an absolute ceiling on the literal itself, checked at &lt;em&gt;validation time&lt;/em&gt;. So &lt;code&gt;limit 1000000000&lt;/code&gt; is valid syntax and per se allowed, but it very likely exceeds the configured cap, and the program would never start running. This prevents an agent from circumventing the bound on loops by declaring an absurdly high number as the limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grotto's take on budgets
&lt;/h2&gt;

&lt;p&gt;Grotto implements a dispatcher-worker pattern where program submission is handled by a dispatcher running in the host thread and the program itself is handled by an isolated worker.&lt;/p&gt;

&lt;p&gt;The dispatcher enforces the program size cap upon reception and the overall program execution duration cap by a timeout on the isolated worker followed by worker termination.&lt;/p&gt;

&lt;p&gt;The worker does the rest. Parser and validator enforce depth and repeat limits, computation and memory budgets are checked cooperatively at every operation and allocation, and a timeout guards every in-thread API call.&lt;/p&gt;

&lt;p&gt;Note the split. Thalers and memory can be counted cooperatively because the interpreter is doing the work and can be trusted to check as it goes. Duration cannot be left to the same mechanism: a program that stops coming back to a checkpoint stops checking its own clock. Grotto does sample the clock inside the worker too, but it does not rely on it. The deadline that actually binds sits on the dispatcher thread, which is a question of isolation rather than budgeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next from the Grotto
&lt;/h2&gt;

&lt;p&gt;That concludes the overview of the Neander budget system, and it ends with a brief visit to Grotto. Since we are here anyway, it makes sense to stay a while and take a closer look under the hood, to better understand how Grotto keeps its embedding host application isolated from the execution of untrusted code.&lt;/p&gt;

&lt;p&gt;In the meantime, read the &lt;a href="https://newadventuresinit.github.io/neander/" rel="noopener noreferrer"&gt;Neander&lt;/a&gt; spec, embed &lt;a href="https://github.com/newadventuresinit/grotto" rel="noopener noreferrer"&gt;Grotto&lt;/a&gt; in your own app, and let me know what it cost you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>neander</category>
    </item>
    <item>
      <title>Sub-Turing: All Good Programs Must Come to an End</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Tue, 14 Jul 2026 02:33:13 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/sub-turing-all-good-programs-must-come-to-an-end-1kkf</link>
      <guid>https://dev.to/newadventuresinit/sub-turing-all-good-programs-must-come-to-an-end-1kkf</guid>
      <description>&lt;p&gt;The decision to create a new purpose-built language instead of reusing an existing general-purpose language is deeply connected with a concept from theoretical computer science called &lt;a href="https://en.wikipedia.org/wiki/Turing_completeness" rel="noopener noreferrer"&gt;Turing completeness&lt;/a&gt;. Loosely speaking, a programming language is Turing-complete if it can express any computation a computer could ever perform. Practically all general-purpose languages in use today have this capability. A necessary condition for Turing completeness is the ability to express unbounded looping, and this is where things get interesting in the context of agentic API-orchestration: &lt;/p&gt;

&lt;p&gt;Do you want to hand an untrusted agent the ability to execute a program in your host application which will run forever?&lt;/p&gt;

&lt;p&gt;I deliberately decided against this when designing the language and gave it a theoretical safeguard: Neander is Turing-incomplete. Every valid Neander program is guaranteed to come to an end. And any invalid program is not started in the first place anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is missing
&lt;/h2&gt;

&lt;p&gt;Neander lacks the ability to express unbounded looping, and this inability comes in two flavors:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No recursion.&lt;/strong&gt; A program has one entry point, &lt;code&gt;main&lt;/code&gt;, and no way to turn around and re-enter the program. Also, it has no means to define functions of its own which it could then call. A &lt;code&gt;call&lt;/code&gt; reaches only a registered API and returns. The call stack simply cannot grow without end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every loop is bounded.&lt;/strong&gt; Neander has exactly two iteration constructs, &lt;code&gt;each&lt;/code&gt; and &lt;code&gt;repeat&lt;/code&gt; (there is no &lt;code&gt;while&lt;/code&gt;), and neither can run away:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;each&lt;/code&gt; walks a list or a map — a finite, immutable value that already exists, so the iteration count is fixed before the loop starts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;confirmed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Booking&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=?&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;bookings&lt;/span&gt;&lt;span class="nf"&gt;.list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"confirmed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="n"&gt;confirmed&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="py"&gt;.guestId&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;repeat&lt;/code&gt; runs a block a counted number of times, and its ceiling is a literal you must write into the source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;repeat&lt;/span&gt; &lt;span class="n"&gt;pageCount&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="nf"&gt;.list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What is still missing
&lt;/h2&gt;

&lt;p&gt;Are we safe now? In theory, yes. Unfortunately, systems have to reliably work in practice, not in theory.&lt;/p&gt;

&lt;p&gt;Will a Neander program terminate? &lt;/p&gt;

&lt;p&gt;Yes, guaranteed. &lt;/p&gt;

&lt;p&gt;OK, but &lt;em&gt;when&lt;/em&gt;, exactly?&lt;/p&gt;

&lt;p&gt;Well, &lt;em&gt;eventually&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;And that is where reality bites you. In practice, &lt;em&gt;eventually&lt;/em&gt; might be long enough to overload your host application and cause an incident. The same goes for the loops. They are bounded, yes, but where exactly does this bound sit? Lists could be huge and &lt;code&gt;limit 1000000000&lt;/code&gt; is valid syntax in Neander.&lt;/p&gt;

&lt;p&gt;Something is still missing.&lt;/p&gt;

&lt;p&gt;As we will find out next time, this &lt;em&gt;something&lt;/em&gt; is of a very practical nature: Neander programs are living on a budget.&lt;/p&gt;

&lt;p&gt;In the meantime, read the &lt;a href="https://newadventuresinit.github.io/neander/" rel="noopener noreferrer"&gt;Neander&lt;/a&gt; spec, embed &lt;a href="https://github.com/newadventuresinit/grotto" rel="noopener noreferrer"&gt;Grotto&lt;/a&gt; in your own app, and let me know how it ended.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>neander</category>
    </item>
    <item>
      <title>The Cold Start: Learning the Language by Submitting a Program</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Fri, 10 Jul 2026 03:05:44 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/the-cold-start-learning-the-language-by-submitting-a-program-39il</link>
      <guid>https://dev.to/newadventuresinit/the-cold-start-learning-the-language-by-submitting-a-program-39il</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/newadventuresinit/discovery-how-an-agent-finds-your-apis-hh7"&gt;Last time&lt;/a&gt; I showed how an agent discovers the available APIs at runtime using the &lt;code&gt;discover&lt;/code&gt; verb. But one question remains unanswered: how does a cold-start agent that has never seen the language before discover discovery? You need to know the language in order to write a program, so how is this chicken-and-egg problem solved?&lt;/p&gt;

&lt;p&gt;It turns out that there exists, in fact, one particular program an agent can submit without either knowing or guessing the grammar.&lt;/p&gt;

&lt;h2&gt;
  
  
  The empty program
&lt;/h2&gt;

&lt;p&gt;When a Neander runtime like Grotto receives the empty program, it is not treated as a syntax error but as valid input. The specification requires the runtime to answer with a special response envelope that contains the &lt;strong&gt;Neander Reference&lt;/strong&gt; document explaining the language, plus the runtime's current configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"# The Neander Reference..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"neanderVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"thalerBudget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memoryBudgetKb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maxDurationMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"perCallTimeoutMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maxProgramSizeBytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;262144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maxNestingDepth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maxRepeatLimit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the entire bootstrap mechanism. One round-trip, and the agent now knows about the Neander language and every limit the runtime enforces. The rule that &lt;em&gt;everything in Neander is a program&lt;/em&gt; extends even to requesting the manual for how to write a program.&lt;/p&gt;

&lt;p&gt;It is worth mentioning that Grotto optimizes the request processing in this special case. The empty program never spawns a worker that lexes or parses it. The dispatcher immediately returns a prepared Reference Response, which makes bootstrapping an agent a very cheap task.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little nudge to break the ice
&lt;/h2&gt;

&lt;p&gt;For an embedding application, out-of-the-box bootstrapping and discovery are real convenience benefits. All that it still needs to take care of itself is to register its APIs with the runtime and to connect agents to itself.&lt;/p&gt;

&lt;p&gt;The latter must in some shape or form, depending on the underlying technology, inform the agent that it now has access to a &lt;code&gt;submitProgram&lt;/code&gt; operation. And ideally, this memo also contains a little nudge to break the ice, namely: &lt;em&gt;Send the empty program first to learn about the language.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A little kindness goes a long way. Agents are smart, but not (yet) mind readers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two documents, separate audiences
&lt;/h2&gt;

&lt;p&gt;Now that the Neander Reference is introduced, it is worth stressing that there exist, in fact, two documents describing Neander, and this split is deliberate because they address two very different audiences.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Reference&lt;/strong&gt; is served by the runtime to agents who want to write programs to call the APIs of the embedding application. It is example-driven and has three parts: a get-started, a cookbook of copy-and-ship recipes, and the actual language reference for lookup. It answers the &lt;em&gt;how&lt;/em&gt; question, but does not explain &lt;em&gt;why&lt;/em&gt; the language is the way it is. This is the job of the other document.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;specification&lt;/strong&gt; is the normative, precise, and exhaustive definition of the language. It informs coding agents who plan and execute a runtime implementation as well as humans who evaluate the technology to assess whether embedding it into their own systems is a viable option. It also serves as the single source of truth for generating or updating the Reference. The two documents are supposed to always be in sync, but if push comes to shove, the specification wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next from the Grotto
&lt;/h2&gt;

&lt;p&gt;We have now covered bootstrapping and discovery at the beginning of a dutiful friendship between the agent and the API. And so it is time to shift our focus to what comes next: The End. Or rather, the question: is there an ending?&lt;br&gt;
We all know that programs can run forever. And thanks to Mr. Turing, we know that we can never know for sure.&lt;/p&gt;

&lt;p&gt;But Neander lives up to its namesake's reputation and proves to be way too predictable for that.&lt;/p&gt;

&lt;p&gt;In the meantime, read the &lt;a href="https://newadventuresinit.github.io/neander/" rel="noopener noreferrer"&gt;Neander&lt;/a&gt; spec, embed &lt;a href="https://github.com/newadventuresinit/grotto" rel="noopener noreferrer"&gt;Grotto&lt;/a&gt; in your own app, and let me know how the cold start goes.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>programming</category>
      <category>neander</category>
    </item>
    <item>
      <title>Discovery: How an Agent Finds Your APIs</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Tue, 07 Jul 2026 03:32:50 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/discovery-how-an-agent-finds-your-apis-hh7</link>
      <guid>https://dev.to/newadventuresinit/discovery-how-an-agent-finds-your-apis-hh7</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/newadventuresinit/neander-and-grotto-beyond-code-mode-21im"&gt;Last time&lt;/a&gt; I made the case for why &lt;strong&gt;Neander&lt;/strong&gt; and &lt;strong&gt;Grotto&lt;/strong&gt; exist at all — a purpose-built, safe-by-construction language instead of a sandboxed general-purpose one. That was the argument for the whole language. From here, &lt;strong&gt;Field Notes from the Grotto&lt;/strong&gt; takes it apart one feature at a time, and the first one is the feature that explains why the rest exists: discovery.&lt;/p&gt;

&lt;p&gt;Classical integration had a shape we all recognize: a developer reads one system's API documentation, then writes integration code that calls it in the right order with the right data. Two steps, one human. Make the calling system agentic — let it decide at runtime what it needs — and the human in the middle vanishes. The two steps do not. They have to land somewhere.&lt;/p&gt;

&lt;p&gt;The writing step is the half everyone talks about: the agent writes the program instead of the developer. It is the &lt;em&gt;reading&lt;/em&gt; step that gets skipped over. Before you can write a line against an API you have to find out what the API even is — what exists, what it takes, what it returns. That was the developer with a browser tab open on the documentation. When the developer leaves, that finding-out does not leave with them; something has to inherit it. That something is discovery.&lt;/p&gt;

&lt;p&gt;The eager answer is the tool catalog: hand the agent every function definition up front and let it pick. That is discovery too — just total, and paid in advance. It does not scale. Hundreds of definitions clutter the context before the agent has done anything, every intermediate result piles on top, and latency and cost climb with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the documentation used to live
&lt;/h2&gt;

&lt;p&gt;Take the pragmatic route from my post &lt;a href="https://dev.to/newadventuresinit/source-code-as-the-seam-between-systems-5b1p"&gt;&lt;em&gt;Source Code as the Seam Between Systems&lt;/em&gt;&lt;/a&gt; — a general-purpose language the model already writes fluently, its APIs behind a sandbox — and you inherit a quieter awkwardness. That language and those API packages were built for human developers, and human developers read documentation: a reference site, a PDF, a README, a page of generated typedocs. But a running agent is not a human developer. It may not have unrestricted access to the web to search for and reach that reference page at all — and even where it does, the page is shaped for a person: prose, worked examples, a layout to skim, none of it meant to be consumed by an agent. So every system that takes this route has to bolt something on — a meta-tool that lists the available functions, or a search endpoint the agent queries before it writes anything. It works. But it is an appendage: out of band, and reinvented for every stack.&lt;/p&gt;

&lt;p&gt;Neander makes discovery part of the language itself. &lt;code&gt;discover&lt;/code&gt; is a verb you write into a program and submit exactly the same way you submit a program that does real work — and that is the unification worth stressing. In Neander, &lt;em&gt;everything is a program&lt;/em&gt;. Finding out what exists and calling it are the same kind of act, in the same language, through the same entry point. The runtime exposes one operation to the agent — &lt;code&gt;submitProgram&lt;/code&gt; — and every interaction, whether the agent is asking what is available or getting something done, flows through it. With this, the finding-out moved in-band. What the developer used to do with a browser tab, the agent now does with a program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two verbs
&lt;/h2&gt;

&lt;p&gt;Almost everything in Neander exists to glue two verbs together. &lt;code&gt;call&lt;/code&gt; invokes one of the registered API functions. &lt;code&gt;discover&lt;/code&gt; asks the runtime what there is to call.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;discover&lt;/code&gt; has six forms — three things you can look for (namespaces, functions, documents) crossed with two ways to look (search a list, or get one by exact name):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;discover&lt;/span&gt; &lt;span class="n"&gt;namespaces&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;              &lt;span class="c1"&gt;// [Namespace]  — search&lt;/span&gt;
&lt;span class="n"&gt;discover&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;  &lt;span class="s"&gt;"shipping"&lt;/span&gt;               &lt;span class="c1"&gt;// Namespace?   — exact lookup of a namespace&lt;/span&gt;
&lt;span class="n"&gt;discover&lt;/span&gt; &lt;span class="n"&gt;functions&lt;/span&gt;  &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"estimate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"intl"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;// [Function]   — search the namespace's functions&lt;/span&gt;
&lt;span class="n"&gt;discover&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt;   &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="s"&gt;"estimateBatch"&lt;/span&gt;       &lt;span class="c1"&gt;// Function?    — exact lookup of a function&lt;/span&gt;
&lt;span class="n"&gt;discover&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;  &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;            &lt;span class="c1"&gt;// [Document]   — search the namespace's documents&lt;/span&gt;
&lt;span class="n"&gt;discover&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;   &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="s"&gt;"requestFormat"&lt;/span&gt;       &lt;span class="c1"&gt;// Document?    — exact lookup of a document&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Search terms are case-insensitive substrings, AND-combined, matched against each candidate's name and description. The empty list matches everything. That is the entire discovery surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;A cold agent does not know your API, so it works inward. The power lies in the sequential writing and execution of code. One program to list namespaces, a second to look for functions inside a namespace, a third to make the function call — reading and learning from the response envelopes the runtime returns as the result of every program submission.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;neander&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;discover&lt;/span&gt; &lt;span class="n"&gt;namespaces&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runtime answers with a standard envelope — a &lt;code&gt;success&lt;/code&gt; flag, the &lt;code&gt;result&lt;/code&gt;, and a &lt;code&gt;meta&lt;/code&gt; block of telemetry. The return value of type &lt;code&gt;[Namespace]&lt;/code&gt; is the fully serialized list of available namespaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bookings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Booking management — retrieve, list, confirm, and escalate reservations for the hospitality backend"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"guests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Guest profile lookup — retrieve and list guests for the hospitality backend"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Payment processing — charge bookings, issue refunds, and look up prior charges"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"thalersConsumed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memoryConsumedKb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"durationMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"apiCalls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this list in its context the agent can now further inspect the namespace of interest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;neander&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Namespace&lt;/span&gt; &lt;span class="o"&gt;=?&lt;/span&gt; &lt;span class="n"&gt;discover&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="s"&gt;"bookings"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;discover&lt;/span&gt; &lt;span class="n"&gt;functions&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time the result contains the list of available functions in this namespace, each function serialized in full (only one function shown here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"qualifiedName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bookings.get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get a booking by its numeric id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"int"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"returnType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bookings.Booking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"404"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Booking not found"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"bookings.Booking"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A reservation record with id, fare amount, state, and the guest who placed it"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"int"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"fare"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"decimal(2, half_away)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"guestId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"int"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"thalersConsumed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"memoryConsumedKb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"durationMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"apiCalls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every function name listed is the exact string to write next — &lt;code&gt;qualifiedName&lt;/code&gt; is what you pass to &lt;code&gt;call&lt;/code&gt;, and the &lt;code&gt;types&lt;/code&gt; side-table is transitively complete, so the shape of &lt;code&gt;bookings.Booking&lt;/code&gt; arrives with the function that returns it. The agent reads it straight out of the JSON response envelope, then writes the program that does the work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;neander&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;half_away&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bookings&lt;/span&gt;&lt;span class="py"&gt;.Booking&lt;/span&gt; &lt;span class="o"&gt;=?&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;bookings&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8821&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;booking&lt;/span&gt;&lt;span class="py"&gt;.fare&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Discover, call, return. Everything else in the language is detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opaque on purpose
&lt;/h2&gt;

&lt;p&gt;The design choice worth pointing out is that the values &lt;code&gt;discover&lt;/code&gt; returns — handles of type &lt;code&gt;Namespace&lt;/code&gt;, &lt;code&gt;Function&lt;/code&gt;, &lt;code&gt;Document&lt;/code&gt; — are &lt;em&gt;opaque&lt;/em&gt;. A program can hold one and return it, but it cannot read its fields. The envelope contains what the runtime serializes when the discovery handle is &lt;em&gt;returned&lt;/em&gt; from &lt;code&gt;main&lt;/code&gt;. Inside the program the handle is a sealed token. Only on the way out does it become readable.&lt;/p&gt;

&lt;p&gt;What might sound like a restriction is, in fact, a guarantee. A discovery handle can only ever come &lt;em&gt;from&lt;/em&gt; &lt;code&gt;discover&lt;/code&gt; — you cannot forge one out of a record literal or return it from an API function. So a discovery handle the agent sees is always a handle the runtime minted, pointing at something the runtime actually registered. Discovery is not a convention, it is the only way in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next from the Grotto
&lt;/h2&gt;

&lt;p&gt;Discovery answers &lt;em&gt;what APIs exist&lt;/em&gt; — but one question remains open: how does an agent discover discovery? The disadvantage of a new, purpose-built language like Neander is that a cold-start agent has never heard of it in stark contrast to any well-established general-purpose language. So before the agent can write even its first three-line namespace discovery program, it has to learn the language itself with nothing more at its disposal than the &lt;code&gt;submitProgram&lt;/code&gt; operation. If you do not know the language, what is the only program you can write which is not guesswork?&lt;/p&gt;

&lt;p&gt;The empty program, exactly!&lt;/p&gt;

&lt;p&gt;In the meantime, read the &lt;a href="https://newadventuresinit.github.io/neander/" rel="noopener noreferrer"&gt;Neander&lt;/a&gt; spec, embed &lt;a href="https://github.com/newadventuresinit/grotto" rel="noopener noreferrer"&gt;Grotto&lt;/a&gt; in your own app, and let me know what you discovered.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>neander</category>
    </item>
    <item>
      <title>Neander and Grotto: Beyond Code Mode</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Thu, 02 Jul 2026 14:42:42 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/neander-and-grotto-beyond-code-mode-21im</link>
      <guid>https://dev.to/newadventuresinit/neander-and-grotto-beyond-code-mode-21im</guid>
      <description>&lt;p&gt;&lt;strong&gt;Field Notes from the Grotto&lt;/strong&gt; starts here — a feature-by-feature tour of the &lt;strong&gt;&lt;a href="https://dev.to/newadventuresinit/neander-an-agent-first-programming-language-3i3o"&gt;Neander&lt;/a&gt;&lt;/strong&gt; language and its runtime, &lt;strong&gt;&lt;a href="https://dev.to/newadventuresinit/grotto-where-neander-programs-live-17bp"&gt;Grotto&lt;/a&gt;&lt;/strong&gt;. And I am opening with the biggest feature of them all: Neander itself. Why create a &lt;em&gt;whole new language&lt;/em&gt; when we already have an abundance of well-established programming languages at our disposal?&lt;/p&gt;

&lt;p&gt;In a previous &lt;a href="https://dev.to/newadventuresinit/source-code-as-the-seam-between-systems-5b1p"&gt;post&lt;/a&gt; I have made the case that the seam between systems is turning into a language — that instead of calling your tools one at a time, an agent should send you a small program and let it orchestrate the work on your side. That idea has a name — &lt;em&gt;code mode&lt;/em&gt; — and it is not mine. By now it is not even contentious: others arrived at it from their own directions, there are real solutions already shipping it, and the underlying claim — that a model does better writing code than emitting tool calls — has been measured, not just asserted. So the &lt;em&gt;what&lt;/em&gt; is settled. This post is about a narrower quarrel with the &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;All existing solutions I came across share an answer that is, frankly, the obvious one. Take a well-known language the model already writes fluently, generate an API from your tools, and run the agent's code in a sandbox. Pragmatic. Available today. And there is serious effort behind making it safe — an entire industry of ways to run untrusted code: lightweight virtual machines, isolated containers, syscall firewalls, network proxies whose sole job is to say &lt;em&gt;no&lt;/em&gt;. Real engineering, and it delivers.&lt;/p&gt;

&lt;p&gt;So why did I not reach for any of that? Why start from an empty grammar instead?&lt;/p&gt;

&lt;p&gt;Because all of it shares one shape — safety by subtraction — and I wanted a different one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe by construction
&lt;/h2&gt;

&lt;p&gt;Every one of those approaches begins with a language that can do anything, then spends its effort taking things away: walling off the filesystem, blocking the network, killing the process when the clock runs out. The language is a threat, and safety comes from a prison built around this culprit.&lt;/p&gt;

&lt;p&gt;Neander is no such threat. It cannot run forever — it is not Turing-complete, there is no recursion, every loop is statically bounded, and termination is decided &lt;em&gt;before&lt;/em&gt; the program runs. It cannot reach out — there is no file, no socket, no system call anywhere in the grammar. It cannot run up a bill — every program runs under hard ceilings on computation, memory, and time. Whole categories of exploit — sandbox escapes, privilege escalation, data exfiltration — simply do not apply, because the capability they would abuse was never there.&lt;/p&gt;

&lt;p&gt;There is no prison because there is no prisoner. The sandbox approach asks you to trust the cage. Neander's safety is the absence of anything that would need a cage. The less a language can do, the less can go wrong — and the less you have to take on faith. The entire sandbox industry exists to contain general-purpose code; Neander opts out of needing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uniform by construction
&lt;/h2&gt;

&lt;p&gt;No matter what program the agent submits, the answer comes back in the same form: a single response envelope, defined by the language itself. It carries either the value the program produced or a precise account of why it produced none. That uniformity holds because failure is never allowed to escape into the mess: errors raised mid-run and exhausted budgets are caught and classified rather than left to surface however they please, and even an invalid program that never executes still produces a response. An envelope also carries metadata — among it the resources used by the program or the usage limits the runtime enforces. The agent learns the very ceilings it operates under from the responses it receives.&lt;/p&gt;

&lt;p&gt;The uniform response envelope is not simply a convenience. The whole point of sending code instead of a stream of tool calls was to keep the agent's context clean — one compact result in, rather than every intermediate step piling up. A uniform envelope is what makes that payoff real: the agent gets back a single machine-readable verdict it always knows how to read, and &lt;em&gt;only&lt;/em&gt; that verdict costs it any context. It never has to parse prose, squint at a stack trace, or reconcile different failure formats. It reads the envelope, and it knows exactly where it stands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open by construction
&lt;/h2&gt;

&lt;p&gt;The existing solutions tend to arrive bolted to something — a cloud you deploy on, a framework you adopt, a tool protocol you have to speak. Neander is a specification, with a conformance suite growing up beside it. Anyone can implement a runtime; Grotto is simply the first. Nothing ties you to one vendor, and nothing ties you to one tool protocol — the host embeds a Neander runtime by wiring it into its own application. A standard you can build on, not a product you sign up for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next from the Grotto
&lt;/h2&gt;

&lt;p&gt;That is the case in outline. The rest of the series is the case in detail — one feature per entry, each of them a piece of the argument above made concrete. First up, the one that makes the whole inversion possible: how an agent finds out what your APIs even are, at runtime, without ever carrying a catalog of them around.&lt;/p&gt;

&lt;p&gt;In the meantime, read the &lt;a href="https://newadventuresinit.github.io/neander/" rel="noopener noreferrer"&gt;Neander&lt;/a&gt; spec, embed &lt;a href="https://github.com/newadventuresinit/grotto" rel="noopener noreferrer"&gt;Grotto&lt;/a&gt; in your own app, and tell me where it falls short.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>neander</category>
    </item>
    <item>
      <title>Grotto: Where Neander Programs Live</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Mon, 29 Jun 2026 08:04:38 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/grotto-where-neander-programs-live-17bp</link>
      <guid>https://dev.to/newadventuresinit/grotto-where-neander-programs-live-17bp</guid>
      <description>&lt;p&gt;Last time, in &lt;a href="https://dev.to/newadventuresinit/neander-an-agent-first-programming-language-3i3o"&gt;Neander: An Agent-First Programming Language&lt;/a&gt;, I published the language but not the one thing a host actually needs in order to put it to work: a runtime reference implementation. I promised it already existed, and that I would show it next time.&lt;/p&gt;

&lt;p&gt;Here it is.&lt;/p&gt;

&lt;p&gt;It's called &lt;strong&gt;Grotto&lt;/strong&gt;. The naming keeps the theme going: the first Neanderthal fossil was pulled from a &lt;a href="https://en.wikipedia.org/wiki/Kleine_Feldhofer_Grotte" rel="noopener noreferrer"&gt;grotto&lt;/a&gt; — the place "where the Neanderthal lived." Grotto is where Neander programs live and run.&lt;/p&gt;

&lt;p&gt;A specification is a plan. A reference implementation is the proof that the plan can be put into action. Grotto is that proof: an embeddable TypeScript library, running on Node.js, that takes a Neander program as plain text and runs it — the whole language, end to end, every expression, every data type and every built-in function the spec defines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trusted component
&lt;/h2&gt;

&lt;p&gt;Recall the setup from the last two posts. The agent is on the outside, untrusted, writing small programs. The host is on the inside, with the APIs worth calling. Between them sits the runtime — and the runtime is the one component in the whole arrangement that everyone has to trust.&lt;/p&gt;

&lt;p&gt;That is a heavy crown to wear. Grotto's entire design is an argument that the trust is warranted.&lt;/p&gt;

&lt;p&gt;It starts with the design process itself: Grotto is architected, &lt;em&gt;not&lt;/em&gt; vibe coded. I am a certified software architect (&lt;a href="https://www.isaqb.org/" rel="noopener noreferrer"&gt;iSAQB®&lt;/a&gt; CPSA-Advanced Level), so on a good day I even know what I am doing. The Grotto implementation started with the creation of an architecture specification (&lt;a href="https://arc42.org/" rel="noopener noreferrer"&gt;arc42&lt;/a&gt;, &lt;a href="https://c4model.com/" rel="noopener noreferrer"&gt;C4&lt;/a&gt;) and a technical design document (co-authored by me and an agent). Only then did a coding agent create the codebase.&lt;/p&gt;

&lt;p&gt;It continues with the dependencies — or rather their absence. Grotto has &lt;em&gt;zero&lt;/em&gt; runtime dependencies and leans on nothing but the Node built-ins. That is not housekeeping; it is a security boundary. An npm package you never install is a package that can never turn on you: no CVE, no compromised maintainer, no supply-chain attack can reach Grotto through a dependency, because there is not one to reach it through. Which leaves only Grotto's own code, and its design does the rest of the arguing.&lt;/p&gt;

&lt;p&gt;The host-facing library is a small dispatcher of under two hundred lines, and it holds no language code at all. Everything that actually touches a stranger's program — the lexer, the parser, the validator, the interpreter — runs somewhere else entirely: in a fresh worker thread, spawned for that one submission and thrown away the moment it's done. Isolation by construction, not by good manners. Add it all up, and the only Grotto code that ever executes on the embedding application's own thread is that small dispatcher — everything else is quarantined in an isolated worker you can kill.&lt;/p&gt;

&lt;p&gt;Every program runs under the hard ceilings of a budget system as mandated by the Neander spec — on computation, on memory, and on wall-clock time. Overrun the time limit and the worker is simply killed; the dispatcher keeps the clock, so even a wedged program can't outlast it. The language itself has no recursion and no unbounded loops, so termination was never in doubt to begin with — the budgets are there for everything else.&lt;/p&gt;

&lt;p&gt;The last line of defense is quality assurance. The Grotto specifications and codebase so far have been reviewed by several frontier coding models: Opus 4.6–4.8, GPT-5.5, and, by sheer luck, Fable 5.&lt;/p&gt;

&lt;p&gt;The codebase quality is verified by over 1,300 unit tests (coverage &amp;gt; 90% but let's not get overexcited about percentages alone) and more than 750 black-box end-to-end tests (program submissions) that are continuously grown toward a Neander conformance test suite.&lt;/p&gt;

&lt;p&gt;You can always do more, and I will. But the groundwork is laid — enough, I'd hope, that anyone weighing Grotto for a real host application can take it, and Neander with it, seriously.&lt;/p&gt;

&lt;p&gt;It's early days, to be clear. The spec is still a draft, the version number starts with a zero, and interfaces might move. But it's real, and it all runs today — a runtime, not a roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you do with it
&lt;/h2&gt;

&lt;p&gt;If Neander reads strangely because only agents write it, Grotto reads normally, because only humans host it. You embed the library, hand it your own APIs as provider modules, and point agents at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grotto1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;neanderVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// The ceilings every program runs under.&lt;/span&gt;
    &lt;span class="na"&gt;thalerBudget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// computation&lt;/span&gt;
    &lt;span class="na"&gt;memoryBudgetKb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// memory&lt;/span&gt;
    &lt;span class="na"&gt;maxDurationMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// wall-clock time&lt;/span&gt;
    &lt;span class="na"&gt;perCallTimeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// per individual API call&lt;/span&gt;
    &lt;span class="na"&gt;maxProgramSizeBytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="nx"&gt;_536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// static caps, checked before a program runs&lt;/span&gt;
    &lt;span class="na"&gt;maxNestingDepth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxRepeatLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Your APIs — each a module the runtime hosts on your behalf.&lt;/span&gt;
    &lt;span class="na"&gt;apiProviderModules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/app/providers/bookings.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseEnvelope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitProgram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;programFromAgent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every submission comes back as a single structured envelope — the value it returned, or the precise way it failed: a program that didn't type-check, a call that errored, a budget that ran out. One program in, one well-formed answer out, every time. Nothing leaks, nothing hangs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Field Notes from the Grotto
&lt;/h2&gt;

&lt;p&gt;There is far more to cover. How an agent discovers your APIs at runtime. The budget system that keeps a program from ever running up a bill. The worker isolation that lets you run a stranger's code at all. Each is worth a post of its own — so I'm starting a new series on the Neander language and its runtime, feature by feature.&lt;/p&gt;

&lt;p&gt;In the meantime, Grotto is on &lt;a href="https://github.com/newadventuresinit/grotto" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, the license is permissive, and the floor is open. Embed it into your app, and let me know what breaks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>neander</category>
    </item>
    <item>
      <title>Neander: An Agent-First Programming Language</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Tue, 23 Jun 2026 12:09:58 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/neander-an-agent-first-programming-language-3i3o</link>
      <guid>https://dev.to/newadventuresinit/neander-an-agent-first-programming-language-3i3o</guid>
      <description>&lt;p&gt;Last time, in &lt;a href="https://dev.to/newadventuresinit/source-code-as-the-seam-between-systems-5b1p"&gt;Source Code as the Seam Between Systems&lt;/a&gt;, I closed by saying I had built a programming language for the seam between systems, and that I would come back to it.&lt;/p&gt;

&lt;p&gt;Here we are.&lt;/p&gt;

&lt;p&gt;The language is called &lt;strong&gt;Neander&lt;/strong&gt; (named after the &lt;a href="https://en.wikipedia.org/wiki/Neanderthal" rel="noopener noreferrer"&gt;Neanderthal&lt;/a&gt;), and its specification is available now.&lt;/p&gt;

&lt;p&gt;A quick recap of where the last post left us. When one system needs another to do something, the seam between them used to be a wire carrying structured data, with a human in the middle writing the integration code. Take the human out, let the calling system be an agent that decides at runtime what it needs, and the seam stops being a wire. It becomes a language. The called system exposes an execution environment, and the caller drives it by sending small programs.&lt;/p&gt;

&lt;p&gt;Neander is that language.&lt;/p&gt;

&lt;h2&gt;
  
  
  The inversion
&lt;/h2&gt;

&lt;p&gt;The model everyone started with is the tool catalog: load every function the host exposes into the agent's context, then let the agent pick. It does not scale. Hundreds of tool definitions clutter the context, every intermediate result piles up on top, and costs and latency climb with them.&lt;/p&gt;

&lt;p&gt;Neander turns that around. The agent's context holds one compact thing — the Neander Reference itself — rather than a catalog of everything the host can do. To get something done, the agent writes a short program. The program asks the runtime what APIs are available, calls what it needs, composes the results, and hands back a single answer. Discovery happens at runtime, inside the program, instead of up front in the context window.&lt;/p&gt;

&lt;p&gt;That gives the language two verbs that carry most of the weight. &lt;code&gt;discover&lt;/code&gt; asks the runtime what namespaces, functions, and documents exist; &lt;code&gt;call&lt;/code&gt; invokes one of those functions. Everything else — branching and bounded loops, the structural type system, explicit error handling — exists to glue those two together.&lt;/p&gt;

&lt;p&gt;First the agent looks around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;neander 1 {
  types {}
  main -&amp;gt; [Function] {
    let ns: Namespace =? discover namespace "bookings"
    return discover functions ns []
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads the returned descriptions, then writes a second program that calls what it found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;neander 1 {
  types {}
  main -&amp;gt; decimal(2, half_away) {
    let booking: bookings.Booking =? call bookings.get(id: 8821)
    return booking.fare
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is essentially the whole shape of it. One call, maybe a loop over the result, some conditional logic, another call. The agent writes it for a single task, sends it to the runtime, and throws it away.&lt;/p&gt;

&lt;p&gt;The interesting bit is where the code runs. The Neander runtime lives &lt;em&gt;inside&lt;/em&gt; the host: the embedding application registers its own APIs with it, so execution happens server-side, right next to these APIs. As described above, the agent stays on the outside, untrusted, and uses the language to talk to the embedding application.&lt;/p&gt;

&lt;p&gt;This setup, obviously, raises a few eyebrows.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Neander deliberately cannot do
&lt;/h2&gt;

&lt;p&gt;The reassurance is structural, not a promise to behave. The things that make running a stranger's code dangerous don't exist in Neander — there's nothing to wall off because there's nothing there.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It cannot run forever.&lt;/strong&gt; Deliberately not Turing-complete: no recursion, every loop statically bounded. Termination is proven before the program runs — no halting question to lose sleep over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It cannot reach out.&lt;/strong&gt; No file I/O, no sockets, no system access. The only thing a program can touch is an API the host chose to register. There's no sandbox because there's nothing to put in one — the language &lt;em&gt;is&lt;/em&gt; the sandbox.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It cannot run up a bill.&lt;/strong&gt; Every execution runs under hard ceilings on computation, memory, and time (the budget system). Exceed one and &lt;em&gt;that program&lt;/em&gt; is stopped — not the host it runs in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It cannot misuse the host's APIs.&lt;/strong&gt; A program that fails validation never runs; one that passes calls only functions that exist, with correctly-typed arguments, and can never treat a value that might be missing as if it were there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The name fits the philosophy. Like the spare, limited languages of computing's early days, it can do very little — and the less a language can do, the less can go wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The audience
&lt;/h2&gt;

&lt;p&gt;Neander is written for an unusual audience. Only agents author it; humans host it. Hence no getting-started guide, no tutorials — nothing for a manual coder.&lt;/p&gt;

&lt;p&gt;Besides the normative &lt;a href="https://newadventuresinit.github.io/neander/specification.html" rel="noopener noreferrer"&gt;specification&lt;/a&gt;, the example-driven Neander Reference is aimed squarely at the agents that will write the programs — and the runtime hands it to them in-band.&lt;/p&gt;

&lt;p&gt;The website at &lt;a href="https://newadventuresinit.github.io/neander/" rel="noopener noreferrer"&gt;newadventuresinit.github.io/neander&lt;/a&gt; is for the humans evaluating Neander and deciding whether to embed it into their systems. But before they can put it to work, one thing has yet to be published: a runtime reference implementation.&lt;/p&gt;

&lt;p&gt;It already exists — more on that next time.&lt;/p&gt;

&lt;p&gt;In the meantime, the Neander specification is live, the license is permissive, and the floor is open. Have a look around, and let me know what you think.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>neander</category>
    </item>
    <item>
      <title>Source Code as the Seam Between Systems</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Wed, 17 Jun 2026 13:51:37 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/source-code-as-the-seam-between-systems-5b1p</link>
      <guid>https://dev.to/newadventuresinit/source-code-as-the-seam-between-systems-5b1p</guid>
      <description>&lt;p&gt;In my previous blog post &lt;a href="https://dev.to/newadventuresinit/speccing-is-the-new-coding-493g"&gt;Speccing Is the New Coding&lt;/a&gt;, I claimed that source code will not entirely vanish in an agentic world but will change jobs, from the substance of applications to the seam between them. The promise was to come back and dive deeper into the matter.&lt;/p&gt;

&lt;p&gt;Let's take a look.&lt;/p&gt;

&lt;h2&gt;
  
  
  The way it used to be
&lt;/h2&gt;

&lt;p&gt;In classical software development, when two systems need to talk to each other, a developer reads one system's API documentation, then writes integration code for the other system that calls those APIs in the right order with fittingly structured data. It works, and we have decades of practice at it. But the calling system never actually understands the API it is calling. Only the developer does. The system is just faithfully carrying out instructions written by a human during design time.&lt;/p&gt;

&lt;p&gt;That arrangement has already started to look somewhat quaint. When at least the calling system is agentic, deciding at runtime what it needs from the other system, the human in the middle must go.&lt;/p&gt;

&lt;h2&gt;
  
  
  The top-down approach: mathematical musings
&lt;/h2&gt;

&lt;p&gt;We humans have the built-in ability to "prompt" each other. We call it a conversation.&lt;/p&gt;

&lt;p&gt;This technique has proven so successful over the centuries that we even modeled our latest human-machine interface after it. So, why stop there? If it's this successful, it makes sense to apply the concept to machine-machine interfaces, right? Let one system tell the other, in plain English, what it wants, and together they can purposefully harness each other's capabilities to achieve a common goal.&lt;/p&gt;

&lt;p&gt;It would work. It would go down in IT history as the most generic, most flexible interface technology ever invented.&lt;br&gt;
And as the most memorable security vulnerability ever shipped.&lt;/p&gt;

&lt;p&gt;This naive approach is doomed. Plain natural language is unbounded by design. There are no limits to what can be said, and no formal guarantees about what the words mean. &lt;/p&gt;

&lt;p&gt;But there need to be boundaries. System boundaries.&lt;/p&gt;

&lt;p&gt;So if we want one system to tell another what to do, two things have to be true: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;there have to be &lt;strong&gt;limits on what can be said&lt;/strong&gt;, and &lt;/li&gt;
&lt;li&gt;what &lt;em&gt;is&lt;/em&gt; said has to have &lt;strong&gt;unambiguous meaning&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These requirements clearly point to formal languages in the mathematical sense. And programming languages are of course &lt;em&gt;exactly&lt;/em&gt; formal languages with finite syntax, precise semantics, and bounded expressiveness. We invented them to talk to machines, and they happen to be the right shape for machines talking to other machines, too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom-up approach: tool time
&lt;/h2&gt;

&lt;p&gt;Early in the development of agentic systems, it became clear that they needed a way to interact with the outside world. So a string of techniques was invented, which we now refer to as &lt;em&gt;tool calling&lt;/em&gt;. In essence, it is a way for an agent to first absorb an API spec and then execute API calls.&lt;/p&gt;

&lt;p&gt;Sounds straightforward. It wasn't.&lt;/p&gt;

&lt;p&gt;In November 2025, the creators of the &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;MCP standard&lt;/a&gt; published an &lt;a href="https://www.anthropic.com/engineering/code-execution-with-mcp" rel="noopener noreferrer"&gt;article&lt;/a&gt; highlighting two important lessons learned about tool calling. &lt;/p&gt;

&lt;p&gt;First, for an agent to call a tool, it needs to know it exists; hence, all tool definitions are loaded into the agent's context up-front. This did not pose a problem for a handful of tools, but, as it turned out, customers actually needed to expose an agent to hundreds, even thousands, of tools. This led to cluttered contexts, increased response times, and also increased costs.&lt;/p&gt;

&lt;p&gt;Second, since the agent acts as the orchestrator of tool calls, every intermediate tool call result is added to the context, leading to the same problems as above, plus reliability and even data protection issues.&lt;/p&gt;

&lt;p&gt;The proposed remedy, in a nutshell, is to make the agent write code against the API and add only the end result to its context.&lt;/p&gt;

&lt;p&gt;What is interesting is that we arrive at the very same conclusion as in our top-down approach, although we started from two very distinct places: &lt;/p&gt;

&lt;p&gt;The seam between two systems is no longer a wire that carries structured data. It is a language, an exposed execution environment that the calling system uses by sending programs to it.&lt;/p&gt;

&lt;p&gt;It is worth noting that the above-mentioned article served as my initial inspiration for what I am about to suggest, even though my take on this does not follow the original proposal.&lt;/p&gt;

&lt;h2&gt;
  
  
  An agent-first programming language
&lt;/h2&gt;

&lt;p&gt;Now that we have arrived at this conclusion, the next step seems obvious: select your favorite language, make an API of your choice available to it, and tell the agent to start sending programs.&lt;/p&gt;

&lt;p&gt;Entirely possible. Pragmatic. But is it such a good choice after all?&lt;/p&gt;

&lt;p&gt;Practically all of our existing mainstream programming languages are general-purpose languages. By design, you can do literally anything with them. Sure, they will meet our requirements, no matter what they are. And we can always use sophisticated technologies, such as sandboxing, to suppress any superfluous or dangerous features we do not need.&lt;/p&gt;

&lt;p&gt;But as the famous quote by Antoine de Saint-Exupéry goes: "Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away."&lt;/p&gt;

&lt;p&gt;So, let's do this the right way round and start from scratch. What do we actually need for this specific use case?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt;: An agent-centric language design aimed at API-orchestration. One call, a loop over the result, some conditional logic, another call. That's it. Nothing fancy, and no syntactic sugar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictability&lt;/strong&gt;: Every program is guaranteed to terminate by design. No file I/O, no sockets, no system access. The language &lt;em&gt;is&lt;/em&gt; the sandbox.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discoverability&lt;/strong&gt;: A program can find out about the available APIs and even the language itself &lt;em&gt;at runtime&lt;/em&gt;. No upfront documentation, no out-of-band integration step.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The human in the middle has vanished. The seam no longer sits between the two systems where the translator had to place it, but has moved into the called system. It exposes the language, sets the rules, and defines the vocabulary. The calling system arrives ready to speak whatever it finds. And the three design principles above aim to make this integration seamless: simple enough that the calling system doesn't struggle to use it. Predictable enough that the called system doesn't have to fear it. Discoverable enough that no human has to explain it in advance.&lt;/p&gt;

&lt;p&gt;With all this in mind, I have created, from the ground up, a new programming language and its runtime reference implementation for precisely this use case.&lt;/p&gt;

&lt;p&gt;More on that next time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
    </item>
    <item>
      <title>TaskTrack — A Specify Spec for Agent Task Management</title>
      <dc:creator>Dirk Mattig</dc:creator>
      <pubDate>Fri, 05 Jun 2026 06:45:44 +0000</pubDate>
      <link>https://dev.to/newadventuresinit/tasktrack-a-specify-spec-for-agent-task-management-2bhh</link>
      <guid>https://dev.to/newadventuresinit/tasktrack-a-specify-spec-for-agent-task-management-2bhh</guid>
      <description>&lt;p&gt;It is time to put my proposition made in my &lt;a href="https://dev.to/newadventuresinit/speccing-is-the-new-coding-493g"&gt;previous blog post&lt;/a&gt; to the test. Is it possible to spec an application for execution by an agent without encoding it in source? Let's find out.&lt;/p&gt;

&lt;p&gt;One type of application every knowledge worker is familiar with is task management. Every task has a lifecycle status, dependencies on other tasks, and a history of progress.&lt;/p&gt;

&lt;p&gt;Let's give agents their own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/newadventuresinit/tasktrack" rel="noopener noreferrer"&gt;&lt;strong&gt;TaskTrack&lt;/strong&gt;&lt;/a&gt; is a simple but non-trivial task management system variant implemented as a &lt;a href="https://newadventuresinit.github.io/specpack/specify/" rel="noopener noreferrer"&gt;Specify&lt;/a&gt; spec. It goes beyond checkbox-based to-do lists that agents sometimes use internally and mimics the key system features listed above.&lt;/p&gt;

&lt;p&gt;TaskTrack defines two procedures: a "Plan Authoring Run" to create an interconnected set of tasks from requirements and a "Plan Execution Run" to advance a previously authored plan toward completion. One execution run might not always be enough to achieve completion, because TaskTrack allows requesting human feedback and incorporating it during the next execution run. Furthermore, every execution run is divided into "Task Processing Run" sub-procedures to allow for advanced agent context management.&lt;/p&gt;

&lt;p&gt;TaskTrack implements all of this in less than 300 lines of text. If the implementation used source code, then, depending on the programming language, this would be enough space to implement only the required file I/O operations (TaskTrack uses files for simplicity, not a database). Natural language can easily become quite bloated, but a stringent, scientific writing style and extensive use of what the Specify standard offers can effectively counter that.&lt;/p&gt;

&lt;p&gt;The official test is, how could it be any other way, the implementation of yet another uninspired Breakout clone. The requirements, the completed TaskTrack plan, and the deliverable are contained in the repository.&lt;/p&gt;

&lt;p&gt;If you want to run the test yourself, the included README file contains the necessary information, including the launch prompts for both the authoring agent and the execution agent. Please note how both launch prompts are structured. They use TaskTrack terminology and point to the relevant files. They do not contain task-related behavioral instructions. The execution agent launch prompt contains agent-specific instructions for mapping agent features to the generic TaskTrack specification. The principles behind the good old manual coding design patterns remain valid even in the agentic era!&lt;/p&gt;

&lt;p&gt;And now, finally, for the test result. In a nutshell: It works!&lt;/p&gt;

&lt;p&gt;The authoring agent created all TaskTrack files as indicated, which is, maybe, less surprising or impressive. More importantly, the execution agent showed deterministic behavior over all 16 tasks and two execution runs. I often hear that deterministic behavior must remain encoded in source due to the inherently random, and hence non-deterministic, nature of LLMs. I cannot confirm this based on the test result. The execution agent followed the step-by-step procedure definition by the book each and every time. Even the defined textual output was created as reliably and repeatably as if it were produced by a &lt;code&gt;print&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;It goes without saying that this single test result does not deliver a general proof of the viability of speccing. It shows it can work; it is possible. Maybe non-deterministic agent behavior is more often than not the result of unspecific instructions rather than randomness in the underlying LLM.&lt;/p&gt;

&lt;p&gt;Having said all this, the test run was far from being perfect. It produced several so-called valuable learning experiences.&lt;/p&gt;

&lt;p&gt;The first and most obvious finding is that all but one of the timestamps are incorrect. The authoring agent wrote and executed a Python script to retrieve the current UTC time. All task processing subagents simply invented timestamps. When I later asked the system about this difference in behavior, it gave an interesting answer: Creating a new timestamp is a "single, salient, one-off step... worth a real python/date call." Updating timestamp fields is "a repeated, mechanical step... every task, every run, in fresh subagent contexts," and that "models systematically deprioritize repeated boilerplate."&lt;/p&gt;

&lt;p&gt;This is not a TaskTrack issue but rather the result of an ill-equipped agent. And it is at this point, where, no matter how hard I try, I cannot stop myself from making the tongue-in-cheek remark that the machines that are feared to first fire and then nuke us apparently have no built-in access to the current time... I will keep this in mind, just in case.&lt;/p&gt;

&lt;p&gt;The second finding is that, as the agent itself remarked when reviewing the test results, task resolutions are not necessarily as brief as mandated by the TaskTrack specification. But then, what is brief? Precisely. This is the kind of hastily written, hand-wavy instruction that is open to interpretation and leads to varying results. Just because we are using natural language now does not mean we are allowed to let our rigor slip.&lt;/p&gt;

&lt;p&gt;Luckily, it is not a major pain point, since it only affects the resolution, not the core processing logic. Still, it is worth fixing in a future publication.&lt;/p&gt;

&lt;p&gt;The third finding is that, strictly speaking, the test run was flawed because these wonderful machines now have memory. Both the authoring and execution agents revealed in their thinking output that they were aware that this was a test. I do not think this flaw invalidates the qualitative test result as such. Still, future test setups will require more care and consideration.&lt;/p&gt;

&lt;p&gt;In the meantime, the TaskTrack specification is live, the license is permissive, and the floor is open. Have a look around, and let me know what you think.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
