Files
AILang/docs/roadmap.md
T
Brummel 48e7774d01 audit: prose-loop-binders milestone CLOSE — clean / carry-on
Architect drift review clean (zero drift/debt, diff-verified: single
Term::Loop write_term hunk, no non-render site / Term::Recur / doc /
lockstep touched). Bench: compile_check 0/24 + cross_lang 0/25 exit
0; check.py's latency-tail firings proven single-sample jitter by an
identical confirmatory re-run (p99.9 +37%→+10% ok, max +114%→+22%
ok); the sole persistent firing is the pre-tracked P2 *.bump_s
environmental staleness, causally impossible to attribute to a
projection-only ailang-prose change. All items carry-on — no fix, no
baseline ratify. Fieldtest not applicable (no authoring-surface
change). Roadmap P0 flipped to closed; WhatsNew user-facing entry
appended (user present → notification suppressed per policy).
2026-05-18 01:10:20 +02:00

34 KiB
Raw Blame History

AILang Roadmap

Priority-ordered list of upcoming work — milestones, features, todos, and ideas. The orchestrator maintains this file. The user can request additions; the orchestrator chooses what to remove or reprioritise as work progresses.

Conventions

  • One checkbox per entry. - [ ] is open; - [~] is in progress (work has started — a plan exists, a branch is open, or commits are landing); - [x] is done. A finished entry may stay briefly for context, then is removed (with a one-line mirror in docs/journals/).
  • Each entry is tagged by kind and lives under a priority bucket:
    • [milestone] — big chunk that will get a docs/specs/<milestone>.md.
    • [feature] — smaller addition inside a milestone, no full spec.
    • [todo] — concrete task that can run without a brainstorm (cleanup, doc fix, mechanical refactor, test backfill).
    • [idea] — not yet decision-ready, no commitment.
  • Optional depends on: line names another entry that has to land first.
  • Optional context: line points to the journal entry (per-iter file under docs/journals/ or, for pre-2026-05-11 entries, the archived docs/journal-archive.md) where the rationale lives. The roadmap is intentionally terse; rationale stays in the journals.
  • Priority buckets:
    • P0 — in flight. Spec or plan already exists.
    • P1 — next up. Decision made; not yet started.
    • P2 — medium-term. Decided in principle, scheduled later.
    • P3 — ideas. No commitment; may be cut.

P0 — In flight

  • [milestone] Prose loop binders — projection redesign — CLOSED 2026-05-18. Form-B prose now renders loop binders as a parenthesised init-list on the keyword (loop(acc = 0, i = 1) { … }, positionally isomorphic to recur(...)) instead of bare name = init; statements inside the body block (which misread as C/Rust re-init-every-iteration). Single-iteration, projection-only: one ailang-prose write_term Term::Loop arm + 2 committed byte-equality .prose.txt snapshots/tests; loop/recur AST, Form A, JSON-AST, typecheck, codegen, and the Form-A↔JSON round-trip invariant all byte-unchanged. brainstorm→plan→implement (c657e74/6533134/c9355d7); milestone-close audit clean (architect zero drift; bench carry-on — sole firing is the tracked P2 *.bump_s environmental staleness, latency-tail proven sampling noise by re-run); no fieldtest (projection-only, no authoring- surface change). Stays here briefly for context; remove once stale.

    • context: docs/specs/2026-05-18-prose-loop-binders.md; docs/journals/2026-05-18-audit-prose-loop-binders.md; surfaced from the loop/recur prose review (2026-05-18 chat).
  • [milestone] Standalone loop / recur — CLOSED 2026-05-18. Strictly-additive strict-iteration surface, recur tail-position-only, no totality claim. Shipped: iter 1 a179ec3 (additive AST nodes), iter 2 1566ce0 (typecheck), iter 3 edd2558 (codegen + run-to-value E2E), tidy 39380d3/2ee9794 (lambda-captures-loop-binder rejected at check). Milestone-close audit clean (architect drift resolved, bench pristine 25/0 carry-on); fieldtest 2026-05-18 clean on all four milestone axes (0 bugs; diagnostics point-exact+self-fixing, recur-tail through match/let, loop-as-value-subexpr byte-stable, no-termination exact). Two orthogonal non-blocking findings routed to P2 todos (niladic (app f) — re-confirms mut-local F3; module (doc) diagnostic hint). Stays here briefly for context; remove once stale (full record in docs/journals/INDEX.md).

    • context: docs/specs/2026-05-17-loop-recur.md; docs/specs/2026-05-18-fieldtest-loop-recur.md; principles entry docs/specs/2026-05-17-llm-surface-discipline.md §6.2.

