At All Things Open 2026, held in Raleigh, North Carolina, on October 20, 2026, University of Michigan professor Jason Mars presented Jac, a programming language that builds AI agents in as a language feature, along with its runtime platform, Jaseci. Jac uses Python-like syntax while integrating graph-based data management, large language model (LLM) calls and native compilation through LLVM. It aims to reduce the burden of combining different languages and development tools for different purposes, such as Python, JavaScript, TypeScript and C, and to make application development easier for both humans and AI agents.

As AI-agent-driven software development spreads, the complexity of existing toolchains has become a problem. Converting code between languages, connecting libraries and managing multiple build systems add development effort, and they also drive up the tokens and execution costs that AI agents consume. Jac is an attempt to absorb this work into the compiler and runtime so that an entire application can be written in a single language. However, a closer look at the official documentation shows that the available compilation targets differ by feature, and that memory management and concurrency are also constrained. Drawing on publicly available materials as of late October 2026 and on the conference presentation, this article outlines Jac's technical characteristics and the challenges of adopting it in practice.

AD

Three execution environments unify Python, JavaScript and native code

Jac's basic design aims at a superset of Python syntax while building AI-agent features into the language itself. In his All Things Open talk, Mars pointed out that in conventional multilingual environments, connecting different tools and libraries takes a great deal of work. To reduce that complexity, Jac lets code written in a single language be deployed to different execution environments depending on its purpose.

According to Jac's official materials, code is placed in one of three main "code spaces." Code with no special designation runs on the server side using the Python runtime. On the client side, such as in a browser, it uses the JavaScript and TypeScript ecosystem, and processing that demands computational performance is compiled to native machine code through LLVM. Each code space can also be specified explicitly, so the language itself manages where processing is placed.

Code space Execution environment Ecosystem used Main uses
Server Python runtime PyPI Backend processing, data management, running AI agents
Client JavaScript / TypeScript npm Browser-side processing, frontend, UI
Native Machine code via LLVM C ABI High-speed numerical computation, low-latency processing, system-level integration

One of Jac's defining features is the by llm() syntax, which hands a function's processing over to an LLM. In conventional applications, using an LLM often means preparing an API client and separately implementing prompt generation and response parsing. In Jac, a developer defines a function's name, parameters and return type, and can delegate its implementation to an LLM. This is not a mechanism by which the compiler autonomously generates code; it is a feature that delegates processing to a designated model while the program runs.

For example, a function that hands pull-request code review to an LLM can be written as follows.

plaintext
def review_pull_request(diff: str, context: str) -> list[str] by llm();

sem review_pull_request = "コード差分に潜むバグやセキュリティ上の問題を列挙する。";

Supporting this feature is byLLM, which is integrated into Jac. According to the official reference, byLLM assembles the prompt passed to the LLM from elements such as the function name, the parameter and return types, and the natural-language description supplied with sem. At runtime it calls a cloud LLM API or a local model depending on configuration, parses the response and verifies that it conforms to the specified return type. This reduces the need for developers to implement prompt handling from scratch, but it does not guarantee the accuracy of generated results or exception handling.

The claims that Jac is a "Python superset" with "full compatibility with the Python ecosystem" need to be evaluated separately for the code actually used and for each compilation target. In the code space that uses the Python runtime, the emphasis is on integration with existing libraries, but not every Python construct or complex C-extension library runs unmodified in every code space. On the native path that uses LLVM in particular, many of the features available on the Python side are restricted.

Graph-oriented programming and the limits of automatic persistence

Another pillar of Jac is Object-Spatial Programming (OSP). It is a programming model in which data is represented as a graph of nodes and edges, and computation moves across that graph. In conventional object orientation, it is common to call an object's methods or to retrieve data and then process it. OSP, by contrast, defines at the language level both the relationships between nodes and the processing that travels along those relationships.

The main elements of OSP are three: Node, Edge and Walker.

Element Role Difference from conventional programming models
Node A graph vertex that holds attributes and serves as the basic unit of data Similar to a class instance or database record, but with connections in the graph
Edge Represents a relationship between nodes and can carry direction and attributes Unlike foreign keys or object references, treats the relationship itself as an explicit graph element
Walker The agent that executes processing while moving through the graph Invokes the defined processing (abilities) according to the node it reaches

