From b3fc62eefc8d2e07093187ac08a18b04baaf9d68 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 12:40:30 +0000 Subject: [PATCH] Rewrite why-astra.md and README to be honest about advantages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove false claim that Rust lacks machine-readable diagnostics (cargo check --message-format=json exists) - Remove weak "one way to write things" claim (Rust has rustfmt, Go has gofmt) - Soften ownership argument — acknowledge it's an empirical observation about current models, not a permanent limitation - Remove ecosystem references — obvious for a new language - Add "Honest comparison" sections that acknowledge what other languages already provide - Add "Known Trade-offs" section (interpreted only, GC overhead, no training data) - Restructure README problem statement to be specific and fair - Focus value proposition on the three genuine differentiators: mandatory effect tracking, agent-oriented diagnostics, enforced test determinism https://claude.ai/code/session_01PHDK21bkgNsWCjdtfzxLCE --- README.md | 37 ++-- docs/why-astra.md | 504 ++++++++++------------------------------------ 2 files changed, 123 insertions(+), 418 deletions(-) diff --git a/README.md b/README.md index 24d53ae..8d7fed6 100644 --- a/README.md +++ b/README.md @@ -6,30 +6,29 @@ ## The Problem -When LLMs generate code in existing languages, they face fundamental challenges: +When LLMs generate code, they enter a feedback loop: generate, check for errors, interpret diagnostics, fix, repeat. Existing languages work for this — agents write Rust, TypeScript, Go, and Python every day — but none were designed with this loop as the primary use case. -| Language | Problems for LLMs | -|----------|-------------------| -| **Python/JS** | Runtime-only errors, non-deterministic tests, hidden side effects | -| **TypeScript** | Opt-in null safety, no effect tracking, non-deterministic tests | -| **Go** | No sum types, no pattern matching, verbose error handling, nil panics | -| **Rust** | Ownership complexity, human-oriented error messages | +Three sources of friction slow every mainstream language: -**The result**: LLM generates code -> it fails -> error is ambiguous -> LLM guesses at fix -> cycle repeats. +1. **Side effects are invisible.** No mainstream language tracks I/O, network, or clock access in function signatures. Agents must read implementations to know what a function actually does. +2. **Diagnostics are human-first.** Even languages with structured error output (Rust's `--message-format=json`, TypeScript's stable codes) don't consistently bundle machine-actionable fix suggestions with exact edit locations. +3. **Test determinism is opt-in.** Flaky tests from time, randomness, or I/O are a discipline problem everywhere. The language doesn't prevent them. ## Astra's Solution -Astra provides **fast, deterministic feedback loops** designed for machine consumption: - -- **Machine-readable diagnostics** with stable error codes and suggested fixes -- **Explicit effects** - function signatures declare all capabilities (Net, Fs, Clock, etc.) -- **Deterministic testing** - seeded randomness, mockable time, no flaky tests -- **One canonical format** - no style choices, the formatter decides everything -- **No null** - use `Option[T]` and exhaustive matching; compiler catches missing cases -- **Full JSON support** - parse and stringify JSON natively via `std.json` -- **Regular expressions** - pattern matching, replacement, and splitting via `std.regex` -- **Async/await** - declare `async` functions and `await` their results -- **Package management** - manage dependencies with `astra pkg` +Astra is designed around three capabilities that no single mainstream language provides together: + +- **Mandatory effect tracking** - function signatures declare all capabilities (`Net`, `Fs`, `Clock`); the compiler rejects undeclared effects +- **Agent-oriented diagnostics** - every error includes structured JSON with stable codes and suggested fixes with exact edit locations +- **Enforced test determinism** - effects must be mocked in tests; seeded randomness and fixed clocks are the default, not opt-in + +Plus the building blocks you'd expect: + +- **No null** - `Option[T]` with exhaustive matching; compiler catches missing cases +- **Typed error handling** - `Result[T, E]` with `?` and `?else` for concise propagation +- **Canonical formatting** - mandatory built-in formatter, no configuration +- **JSON / regex / async** - built into the standard library +- **Package management** - `astra pkg` for dependency management ``` LLM generates code -> astra check -> JSON errors with fix suggestions -> LLM applies fixes -> repeat until passing diff --git a/docs/why-astra.md b/docs/why-astra.md index 484c7c8..23fd8ed 100644 --- a/docs/why-astra.md +++ b/docs/why-astra.md @@ -2,60 +2,23 @@ ## The Problem -When LLMs generate code in existing languages, they face fundamental challenges: +When LLMs generate code, they enter a feedback loop with the compiler or runtime: generate code, check for errors, interpret diagnostics, fix, repeat. The speed and reliability of this loop determines how effectively agents can write software. -### Python / JavaScript -- **Runtime errors**: Null references, type mismatches, and undefined variables only appear at runtime -- **Non-deterministic**: Tests involving time, randomness, or I/O can flake -- **Ambiguous semantics**: Many ways to express the same logic -- **No effect tracking**: Side effects are invisible in function signatures +Existing languages weren't designed for this loop. They work — agents write Rust, TypeScript, Go, and Python every day — but each language introduces friction that slows the cycle: -### TypeScript -- **Opt-in strictness**: Null safety requires `strictNullChecks`; many projects leave it off or use `any` as an escape hatch -- **No effect tracking**: Side effects are invisible, same as JavaScript -- **Multiple paradigms**: OOP classes, functional patterns, and various module systems offer many equivalent approaches -- **Non-deterministic tests**: Same flaky-test problems as JavaScript (time, randomness, I/O) -- **Complex toolchain**: Bundlers, transpilers, and runtime choices add configuration overhead +- **Diagnostics are human-oriented.** Error messages are prose meant for humans to read. Even languages with stable error codes (Rust, TypeScript) don't bundle structured fix suggestions with exact edit locations as a first-class feature. +- **Side effects are invisible.** In every mainstream language, a function's signature doesn't tell you whether it hits the network, reads the filesystem, or accesses the clock. Agents must infer this from implementation details or documentation. +- **Test determinism is opt-in.** Flaky tests caused by time, randomness, or I/O are a discipline problem in every mainstream language. The language doesn't prevent them; the programmer must. -### Go -- **No sum types**: No enums with data, no `Option`/`Result` — `nil` is used for absent values, leading to nil-pointer panics -- **No pattern matching**: Error handling relies on `if err != nil` repetition rather than exhaustive matching -- **Verbose error handling**: Explicit errors are good, but the `if err != nil { return err }` pattern is repetitive and easy to get wrong -- **No effect tracking**: Side effects are invisible in function signatures -- **Limited generics**: Generics added in Go 1.18 remain restricted compared to other statically typed languages +These aren't fatal problems. Agents work around them. But Astra asks: what if a language were designed from the ground up to eliminate this friction? -### Rust -- **Ownership complexity**: Borrow checker requires reasoning about lifetimes that LLMs struggle with -- **Steep learning curve**: Even correct code may be rejected for subtle ownership violations -- **Multiple idioms**: Traits, generics, macros offer many equivalent approaches -- **Human-oriented errors**: Error messages assume human interpretation +## What Astra Actually Provides -### The Result -LLMs generate code → it fails → error message is ambiguous → LLM guesses at fix → cycle repeats +### 1. Effect Tracking in Function Signatures -## Astra's Solution - -Astra is designed from the ground up for **fast, deterministic feedback loops** between LLMs and the compiler. - -### 1. Verifiable by Design - -```astra -# No null - use Option[T] -fn find_user(id: Int) -> Option[User] { ... } - -# Exhaustive matching required -match find_user(42) { - Some(user) => greet(user) - None => handle_missing() # Can't forget this! -} -``` - -**Why it matters**: The compiler catches missing cases. LLMs don't need to remember edge cases—the type system enforces them. - -### 2. Explicit Effects +This is Astra's most distinctive feature. No mainstream language offers it. ```astra -# This function's capabilities are visible in its signature fn fetch_data(url: Text) -> Result[Data, Error] effects(Net, Clock) { @@ -64,32 +27,22 @@ fn fetch_data(url: Text) -> Result[Data, Error] parse(response, timestamp) } -# Pure functions have no effects keyword +# Pure functions have no effects — the compiler enforces this fn add(a: Int, b: Int) -> Int { a + b } ``` -**Why it matters**: -- LLMs can see exactly what a function can do -- Tests can inject mock capabilities -- No hidden side effects to reason about +**What this gives agents:** +- An agent can read a function signature and know *exactly* what capabilities it uses — no need to scan the body or chase transitive dependencies. +- The compiler rejects undeclared effects, so agents can't accidentally introduce hidden I/O. +- Tests can inject mock capabilities at the language level, not through external libraries or ad-hoc dependency injection. -### 3. Deterministic Testing +**Honest comparison:** Rust can approximate this with trait bounds (`impl HttpClient + Clock`), and Go uses interface injection. But these are opt-in patterns that require discipline. In Astra, effect tracking is mandatory and compiler-enforced. The trade-off is some annotation overhead. -```astra -test "random behavior is reproducible" { - using effects(Rand = Rand.seeded(42), Clock = Clock.fixed(1000)) - - # Same seed = same results, every time - let value = Rand.int(1, 100) - assert_eq(value, 67) # Always 67 with seed 42 -} -``` +### 2. Agent-Oriented Diagnostic Pipeline -**Why it matters**: Tests never flake. LLMs can write tests confident they'll pass consistently. - -### 4. Machine-Readable Diagnostics +Many languages have structured error output — Rust's `cargo check --message-format=json` is excellent. Astra's difference is that the *entire* diagnostic pipeline is designed holistically for agent consumption: ```json { @@ -99,383 +52,136 @@ test "random behavior is reproducible" { "span": {"file": "app.astra", "line": 15, "col": 3}, "suggestions": [{ "title": "Add missing case", - "edits": [{"line": 18, "insert": " None => ???"}] + "edits": [{"line": 18, "col": 0, "insert": " None => ???\n"}] }] } ``` -**Why it matters**: -- Stable error codes (E1004 always means the same thing) -- Suggested fixes with exact edit locations -- LLMs can parse and apply fixes automatically - -### 5. One Way to Write Things - -```astra -# There's one canonical format -# The formatter enforces it -# No style debates, no variation - -fn process(items: List[Item]) -> List[Result] { - items.map(fn(item) { transform(item) }) -} -``` - -**Why it matters**: LLMs don't have to choose between equivalent approaches. The formatter normalizes everything. - -## Astra Compared to Other Languages - -| Aspect | Python | TypeScript | Go | Rust | Astra | -|--------|--------|------------|-----|------|-------| -| **Type checking** | Runtime only | Compile-time (opt-in strict) | Compile-time | Compile-time | Compile-time | -| **Null safety** | No (`None` crashes) | Opt-in (`strictNullChecks`) | No (`nil` panics) | Yes (`Option`) | Yes (`Option[T]`) | -| **Effect tracking** | None | None | None | None | Built-in (`effects(...)`) | -| **Canonical formatter** | No (black, autopep8, etc.) | No (prettier, dprint, etc.) | Yes (`gofmt`) | Yes (`rustfmt`) | Yes (built-in, mandatory) | -| **Error handling** | Exceptions | Exceptions / thrown values | Multiple returns + `if err != nil` | `Result` + `?` | `Result[T, E]` + `?` / `?else` | -| **Test determinism** | Not guaranteed | Not guaranteed | Not guaranteed | Not guaranteed | Guaranteed by design | -| **Diagnostic format** | Human-readable | Human-readable | Human-readable | Human-readable | Machine-readable JSON | -| **Memory model** | GC | GC | GC | Ownership + borrowing | GC/RC | -| **Primary user** | Humans | Humans | Humans | Humans | LLM agents | - -**Astra is implemented in Rust, but designed for LLMs to write.** Each language above is good at what it was designed for. Astra focuses specifically on the feedback loop between LLMs and compilers. - -## Concrete Examples - -### 1. Effects Are Visible in Signatures +**What this gives agents:** +- Every diagnostic includes structured `suggestions` with exact edit locations, not just the error itself. +- `astra fix` applies these suggestions automatically — the agent doesn't need to interpret the error at all. +- Stable error codes (E0xxx–E4xxx) are consistent across versions. -None of the mainstream languages track side effects in function signatures: +**Honest comparison:** Rust's `--message-format=json` provides structured errors with spans and codes, and `cargo fix` can auto-apply some suggestions. TypeScript has stable error codes. Astra's advantage is that *every* error is designed to include actionable fix suggestions with precise edit locations from the start — it's a design goal, not a bolt-on. But the Rust ecosystem's diagnostics are mature and battle-tested; Astra's are new. -```typescript -// TypeScript - can't tell from signature what side effects this has -async function processData(url: string): Promise { - const response = await fetch(url); // Network I/O - hidden! - const now = Date.now(); // Clock access - hidden! - return parse(await response.json(), now); -} -``` - -```go -// Go - same problem -func processData(url string) (Data, error) { - resp, err := http.Get(url) // Network I/O - hidden! - now := time.Now() // Clock access - hidden! - return parse(resp, now) -} -``` - -```rust -// Rust - same problem -fn process_data(url: &str) -> Result { - let response = reqwest::get(url)?; // Network I/O - hidden! - let now = SystemTime::now(); // Clock access - hidden! - parse(response, now) -} -``` +### 3. Deterministic Testing as a Language Guarantee ```astra -// Astra - effects are explicit in the signature -fn process_data(url: Text) -> Result[Data, Error] - effects(Net, Clock) -{ - let response = Net.get(url)? - let now = Clock.now() - parse(response, now) +test "random behavior is reproducible" { + using effects(Rand = Rand.seeded(42), Clock = Clock.fixed(1000)) + + let value = Rand.int(1, 100) + assert_eq(value, 67) # Always 67 with seed 42 } ``` -**LLM benefit**: When an LLM sees `effects(Net, Clock)`, it knows exactly what capabilities this function needs. No guessing, no hidden surprises. This is unique to Astra — no mainstream language provides this. - -### 2. No Ownership Complexity - -This comparison is specific to Rust. TypeScript and Go both use garbage collection, like Astra, so ownership is not a problem in those languages. +**What this gives agents:** +- Tests never flake. An agent can write a test, see it pass, and trust it will always pass. +- Effect mocking is built into the language — no external mocking libraries needed. +- `test` is a language keyword; tests live inline next to the code they exercise. -```rust -// Rust - LLMs constantly struggle with ownership -fn process(data: Vec) -> Vec { - let filtered: Vec = data - .into_iter() // Consumes data! - .filter(|s| !s.is_empty()) - .collect(); +**Honest comparison:** Deterministic testing in Rust, Go, or TypeScript is achievable through discipline: inject clocks, seed RNGs, mock I/O boundaries. Mature libraries exist for this (`mockall`, `wiremock`, `proptest` in Rust; `testing` in Go). The difference is that Astra makes determinism the default rather than opt-in. If you use `Clock.now()`, you *must* declare the `Clock` effect, and tests *must* inject a mock. The language prevents accidental non-determinism. But the ecosystem libraries in other languages are far more mature and battle-tested. - // ERROR: Can't use `data` anymore - it was moved! - // println!("Original had {} items", data.len()); - - filtered -} - -// Even simple things require lifetime reasoning -fn first_word(s: &str) -> &str { - // LLMs often forget the lifetime connection here - s.split_whitespace().next().unwrap_or("") -} -``` +### 4. Reduced Ownership Complexity (vs. Rust specifically) ```astra -// Astra - no ownership, no borrowing, just works fn process(data: List[Text]) -> List[Text] { let filtered = data.filter(fn(s) { s != "" }) - # Can still use data here if needed + # Can still use `data` here — no ownership transfer filtered } - -fn first_word(s: Text) -> Text { - # No lifetimes to reason about - match s.split(" ") { - [] => "" - [first, ..] => first - } -} -``` - -**LLM benefit**: The #1 failure mode for LLMs writing Rust is ownership errors. Astra eliminates this entire category (as do TypeScript and Go). Astra's advantage over those languages lies in other areas — effects, testing, and diagnostics. - -### 3. Explicit, Composable Error Handling - -Languages handle errors very differently. Each approach has trade-offs: - -```typescript -// TypeScript - exceptions are untyped and invisible in signatures -async function parseConfig(path: string): Promise { - const content = fs.readFileSync(path, "utf-8"); // throws Error - return JSON.parse(content); // throws SyntaxError - // Caller has no idea what can be thrown -} - -// Optional chaining helps with null, but doesn't compose with errors -function getUserAge(id: number): number | undefined { - return findUser(id)?.profile?.age; -} -``` - -```go -// Go - explicit errors, but verbose and no exhaustive checking -func parseConfig(path string) (Config, error) { - content, err := os.ReadFile(path) - if err != nil { - return Config{}, err - } - var config Config - if err := json.Unmarshal(content, &config); err != nil { - return Config{}, err - } - return config, nil -} -``` - -```rust -// Rust - Result + ? is powerful but requires From trait understanding -fn parse_config(path: &str) -> Result> { - let content = std::fs::read_to_string(path)?; // io::Error - let config: Config = serde_json::from_str(&content)?; // serde::Error - Ok(config) -} ``` -```astra -// Astra - ? works naturally, ?else provides fallback -fn parse_config(path: Text) -> Result[Config, Text] - effects(Fs) -{ - let content = Fs.read(path)? - parse_json(content)? -} - -fn get_user_age(id: Int) -> Option[Int] { - let user = find_user(id)? - let profile = user.profile? - Some(profile.age) -} - -# Or use ?else for inline defaults -fn get_age_or_default(id: Int) -> Int { - let user = find_user(id) ?else { age = 0 } - user.age -} -``` +Astra uses garbage collection instead of ownership and borrowing. This eliminates a category of errors that agents frequently encounter when writing Rust. -**LLM benefit**: Go's explicit error returns are a step in the right direction, but require repetitive `if err != nil` checks. Rust's `?` operator is concise but requires understanding `From` trait conversions. TypeScript's exceptions are invisible in signatures. Astra combines the best: `?` for concise propagation, `Result[T, E]` for typed errors, and `?else` for inline fallbacks. +**Honest comparison:** This is an empirical observation about current models, not necessarily a permanent limitation. LLMs improve at Rust with each generation, and the gap is narrowing. Meanwhile, Go and TypeScript also use garbage collection and don't have this problem either. Astra's advantage over *those* languages lies elsewhere (effects, diagnostics, deterministic testing). The trade-off is real: GC means Astra is unsuitable for systems programming, embedded, real-time, or performance-critical hot paths — domains where Rust excels. -### 4. Machine-Readable Error Codes +## What Astra Borrows from Other Languages -All mainstream languages produce human-readable error output. Some have stable error codes (Rust, TypeScript), but none include machine-actionable fix suggestions by default. +Astra is not built in a vacuum. It intentionally preserves good ideas: -```typescript -// TypeScript error (human-oriented, stable code) -error TS2345: Argument of type 'string' is not assignable - to parameter of type 'number'. - src/app.ts:15:3 -``` - -```go -// Go error (human-oriented, no stable codes) -./main.go:15:3: cannot use "hello" (untyped string constant) - as int value in argument to add -``` - -```rust -// Rust error (human-oriented, stable code, detailed) -error[E0382]: borrow of moved value: `data` - --> src/main.rs:5:20 - | -2 | let data = vec![1, 2, 3]; - | ---- move occurs because `data` has type `Vec` -3 | let sum: i32 = data.into_iter().sum(); - | ---- `data` moved due to this method call -5 | println!("{:?}", data); - | ^^^^ value borrowed here after move -``` - -```astra -// Astra error (machine-oriented, with --json flag) -{ - "code": "E1004", - "message": "Non-exhaustive match: missing pattern `None`", - "span": {"file": "app.astra", "line": 15, "col": 3}, - "suggestions": [{ - "title": "Add missing case", - "edits": [{"line": 18, "col": 0, "insert": " None => ???\n"}] - }] -} -``` +**From Rust:** `Option[T]` and `Result[T, E]` instead of null, pattern matching with exhaustiveness checking, immutable-by-default bindings, expression-based returns, `?` operator for error propagation. -**LLM benefit**: TypeScript and Rust have stable error codes, which is helpful. But Astra goes further: every error includes structured JSON with suggested fixes and exact edit locations. LLMs can parse and apply fixes programmatically without interpreting prose. +**From Go:** Single mandatory formatter (no configuration), built-in test runner, simple language surface. -### 5. First-Class Testing +**From TypeScript:** Structural types, type inference. -Most languages require external test frameworks or specific file conventions: +The goal is to combine these while adding what's missing: mandatory effect tracking, guaranteed test determinism, and an agent-first diagnostic pipeline. -```typescript -// TypeScript (Jest) - external framework with its own API -describe("add", () => { - it("returns the sum", () => { - expect(add(2, 2)).toBe(4); - }); -}); -``` +## Astra Compared to Other Languages -```go -// Go - built-in runner, but requires _test.go files and Test prefix -func TestAddition(t *testing.T) { - result := add(2, 2) - if result != 4 { - t.Errorf("expected 4, got %d", result) - } -} -``` +| Aspect | Python | TypeScript | Go | Rust | Astra | +|--------|--------|------------|-----|------|-------| +| **Null safety** | No (`None` crashes) | Opt-in (`strictNullChecks`) | No (`nil` panics) | Yes (`Option`) | Yes (`Option[T]`) | +| **Effect tracking** | None | None | None | Approximable via traits | Built-in, mandatory | +| **Structured diagnostics** | No | Stable codes, no fix suggestions | No | JSON output + `cargo fix` | JSON with fix suggestions by default | +| **Test determinism** | Opt-in (discipline) | Opt-in (discipline) | Opt-in (discipline) | Opt-in (discipline) | Enforced by effect system | +| **Error handling** | Exceptions | Exceptions | `if err != nil` | `Result` + `?` | `Result[T, E]` + `?` / `?else` | +| **Canonical formatter** | Third-party (black) | Third-party (prettier) | Built-in (`gofmt`) | Built-in (`rustfmt`) | Built-in, mandatory | +| **Memory model** | GC | GC | GC | Ownership + borrowing | GC/RC | -```rust -// Rust - tests are macro-based, in separate modules -#[cfg(test)] -mod tests { - use super::*; +## The Feedback Loop - #[test] - fn test_addition() { - assert_eq!(add(2, 2), 4); - } -} ``` - -```astra -// Astra - tests are language primitives, inline with code -test "addition works" { - assert add(2, 2) == 4 - assert add(0, 0) == 0 -} - -test "handles negative numbers" { - assert add(-1, 1) == 0 -} +LLM generates Astra code + | + v + astra check (fast, incremental) + | + v + +-----+-----+ + | Errors? | + +-----+------+ + Yes | No + | +-------> astra test (deterministic) + | | + v v + JSON diagnostics +----+-----+ + with fix suggestions | Passes? | + | +----+-----+ + | Yes | No + | | | + | | +---> Failure details + | | | + +---------------------+--------------+ + | + v + LLM applies fixes + | + +----------- (repeat) ``` -**LLM benefit**: Go deserves credit for having a built-in test runner, and Rust for keeping tests in the same file. Astra goes further: `test` is a language keyword, tests live inline next to the functions they exercise, and effect mocking is built in — no external libraries needed. - -### What Astra Borrows from Other Languages - -Astra isn't built in a vacuum. It intentionally preserves good ideas from each language: - -**From Rust:** - -| Concept | Rust | Astra | -|---------|------|-------| -| Option type | `Option` | `Option[T]` | -| Result type | `Result` | `Result[T, E]` | -| Pattern matching | `match x { ... }` | `match x { ... }` | -| Immutable by default | `let x = 5;` | `let x = 5` | -| Expression-based | Last expression is return value | Same | -| Enums with data | `enum Msg { Text(String) }` | `enum Msg = Text(s: Text)` | -| Canonical formatter | `rustfmt` | Built-in formatter | - -**From Go:** - -| Concept | Go | Astra | -|---------|-----|-------| -| Single canonical format | `gofmt` — one style, no config | Same philosophy — formatter is mandatory | -| Built-in test runner | `go test` | `astra test` | -| Simple language surface | Few features, easy to learn | Same goal — minimal ambiguity | -| Explicit errors | Multiple returns for errors | `Result[T, E]` — same idea, more composable | - -**From TypeScript:** +## When to Use Astra -| Concept | TypeScript | Astra | -|---------|------------|-------| -| Structural types | `{ x: number, y: number }` | `{ x: Int, y: Int }` | -| Type inference | `let x = 5` inferred as `number` | `let x = 5` inferred as `Int` | +**Good fit:** +- Agent-generated automation scripts and business logic +- Sandboxed plugin systems where capability control matters +- Reproducible data pipelines that must never flake +- Any context where code is primarily machine-generated and machine-verified + +**Not a good fit:** +- Systems programming, embedded, or real-time (use Rust, C, or Zig) +- Performance-critical hot paths (Astra is interpreted; use Rust or C++) +- Web frontends (use TypeScript) +- Large existing codebases (use what you already have) -The goal is to combine the best ideas from each language while removing what causes LLMs to fail: Rust's ownership complexity, Go's lack of sum types, TypeScript's `any` escape hatch and exception-based errors. +## Known Trade-offs -## The Feedback Loop +Astra makes deliberate trade-offs. Being transparent about them: -``` -┌─────────────────────────────────────────────────────────┐ -│ │ -│ LLM generates Astra code │ -│ │ │ -│ ▼ │ -│ astra check (fast, incremental) │ -│ │ │ -│ ▼ │ -│ ┌──────┴──────┐ │ -│ │ Errors? │ │ -│ └──────┬──────┘ │ -│ Yes │ No │ -│ │ └──────► astra test (deterministic) │ -│ │ │ │ -│ ▼ ▼ │ -│ JSON diagnostics ┌─────┴─────┐ │ -│ with fix suggestions │ Passes? │ │ -│ │ └─────┬─────┘ │ -│ │ Yes │ No │ -│ │ │ │ │ -│ │ │ └──► Failure details │ -│ │ │ │ │ -│ └────────────────────┴──────────────┘ │ -│ │ │ -│ ▼ │ -│ LLM applies fixes │ -│ │ │ -│ └─────────── (repeat) ───────────┘ -│ │ -└─────────────────────────────────────────────────────────┘ -``` +1. **Interpreted only.** All execution is via a tree-walking interpreter. Adequate for automation scripts and business logic, but not for compute-heavy workloads. -## When to Use Astra +2. **GC overhead.** Garbage collection is simpler but rules out use cases requiring deterministic memory management (embedded, real-time, systems programming). -**Good fit:** -- Agent-generated automation scripts -- Sandboxed plugin systems -- Verifiable business logic -- Reproducible data pipelines -- Any code that needs to be machine-generated and machine-verified - -**Not designed for:** -- Systems programming (use Rust or Go) -- Performance-critical hot paths (use Rust/C++) -- Web frontends (use TypeScript) -- Large existing codebases (use what you have) +3. **No training data.** LLMs have been trained on millions of Rust, Python, and TypeScript examples. Astra has very little. Agents writing Astra code need the language specification and examples as context, and will produce lower-quality code until models are fine-tuned or trained on Astra corpora. ## Summary -Astra isn't trying to be a better Rust, Python, Go, or TypeScript. It's designed for a specific use case: **code that machines write, verify, and maintain**. +Astra isn't trying to replace Rust, Python, Go, or TypeScript. Each of those languages has years of production hardening and a broad set of use cases where it excels. + +Astra targets a specific niche: **code that machines write, verify, and maintain**, where the three things that matter most are: +1. Can the agent see exactly what a function does? (Effect tracking) +2. Can the agent fix errors without guessing? (Structured diagnostics with suggestions) +3. Can the agent trust that tests are reliable? (Enforced determinism) -The goal is simple: when an LLM generates Astra code, it should either work correctly or fail with errors the LLM can fix automatically. +If those three properties matter to your use case, Astra is worth evaluating. If not, use an established language — they're good, and getting better for agents every day.