P1 — Next

  • [milestone] Heap-Str ABI — runtime infrastructure for malloc-backed, refcounted Str values alongside the existing static @.str_* globals. Today the Str path is static-only (DESIGN.md §"Float semantics" notes this explicitly), so any primitive that needs to produce a Str at runtime — int_to_str, float_to_str (currently type-installed but CodegenError::Internal on call), eventual Show.show, future ++ on strings — cannot ship. Scope: pick the representation (likely a {rc_header, len, bytes…} slab consistent with the rest of the RC runtime), teach codegen to accept both static and heap Str at the same ABI slot, wire RC inc/drop/clone/compare/eq on the heap form, and ship int_to_str + float_to_str as the first two callers so the ABI gets exercised end-to-end. Unblocks Show + print rewire below.

    • context: DESIGN.md §"Float semantics" (float_to_str is type- installed, codegen-deferred); spec 2026-05-11-23-eq-ord-prelude §"Show. Defers behind a heap-Str-ABI milestone"; spec 2026-05-10-fieldtest-floats §F4 ("dynamic Str allocation in the runtime"); spec 2026-05-09-22-typeclasses §22b.4b ("Show#Int needs an int_to_str primitive returning heap-allocated Str").
  • [milestone] Post-22 Prelude — Show + print rewire — shipped 2026-05-13 as iters 24.1 (heap-Str runtime + codegen for bool_to_str

    • str_clone, f38bad8), 24.2 (prelude class Show + four primitive instances Int/Bool/Str/Float + 22b TShow/tshow migration), and 24.3 (polymorphic fn print : forall a. Show a => (a borrow) -> () !IO
    • positive 4-prim E2E + user-ADT E2E + Show-aware NoInstance diagnostic + DESIGN.md amendments to §"Prelude (built-in) classes" and §"Float semantics"). The MethodNameCollision workaround that blocked the original spec retired in mq.3 (2026-05-13); spec docs/specs/2026-05-13-24-show-print.md re-derived 24.2 + 24.3 against the post-mq architecture.
    • context: spec docs/specs/2026-05-13-24-show-print.md; per-iter journals 2026-05-12-iter-24.1, 2026-05-13-iter-24.2, 2026-05-13-iter-24.3.