In OSP, where processing runs is tied to the structure of the data. In a conventional approach, for example, information retrieved from a database is converted into Python objects or JSON, and an agent reads and processes that content. In Jac, a walker can be sent into the graph to follow connections and run the processing defined on each node. In his talk, Mars claimed that reducing the work needed to extract and convert data can substantially cut AI agents' token consumption.

However, how much token or cost reduction a design that completes processing on the graph actually delivers depends on the specific workload. The public documentation does not show a generalizable reduction rate or a comprehensive cost comparison with conventional database setups. The comparison needs to cover the whole process, including the cost of building and maintaining the graph structure.

There are also clear rules for data persistence. Under Jac's basic design, graph elements reachable from the built-in root node are the ones that get persisted. Ordinary local variables and isolated objects with no connection to the graph are not saved unconditionally. Temporary data held while a walker runs and variables inside closures must likewise be distinguished from graph persistence.

Changing the structure of persisted data also requires care. Jac provides mechanisms for handling changes such as renaming fields in existing data through explicit rules, but features that automatically infer a migration method from the change, or static analysis that comprehensively detects destructive schema changes at build time, are not yet complete. When attribute names or types change, or when inconsistencies with existing data arise, developers must design the migration procedure. Reducing the amount of persistence code is a separate matter from automating data migration in operation.

Concurrency, too, does not necessarily make CPU-bound work faster through Jac's language features alone. According to the official concurrency reference, Jac offers async/await for handling I/O efficiently and flow/wait for running work on worker threads. The latter suits overlapping blocking I/O or native-extension work that releases the GIL. But because the GIL (Global Interpreter Lock) is enabled in the standard CPython runtime, splitting CPU-heavy computation across threads in pure Jac code does not make it run in parallel. The official documentation likewise recommends using separate processes for such work.

Sending multiple walkers down different paths is also not the same as parallel execution across CPU cores. For processing that traverses an entire graph to aggregate large amounts of data in bulk, the official tutorial explains that a general batch-processing engine may be more suitable. OSP is a model suited to processing that involves relationships and movement on a graph, but it does not replace all computation.

AD

LLVM native compilation and the reality of the ownership model

In addition to native compilation using LLVM, Jac adopts an ownership and borrowing mechanism similar to Rust's. A major difference from conventional scripting languages is that code written in Python-like syntax can be converted to machine code, and memory management that avoids reference counting and garbage collection (GC) can be chosen where needed. However, the mechanism has strict conditions, and simply compiling Jac code natively does not carry over every feature or memory-safety guarantee as is.

The official native compilation reference explains two approaches: compiling part of an ordinary Jac program as native code, and generating a standalone native binary. For example, only numerical computation where speed matters can be placed on the native side, while processing that handles AI agents and data persistence stays on the Python side. On the other hand, many of Jac's defining features, such as OSP and LLM integration, are currently unavailable in the native code space.

Main feature Server (Python) Native (LLVM)
Ordinary functions, control syntax, basic data types Supported Supported, with restrictions on some syntax
OSP (Node, Edge, Walker) Supported Not supported
Model calls via by llm() Supported Not supported
async/await Supported Not supported
Use of PyPI packages Supported Not supported
Embedding Python code Supported Not supported
Generating native machine code via LLVM Not applicable Supported

This difference matters when considering whether to adopt Jac. Even if AI-agent processing, graph data management and fast native computation can all be written in one language, they cannot all run in the same execution environment. In practice, there is a boundary between Python and native code, and processing has to be divided according to function.

Ownership and borrowing checks are opt-in

Jac's ownership model uses annotations such as own, imm, the shared reference & and the mutable reference &mut. These allow the compiler to check the owner of a value and the handling of references at compile time. However, ownership checking is introduced incrementally, and ordinary variables without annotations do not automatically fall under strict ownership management. Standard code is designed to use conventional GC management.

When running on the Python backend, ownership annotations are likewise used for compile-time checks, but that information is not retained at runtime. For this reason, adding ownership annotations to Jac does not give an entire body of Python code Rust-equivalent memory safety or execution performance. To get a native configuration that avoids reference counting, the required ownership information must be made explicit across the target code and pass the compile-time checks.

