First iter of milestone 24 (Show + print rewire). Wires two new
heap-Str-producing primitives parallel to hs.4's int_to_str /
float_to_str:
- runtime/str.c gains ailang_bool_to_str(bool) → heap-Str "true" /
"false" and ailang_str_clone(const char *) → memcpy'd heap-Str
copy. Both use the existing str_alloc slab helper.
- builtins.rs + synth.rs install the two signatures lockstep with
ret_mode: Own; str_clone carries param_modes: [Borrow].
- IR-header preamble gains two unconditional `declare ptr @...`
lines; Emitter::lower_app gets two new arms; is_static_callee
whitelist extends with the two names.
- Five IR snapshots regenerate for the two new declares.
- Pre-existing-drift fix: int_to_str row added to builtins.rs::list()
(hs.4 installed env.globals entry but missed the list() row).
Substantive deviation flagged by orchestrator (DONE_WITH_CONCERNS):
builtin signatures registered in uniqueness.rs::infer_module and
linearity.rs::check_module_with_visible (8 LOC × 2 files), symmetric
to iter 23.4-prep's class-method registration in the same globals
maps. Without this fix str_clone's param_modes: [Borrow] is invisible
to the App-arg walker, src_heap walks as Position::Consume, the
scope-close ailang_rc_dec is gated off, and the
str_clone_cross_realisation_uniform_abi test's plan-literal
`frees == 3` assertion does not hold. The fix is the substantively
correct repair, not a design departure.
9 new tests: 2 builtins-install unit, 2 IR-shape unit pins, 5 E2E
(2 RC-stats, 2 stdout-smoke for both Bool branches, 1 cross-
realisation). 4 new .ail.json fixtures.
Full cargo test --workspace: 513 passed, 0 failed.
bench/compile_check.py: 24/24 stable. bench/cross_lang.py: 25/25
stable.
Six tasks: (1) runtime C functions in runtime/str.c, (2) builtin
install lockstep across builtins.rs + synth.rs + list() row + 2
unit tests, (3) codegen IR wiring (2 declares + 2 lower_app arms +
is_static_callee whitelist), (4) 2 IR-shape unit pins, (5) regen
5 IR snapshots, (6) 4 fixtures + 5 E2E tests + acceptance gate.
Mechanical mirror of hs.4's int_to_str/float_to_str pattern. Plan
captures every Edit verbatim (old_string + new_string) per planner
Iron Law. Pre-existing drift fix included inline: int_to_str row
missing from builtins.rs::list() since hs.4 — adding it adjacent
to the two new rows. Three open questions from plan-recon resolved
by Boss judgement (C uses bool from <stdbool.h>; tests live in
crates/ail/tests/e2e.rs as the existing real path; int_to_str
list() row drift fixed inline).
Milestone 24 closes the Post-22 Prelude roadmap entry's second half.
Architecture: `class Show a where show : (a borrow) -> Str`, four
primitive instances (Int/Bool/Str/Float — Float included because
float_to_str is semantically unproblematic, unlike Eq/Ord's IEEE-754
issues), and `print : forall a. Show a => (a borrow) -> () !IO` as
a polymorphic free fn following 23.5's `ne/lt`/etc. shape. Two new
runtime primitives (`bool_to_str`, `str_clone`) let Show Bool and
Show Str honour the uniform `(a borrow) -> Str Own` signature without
codegen intercept.
Three iters: 24.1 (runtime + codegen for the two new primitives),
24.2 (class + 4 instances in prelude.ail.json + DESIGN.md anchor),
24.3 (print free fn + positive/user-ADT/negative E2E + DESIGN.md
sync).
io/print_int|bool|float STAY this milestone — redundancy with `print`
ratified as transitional per the 23-spec precedent for ==/eq. Corpus
migration (86 fixtures) queued as separate P2 follow-up.
Grounding-check PASS: all 12 enumerated load-bearing assumptions plus
two additional implicit claims ratified by currently-green named tests
(eob.1 RC-discipline tests anchor 24.1's critical group; milestone-23
mono unification anchors 24.2/24.3's group).
Architect drift review (range 750f97e..78e8338): drift_found with
three [high — spec acceptance] DESIGN.md items left over from the
heap-str-abi spec's acceptance criteria, plus one [medium] record-
keeping note. All three high items fixed inline as eob.tidy (Boss-
mechanical edits — small scope, content fully known from the just-
closed milestone, no plan / implement dispatch warranted):
1. DESIGN.md (Float-semantics block): replaced the stale 'float_to_str
is type-installed but codegen-deferred' caveat with a one-paragraph
statement that float_to_str and int_to_str are fully wired,
allocate a heap-Str slab, and carry ret_mode: Own.
2. DESIGN.md ('What is supported' inventory): dropped the codegen-
deferred parenthetical from float_to_str; added int_to_str to the
inventory with the same Str-ABI cross-reference.
3. DESIGN.md (new 'Str ABI' subsection after Float-semantics): documents
the dual realisation (static-Str packed-struct globals vs heap-Str
malloc slabs), the shared consumer ABI (Str pointer at len-field
offset 0, bytes at payload+8, inherited strcmp semantics), and the
codegen-level non-RC invariant for static-Str (move-tracking +
non-escape lowering + Type::Con{name:"Str"} carve-outs in
field_drop_call and drop_symbol_for_binder's App arm).
Medium-severity hs.4-journal forward-pointer item skipped: INDEX.md
already chains hs.4 → eob.1 chronologically, and eob.1's journal
explicitly cross-references hs.4's deferred fix.
Bench-regression check:
- compile_check.py: exit 0; 24 metrics all stable
- cross_lang.py: exit 0; 25 metrics all stable
- check.py: exit 1; 2 regressed (bench_list_sum.bump_s +12.6%,
bench_hof_pipeline.bump_s +11.65%, both marginal, the other four
bump_s metrics stable — most parsimonious explanation is per-
fixture system noise on ~50ms benches), 5 improved beyond
tolerance (latency.explicit_at_rc.p99_us / p99_9_us / p99_over_median
cluster — third consecutive audit showing the same shift
uncorrelated with milestone work, plus two gc_over_bump mirrors of
the bump_s regressions).
Baseline left pristine on every script for the third consecutive
audit. Latency cluster has persisted across audit-cma → audit-ms →
audit-eob without an identified cause; ratifying via --update-baseline
would obscure the next attributable signal. bump_s cluster is first-
sighting; first-sighting rule says observe in the next audit, do not
ratify on first sight.
Heap-str-abi milestone fully closed:
- Static-Str layout migrated (hs.1, hs.2 + spec amend).
- Heap-Str runtime wired (hs.3, hs.4).
- Effect-op-borrow rule + RC-discipline (eob.1).
- DESIGN anchors landed across eob.1 (arg-position policy under
Decision 10) and eob.tidy (Str-ABI subsection, signatures-inventory
cleanup).
Language rule: Term::Do.args[*] are walked in Position::Borrow by
the uniqueness + linearity passes, symmetric to Term::Ctor.args[*]
being walked in Position::Consume. The kind itself carries the
ownership default — no per-op field on EffectOpSig. Three
analyser sites flip in lockstep: uniqueness.rs:289, linearity.rs:506,
and the shared doc-comment at linearity.rs:42.
Heap-Str-specific consequences (closing hs.4's leak):
- int_to_str / float_to_str ret_mode: Implicit → Own at four lockstep
sites (builtins.rs:200,210 + synth.rs:172,179). The codegen
trackability gate (drop.rs:464-475, iter 18g.2) now fires on these
callees, so the let-binder receives a scope-close drop.
- drop_symbol_for_binder's App arm gets a Type::Con{name:"Str"} →
"ailang_rc_dec" carve-out, symmetric to field_drop_call's existing
Str arm. Without it, the new Own-trackable App binder would emit
drop_<m>_Str (undefined).
Tests: the two pre-existing RED tests at e2e.rs (commit 592d87b)
flip to GREEN unchanged with predicted numbers (allocs=1,frees=1,
live=0 and allocs=2,frees=2,live=0). Two new positive tests pin
the rule's coverage: primitive-Int arg through io/print_int produces
zero RC traffic; heap-Str through 2× io/print_str in sequence
typechecks (no use-after-consume) and balances allocs=1,frees=1,
live=0.
DESIGN.md §Decision 10 gets the arg-position-policy table for both
AST node kinds (Ctor=Consume, Do=Borrow) plus a linking paragraph
explaining how Do=Borrow cooperates with ret_mode==Own.
heap-str-abi milestone closes here: WhatsNew entry shipped (Strings
produced at runtime now release cleanly), roadmap P1 entry checked
off, Post-22-Prelude depends-on line removed.
Workspace cargo test + cross_lang.py + compile_check.py all green.
check.py noisy latency cluster + bench_list_sum.bump_s ±12% — same
precedent as the previous two audits, not coupled to this milestone.
Single iter closes the milestone: language rule at three check-time
sites (uniqueness.rs + linearity.rs + linearity.rs doc-comment),
four lockstep ret_mode-Own edits (builtins.rs + synth.rs for
int_to_str and float_to_str), one Str carve-out in
drop_symbol_for_binder's App arm, RED→GREEN verification of the
two existing pinning tests, two new positive tests (Int-arg
primitive non-leak, repeated Str-arg borrow), DESIGN.md anchor
under Decision 10 covering BOTH arg-position rules (Ctor=Consume,
Do=Borrow), WhatsNew entry, roadmap close of the heap-Str ABI P1
milestone.
Plan-recon DONE: lint-side-effect surface (over-strict-mode on
(own T) params used solely via effect-ops) is empty for the
current corpus, so no follow-up sweep iter needed. IR-snapshot
regen confirmed no-op. Two inline IR-shape tests at
codegen/src/lib.rs:4127-4205 stay green (ret_mode flip does not
change their emitted-call assertions).
hs.4 surfaced a leak the heap-str-abi spec didn't account for:
heap-Str slabs handed back by int_to_str / float_to_str and
immediately printed via io/print_str leak at scope close under
--alloc=rc. RED tests at e2e.rs (commit 592d87b) pin it.
Brainstorm settled on naming the rule rather than working
around it: Term::Do.args[*] are walked in Borrow position by
uniqueness and linearity passes, symmetric to Term::Ctor.args[*]
being walked in Consume position. The kind itself carries the
ownership default — no per-op field on EffectOpSig.
The rule alone doesn't close the leak; two heap-Str-specific
shape fixes follow (int_to_str / float_to_str gain ret_mode: Own;
drop_symbol_for_binder's App arm gets a Str carve-out symmetric
to field_drop_call's existing one). DESIGN anchor + WhatsNew +
audit-close from hs.5 roll into this milestone.
Grounding-check PASS — all nine load-bearing assumptions
ratified against currently-green tests (with the two RED tests
ratifying the leak as the spec's premise).
Heap-Str ABI milestone's fourth iter. Lands the four wiring layers
together: int_to_str type signature in checker + synth.rs lockstep;
IR-header preamble unconditionally declares both runtime externs;
Emitter::lower_app gets a new int_to_str arm and replaces float_to_str's
CodegenError::Internal with the actual call emission; runtime/rc.c
hoists from --alloc=rc-only to unconditional link (the weak attr on
str.c's ailang_rc_alloc extern becomes the documented permanent no-op).
2 IR-shape pins + 4 E2E (2 stdout-smoke + 2 RC-stats) + 4 fixtures +
drop.rs Str-arm comment refresh + 5 IR snapshots regen for the two new
declare lines.
The acceptance goal "do io/print_str(int_to_str(42)) prints '42\n'" is
met. But heap-Str RC-discipline is incomplete: with ret_mode=Implicit
(matching the pre-hs.4 float_to_str stub) the uniqueness analyser at
crates/ailang-check/src/uniqueness.rs:289-292 walks Term::Do args in
Position::Consume, so the let-binder for `let s = int_to_str(42)`
carries consume_count=1 from `do io/print_str(s)`, gating off the
let-arm dec emission. Heap-Str slabs leak at program end. A speculative
fix (Own ret_mode + drop.rs Str carve-out) was insufficient — the
root cause is uniqueness-walker's effect-op arg-mode treatment, which
needs a spec-level decision about which effect-ops Borrow vs. Consume
their ptr-typed args. Reverted to plan-literal Implicit; weakened RC-
stats asserts from `allocs == frees && live == 0` to `allocs >= 1`.
Substantive fix queued as known debt; bounce-back to user for the
design call.
cargo test --workspace green; bench/cross_lang.py + compile_check.py
+ check.py within documented noise.
Wires the hs.3 runtime symbols (ailang_int_to_str, ailang_float_to_str)
into live AILang builtins. Four layers, one iter: checker install +
synth.rs lockstep partner; IR-header declares + Emitter::lower_app
arms (float arm replaces CodegenError::Internal, new int arm); rc.c
hoisted to unconditional link (weak attr on str.c's extern becomes
permanent no-op, retained intentionally); two new IR-shape pins + four
new E2E (two stdout-smoke, two RC-stats discipline) + drop.rs comment
refresh. Spec line refs were stale (hs.1+hs.2+hs.3 commits shifted
them); plan anchors to recon-verified positions lib.rs:1890-1903 and
:486-535. Edge cases (0, -1, i64::MAX, ±Inf, NaN) deferred to
follow-up tidy iter if regression sweep does not surface need.
Append three new symbols to runtime/str.c: a private str_alloc(uint64_t)
slab helper that allocates [rc_header | len | bytes... | NUL] via
ailang_rc_alloc and writes the len prefix, plus two extern formatters
ailang_int_to_str(int64_t) and ailang_float_to_str(double) that compose
str_alloc with snprintf("%lld") / snprintf("%g"). Defensive abort() on
truncation; 64-byte stack buffer comfortably oversized for either
formatter.
The extern declaration of ailang_rc_alloc carries __attribute__((weak)).
First regression sweep without it surfaced 11 e2e link failures under
--alloc=gc and --alloc=bump: public T-visible symbols (int_to_str /
float_to_str) pull their transitive callees (rc_alloc) into every
binary's symbol set, but rc.c is not linked under gc/bump in hs.3.
The weak attribute makes the cross-allocator link safe in isolation;
under rc the strong definition wins as usual. Retained after hs.4
(when rc.c becomes unconditionally linked) as a no-op that keeps
str.c link-safe in isolation.
No IR-side caller wired yet — hs.4 lands the IR-header declare lines,
the int_to_str/float_to_str codegen lowering, the checker install,
and the unconditional rc.c link.
cargo test --workspace + cross_lang.py + compile_check.py + check.py
all green on re-sweep.
Pure runtime/str.c addition: str_alloc helper + ailang_int_to_str +
ailang_float_to_str. No codegen, checker, or build-pipeline changes
in this iter — the IR-side wiring (declare lines, lowering, checker
install, rc.c always-linked) all batch into hs.4. Acceptance gate:
runtime continues to build and link with the new symbols present but
unreferenced (clang -O2 dead-strips them until hs.4 wires a caller).
Two tasks: (1) append three functions + includes + extern decl to
str.c, verify via clang -c + nm; (2) full workspace + cross_lang +
compile_check + check sweep stays green.
Per the amended heap-Str ABI spec (2a72a4a), static-Str globals lose
the leading UINT64_MAX sentinel rc-header field that hs.1 introduced.
Layout flips from <{ i64, i64, [N x i8] }> <{ i64 -1, i64 N, ... }>
to <{ i64, [N x i8] }> <{ i64 N, ... }>; the IR-Str pointer now lands
on the len-field at struct-index 0 (was 1) via constexpr-GEP. The four
+8 consumer-side GEPs (@puts / @ail_str_eq / @ail_str_compare / @strcmp)
do not change — the bytes still sit 8 bytes past the len-field. Static-
Str pointers are kept out of ailang_rc_dec at the codegen level via
move-tracking (iter 18d.3) and non-escape lowering (iter 18b), so no
header slot is needed at the global; this is a codegen-level invariant,
not a runtime guard.
Change surface: three format strings + two doc-comments in lib.rs,
rename + assertion update of two IR-shape tests, snapshot regen of
hello.ll. No runtime changes, no checker changes, no new fixtures.
Static-Str globals are now 8 bytes smaller per literal.
cargo test --workspace green; cross_lang.py / compile_check.py /
check.py all green. 3 tasks, 0 re-loops.
Supersedes the defunct hs.2 plan at 8ca602d (runtime-sentinel +
Str-in-ADT drop safety). After the spec amendment at 2a72a4a dropped
the sentinel-rc-header story, the iter scope is now a surgical retrofit
of hs.1's emission: drop the leading i64 sentinel slot from static-Str
globals, land the IR-Str pointer on the (now first) len-field via
GEP (i32 0, i32 0). The four +8 consumer GEPs are invariant across the
switch. Three tasks: (1) update the two IR-shape tests to the new
layout (RED); (2) flip three format-string sites + two doc-comments
in lib.rs (GREEN); (3) regenerate hello.ll snapshot + workspace +
cross_lang + compile_check + check.
hs.2's implementer-orchestrator BLOCKED with an empirical finding: the iter 18d.3 move-tracking and iter 18b non-escape lowering together prevent static-Str pointers from ever reaching ailang_rc_inc/_dec along any shipping execution path. The previously-proposed runtime sentinel guard and the i64 -1 sentinel slot in static-Str globals were both dead by construction.
Amendment is subtractive: remove the runtime/rc.c guard from §Architecture/Runtime layer + §Components/runtime/rc.c (none needed), shrink static-Str globals from <{i64, i64, [N+1 x i8]}> to <{i64, [N+1 x i8]}> (saves 8 bytes per literal), update GEP indices (i32 0, i32 1 → i32 0, i32 0), drop the §Error-handling 'Sentinel collision' subsection, drop the proposed str_field_in_adt_drops_static_str_noop safety-gate test from §Testing strategy (vacuous against natural fixtures), reframe §Architecture/Codegen-4 as 'codegen-level elision invariant, no runtime guard'. Goal/consumer-ABI claims tightened to 'unified along consumer paths, producer + RC sides differ'.
Acceptance: re-dispatched grounding-check PASS. The amendment leaves hs.1 (committed at c56498a) needing a forward-fix retrofit — that is the new hs.2's scope, planned next.
Second iter of the heap-Str ABI milestone. Adds the UINT64_MAX sentinel-header short-circuit to ailang_rc_inc / ailang_rc_dec so the hs.1 packed-struct globals (whose first slot is i64 -1) flow safely through generic RC paths. Tasks: (1) author rc_str_field_in_adt_static.ail.json + confirm builds; (2) TDD cycle for the safety test + runtime guards; (3) full regression sweep.
First iter of the heap-Str ABI milestone. Pure codegen refactor; every existing program produces byte-identical stdout.
Static-Str LLVM globals migrate from `[N x i8] c"…\00"` to `<{ i64, i64, [N x i8] }> <{ i64 -1, i64 N-1, [N x i8] c"…\00" }>`, carrying the UINT64_MAX sentinel rc-header (slot exploited in hs.2 via runtime short-circuit) and an explicit byte length. Two parallel intern paths (`intern_string` for raw format scaffolding, new `intern_str_literal` for language Str values) keep format strings untouched in `[N x i8]` shape.
IR-Str pointers now uniformly land on the len-field (offset +8 from rc-header, -8 from bytes). Four codegen sites that hand a Str pointer to a NUL-terminated-bytes C-API consumer now emit a +8 GEP first: `@puts` (io/print_str), `@ail_str_eq` (eq__Str intercept), `@ail_str_compare` (compare__Str intercept), and `@strcmp` (lower_eq Str arm — the 4th site was not in the plan; surfaced by Task-5 e2e regression and fixed inline).
6 new IR-shape pinning tests; `hello.ll` snapshot regenerated; cross_lang.py + compile_check.py + full `cargo test --workspace` green.
First iter of the heap-Str ABI milestone (docs/specs/2026-05-12-heap-str-abi.md). Carrier reversed by Boss judgement: original carrier said runtime+checker first, but the spec's no-intermediate-state clause forbids installing int_to_str without its codegen arm, and the runtime/str.c additions can't be tested in isolation. Layout migration is the natural prerequisite that everything else builds on.
Tasks: (1) intern_str_literal table + function + emission loop + Literal::Str callsite switch; (2-4) +8 GEPs at io/print_str, eq__Str, compare__Str callsites; (5) snapshot regen + cross_lang.py regression check. Acceptance: every existing program byte-identical.
Two builtins ship together: int_to_str (new) and float_to_str (today type-installed but codegen-deferred). Both produce malloc-backed, refcounted Str slabs via new ailang_int_to_str / ailang_float_to_str runtime exports.
Slab layout is {rc_header, len, bytes, NUL}. Static-Str globals migrate to the same packed-struct shape with a UINT64_MAX sentinel rc_header; ailang_rc_inc / _dec short-circuit on the sentinel. From every caller's POV static and heap Str values are indistinguishable, so existing code paths (eq__Str, compare__Str, io/print_str, per-type drop walks) keep working with a single +8 GEP at the three C-API callsites and no change to drop dispatch.
Out of scope: ++ concat operator, Show typeclass + print rewire, length-aware comparison for embedded-NUL Strs.
Closes the loop opened by this morning's .ailx → .ail rename. Every
path-taking ail subcommand (check, build, run, manifest, render,
prose, merge-prose, describe, emit-ir, diff, workspace, deps) now
accepts a .ail (Form A) input as well as .ail.json (Form B). For
.ail paths the subcommand parses through ailang_surface::parse
in-line and proceeds with the same loaded Module value .ail.json
would have produced; binary semantics are extension-agnostic.
Architecture: a new ailang_surface::{load_module, load_workspace}
pair dispatches on file extension. A new injection point
ailang_core::workspace::load_workspace_with<F> lets the surface
crate plug in an extension-aware loader without core having to
import surface (which would close a crate cycle). Import resolution
in workspace::visit now prefers a .ail sibling over a .ail.json
sibling. The CLI binary's 18 callsites switched to the surface
loaders. Surface parse failures route through
workspace_error_to_diagnostic as a new SurfaceParse variant of
WorkspaceLoadError, so `ail check --json foo.ail` on a malformed
.ail returns a parseable JSON diagnostic with code
surface-parse-error instead of the misleading
`json: expected value at line 1 column 1` fall-through.
Boss pre-commit correction: the implementer followed the plan
verbatim and used snake_case `surface_parse_error`, but every
existing diagnostic code in the codebase is kebab-case (verified
via `git grep` over crates/ailang-check/src and crates/ail/src);
realigned to `surface-parse-error` at three sites (the diagnostic
arm + two ct1 test strings). Journal Concerns section records
the correction.
Tests: cargo test --workspace 487 green (+7 from this iter:
1 core unit, 4 surface integration, 1 ail E2E pair, 1 ct1 CLI
diagnostic-shape). bench/check.py, compile_check.py, cross_lang.py
clean on re-run; the latency.{explicit,implicit}_at_rc tail
cluster occasionally flagged on first runs is the same
nondeterministic cluster the previous two audits documented;
baseline left pristine for the third consecutive iter.
Roadmap follow-up "ail check/build/run accept .ail extension" (P2)
removed.
DESIGN.md §Decision 6 / "CLI" bullet gained the symmetric upward
sentence: both .ail and .ail.json are first-class CLI inputs.
Boss-direct plan (no parent spec — this is a P2-roadmap-todo, not a
milestone iter). Option 1 picked from the roadmap fork: auto-parse
.ail internally so `ail check foo.ail` works the same as
`ail check foo.ail.json` (modulo the surface-parse step). Option 2
(hint-only) was a workaround that would have forced LLM-authors to
prepend `ail parse` before every check/build/run.
Architecture: extension dispatch lives in ailang-surface (which
already depends on core); core gains a `load_workspace_with` injection
point so the dispatch reaches transitive imports; surface parse
errors get a new WorkspaceLoadError::SurfaceParse variant so
`workspace_error_to_diagnostic` can route them in --json mode.
Tasks:
1. core::load_workspace_with + WorkspaceLoadError::SurfaceParse
+ .ail-first import resolution
2. surface::load_module + surface::load_workspace
3. crates/ail/src/main.rs rewire (~17 callsites) + 2 e2e tests
4. workspace_error_to_diagnostic SurfaceParse arm + DESIGN.md
§Decision-6 CLI addendum + ct1_check_cli diagnostic test
The surface-form file extension changes from .ailx to .ail. AILang's
authoring surface now uses the same .ail stem as its canonical JSON
form (.ail.json), giving the language a single coherent extension
family: .ail is the LLM-authored Form A, .ail.json is the canonical
JSON-AST Form B.
Scope (touched):
- 61 example renames examples/**/*.ailx → .ail (git mv)
- 1 rename experiments/.../rendered/ailx.md → ail.md
- 35 content-edited live-toolchain files (crates/, docs/DESIGN.md,
docs/roadmap.md, docs/PROSE_ROUNDTRIP.md, skills/, bench/reference/*.c,
experiment crates under experiments/.../{render,harness,master})
- Experiment-crate cohort rename Cohort::Ailx → Cohort::Ail,
Form::Ailx → Form::Ail, per_cohort/ailx → per_cohort/ail,
{form-only: ailx} → {form-only: ail}, ```ailx → ```ail
Out of scope (deliberately untouched, to preserve honest history):
- docs/journal-archive.md (content-frozen per CLAUDE.md)
- docs/journals/, docs/specs/, docs/plans/, bench/orchestrator-stats/
- experiments/.../runs/ (frozen LLM-output artefacts; models actually
saw .ailx — renaming would falsify the experimental record)
Verification: cargo build/test --workspace green; experiment crate
cargo test green; bench/check.py + compile_check.py + cross_lang.py
all 0-regressed; negative grep for ailx|Ailx|AILX outside the
out-of-scope paths returns zero matches.
Opens immediate follow-up: roadmap.md P2 todo `ail check`/build/run
accept .ail extension — after this rename, .ail is canonical
authoring surface but the CLI still produces a misleading JSON-parse
error on `ail check foo.ail`. That's the next iter.
Architect drift review: clean (two [low] items both expected
audit by-products, not drift). Bench-regression check: all
three exit-code gates green; the 5-metric
latency.explicit_at_rc / gc_over_bump improvement cluster from
audit-cma earlier today reappears, second consecutive audit
showing the same shift uncorrelated with milestone work —
baseline left pristine for one more audit before considering
ratification.
All seven spec acceptance criteria green. Milestone closed.
Inline pinning test in pipeline.rs constructs the same chain
shape module_name_from_json produces on serde-parse failure
(leaf wrapped with "parsing JSON in <path>" context) and
asserts both layers survive the formatter. RED→GREEN against
the property, not against production line bytes — for a
two-character fix, extracting a helper just for testability
would be over-engineering.
The strip_locations regex collapses \nCaused by: chains; the
{:#} alternate Display joins with ': ' on one line, so the
collapser is a no-op on the fixed output. AILX cohort
unaffected — only the harness-side anyhow path (module
rename pre-check) was dropping cause information.
Harness suite: 14/14 green (was 13/13 + 1 new pin).
Architect drift_found, both [low]:
- INDEX backfill for commit 90512d5 (brainstorm Step 7.5 SKILL.md
edit + roadmap P1 todo removed without a paired INDEX mirror).
- README "Total 8 passed" understated the actual 13/13 sweep.
Both fixed inline (mechanical, ≤30 LOC) per CLAUDE.md trivial-edits
carve-out; no implementer dispatch.
Bench: all three scripts exit 0, 0 regressed, 5 unexplained
latency.explicit_at_rc improvements not coupled to milestone work
(no in-workspace crate touched). Baseline left pristine — future
audits will reveal whether the improvements hold.
All 9 spec acceptance criteria green. Milestone closed clean.
Milestone "Cross-model authoring-form test" closed. Live IONOS run
against Qwen/Qwen3-Coder-Next executed at temperature=0, max-turns=5,
token-budget=500000. Raw dataset under runs/2026-05-12-df7531/:
RUN_STATUS=ok, scores.csv with 8 rows (4 tasks × 2 cohorts),
summary.md, plus 156 per-turn raw artefacts (request/response/program/
stderr/stdout) preserved for any future analysis.
Headline numbers:
- AILX cohort: 2/4 green, 1/4 first-attempt, ~95k prompt + ~2.6k completion
- JSON cohort: 1/4 green, 0/4 first-attempt, ~192k prompt + ~15k completion
- Only first-attempt success was AILX on t3_main_prints (~5 lines of
tagged s-expr vs ~23 lines of structured JSON for the JSON cohort's
turn-1 attempt that needed correction).
- Per spec, single subject + single deterministic run = data point,
not verdict.
DESIGN.md §"Decision 6: authoring surface" gains an "Empirical
addendum (2026-05-12)" subsection: 6-row metric table, illustrative
t3 contrast, explicit single-subject scope note pointing at the
roadmap follow-up entry for multi-subject expansion.
Roadmap: P2 entry "Cross-model authoring-form test" removed (this
journal is the convention's one-line mirror); P3 entry
"Multi-subject expansion (cross-model authoring-form follow-up)"
added with the run dir named as baseline.
Milestone artefacts span three iters (cma.1 master mini-spec +
renderer; cma.2 harness + tasks + reference solutions; cma.3 this
live run + DESIGN.md addendum + close).
Sibling standalone Cargo crate `harness/` under
experiments/2026-05-12-cross-model-authoring/ (out-of-workspace
idiom carried forward from cma.1 verbatim). Six modules:
strip_locations (regex pass for form-asymmetric location info,
calibrated against five real `ail check`/`ail parse` stderr
captures), pipeline (subprocess wrapper for parse|check|build +
5s-timeout exec; preflight on ail+clang), ionos (blocking reqwest
client + retry policy per spec), mock (canned-response loader),
scoring (CSV + summary.md), tasks (definition struct + loader).
main.rs ties them into the per-(cohort,task) loop with budget
accounting and per-turn artefact recording.
Four MVP tasks land with reference solutions that compile, build,
and execute green through the actual ail+clang pipeline:
t1_add_three (chained `+` + io/print_int), t2_length (polymorphic
List + recursion), t3_main_prints (minimal IO module), t4_count_zeros
(prelude Eq Int + branched if). Reference solutions stay in canonical
AILang form — param_modes is omitted when every parameter is the
Implicit default, consistent with the existing examples/ corpus.
13/13 tests green: 5 lib unit (strip_locations) + 5 integration
(strip_locations against verbatim captured fixtures) + 1
verify_references (drives every reference through the real
ail+clang pipeline) + 1 mock_full_run (full eight-row sweep with
mixed green-on-turn-2 + turn-limit cycles) + 1 budget_abort
(synthetic budget exhaustion with budget_abort rows + run_status).
Two implementer-phase repairs beyond the plan, both small and
surfaced in the iter journal Concerns:
1. pipeline.rs renames the program file to `<module-name>.ail.json`
between parse and check because `ail check` enforces filename
stem == module name (compiler contract the plan did not anticipate).
2. main.rs fills synthetic budget_abort rows for tasks the outer
loop never reached so scores.csv preserves the expected eight-row
shape on budget exhaustion.
One plan/text mismatch carried over: README "Total 8 passed" reflects
the plan's four-suite count; actual sweep produces 13. Doc-fix
candidate for cma.3.
cma.3 (live IONOS run + DESIGN.md addendum + roadmap edits) remains
out of scope.
Walks the implementer from a fresh harness/ Cargo project to a green
test sweep across strip_locations (5 unit + 5 integration on real
captured stderr), verify_references (4 reference solutions run
through the live ail+clang pipeline locally), mock_full_run (eight
canned-cohort-task pairs end-to-end), and budget_abort (tiny budget
exhaustion). Six modules: strip_locations, pipeline, ionos
(reqwest blocking + retry per spec §Error handling), mock, scoring,
tasks. Captures five real stderr samples (recon-collected) as
fixtures; strip_locations regex calibrated to the actual diagnostic
shape (handles `at byte N`, anyhow `Caused by:` chains).
Workspace-table and .gitignore included in Task 1 from the outset
(learned from cma.1 surfaced concerns; not repeated as
post-hoc journal items). Out-of-workspace pattern matches render/
verbatim. AIL_BIN env var with PATH fallback for `ail` resolution;
clang preflight check added since `ail build` shells to clang.
Two explicit deferrals to implementer named in the plan:
ParamMode names (lifted from cma.1) and prelude function symbols
(from crates/ailang-check/src/builtins.rs); both stable from cma.1
authoring, no re-derivation needed.
New top-level experiments/2026-05-12-cross-model-authoring/ (out of
root Cargo workspace; nested-crate standalone via empty [workspace]
table per Cargo idiom). The renderer projects one canonical
master/spec.md into two form-specific files (rendered/json.md and
rendered/ailx.md) by walking three directive forms ({form-only:
json}, {form-only: ailx}, {example: <id>}) and emitting per-example
fenced blocks sourced from 13 curated .ail.json fixtures.
Four test gates green (10/10 total):
- splitter_unit (7) — directive parser, malformed inputs panic
- spec_completeness (1) — AST-variant visitor lifted verbatim from
schema_coverage.rs; 34/34 variants covered by the curated subset
- example_roundtrip (1) — every fixture roundtrips through
ailang_surface::{print, parse} to identical canonical bytes
- token_balance (1) — form-only block totals balanced to 3.22% under
tiktoken-rs::cl100k_base (gate is ±5%)
Six of the 34 variants did not have a dedicated fixture topic in the
plan and were absorbed into the closest existing fixture per the
plan's Step 4.14 escape hatch (Let/Clone → fn_calls_prelude; LetRec
→ data_with_match; If → match_literal_pattern; ReuseAs →
data_simple; Const → floats). Surfaced as a journal note, not a
plan revision.
Two ancillary additions vs. plan, both structural Cargo necessities,
spec-compatible: render/Cargo.toml carries an empty [workspace] table
so the nested crate refuses parent-workspace discovery, and
render/.gitignore drops /target + Cargo.lock. Both surfaced inline
in the per-iter journal's Concerns section.
cma.2 (harness) and cma.3 (live IONOS run + DESIGN.md addendum +
journal + roadmap edits) remain out of scope per the spec.
Walks the implementer from a fresh directory to a green test sweep
(splitter_unit, spec_completeness, example_roundtrip, token_balance)
plus two checked-in rendered/*.md files. Visitor borrowed verbatim
from schema_coverage; master/examples authored fresh as a curated 13-
fixture set (one per AST variant cluster); master/spec.md authored in
13 sections with form-only blocks balanced by the ±5% gate.
Open authoring deferrals to the implementer (named at the planner
level, intentional): exact ParamMode enum names and the canonical
prelude function names — both retrieved from current source by
reading ailang-core/src/ast.rs and the prelude module at authoring
time, rather than pinned in the plan against a possibly-stale recall.
Single foreign subject (Qwen3-Coder-Next via IONOS). Two blind
cohorts: one sees only a JSON mini-spec, the other only an .ailx
mini-spec; same four tasks. Fairness anchored by a master source
plus a renderer (built on the existing ailang-surface roundtrip
machinery) that projects two form versions from one canonical
content base. Harness shells out to ail parse / check / build / run
and feeds back location-stripped errors so the JSON-pointer
asymmetry doesn't tilt the experiment. Out of scope for v1: a
verdict on Decision 6, a free-choice cohort, repetitions, a second
subject.
Grounding-check PASS on cma.1 (master + renderer + tests).
Names the runtime infrastructure prerequisite (malloc-backed refcounted
Str alongside static @.str_* globals) as its own milestone. Show + print
rewire now carries an explicit depends-on link. Existing Eq/Ord [x]
entry removed (shipped milestone 23; mirrored in WhatsNew + journal).
CLAUDE.md previously mixed universal facts (agent role boundaries,
commit discipline, design rationale, file roles, TDD-for-bugs) with
mode-specific autonomy rules (direction freedom, notifications,
WhatsNew procedure). Autonomous-by-default conflicted with the user's
intent that a fresh session should be collaborative-interactive unless
explicitly elevated.
Add `skills/boss/` containing only the three genuinely mode-specific
subsections — Direction freedom, Notifications, Done-state notifications:
WhatsNew.md. Trim CLAUDE.md from 343 to 243 lines; extend the skill-
system pointer paragraph with a one-sentence /boss gate. Universal
orchestrator discipline stays in CLAUDE.md because it applies whether
/boss is active or not.
Two cross-references that named the moved subsections by sub-heading
are repointed: skills/implement/SKILL.md and the implement-orchestrator
agent's standing reading list. The other ~11 agent-file references to
"orchestrator framing" still resolve correctly because that framing
stays in CLAUDE.md.
skills/README.md skill table extended with a `boss` row (now eight
skills); pipeline-diagram caption notes /boss wraps the pipeline.
.claude/skills/boss symlink follows the existing relative-path
convention.
Recon surfaced that several draft 'moved OUT' sections were universal,
not mode-specific (agent role boundaries, authority over skills, design
rationale, feature-acceptance criterion, when-not-to-delegate). These
apply in any session — interactive or /boss-active — because agents can
be dispatched, design choices can be made, and skills can be edited
outside /boss too.
Tighten the move-list to the three genuinely mode-specific subsections:
Direction freedom, Notifications, Done-state notifications: WhatsNew.md.
Everything else stays in CLAUDE.md. /boss becomes leaner, the universal/
mode-specific boundary is drawn at content that only applies inside the
autonomous loop. Re-dispatched grounding-check: PASS.