P2 — Medium-term

  • [milestone] Iteration-totality story — structural + Int-bounded total recursion with enforced non-negativity. AILang's iteration story stays as-is (structural / tail recursion; tail-app intact). The genuine ambition — make f(n-1)-family recursion (incl. branching tree builders) total by construction with the non-negative-entry precondition enforced, not merely documented — is deferred here because doing it without a purity- pillar concession requires refinement/Nat type machinery the language has not built (Decision 4 keeps refinements opaque, no SMT). Not abandoned; correctly sequenced after the type machinery.

    • depends on: a future Nat/refinement-types milestone (no spec yet).
    • context: docs/specs/2026-05-16-iteration-discipline-revert.md (why the 2026-05 attempt was reverted) and docs/journals/2026-05-15-iter-it.3.md (the branching-builder counter-example that surfaced the gap).
  • [milestone] Retire io/print_int / io/print_bool / io/print_float effect-ops + migrate example corpus to print. Shipped 2026-05-14 as iter rpe.1.

    • context: per-iter journal docs/journals/2026-05-14-iter-rpe.1.md.
  • [todo] Author examples/prelude.ail alongside examples/prelude.ail.json. (Satisfied 2026-05-13 by iter form-a.0 — examples/prelude.ail rendered via ail render, 116 lines / 6386 bytes, round-trip-CI green.)

  • [milestone] Form-A as the default authoring surface for examples and docs. (Closed 2026-05-13 by iter form-a.1.) Render every examples/*.ail.json to its .ail sibling via ail render, delete the now-redundant .ail.json, and regenerate the JSON-AST per ail parse at build / test time. Same for inline JSON-AST blocks in docs/ markdown (~7 in DESIGN.md plus ~29 other md files) — convert to Form-A snippets where the snippet's purpose is to show "what the language looks like", not "what the schema is".

    After this milestone the working tree contains exactly one representation per program: the .ail source. The JSON-AST is a build artefact, not a checked-in twin. Tests and benches that currently glob *.ail.json either parse-then-consume or are rewired to point at .ail directly. The round-trip invariant flips role: today it gates that the two forms agree; afterwards it gates that parse is deterministic.

    Carve-outs that MUST stay JSON-AST (no Form-A counterpart, and no .ail sibling shall be created — these are the only seven files that remain .ail.json-only post-milestone):

    • Fixtures that test canonical-form rejection — Form A would reject them at parse, defeating the test: test_ct1_bare_xmod_rejected.ail.json, test_ct1_qualified_class_rejected.ail.json, test_ct1_bad_qualifier.ail.json, broken_unbound.ail.json, test_22b2_invalid_superclass_param.ail.json, test_22b2_kind_mismatch.ail.json, test_22b2_unbound_constraint_var.ail.json.
    • DESIGN.md schema documentation blocks where the JSON-AST shape is the point (Decision 11, ParamMode, canonical-form invariants).
    • Any other case where the structured form is the artefact under discussion, not a vehicle for a program.

    Touches CLAUDE.md ("source of truth is structured data") — the language doctrine doesn't change (JSON-AST is still the canonical hashable form), but the authoring doctrine does: authors write .ail, the build derives JSON-AST. Sentence in CLAUDE.md needs rewording in the same milestone.

    Mechanical for the bulk; per-fixture judgement call only for the carve-out list. Run as one milestone so the corpus flip-over happens at a single, audit-gated point.

    • context: post-Form-A-as-canonical-authoring corollary of the examples/prelude.ail entry above. Generalises the same idea to the rest of the corpus and follows through on the cross- model authoring data (textual form cheaper, more first-try hits) by treating Form A as the privileged surface in the working tree, not just in flavour text.
  • [feature] Operator routing through Eq / Ord==, < etc. resolved via the typeclass instead of the built-in primitive comparators. No commitment; gated on bench re-baselining to make sure the indirection doesn't tank latency.

    • context: JOURNAL 2026-05-09
    • depends on: Post-22 Prelude — Eq/Ord (shipped 23.5)
  • [todo] types / ctor_index overlay shape question — decide whether the env's two parallel ctor maps should collapse into one overlay, or stay split. Surfaced during the env-construction unify audit.

    • context: JOURNAL 2026-05-10 ("Audit close: env-construction unify"); closed by iter ctt.1 — DESIGN.md §"Env construction" anchors the split decision.
  • [todo] CLI human-mode diagnostic surface for WorkspaceLoadError. Shipped 2026-05-14 as iter cli-diag-human — a new load_workspace_human helper in crates/ail/src/main.rs routes 9 non-JSON ailang_surface::load_workspace(&path)? call sites through workspace_error_to_diagnostic, so the bracketed [code] prefix is preserved across ail check, build, run, emit-ir, prose, describe, deps, diff, manifest.

    • context: per-iter journal docs/journals/2026-05-14-iter-cli-diag-human.md.
  • [todo] Retire dead KindMismatch arm — validate_classdefs's walk_kind_mismatch path is structurally unreachable through well-formed schema post-ct.1 (the canonical-form validator catches the malformed Type::Con { name: param } shape earlier). The enum variant + walk_kind_mismatch helper stay as dead-but-defensive code; a future tidy can delete both.

    • context: JOURNAL 2026-05-11 ("Iteration ct.1"); closed by iter ctt.3 — variant + helper + dispatch + Display arm all deleted; canonical-form-rejection test stays green asserting BareCrossModuleTypeRef.
  • [todo] Re-key Registry.type_def_module to handle bare-name-collision-across-modules — BTreeMap<String, String> keyed by bare type name silently overwrites when two modules each define type Foo; normalize_type_for_registry would then collapse M.Foo and N.Foo to whichever insert won. Acceptable for current corpus (distinct bare type names across modules), but the proper fix is to key by (owning_module, bare_name) → defining_module.

    • context: JOURNAL 2026-05-11 ("Iteration ct.1") — flagged by ct.1.5a quality reviewer.
  • [feature] 22c — typeclass corpus expansion. User-defined classes beyond the prelude four; multi-parameter classes; superclass chains; richer instance bodies. Deferred from milestone 22.

    • context: JOURNAL 2026-05-09
  • [milestone] Module-qualified class names + type-driven method dispatch — retired the MethodNameCollision workaround; shipped 2026-05-13 as iters mq.1 (canonical-form extension for class-ref fields + workspace-internal qualification), mq.2 (type- driven dispatch mechanism installed: method_to_candidate_classes inverse index, multi-candidate ResidualConstraint, resolve_method_dispatch 5-step rule, AmbiguousMethodResolution + UnknownClass diagnostics), and mq.3 (retirement + multi-class E2E

    • class-method-shadowed-by-fn warning + DESIGN.md sync). Two libraries can now each declare class Eq with their own eq; cross-class method ambiguity is resolved at the call site via type-driven dispatch with <module>.<Class>.<method> as the disambiguation form.
    • context: docs/specs/2026-05-10-canonical-type-names.md "Out of scope: Class names" — the workaround was named there so it stayed visible until this milestone retired it.
  • [todo] Boehm full retirement — remove the transitional Boehm GC path now that RC + uniqueness is the canonical memory story.

    • context: JOURNAL pre-22, "Boehm transitional"
  • [feature] Closure-pair slab / pool — codegen tweak to pool the env+code closure pairs instead of one-shot heap allocs. Bench-gated.

  • [todo] FnDef::synthetic flag — formalise the monomorphiser-emitted FnDefs so downstream passes can tell user-authored from synthesised at a glance. Currently inferred from symbol naming.

  • [todo] 21'h iteration — final 21' carry-over (latency methodology pass). Numbering kept for continuity with the 21' arc.

  • [todo] DESIGN.md effect-prose is fiction — standalone documentation-honesty tidy. Shipped 2026-05-16 as iter effect-doc-honesty (a29700c): the three false effect-system claims (row-polymorphic ![IO | r]; IO+Diverge both wired up; Term::Do "effect-handler table at link time") reconciled to the real flat-closed-set / IO-only / IndexMap+codegen-match mechanism, satellite lockstep done, guarded by a 4-test doc-presence pin. No language/checker/codegen change.

    • context: per-iter journal docs/journals/2026-05-16-iter-effect-doc-honesty.md.
  • [todo] io/print_float always-emit-.0 — surface printer always emits . or e/E so re-lex routes to Float; the runtime printer (printf("%g\n", v)) doesn't, so 2.0 prints as 2 (Int-shaped). Asymmetric. Either switch the runtime path to a .0-fallback printer (matching surface) or document the %g contract in DESIGN.md §"Float semantics" so the LLM-author knows io/print_float's output is for-humans not round-trip.

    • context: docs/specs/2026-05-10-fieldtest-floats.md finding F1.
  • [todo] Rustdoc warning sweep — cargo doc --no-deps reports 16 pre-existing warnings (15 in ailang-check, 1 in ailang-core: private-item links from public doc, unresolved intra-crate links). All predate the design-md-consolidation milestone; treat as a one-off sweep.

    • context: JOURNAL 2026-05-10 ("Audit close: design-md-consolidation").
  • [todo] ailang-plan-recon cross-crate-caller-undercount countermeasure — the recon agent hand-lists exhaustive-match/caller sites and has under-counted the true blast radius three times in one milestone (loop-recur.1 walker arms, loop-recur.2 cross-module synth callers, loop-recur.tidy implicit). Each was caught only by the implement orchestrator's compile-driven sweep, not by the plan. The pattern is structural: a hand-enumerated site list for a workspace-wide signature/exhaustive-match change is inherently lossy. Tighten skills/planner/agents/ailang-plan-recon.md so that for any signature-change / additive-enum-variant scope the recon REPORTS the compile-driven enumeration command as the authoritative site set (and frames its own hand-list as advisory), rather than presenting the hand-list as complete. No language change; an agent-definition discipline fix.

    • context: loop/recur milestone-close audit (architect [low], 2026-05-18); pairs with the planner Step-5 items 7+8 added the same milestone (those scrub the plan; this scrubs the recon).
  • [todo] design_schema_drift.rs fidelity widening — current test checks anchor presence anywhere in DESIGN.md; the audit found that [high] schema gaps in §"Data model" are invisible because anchors live in Decision 11 instead. Constrain the test to scan only §"Data model" + ParamMode block, or extract JSON-schema blocks into a machine-readable file the test consumes.

    • context: JOURNAL 2026-05-10 ("Audit close").
  • [todo] Split BadCrossModuleTypeRef into two diagnostics — the current single shape collapses unknown-owner and known-owner / unknown-type-in-owner into one message. The two cases suggest different fixes (add (import <owner>) vs. fix the type name). In the known-owner branch, list the owner's available type defs as candidates the way bare-cross-module-type-ref lists candidates from imports.

    • context: fieldtest 2026-05-11 — examples/ct_3*.ail exhibits both branches with identical-shape diagnostics.
  • [todo] Zero-arg (app f) rejected at parse — the Form-A parser refuses an application with an empty argument list, so a nullary call has no surface form. Surfaced by the mut-local fieldtest (F3) AND independently re-confirmed by the loop/recur fieldtest (2026-05-18, finding spec_gap): the maximally-natural infinite-event-loop shape is a niladic run_forever : fn() -> Int called (app run_forever), which dies at parse before reaching loop/recur semantics. Two independent fieldtests hitting the same gap raises the priority signal. Decide: accept (app f) as the nullary-call surface, or ratify in DESIGN.md that nullary functions are expressed differently (and say how). DESIGN.md's Term::App args:[Term...] states no minimum, so the surface "expected at least one argument" rule is unbacked by spec — a genuine design fork, deliberately NOT auto-ratified under autonomous orchestration. Not a blocker; no current corpus program needs it, but an LLM author reaches for it.

    • context: docs/specs/2026-05-15-fieldtest-mut-local.md finding F3; docs/specs/2026-05-18-fieldtest-loop-recur.md spec_gap.
  • [todo] Module-level (doc …) diagnostic omits where doc belongs — a (module NAME (doc "…") (fn …)) is correctly rejected (unknown def head \doc`) but the message lists valid def heads without saying doc strings attach *inside* fn/datadefs; an author can read it and conclude doc strings are unsupported. One-line tidy: append a hint like "(doc strings attach insidefn/data` defs, not at module level)". Low cost; verbose-diagnostic-philosophy gap, not a bug.

    • context: docs/specs/2026-05-18-fieldtest-loop-recur.md friction.
  • [todo] Workspace search beyond entry-module's directory — load_workspace only finds sibling .ail.json files in the same directory as the entry module, so any consumer of prelude/std in a subdirectory has no way to resolve cross-module imports. Add either a --workspace-root flag on ail check / ail build / ail run, or upward-search from the entry module's directory. Alternatively ratify the flat-workspace assumption in DESIGN.md if intentional.

    • context: fieldtest 2026-05-11 — fieldtest fixtures could not be placed under examples/fieldtest/ because of this; predates the canonical-type-names milestone but surfaces every time.
  • [todo] *.bump_s throughput baseline is stale vs current hardware — bench/check.py's throughput.*.bump_s family (the fastest, most jitter-prone metrics) reads ~+513% over bench/baseline.json on the current machine. Localised at the iteration-discipline-revert audit (2026-05-16): an interleaved 3×60-run measurement of bench_list_sum bump_s built from the byte-oracle commit 1ff7e81 shows the same ~+11% elevation as HEAD, and the two bump binaries are cmp-identical — so this is environmental drift relative to the 2026-05-09 baseline-capture machine state, not a codegen regression. Re-capture the full *.bump_s set on current hardware from a known-clean commit and recalibrate the bump_s baseline+tolerance pair (or widen the tolerance) so the noise floor stops tripping check.py exit 1. Pure bench-harness recalibration; no language change.

    • context: docs/journals/2026-05-16-audit-iteration-discipline-revert.md (the bencher localisation evidence).
  • [todo] check_in_workspace per-module overlay narrowing — crates/ailang-check/src/lib.rs:1234 still clears+rebuilds env.ctor_index per-module; ct.3.2 narrowed the analogous mono overlay to types-only. The typecheck-side overlay's env.ctor_index half serves the duplicate-detection diagnostic at workspace-build time, not the runtime ctor lookup (which is type-driven post-ct.2.2). Narrowing is mechanical but needs a careful read to confirm no other consumer survives.

    • context: JOURNAL 2026-05-11 ("Iteration ct.4") — milestone close follow-up; closed by iter ctt.1 — recon confirmed the rebuild is the load-bearing consumer of in-band DuplicateCtor (pinned by crates/ailang-check/tests/duplicate_ctor_pin.rs); the asymmetry with the mono side is intentional.
  • [feature] str_concat : (borrow Str, borrow Str) -> Str — heap-Str concatenation primitive. Shipped 2026-05-13 as iter str-concat (closes fieldtest-form-a friction #4). Symmetric to the iter 24.1 str_clone / int_to_str / bool_to_str plumbing: runtime C helper (ailang_str_concat), ailang-check builtin registration, ailang-codegen extern + lower_app arm, plus a fresh examples/show_user_adt_with_label.ail corpus fixture exercising the LLM-natural Show-body shape.

    • context: per-iter journal 2026-05-13-iter-str-concat.md.
  • [~] [milestone] Stateful islands — bounded mutation for streaming workloads (Stateful a b + !Mut effect + mut syntactic block). Adds a sealed mutable-state layer on top of the pure core: a Stateful a b first-class type whose interior is mutable, whose exterior is a typed callable with !Mut in its effect set, and whose state non-aliasing is enforced by uniqueness inference at the block boundary. Targets the online / streaming workload class — rolling indicators, IIR filters, online aggregates, sensor fusion, online learning — whose mathematics is "new sample + old state → new state + output" per step, and which today's pure-functional state- threading makes ergonomically expensive at scale (multi-record explicit threading for the canonical sliding-window SMA, no zero-allocation pipe combinator, growing tuple-state types as pipelines lengthen).

    Progress. Decomposed at brainstorm time (2026-05-15) into a sequence of shippable sub-milestones. Sub-milestone 1 closed 2026-05-15: mut-local — sealed-by-construction mut/var/ assign blocks for Int/Float/Bool/Unit scalars, alloca-resident, no escape, no !Mut effect leakage. Foundation in place; fn signatures stay pure while LLM authors can write imperative accumulators directly. Spec docs/specs/2026-05-15-mut-local.md; iters mut.1/mut.2/mut.3/mut.4-tidy (commits 7b92719, b24718a, 03fb633, 20add51).

    Sub-milestone sequencing is UNDER RE-THINK with the user (2026-05-16) — no further sub-milestone is planned or brainstormed until that conversation happens. Why: the 2026-05-15 decomposition named "(2) effect-handler infrastructure as a prerequisite for any non-IO/non-Diverge effect" as the next step. A read-only recon of the live effect subsystem (2026-05-16) showed that premise is false: the effect raise / declared-set / UndeclaredEffect-subset / call- propagation machinery is already fully generic over an arbitrary effect string; !Mut needs no effect-handler machinery. The only real code prerequisite the recon found is one hard-coded assumption (linearity.rs walks every Term::Do arg as Borrow; EffectOpSig has no per-arg mode field) — a single struct field, not a milestone. A brainstorm spec that tried to make sub-ms-2 a standalone deliverable bundled that speculative one-field capability with an unrelated DESIGN.md honesty fix; the user correctly rejected the bundle as incoherent and the build-ahead-of-consumer half as the iteration-discipline trap repeated. Resolution: (a) the DESIGN.md effect-honesty correction is split out and runs now as a standalone documentation tidy (see the P2 [todo] "DESIGN.md effect-prose is fiction" below); (b) the arg_modes finding is recorded as recon context for the future !Mut design, NOT built ahead — it is first-iteration material of whatever the !Mut milestone turns out to be, validated there against a real consuming op, never a synthetic test op; (c) the old "(2)…(5)" sequence (effect-handler infra → !Mut+ref aMutArray aStateful a b+pipe) is no longer treated as settled — the whole ordering and granularity is what the pending user conversation re-decides.

    Motivation. AILang's signature-as-contract thesis is better served by an explicit Stateful a b + !Mut annotation than by the implicit-closure-mutation idiom of myc / Lua / JS factory patterns: the signature tells the truth about time-identity without the body needing to be read — exactly the LLM-author correctness affordance AILang exists to deliver, extended to a workload class it does not yet serve. Decision 10's constraint #3 forbids shared mutable refs (DESIGN.md:1082); it does not forbid uniqueness-bounded mutation. Lean 4 (ST), Roc (Task), Haskell (ST/IO), and Koka (effects) all use a layered design of this shape to host exactly this workload. AILang's existing own / borrow mode machinery plus its effect-slot architecture are the foundation; this milestone supplies the layer that sits on top.

    Scope (subject to brainstorm refinement).

    • Stateful a b as a first-class type — a sealed callable with a hidden mutable env, externally a typed callable whose effect set includes !Mut.
    • !Mut effect, the first non-IO / non-Diverge effect; forces the effect-handler infrastructure forward.
    • mut block as the syntactic boundary in Form A — outside, AILang's pure self; inside, var / assign / mutable arrays legal. (Iteration is not part of this boundary: AILang has no while/for and this milestone does not introduce one — repetition stays recursion, exactly as the language has it today (structural / tail recursion); a mut block is a sealed expression, never a loop over mutable state.)
    • var x = expr + assign x expr AST nodes, legal only inside mut.
    • Mutable array primitive (MutArray a, O(1) index/update under uniqueness) — co-developed because the ring-buffer use case that motivates the whole effort has no carrier today.
    • pipe : Stateful a b → Stateful b c → Stateful a c as a built-in combinator with zero-allocation lowering — composition is fusion at codegen, not closure-pair layering at runtime.
    • Decision 12 (or amendment to Decision 10) that names uniqueness-bounded mutation as the legitimate exception to "no shared mutable refs", and the pure / mutable-island layering as the architectural shape.

    Blockers (hard prerequisites + open design questions).

    • Effect handlers absent. DESIGN.md:2663 — "No effect handlers — only the built-in IO and Diverge ops." !Mut is the first non-trivial effect and forces handler infrastructure to ship. May warrant lifting out as its own prerequisite milestone; brainstorm decides.

    • Uniqueness inference through var captures. Current inference operates over the immutable RC graph; mutable bindings captured by closures need a more powerful pass. Lean 4 has a known algorithm; AILang does not implement it.

    • No mutable-array primitive. No Array a, no slab container of any kind in the language today. Separate spec needed inside this milestone (representation, own-only or borrow-able, bounds-checking discipline).

    • runST-equivalent escape discharge. Producing a Stateful that legitimately escapes its mut block while proving the inner state doesn't leak. Haskell's rank-2 trick (runST :: (forall s. ST s a) -> a) is unavailable — DESIGN.md:1930 confirms higher-rank polymorphism is not supported. Need an alternative — likely sealed-by-construction at the factory boundary, or an effect-mode tag that gates escape.

    • Form A surface design. Decision 1 forbids macros (source = data, not text), so mut / var / assign are genuine AST nodes with round-trip-invariant coverage. Surface needs LLM- utility validation (does the author reach for var / mut unprompted?) before implementation.

    • Codegen for in-place struct-field writes. Term::ReuseAs today lowers ADT in-place rewrite; no direct mutable struct- field-write path exists. crates/ailang-codegen/src/escape.rs plus the lowering passes need extension.

    • Boundary escape analysis. "Interior state doesn't leak" is an escape analysis specifically over var / ref values; the existing pass covers allocations, not mutable cells.

    • Decision-10 status. Whether to amend Decision 10 inline or add a Decision 12 that names the mutable-island layer alongside the pure layer. The latter is probably cleaner — Decision 10 stays load-bearing for the pure layer, Decision 12 names the extension and its discipline.

    • Streaming bench corpus. Current corpus (list_sum, tree_walk, closure_chain, hof_pipeline) is all pure. Streaming-specific benches (SMA pipeline, IIR filter, online stats) plus an external baseline (myc, hand-C, Python/NumPy) need to exist to validate that the layered design hits the zero-alloc performance target that myc demonstrates.

    • LLM-utility test. DESIGN.md §"Feature-acceptance criterion" is the gate: the surface must produce code an LLM author naturally writes. Fieldtest after spec, before any code commits.

    • context: 2026-05-15 chat on the ~/sma_factory.myc analysis — myc's stateful-closure-plus-pipe idiom delivers a streaming workload pattern (3-line SMA factory, 2-line pipeline, zero runtime allocation per tick) that AILang's pure-only model cannot match without this layered extension. Pending brainstorm will produce docs/specs/<date>-stateful-islands.md and decide the effect-handler-prerequisite question.

P3 — Ideas

  • [todo] compare_primitives_smoke.ail counterpart. Satisfied 2026-05-13 by milestone form-a-default-authoring — examples/compare_primitives_smoke.ail is the canonical authoring form for that fixture. Form A is now the default authoring surface across the entire corpus.

    • context: closed incidentally by form-a iter form-a.1.
  • [todo] Codegen lookup_ctor_in_pattern type-anchoring — crates/ailang-codegen/src/lib.rs:1790 still walks every module's ctor_index by bare ctor name. Plumbing the scrutinee's qualified Type::Con through pattern lowering would type-anchor it symmetric to the ct.2.2 typecheck-side fix. Not load-bearing (uniqueness is enforced at typecheck), but cleaner.

    • context: JOURNAL 2026-05-11 ("Iteration ct.3" + ct.4 close).
  • [todo] compare_primitives_smoke IR-shape assertion — observe compare__Int / compare__Bool / compare__Str symbols in the emitted IR. Blocked on emit-ir CLI not running mono; resolution paths include extending the CLI to run mono, using library APIs directly in e2e.rs, or adding --dump-ir to ail build. The E2E stdout assertion already covers correctness; the IR-shape test would catch refactor regressions that rename mono symbols.

    • context: JOURNAL 2026-05-11 ("Iteration ct.4") — dropped from ct.4.4 when the BLOCKED condition surfaced.
  • [idea] Latency methodology rework — switch from per-run timing to a histogram-based approach so tail-latency regressions are visible without re-running. Queued from 21'g.

  • [idea] Parser-test backfill for 22b.4a-era duplicate-clause sites (class × 3, class method × 2, instance × 3, instance method × 1). No regression pin today; only worth it if a 22b.4a-era diagnostic needs to change.

    • context: JOURNAL 2026-05-10 ("Iteration 22-tidy.7")
  • [idea] write_type Type::Forall arm in crates/ailang-prose/src/lib.rs silently drops constraints in inline-type rendering. Dormant — surface forms today only carry forall at fn-signature top level. Fix only if a future feature carries forall + constraints inline.

    • context: JOURNAL 2026-05-10 ("Iteration 22-tidy.6")
  • [idea] Richer integration paths between RC and uniqueness — deferred from the 21' arc; revisit once the uniqueness inference covers more program shapes.