Files
AILang/docs/WhatsNew.md
T
Brummel 9a609aecc7 roadmap: close Embedding ABI — M2 ([~]->[x]); WhatsNew
M2 (per-thread runtime context + concurrency safety) fully closed:
spec 1c58055 (grounding-check PASS 9/9) -> plan b3388c8
(Boss-corrected) -> iter c9a84b3 (PARTIAL 4/9 + Boss spec-defect
repair) + fbeeade (DONE 5-9) -> audit ad88dec (CLEAN-after-tidy;
bench exit 1 causally exonerated, NO ratify) -> tidy a80d495
(doc-honesty lockstep rename). No fieldtest (zero authoring-surface
change). Sanitiser-verified coherent stop. WhatsNew entry mirrors
the done-state notify text verbatim. Next: Embedding ABI — M3 (a
new milestone, no spec — a /boss new-milestone bounce-back).
2026-05-18 17:53:13 +02:00

30 KiB
Raw Blame History

What's New in AILang

A user-facing changelog. It runs in parallel to the technical per-iter journals (docs/journals/) and is written for the user-as-reader who did not watch the work happen — no crate names, no iteration codes, no implementation mechanics.

Each entry corresponds to a done-state notification I sent to the user when he was away. The text is identical to what arrived on his phone. New entries are appended at the bottom.

What gets described here is what changed in the project — what is now possible, fixed, or visible. For the internals, see docs/journals/INDEX.md and the per-iter journal files it points at.


2026-05-11 — implement cycle no longer eats your context

The implement cycle no longer eats your context. From the next time we run it, the whole per-task review loop runs inside a dedicated subagent and sends back a short end-report — typing the chatter into someone else's window, so to speak. Each iter gets its own branch; merging or discarding is your call, and rollbacks become trivial. The 14k-line journal monolith is split into per-iter files with a small index for navigation; the old file is archived. Audit ran clean — no benchmark regressions; a small follow-up sweep fixed four spots where I'd missed stale references to the old layout.

2026-05-11 — plan-writing offload + first-run debrief

Plan-writing also offloads its read-heavy phase now — the part where I walk the codebase looking for the right files for a plan. Should feel lighter on the chat side from the next plan onwards. First real test of yesterday's per-task isolation refactor exposed a tool-wiring bug along the way: the Boss-context offload part works (you still see only an end-report at the end), but the per-task fresh-subagent isolation didn't actually fire — all tasks ended up running in one bigger context inside the orchestrator. Diagnosis is queued at the top of the next-up list. A small iter-branch setup wording bug got fixed in the same session. Audit clean, no benchmark regressions.

2026-05-11 — implement-cycle correction

Yesterday's note claimed the per-task review loop runs as separate fresh-context phases. That isn't possible — Claude Code does not allow a sub-helper to spawn further sub-helpers, full stop. The project's older "no helper calls another helper" convention turns out to have been a restatement of that platform rule all along, not a local design choice with a documented exception. So the architecture is corrected: the per-task loop still runs offloaded from your chat into one dedicated helper context (the part that does save your chat budget), but the review phases now run as sequential checkpoints inside that helper rather than as separate fresh contexts. The chat-side experience does not change. The fresh-per-phase isolation that the earlier note implied does not exist and never could; honestly scoped now.

2026-05-11 — Spec grounding-check

Every design spec now has to pass an independent check before approval. The check reads the spec with no prior context, extracts every claim it makes about how the project already works, and confirms each one against a currently-green test. If even one claim has no test backing it, the spec is discarded and the idea moves to the roadmap until the missing piece ships.

An in-flight design for the eq/ord prelude was retired today because it would have failed this check: a load-bearing claim about how the compiler currently handles polymorphic helpers was never verified, and three implementation attempts on it had stalled. The re-think happens in a separate session once the missing mechanism exists.

2026-05-12 — Eq/Ord prelude work back on track

The blocker that stranded yesterday's eq/ord prelude work is fixed. The language now handles polymorphic helper functions that call class methods inside their body — the canonical shape every prelude helper takes — by routing both kinds of specialisation through one unified pass instead of two competing ones. A small fixture (cmp_max at three integer values) confirms it end-to-end, and the regression check shows the six existing primitive eq/ord symbols still produce bit-identical output.

