# Prose round-trip cycle ## Why The prose surface (`ail prose`, Iter 20a/20b) is a deliberately **lossy** projection of a `.ail.json` module: it strips `(con T)` wrappings, drops redundant parentheses, infixes arithmetic, suppresses schema rigor that the LLM can re-derive. That makes prose pleasant to read and edit — but it also means there is no syntactic parser that turns edited prose back into a `.ail.json`. Re-integration of free-text edits therefore requires an **LLM mediator**: a model that understands both the prose intent and the load-bearing detail in the original `.ail.json`, and emits a new `.ail.json` that respects both. This document describes the workflow, the prompt template `ail merge-prose` composes for that mediator, and the failure modes to watch for. ## The cycle ``` 1. ail prose foo.ail.json > foo.prose.txt 2. $EDITOR foo.prose.txt # human edits freely 3. ail merge-prose foo.ail.json foo.prose.txt > prompt.txt 4. cat prompt.txt | > foo.new.ail.json 5. ail check foo.new.ail.json 6. mv foo.new.ail.json foo.ail.json # if check is clean ``` Step 1 is the deterministic projection (Iter 20a/20b). Step 3 is the prompt composer this iter ships. Step 4 is the user's external LLM client — Claude Code, the Anthropic API, OpenAI's CLI, anything that takes a prompt on stdin and emits text on stdout. AILang ships no client of its own; see *Why no built-in API client* below. Steps 5 and 6 reuse existing tooling: `ail check` validates schema + typechecks, then the user accepts the new module. ## The prompt template `ail merge-prose ` reads both files and prints the following text to stdout. Nothing more, nothing less — no fences, no JSON envelope. The shape is exactly what a human would compose by hand if they wanted to drive the same cycle without the CLI helper. ``` You are integrating prose edits back into an AILang module. ROLE Your job is to produce an updated AILang JSON-AST (.ail.json) that reflects the human's prose edits while preserving the load-bearing semantic detail from the original .ail.json. CONTRACT The prose is the source of intent: the human edited it to express what the program should now do. The original .ail.json carries load-bearing detail that the prose surface elides — preserve those details unless the prose explicitly contradicts them. Specifically: - Mode annotations on fn parameters (`own T`, `borrow T`). These are hard contracts (memory model). The prose shows them, but if the prose is ambiguous, default to the original. - Effect annotations on return types (`with IO`, `with Diverge`). Prose shows these too, but again: if uncertain, keep what the original had. - `tail` flags on calls. The prose prints `tail f(x)`; if the edit moved a call, decide whether the new position is still in tail position and flag accordingly. - Doc strings (`///` lines). - Type annotations on signatures and lambdas. - Constructor names and arities (the prose `Cons(h, t)` must round-trip to a `Term::Ctor` with the right `type` field). OUTPUT Output ONLY the new .ail.json bytes. No commentary, no markdown fences, no preamble or postscript. The output must be valid JSON parsable by `ail parse` (i.e. by `serde_json` against the `ailang/v0` schema). The first byte should be `{` and the last non-whitespace byte should be `}`. SCHEMA ESSENTIALS A full reference lives in docs/DESIGN.md ("Data model (MVP)"). The shape is: - Module: { "schema": "ailang/v0", "name": ..., "imports": [...], "defs": [...] } - Defs are tagged via `kind` ("fn" | "type" | "const"). - Types are tagged via `k` ("con" | "fn" | "var" | "forall"). - Terms are tagged via `t` ("lit" | "var" | "app" | "let" | "if" | "do" | "ctor" | "match" | "lam" | "seq"). - Patterns are tagged via `p` ("var" | "lit" | "ctor" | "wild"). - On `fn-type`: `param_modes` (one of "own"/"borrow" per parameter) and `effects` are mandatory. `ret_mode` is mandatory when the return type carries a mode. - Constructor application uses `Term::Ctor` (`"t": "ctor"`, fields `type` + `ctor` + `args`), NOT `Term::App`. The prose surface elides the `type` tag (`Cons(h, t)` instead of `(term-ctor IntList Cons h t)`) — re-introduce it. - Base type names that appear bare in prose (`Int`, `Bool`, `Unit`, user types like `IntList`) must be wrapped as `{"k": "con", "name": "Int"}` etc. in the JSON. ORIGINAL .ail.json <<