Specifically, native compilation is run with --gc none and --enforce-nogc, treating code that fails to meet the ownership conditions as an error. Using --assert-no-rc additionally lets you confirm that no reference-counting or GC-related processing remains in the generated LLVM IR. When these conditions are met, a binary can be produced that avoids reference counting and frees memory at statically determined points. In other words, zero-GC is not a standard property of native compilation; it holds depending on how the code is written and how the build is configured.

For native environments, the supported platforms listed are x86_64 and aarch64 on Linux, x86_64 and arm64 on macOS, and x86_64 on Windows. The C ABI can be used to integrate with external libraries, but not every C++ library can be called directly as is. When passing complex types or objects, ABI compatibility and data representation must be checked. Jac's goal of unifying the development environment in a single language should be distinguished from the idea that the constraints of the different execution platforms disappear entirely.

Source-code "eject" is not a cure-all either

The talk also introduced an "Eject" feature that extracts an application written in Jac as source code for existing development environments. It was previously called jac eject, but in the current CLI reference it has been merged into jac build --as source. This command generates an editable source tree of FastAPI and JavaScript from a Jac project. It is an option for handling code independently of Jac's toolchain or for building it into an enterprise's development and audit procedures.

However, this feature is not a general-purpose decompiler that completely and reversibly converts arbitrary Jac code into Python, TypeScript or C. The output assumes a specific application structure, and it does not guarantee that every language feature can be converted back and forth with its original meaning intact. How well the output code can be understood and edited, and whether it can continue to be maintained without Jac, must be verified project by project.

Mapping the specifications so far to points to check during practical adoption gives the following.

Item Main Jac features Constraints and unverified points Points to check when adopting
Core syntax and LLM integration Python-like syntax, delegating processing via by llm() Full compatibility with Python and the accuracy of model responses are not guaranteed Compatibility of existing code, type validation of responses, error handling, inference cost
Data persistence OSP, persistence based on reachability from root Isolated objects are not saved automatically, and schema changes are also constrained Data migration, disaster recovery, graph traversal performance
Concurrency and parallelism async/await, flow/wait CPython's GIL limits CPU-parallel execution of pure Jac code Process isolation, contention under concurrent execution, effect of thread use
Native compilation Machine code generation via LLVM, C ABI integration OSP, LLM integration, async processing, PyPI use and more are unsupported Boundary with the Python side, data conversion, call overhead
Memory management Ownership and borrowing checks, zero-GC configuration Checking is introduced incrementally, and true zero-GC requires additional conditions Burden of ownership annotations, build constraints, verification of generated code
Source code output jac build --as source Output targets a specific stack and is not a general reversible conversion Readability and editability of generated code, long-term maintenance

What did the LangGraph comparison at All Things Open show?

To demonstrate Jac's effectiveness, Mars presented a comparison with existing AI-agent development frameworks in his All Things Open talk. According to the conference's official session description, the three subjects of comparison were Jac, LangGraph and CrewAI, and the session involved building a replica of the AI code-review tool Greptile using the same specification and the same LLM. The aim was to compare the amount of code required, the code for connecting infrastructure, and design trade-offs. This was a demonstration of differences in development approaches, not a peer-reviewed, comprehensive performance benchmark.

According to The Register's October 23 report, Mars had AI agents use each framework to generate an application. With Jac, a multi-stage configuration that called several tools in parallel was reportedly generated, whereas with LangGraph a simple linear state machine with no parallelism was generated. Mars presented this difference as an example of how the level of tools and abstractions given to the AI affects the quality of what it generates.

However, it cannot be concluded from this demo that LangGraph itself is less capable than Jac at concurrency. The LangGraph official documentation explicitly describes mechanisms such as parallel execution of multiple nodes, fan-out and fan-in, and reducers that merge state updates. Settings to control maximum concurrency are also provided, so the framework does not lack the ability to run processing in parallel.