Next: the five missing prelude utilities (ne, lt, le, gt, ge) and their end-to-end tests.

2026-05-12 — Eq/Ord prelude finished, milestone closed

The eq/ord prelude milestone is done. ne, lt, le, gt, ge now ship in the auto-loaded prelude as polymorphic helpers that work on any type with the matching class instance — verified end-to-end at Int, Bool, Str, and at a user-defined type with hand-written instances. The same helpers applied to Float produce a Float-aware error pointing back to the language reference's Float section, instead of the generic "no instance" message.

A couple of latent bugs in the type-checker surfaced while testing the natural shape of these helpers (one in the linearity check, one in the polymorphic-specialiser collector) and got fixed in the same pass. The benchmark suite is clean except one sub-millisecond noise-band metric just over the tolerance line — expected given the prelude grew, ratifiable at the next baseline refresh.

Show + print-rewire remains the next prelude piece, gated on heap-string runtime support.

2026-05-12 — Milestone-close audit: Eq/Ord prelude ratified

Milestone-close audit done. The compile-time regression that surfaced after the prelude grew was localised: it isn't an optimisation bug, it's the cost of typechecking five more helper functions on every workspace check. Ratified — the new typecheck times are now the baseline. The benchmark suite is back to all-green.

One unrelated grammar stub in the language reference (left over from a doc consolidation two days ago) was caught by the audit drift-review and fixed in the same pass.

Milestone 23 is officially closed. Queue is back to user-direction.

2026-05-12 — Roundtrip safety net for the two program forms

AILang now ships with a continuous safety net that verifies the two program forms (the structured form and the textual form) stay losslessly interchangeable. Three checks run on every build:

  • Every example in the corpus round-trips between the two forms with byte-identical canonical output.
  • Every language construct is required to have at least one example exercising it; coverage gaps fail the build.
  • The command-line render/parse flow preserves canonical bytes end-to-end, verified by a hash check.

All three pass first time on the current corpus — the two forms are exactly equipotent today, no expressive-power gap between them. This is the prerequisite for the cross-model authoring test that was deferred earlier; the language can now go into that test on solid ground.

2026-05-12 — Roundtrip-invariant milestone closed

The roundtrip-safety-net milestone is done. The two program forms — the structured one and the textual one — are now anchored as a named, prominent invariant in the language reference, with five continuous checks enforcing it on every build: byte-identical round-trip in either direction, every language construct exercised by at least one example, and the CLI render/parse flow verified by hash identity. Float literals (NaN, infinities, subnormals) round-trip exactly through the bits-hex encoding — inside the invariant, not an exception.

No language behaviour changed; the corpus already satisfied the property. The milestone made it explicit and locked in against future drift.

Bench all-green. Roadmap is back to user-direction; the Show+print rewire is gated on the heap-string runtime work.

2026-05-12 — Cross-model authoring test: first data point

Cross-model authoring test landed. First run (Qwen3-Coder-Next, 4 tasks, two blind cohorts): textual-form cohort 2/4 green at ~half the token cost; structured-form 1/4. Only first-attempt success was textual-form on a print-two-integers task — five lines vs twenty-three. Recorded as a data point in the language reference; single-model scope explicit. Multi-subject expansion queued as next experimental milestone. Milestone closed; queue back to user-direction.

2026-05-12 — Cross-model authoring test: second subject added

