Astralang
A gilded and silver celestial globe from 1579, driven by clockwork and carried on a winged horse.

The compiler is an API.

This page is its documentation. If you're building an agent, a coding assistant, or a framework that generates Astra, everything you need is here: the CLI surface, the JSON contract, the error-code catalogue, and the canonical loop.

JSON
every diagnostic: a stable code, a line, a column, a suggestion
21 codes
never renumbered, so an agent can branch on them
4–5 ms
per astra verify: cheaper than one model call

Connect any agent: astra mcp v0.13

astra mcp serves the whole toolchain over the Model Context Protocol, so any MCP client can call it, whichever model drives it.

ClientSetup
Claude Codeclaude mcp add astra -- astra mcp
Cursor.cursor/mcp.json: {"mcpServers": {"astra": {"command": "astra", "args": ["mcp"]}}}
VS Code.vscode/mcp.json: {"servers": {"astra": {"type": "stdio", "command": "astra", "args": ["mcp"]}}}
Gemini CLI.gemini/settings.json: {"mcpServers": {"astra": {"command": "astra", "args": ["mcp"]}}}
Codex CLI~/.codex/config.toml: [mcp_servers.astra], command = "astra", args = ["mcp"]
Any othera stdio server: command astra, argument mcp

Its tools are verify, fix, run, build, format, explain and test, and each answers with exactly the JSON the matching command prints. Programs run in a separate process with a time limit and capped output, and paths outside the folder the server started in are refused. The model reads the developer guide once, from the resource astra://guide; astra://errors lists every code.

The commands an agent calls

An agent needs only these commands, each with a stable exit code.

CommandWhat it doesExit code
astra verify --error-format=json <file> Parse + typecheck only. No codegen, no execution. ~100 ms. 0 = verified · non-zero = diagnostics emitted
astra compile --error-format=json <file> --output <bin> Full pipeline to a native executable. 0 = binary written · non-zero = failed
astra run --error-format=json <file> v0.13 Checks first (a compile error is reported without running), then runs with the interpreter. A runtime error goes to stderr as JSON with an R code; stdout stays the program's. the program's · 1 = check or runtime error
astra explain E0002 Expands any error code into a full explanation + guidance; astra explain list lists them all. 0
astra fix <file> v0.12 Applies every machine-applicable fix, re-verifies, repeats, and writes the file. Accepts --error-format=json. 0 = verified after fixing · non-zero = errors remain

Rust callers can skip the CLI entirely:

let report = astra::compiler::verify(source);
let result = astra::compiler::compile_to_result(source);

The diagnostic JSON shape

One object per invocation. Fields are stable; new fields may be added, existing ones are never repurposed.

{
  "success": false,
  "diagnostics": [
    {
      "phase": "typecheck",       // "parse" | "typecheck" | "codegen"
      "kind": "TypeError",        // coarse class
      "error_code": "E0002",      // stable, catalogue below
      "message": "Type error: Undefined variable 'total'",
      "file": null,
      "line": 4,                  // 1-based; null if unresolvable
      "column": 12,               // 1-based; null if unresolvable
      "severity": "error",
      "suggestion": "Declare 'total' with let before use, e.g. let total = ...;"
    }
  ]
}
  • Parse errors always carry line and column of the offending token.
  • Typecheck errors: every type error in the main file carries the line and column of the statement that raised it (human output ends with (line N, column M)); a function reports all of its independent errors in one run, not just the first; capability errors (E0014) point at the offending built-in call. Errors in imported modules and in function signatures still have no line (null, never 0).
  • suggestion is a human/LLM-readable repair hint, name-aware where possible.
  • Success shape: {"success": true, "diagnostics": []}.

Machine-applicable fixes v0.12

When the repair is unambiguous, a diagnostic also carries a fix: text edits that repair the error without an LLM call. Each edit replaces the text from line/column up to, not including, end_line/end_column (1-based) with text. This is real output from main:

{
  "phase": "typecheck",
  "error_code": "E0002",
  "message": "Type error: Undefined variable 'totl'; did you mean 'total'?",
  "line": 4,
  "column": 17,
  "fix": {
    "edits": [
      { "line": 4, "column": 17, "end_line": 4, "end_column": 21, "text": "total" }
    ]
  }
}
  • Fixed since v0.12: misspelt variable and function names (E0002, E0003) and a missing or extra mut at a call (E0022).
  • Next: a missing match arm (E0021) and a missing trait method (E0020).
  • No fix field means no unambiguous repair is known; hand the diagnostic to the model as before.

Canonical integration pattern

The whole integration is one loop: generate, verify, repair from the JSON, then compile.

# pseudocode for any agent framework
source = llm.generate(task)

for attempt in 1..MAX_REPAIRS:
    report = run("astra verify --error-format=json", source)
    if report.success:
        break                                # verified, safe to compile
    source = llm.repair(source, report.diagnostics)
                                             # feed error_code + line +
                                             # column + suggestion back in

binary = run("astra compile --error-format=json", source)
execute(binary)                              # native, already typechecked

Apply the compiler's own fixes first, and spend a model call only on what remains:

for attempt in 1..MAX_REPAIRS:
    report = run("astra verify --error-format=json", source)
    if report.success:
        break
    if any(d.fix for d in report.diagnostics):
        run("astra fix", source)          # apply edits, re-verify, repeat
        continue                             # no LLM call for these errors
    source = llm.repair(source, report.diagnostics)

Why this converges better than a Python retry loop:

  • Diagnostics are positional: the model repairs one pinpointed line instead of regenerating the file.
  • Codes are categorical: frameworks can route E0002 to the compiler's own fix and reserve the LLM for E0004/E0011.
  • Verification takes 4–5 ms: cheaper than any token round-trip, so verify after every single edit.
  • Whole error classes cannot escape to runtime, so the expensive failure mode (bad code executing) is structurally removed.

Versioning promise: the JSON field set and the E-code catalogue only grow, existing codes and fields are never renamed or repurposed. Breaking changes to this contract would get a major version bump and a migration note on this page.

Stable error codes

Codes are permanent identifiers, safe to branch on, cache repair strategies against, or fine-tune with. astra explain E#### returns the long-form description for any of them.

CodeMeaningTypical agent repair
E0001Syntax errorRe-emit the statement; check delimiters and semicolons
E0002Undefined variableDeclare with let, or fix the identifier spelling (a near-miss name carries a fix)
E0003Undefined functionDefine the function or correct the call target (a near-miss name carries a fix)
E0004Type mismatchAlign value type with the annotation / return type
E0005Wrong number of argumentsMatch the call to the function signature
E0006Condition must be booleanUse a comparison, not a bare value, in if/while
E0007Unknown methodCheck the receiver type's supported methods
E0008Cannot index non-arrayIndex arrays only; check the variable's type
E0009Duplicate definitionRename or remove the redefinition
E0010Struct field errorUse fields declared on the struct; check literal completeness
E0011Generic type errorMake type parameters consistent at the call site
E0012IO errorCheck the file path passed to the CLI
E0013Module errorCheck the module file exists (next to the importer or on ASTRA_PATH), the item is pub, and there is no import cycle
E0014Capability deniedRemove the call at the end of the reported chain, or allow the effect in the build policy
E0016Assignment to immutable bindingDeclare it with let mut, or assign a different binding
E0017Tuple destructuring errorBind as many names as the tuple has elements
E0018Operator requires integersUse integers on both sides of bitwise operators and shifts
E0020Trait not implementedAdd the missing method (or fix its signature) named in the message in the impl Trait for Type block; for a bound, pass a type that implements the trait
E0021Non-exhaustive matchAdd an arm for each missing variant named in the message, or end with _ =>
E0022Value cannot be modified hereDeclare the parameter mut, pass it as mut at the call, or copy it and modify the copy (a missing or extra mut at the call carries a fix)
E0099Internal / uncategorizedReport upstream; do not retry blindly

Runtime error codes v0.13

When a program stops with an error, astra run --error-format=json, the run tool of astra mcp and the playground all report one of these.

CodeMeaning
R0001Integer overflow
R0002Division by zero
R0003Invalid shift amount
R0004Index out of bounds
R0005Stack overflow (runaway recursion)
R0006Uncaught throw
R0007Assertion failed
R0099Any other runtime error