The difference this time may reflect how the AI agent understood and used each development environment. For example, the generated architecture can change depending on the content of the prompt, the materials given to the agent, the library versions used and the tools made available. Without public details on the specific instructions, the number of attempts, retry conditions and model settings, it is also impossible to judge whether the same result could be reproduced. At the very least, it is not appropriate to generalize the relative merits of frameworks from a single generated example.

A fair comparison would need to evaluate not only the line count and structure of the generated code but also execution performance and operational aspects. Specifically, processing latency, the tokens and costs consumed by the LLM, error rates, recovery from failures and response to specification changes would all be subjects of comparison. Even if Jac can express complex processing with less code, that does not always translate into low-cost, stable operation.

At the start of his talk, Mars also mentioned an episode in which Bun developer Jarred Sumner reportedly consumed $165,000 worth of Anthropic tokens to port code written in Zig to Rust. It was offered as a case illustrating how costly code conversion between languages can be. However, it does not verify how much that cost could have been reduced by using Jac, and it is not data that directly supports Jac's performance or economic advantage.

AD

Jac's maturity and the challenges of practical adoption

When adopting Jac in a production environment, the maturity of the toolchain and long-term maintainability matter as much as the language features. The Jaseci open-source project is released under the MIT license, and the Jaseci repository on GitHub was created on October 29, 2021. It is not a technology that appeared for the first time at this conference; research and implementation have been under way for several years. Development of the language specification and runtime has continued since, and the distribution method has changed as well.

A major turning point was the distribution as a self-contained native binary, introduced in Jac v0.30.0. With the current installation method, the required runtime is embedded in the jac binary, so there is no need to separately prepare Python itself, pip, uv and the like on the host environment. This simplifies setting up development environments and installing tools, but the Jac version used by an application needs to be managed properly.

Of particular note is that the official configuration reference states explicitly that it does not guarantee backward compatibility between versions of the Jac toolchain. Because breaking changes may be introduced in newer versions, the developers recommend explicitly specifying the target toolchain in the project configuration file jac.toml. jac-version accepts PEP 440-style version specifiers and can be pinned to a specific version, for example ==0.34.3. Official materials include examples using versions such as 0.34.1 and 0.34.3, but in actual adoption the release used should be pinned and behavior verified in that environment.

The lack of a backward-compatibility guarantee is not a trivial issue for systems under continuous development. Even as new language features and compiler improvements arrive, if an update stops existing code from working, migration and testing costs follow. In Jac in particular, multiple features operate as a whole: not only the language specification but also integration with Python and JavaScript, native compilation and data persistence. When updating, one must check that no compatibility problems arise at these boundaries.

On cost, too, Jac being open source is a separate matter from being able to run applications for free. Calling a cloud LLM with by llm() incurs pay-as-you-go API charges, and using a local model brings GPU and hosting costs. Maintenance effort from data schema changes, integration across different code spaces and toolchain updates must also be considered. Even if Jac reduces the amount of code written, operating costs do not necessarily fall in proportion.

Given these constraints, it is realistic to start adopting Jac with a small proof of concept (PoC). Rather than using every code space and OSP feature from the outset, one can limit the scope to server-side processing and the minimum necessary native processing, and compare it with the existing development environment. The four items worth checking in particular are as follows.

  1. Reliability of LLM integration: the rate at which by llm() return values conform to the type definition, handling of incorrect responses, the rate of recovery from errors, and inference cost.
  2. Maintainability of data persistence: migration work when attributes or types change, handling of breaking changes, and procedures for restoring existing data.
  3. Interoperability with existing libraries: connections to Python and C libraries, data passing between code spaces, and the overhead of calls.
  4. Toolchain stability: the frequency of build errors or behavior changes from version updates, the fixes required, and compatibility with existing code.

What distinguishes Jac is that it tries to treat AI-agent features not as libraries added on top of an existing language but as part of the language specification and runtime. The idea of managing LLM calls, graph structures, data persistence and multiple execution environments in a single language suggests one direction for rethinking increasingly complicated development environments. At present, however, there are still many issues to evaluate carefully, including feature restrictions on the native path, the parallel-processing limits imposed by the GIL and compatibility between versions. Whether Jac takes hold as a practical option will be decided not by the conciseness of its syntax or the impression left by demos, but by how far it can actually reduce development and operating costs in real applications.