Cross-model authoring test now has a second subject. Ran CodeLlama alongside Qwen on the same four tasks, fresh runs both. Both models point the same way: textual form is cheaper than structured form on tokens (6180% of structured form's cost across the two models) and reaches working code at least as often (Qwen ties on this run; CodeLlama strictly dominates 2/4 vs 0/4). Three of four first-attempt successes across the two models are textual. A feedback-asymmetry bug surfaced during planning — the structured form was silently losing its underlying parse error on the way back to the model — and got fixed in the same milestone, so both subjects ran with the fix in place. Not a universal verdict yet (would need three or more models with repetitions), but the second data point points the same way as the first. Milestone closed; queue back to user-direction.

2026-05-12 — Strings produced at runtime now release cleanly

int_to_str, float_to_str, and similar built-in primitives that produce a freshly allocated string at runtime now participate in reference counting like every other heap value. A string passed into a print call is now treated as borrowed, not consumed, so it is freed exactly once when it leaves scope. Earlier programs that converted a number to a string and printed it leaked the string buffer at program exit; that no longer happens.

A side effect: a function parameter declared (own T) whose only in-body use was passing it into a print call will now correctly be flagged as over-strict — that annotation should be relaxed to (borrow T) (or removed). No current example programs are affected.

2026-05-12 — Same-named types across modules no longer collide

Two modules in one workspace can each declare a type with the same bare name (for example, both declaring type Foo) and carry their own type-class instances — both now register under their owning modules and stay distinct. Before this round, one declaration silently overwrote the other in an internal index and the loser's instance tripped a misleading "duplicate instance" error.

Internal housekeeping in the same round: a structurally unreachable validation path was removed, and a previously implicit design decision about the type checker's overlay structure was written down.

2026-05-13 — Two libraries can each declare their own type class

A workspace can now have two libraries each declaring class Show (or class Eq, class Ord, …) with their own method, and both can ship instances for overlapping types. Before this round, the loader rejected the workspace the moment two classes anywhere declared the same method name. A workspace-global ban that was never load-bearing for correctness — just a workaround making bare method calls unambiguous.

The replacement is type-driven dispatch at the call site. When show 42 is reached and only one class in scope declares show, the call resolves automatically. When two do, the checker first narrows by the argument's type (only one class might have an instance for Int); if that still leaves more than one, it reports the ambiguity with the candidate class names and asks the author to disambiguate via <module>.<Class>.show 42. A class method that happens to share a name with a free function is handled the same way: the free function wins by the existing precedence rule, but the author gets a warning so an accidental shadow doesn't pass silently.

This unblocks the deferred prelude rewire — the prelude is free to ship its own class Show now that it no longer collides with the 14 test fixtures that historically declared a user-class named Show.

The new dispatch model is now anchored in the language reference as a named subsection; the related class-reference fields (InstanceDef.class, Constraint.class, SuperclassRef.class) follow the same bare-for-same-module / <module>.<Class>-for-cross-module rule that type names already follow.

Bench all-green; the noise envelope on a tail-latency metric continues for the sixth consecutive audit and remains baseline-pristine pending a clear signal-vs-variance call.

Queue back to user-direction: the prelude-rewire milestone is ready for a fresh design pass against the new dispatch model.

2026-05-13 — Show and print

AILang now ships show and print in the prelude. show x produces a Str representation for Int, Bool, Str, and Float (Float is included because textual representation is well-defined modulo libc's NaN spelling, whereas Eq/Ord deliberately exclude Float). print x is a polymorphic helper that formats x via show and writes to stdout — it works on any type Show has been declared for, including user-defined ones via instance Show MyType.

Calling print on a type without a Show instance is rejected at typecheck with a Show-aware diagnostic pointing at the prelude documentation.

The per-type io/print_int, io/print_bool, io/print_float operators stay for now; new code is expected to use print. A future milestone migrates the existing example corpus.

Bench continues on the same noise envelope observed across the last ten audits — baseline pristine.

Queue back to user-direction: the next roadmap item is that corpus migration, which retires the per-type print operators.

2026-05-13 — Form A is now the authoring surface

.ail is now the form you read, edit, and check in. The JSON-AST counterpart is still the canonical hashable representation, but it is no longer something you look at or maintain by hand — the tools derive it from .ail whenever they need it. About a hundred example programs that previously existed as JSON-AST were converted to .ail and the JSON files were deleted; only eight stay as .ail.json (seven negative-test fixtures whose subject matter cannot be expressed in .ail, plus the prelude, which is loaded at compile time and depends on internal plumbing that lands in a later milestone).

If you were used to opening a .ail.json file to see what a program looks like, you now open the .ail sibling instead.

2026-05-13 — Three follow-ups from the Form-A field test

Post-fieldtest follow-up complete; three iterations landed back-to-back.

  • ail check now catches unbound variables inside instance method bodies. Before this, the typecheck step silently passed the program and the error only surfaced at build time as a degraded internal-error diagnostic with no source location or symbol hint.

  • The Form-A authoring spec now documents the typeclass surface — class declarations, instance declarations, and constraints on polymorphic functions — so a downstream LLM author can write typeclass programs from the spec alone, without having to mine the example corpus to reverse-engineer the grammar.

  • New str_concat primitive for combining two strings. The natural Show-MyType body shape ("Item " ++ int_to_str x-style labelled output) is now expressible; previously every such instance had to either drop the prefix or punt entirely.

Queue back to user-direction: two next-milestone candidates queued on the roadmap — retiring the per-type io/print_int|bool|float operators in favour of polymorphic print (about eighty-six fixtures to migrate), or moving the prelude embed to Form-A as compile-time source (resolves the one remaining .ail.json carve-out that exists for architectural reasons rather than subject-matter reasons).

2026-05-13 — Documentation and drift-test hygiene

Two internal cleanups while you were away. Compiler documentation builds without warnings now — twenty-three stale cross-references between docs and code were repaired. And the test that guards DESIGN.md against drifting out of sync with the schema is now stricter — it scans only the schema section, so an anchor that happens to appear somewhere else in the document can no longer mask a missing entry in the canonical reference.

2026-05-14 — Compiler now builds clean under cargo clippy

Sixty-one linter warnings across the whole compiler workspace are gone — the project now builds clean under cargo clippy --workspace --all-targets. Most were documentation: multi-line comments that the linter misread as bullet lists, and orphan doc-blocks left over from the recent test relocations. A handful were idiomatic refactors (.expect_err instead of .err().expect(), dropped redundant closures, collapsed nested pattern matches, two manual Default impls replaced by #[derive(Default)]). Three places kept the original code with an inline #[allow] and a one-line reason, where the linter's suggestion would have lost meaning. All tests stay green, the documentation build stays warning-free, and all three benchmark scripts pass at the existing baselines — confirming the sweep touched no semantics.

The per-type print operators are retired. print (polymorphic, ships in the prelude) is now the only way to print a value of a primitive type — the older print_int / print_bool / print_float operators have been removed from the compiler, and every example program in the corpus is migrated. The byte-channel primitive that emits a string directly remains as the one surviving direct-output operation; everything else routes through print via the Show class.

Two compiler bugs surfaced during the migration and were fixed before the retirement landed. First, calling print with a class-method-call expression as its argument (for example print (eq 1 2)) crashed during code generation — the compiler was mis-aligned by one slot when resolving the class constraint, and the inner eq was being mis-rewritten as a show call. The fix straightens the slot accounting at every polymorphic free-function call site. Second, print was leaking one heap-allocated string per call under reference-counting mode — the let-binder for the rendered text inside print's body was not being recognised as trackable, so its scope-close drop never fired. The fix annotates the prelude Show method correctly and stops a type-rewriting helper from silently stripping mode metadata during substitution.

Plus one CLI polish item that the field test from the last cycle had flagged: ail check and eight other subcommands now show the same [error-code] prefix in their text output that the JSON mode has always shown — so a failed import, a module cycle, a schema mismatch, all arrive on the human path with the same diagnostic code that scripted consumers were already using. Before this fix the text path used a different formatter that dropped the code; now both paths share one source.

All tests green. Compiler builds clean under both the linter and the documentation builder. Benchmark scripts all pass at the existing baselines.

2026-05-14 — Prelude is now authored as .ail like every other source

The prelude — until today the one file in the project authored as JSON-AST instead of as .ail — has been retired in its old form. Every source in the project, including the prelude itself, is now written in the .ail authoring surface; the JSON form is derived by the build, not hand-edited. The principle "authors write .ail; the build derives the JSON form" now holds for every source without exception.

Compiled programs are unchanged. Before deleting the old JSON file a one-time identity check ran to confirm that parsing the .ail prelude produces the exact same canonical module the JSON used to deserialise to; the check fired green, so the deletion was safe. A long-term hash anchor stays in place to catch accidental drift.

All tests green; benchmark scripts all pass at the existing baselines.

2026-05-15 — Local mutable state arrives

Programs can now declare mutable bindings inside a function body. A new block form opens a sealed region where the author names one or more mutable cells (with explicit types and initial values), can reassign them as the block runs, and can read their current value as any other identifier. Once the block ends the cells go out of scope — they cannot escape into a returned value, a closure, or anywhere else. The enclosing function's type signature stays pure: no new effect leaks out, so the caller sees the same contract as before.

The supported cell types are the four stack-resident scalars: integer, floating-point, boolean, unit. The heap-managed string type and any user-defined data type are deliberately not supported in this first cut — extending to them needs separate design work on how reassignment interacts with reference counting.

Two example programs ship to show the shape: one sums the integers from 1 to 10 using a mutable accumulator (prints 55), the other does the same for floating-point. Both run end-to-end through the build pipeline.

This is the first step on a longer arc. The motivation is real-world streaming computations — rolling indicators, online aggregates, sensor fusion — whose math is intrinsically "old state plus new input gives new state plus output" per step. Pure-functional state-threading expresses that, but at a real ergonomic cost as pipelines lengthen. Local mutable state is the foundation; later steps add escaping references, the effect annotation that goes with them, and a composable transducer type. The roadmap entry for the broader vision sits under the heading "Stateful islands — bounded mutation for streaming workloads".

A safety guard added at the same time: a lambda whose body refers to a mutable cell from an enclosing block is rejected by the type checker with a clear diagnostic, since cells are tied to their block's lifetime and cannot be lifted into a heap-stored closure without further design work.

All tests green. Benchmark scripts all pass at the (updated for this milestone) baselines.

2026-05-16 — Experimental iteration redesign fully reverted

The experimental iteration redesign is fully reverted. It had added named loops, compiler-checked-terminating recursion, and a non-termination effect — all removed; the language's iteration story is back exactly to plain recursion, nothing new to learn. It was pulled because it had grown from a small documentation gap into a sweeping language-identity change, then hit a wall where it would have made a very natural kind of function impossible to write, and the only escape weakened a core correctness guarantee you co-designed. Backed out cleanly: program behaviour verified byte-identical to the pre-change state, full test suite green, benchmarks clean (one metric flagged as pre-existing machine drift, not caused by this — filed separately). The honest one-paragraph documentation note the original gap actually needed is now in place; the genuine total-recursion ambition is preserved on the roadmap, sequenced behind the type-system work it really needs. Your streaming example was never part of this and is untouched. Its next step is the effect-handler foundation — the heaviest building block, with open design questions I'd rather shape with you than decide solo while you're away. That's the natural place to pick up.

2026-05-18 — Loops are a first-class language construct

AILang can now express iteration directly: a loop block with named carried values and a re-entry form that jumps back to the top with new values. Until now the only way to repeat work was recursion through helper functions; an author can now write the loop the way they would naturally reach for it. It is complete end to end — it parses, type-checks, compiles to a native binary, and runs.

The natural mistakes are caught at compile time with exact, self-explaining messages: re-entering with the wrong number of values, the wrong types, using re-entry outside a loop or as if it returned a value, and capturing a loop variable inside a closure. Each message names the function and says how to fix it.

It was field-tested by writing real programs from scratch against the documentation alone — integer square root by Newton's method, Collatz step counts, Euclidean gcd used as a sub-expression — all worked on the first try, including shapes the documentation did not explicitly show. An endless loop is allowed and compiles cleanly, with no false "this never terminates" complaint.

Two unrelated, pre-existing minor surface gaps were noticed while testing and written down for later; neither affects loops.

The milestone is closed and fully verified — built across three steps plus a follow-up safety fix, drift-reviewed, benchmarked clean, and field-tested. The next milestone is a fresh decision; that is the natural place to pick up when you are back.

2026-05-18 — Loops read correctly in the prose view

The readable prose rendering of a loop no longer misleads. Until now, a loop's carried values were printed as plain assignment lines inside the loop body — which reads exactly like "reset these to their starting values on every pass", the opposite of what a loop actually does. The starting values now sit in parentheses on the loop keyword itself, the way function parameters do, and the body block contains only the body. This also makes the loop header line up one-to-one with the re-entry form that supplies the next round's values, so the relationship between the two is visible at a glance instead of obscured.

Nothing about how loops are written, type-checked, compiled, or run changed — this is purely the human-reading projection telling the truth about the code it shows. You raised this directly; it is a one-change milestone, drift-reviewed clean and benchmarked clean (the one flagged benchmark metric is the same pre-existing machine-drift item already on the list, not caused by this).

2026-05-18 — The mutable-local construct has been removed

AILang no longer has the block of mutable local variables (mut/var/assign). Everything that used it is now written with ordinary value bindings and loops — plain bindings and if for straight-line and conditional code, the loop construct for iteration. A program that still uses the old keyword is rejected at the first check with a clear error that lists the forms to use instead; there is no lingering half-support.

The removal was justified by examining every existing use: in all of them the mutable block was doing nothing a plain binding or a loop accumulator does not already do. It was then field-tested by writing four fresh real-world programs entirely from the documentation — a running sum of squares, a grade-classification cascade, a polynomial evaluation, and a multi-state bracket-balance scanner. All four worked correctly on the first try with no mutable locals. The one thing the old construct did differently — letting a value silently carry from one loop pass to the next without being restated — is exactly the class of hard-to-follow state bug the language is designed to make impossible, so removing it makes programs easier to reason about, not harder.

The milestone is closed and fully verified — built in one atomic change, drift-reviewed (with one documentation-only follow-up fix), benchmarked clean (the one flagged metric is the same pre-existing machine-drift item already on the list, proven unrelated here by byte-identical binaries before and after), and field-tested. The next milestone is a fresh decision — a documentation-honesty sweep so the specification shows only the language as it actually is, with no wishful "we would like" and no "this went wrong" — and that is the natural place to pick up when you are back.

2026-05-18 — The specification now describes only what the language actually is

The canonical specification and the prose-roundtrip guide have been swept so they tell the truth about the present, and nothing else. Two kinds of writing were removed wherever they appeared: wishful "we would like / planned / will eventually" statements that described things the language does not yet do as if it already did them, and backward-looking "an earlier draft said / this was changed on such-and-such date / this was retired in that round" narration of how the document itself got to its current wording. Forward hopes now live only in the roadmap; the story of how decisions were reached lives only in the development journals. What remains in the specification is a plain present-tense account of the language as it stands — including the things it deliberately does not do, which are still stated, just honestly labelled as deliberate exclusions rather than dressed up as future promises.

To keep it that way, the specification now carries a short rule, in its own words, stating the honesty standard it holds itself to, and there is an automatic check that fails the build if a removed wishful-or-backward-looking phrasing ever creeps back in. The reviewer that guards the specification against drift was taught the same rule, so new violations get flagged going forward rather than only the ones cleaned up now.

Nothing about how programs are written, type-checked, compiled, or run changed — this milestone touched documentation only. It was reviewed clean and the one flagged benchmark metric is, once again, the same pre-existing machine-drift item already on the list, proven unrelated here by before-and-after binaries that are byte-for-byte identical.

2026-05-18 — Compiled kernels can now be called safely from many host threads at once

A compiled AILang function can now be called safely from many host threads at the same time. The previous step made a single compiled numeric function callable from a C or Rust host, one caller at a time; this step makes that boundary swarm-safe — each host worker thread gets its own isolated runtime state, so a whole pool of threads can drive the same compiled kernel concurrently with no shared mutable state and no data race.

This is verified, not asserted: the kernels run clean under a thread-race checker across many threads at once, and a deliberately-broken shared-state variant is correctly caught by that same checker, so the safety guarantee has teeth rather than being a claim on paper.

Nothing about how programs are written changed — the source you write is byte-for-byte the same as before; only the generated calling boundary and its runtime became concurrency-safe, which is exactly the point: thread-safety here is a property of the compiled artefact, not a burden on the author. The build was reviewed clean; the one flagged benchmark number is, once again, the same pre-existing machine-drift item already on the list, proven unrelated here.

The next milestone lets structured records — not just single numbers — cross that boundary with a frozen, stable memory layout. That is a fresh piece of work and a natural point to decide how to pick it up.