Known Limitations
Ulexite is a new language. This page lists what it doesn't do yet, where its design trades expressiveness for guarantees, and where you should watch out for rough edges — the facts a prospective user would want before adopting it, not a justification for why each trade-off was made.
No production track record
Ulexite's checkpoint/replay design is unvalidated against the failure modes that only surface at scale in production — a partial write during a crash mid-checkpoint, clock skew across a distributed provider registry, an adversarial trace-log tampering attempt. Systems like LangGraph's checkpointer have years of production edge cases behind them; Ulexite has none yet. Treat it as a new project, not a battle-tested one.
with-block parallelism has an expressiveness ceiling
The rule that a with block's branches can't reference each other's results is a parser-enforced, sound guarantee — but it's strictly less expressive than the inferred-dependency graphs of systems like Pulumi or Beam. A pattern like "three retrieval calls that could theoretically run in parallel, but the second wants to see the first's result to decide whether to bother" can't be expressed inside one with block at all; it has to be written sequentially, giving up the parallelism you might have wanted. This is a deliberate trade, but it's a real ceiling, not a solved problem. (A more permissive, effect-tracked parallelism model is on the roadmap.)
The generics system is thin
Ulexite's generic vocabulary (Draft<T>, dataset<Row>, list<T>) is small and closed. It can't express a user-defined generic container over artifacts with its own merge semantics, or higher-kinded abstractions like "any capability that produces a T." If you need that, you drop to the standard library's Rust implementation layer rather than expressing it in Ulexite itself.
Capability negotiation can't catch everything
Ulexite's structured_output: guaranteed | negotiated | unsupported tiering is a real improvement over silent runtime failure, but it can only reflect a capability difference the provider plugin author correctly declares. It can't detect an undeclared gap — a provider that claims guaranteed but is subtly wrong under some input shape. This converts most of the "provider-agnostic, but leaky" problem into something compile-time-checkable, but it's a mitigation, not an elimination — it still depends on honest, well-tested plugin authors.
Non-determinism is typed, not eliminated
Draft<T> makes non-determinism visible and forces you to handle it exhaustively — it does not make an LLM's output correct or stable. Two runs against the same prompt with caching off can still legitimately produce different Settled(T) values that both satisfy the same type. The type system disciplines how a program reacts to non-determinism; it doesn't and can't reduce the model's actual sampling variance. Judges mitigate this at the evaluation layer, not the type layer, and judges themselves are probabilistic instruments that need real calibration discipline to be trustworthy — a discipline the language encourages but can't force.
The IR interpreter has a performance ceiling for compute-heavy programs
Ulexite interprets its IR rather than natively compiling it, on the bet that network-bound latency dominates interpretation overhead for typical conversation-orchestration workloads. For a program with a large, mostly-pure computational core — heavy client-side artifact post-processing, large-scale embedding math done in-language rather than via a provider capability — that bet doesn't hold, and interpretation overhead becomes a real, measurable cost. The workaround is architectural: push genuinely heavy computation into a python/shell FFI call or a provider/tool plugin written in Rust, rather than expressing it as Ulexite IR.
The declarative/imperative split is a design bet
Ulexite splits programs into a provably-independent declarative region (with blocks) and an imperative region for everything else. It's possible real-world usage reveals a large class of programs that are "almost" parallelizable but don't fit with's strict independence rule often enough that the ergonomic cost outweighs the soundness benefit. If so, a future revision may need a more permissive model instead of the current syntactic one.
Tooling and ecosystem debt is real
Ulexite's package ecosystem is rated "Low (new)" against LangChain's or LlamaIndex's very large integration catalogues, honestly. Every provider, vector store, and tool integration those ecosystems have accumulated over years has to be either reimplemented as an Ulexite plugin or wrapped via FFI before parity is reached. That's real work, not a solved problem, and some long tail of niche integrations may never justify a native port.
Adopting a new language has a real cost
Every team adopting Ulexite has to learn a new grammar, a new type system's vocabulary (Draft<T>, Verdict, capability negotiation), and a new toolchain. Even with migration paths designed to be incremental, this is a strictly higher up-front cost than adding one more Python import to an existing codebase — one that's only worth paying for teams whose conversation-orchestration surface is large or critical enough for the static guarantees to pay for themselves.
Provider coverage has real gaps
The shipped provider adapters cover chat/judge across every supported vendor (OpenAI-compatible servers, Azure OpenAI, Anthropic, Gemini, Cohere, Ollama), and embed/vision across most of them — but:
- There's no general artifact/blob store yet. A file path passed via
--argis read directly off disk by the provider adapter at the HTTP-call boundary, not managed by a pluggable content-addressed store.speak/generate_imageoutput is written to a hash-named temp file, which is close to but not the same as the pluggable store the design eventually calls for. visionis images-plus-Anthropic-PDF, not full PDF/video support. jpg/png/gif/webp work broadly; Anthropic additionally accepts PDFs, routed to a document content block. Every other vendor rejects.pdfoutright, and video artifacts aren't implemented anywhere.transcribe/speak/generate_imageare OpenAI-compatible-only (OpenAI itself, or Groq fortranscribe). Anthropic and Cohere don't expose these APIs at all; adapters for Gemini's, Azure's, or a native Ollama server's equivalents don't exist yet.- Retry/circuit-breaking is real but simple — exponential backoff with jitter and a per-provider circuit breaker, but no per-error-class tuning and no shared breaker state across processes.
- Refusal detection is vendor-specific and not exhaustive — Cohere's Chat v2 API exposes no refusal signal at all, so it never produces a
Refuseddraft.
Some provider-declaration edge cases are unpolished
- A
judge/validatorcall can't carry a per-call provider override the way anaskcall can — an ambiguousjudgecapability can only be disambiguated globally, via--provideron the CLI. .ulxprovider capability params can't express a boolean literal at all (the grammar has none); the manifest's TOMLparamstable can, since TOML has real booleans.ulx check's validation offrom/provider:references is best-effort — it depends on aulexite.tomlbeing discoverable next to the file. A cleanulx checkis not a guarantee that a laterulx runwon't fail on an unresolvable provider reference.- Every declared provider is constructed eagerly, whether or not the conversation you're actually running ever asks for it — a broken provider declaration can fail a run even if that specific conversation never references it.
The compiler front end has known gaps (pre-v0.1)
A pre-v0.1 review surfaced several compiler-quality issues worth knowing about:
- Deeply nested input (tens of thousands of nested parens) can exhaust the parser's stack and crash the process outright instead of producing a diagnostic.
- Text-block interpolation splitting isn't brace/string-aware, so a record or string literal containing
}inside an interpolation can produce a spurious parse error. ///doc comments are discarded identically to//— there's a grammar production for them, but nothing in the compiler recovers doc text yet.- Lexer errors report an imprecise span, and an overflowing integer literal is misreported as "unrecognized character" instead of a numeric-overflow diagnostic.
- Match-arm binding arity isn't validated statically — a missing or extra binding on a
Verdictarm type-checks cleanly and only fails at runtime. Verdictexhaustiveness checking matches the literal type name"Verdict", not a resolved type; general user-declared union exhaustiveness checking isn't implemented at all.retry's rule that a non-total body needs anelseis unenforced —retry(3) { ... }with noelsetype-checks silently instead of being rejected.
IDE support is early
ulx-lsp implements four LSP capabilities today: hover, go-to-definition, document symbols, and completion. It resyncs the whole document on every edit rather than doing incremental analysis, and it has no code lens, no artifact-content hover previews, no live-run attachment, no trace-viewer webview, and no lint warnings beyond hard compile errors. ulx doc and a REPL don't exist yet either. The VS Code extension does correctly launch ulx-lsp, so what's implemented is real and usable — it's just a small subset of where the tooling is eventually headed. See the roadmap for what's planned.
For the full design rationale, see §24 of the spec.