Breaking Changes#
This page documents significant breaking changes in Jac and Jaseci that may affect your existing code or workflows. Use this information to plan and execute updates to your applications.
Note
MTLLM library is now deprecated and replaced by the byLLM package. In all places where mtllm was used before, it can be replaced with byllm.
jac-scale 0.2.15#
1. Identity-Based Authentication System#
The flat username / password user model has been replaced with a flexible identity + credential architecture. A user can now register multiple identities (e.g. username, email) and authenticate with any of them. SSO accounts are stored as type: sso identities inside the user document instead of a separate sso_accounts collection.
Impact: The /user/register and /user/login request payloads have changed shape. JWT tokens now carry a user_id (UUID) claim instead of username. Any client, test, or integration that constructs these requests, inspects JWT claims, or reads the sso_accounts collection must be updated.
Register / Login Payloads#
Before:
After:
POST /user/register
Content-Type: application/json
{
"identities": [
{ "type": "username", "value": "alice" },
{ "type": "email", "value": "alice@example.com" }
],
"credential": { "type": "password", "password": "secret" }
}
POST /user/login
Content-Type: application/json
{
"identity": { "type": "username", "value": "alice" },
"credential": { "type": "password", "password": "secret" }
}
- At least one identity is required at registration; additional identities can be added later.
- Login accepts any identity the user has registered (
usernameoremail); the server resolves it to the same account. identity.valueandcredential.passwordenforcemin_length=1; empty strings are rejected withVALIDATION_ERROR.
JWT user_id Claim#
JWT tokens previously encoded username as the subject. They now encode user_id (a UUID that is stable across identity changes).
Before:
After:
Migration:
- Any middleware or downstream service that reads
usernamefrom the decoded JWT must readuser_idinstead and resolve it to a user record via the user manager if the username is still required. - Existing tokens issued before the upgrade are no longer valid; users must log in again to receive a new token.
Password Hashing Switched to bcrypt#
Stored password hashes are now produced with bcrypt (previously raw hashlib). Legacy users are progressively rehashed on their next successful login, so no manual migration is required.
SSO Accounts Unified Into Identities#
SSO linkages previously lived in a dedicated sso_accounts collection keyed by username. They are now stored as identities on the user document, keyed by user_id:
{
"user_id": "8f2d…",
"identities": [
{ "type": "username", "value": "alice" },
{ "type": "sso", "provider": "google", "external_id": "1098…" }
]
}
Migration: A built-in legacy user migration runs at startup to convert pre-existing flat username/password records into the identity-based shape. Case-colliding legacy accounts are kept for the first insertion and marked disabled for the rest; review disabled accounts after the upgrade.
Update Password Request Shape#
PUT /user/password now requires a typed UpdatePasswordRequest body with both fields non-empty:
Before:
After:
jac-scale 0.2.14#
1. Heavy Dependencies Moved to Optional Install Groups#
pip install jac-scale no longer installs pymongo, redis, prometheus-client, apscheduler, kubernetes, or docker. These are now optional extras.
Impact: Existing installations that rely on any of these packages must update their install command.
Before:
After:
Or install only what you need:
pip install jac-scale[data] # pymongo + redis
pip install jac-scale[monitoring] # prometheus-client
pip install jac-scale[scheduler] # apscheduler
pip install jac-scale[deploy] # kubernetes + docker
No code changes are required - the same APIs, configuration, and behavior apply. When a feature is used without its dependency installed, a clear error message shows the exact install command needed.
Version 0.14.2#
1. Strict any Semantics in .jac Modules#
.jac source no longer treats any as bidirectionally compatible with concrete types. A value of type any cannot be silently assigned to a destination with a declared non-any, non-object type. The check fires at every site where the destination has a declared type:
- annotated assignment (
x: T = src;) has-var initializer (has x: T = src;)- function argument (
f(src)against a declaredparam: T) - return statement (
return srcfromdef f -> T) - yield expression in a typed generator
- edge-connection assignment (
a ++>:Edge:val=src)
The check recurses element-wise into containers, so list[any] -> list[Task] is rejected the same way any -> Task is.
Permissive cases that still work without ceremony:
- Inferred locals:
x = py_call();keepsx: any(no annotation, no error). - Explicit
anyannotation:x: any = py_call();opts in to permissive flow. any -> objectandany -> TypeVar: needed forprint(x)and generic-bound calls.
.py and .pyi files keep PEP 484 semantics -- Any propagates freely inside Python modules. The strict rule only fires at the .jac consumption site.
Impact: Code that flowed any into typed locals through a typed annotation now produces a type error. The most common trigger is the default Walker.reports: list[Any] channel -- tasks: list[Task] = result.reports; now errors.
Before:
node Task { has title: str; }
walker ListTasks {
can collect with Root entry {
report [-->][?:Task];
}
}
with entry {
result = root spawn ListTasks();
tasks: list[Task] = result.reports[0]; # silently widened pre-0.14.2,
# now: Cannot assign list[any] to list[Task]
}
After (preferred): type the source. Declare has reports: list[T] on the walker so the report channel is typed end-to-end:
walker ListTasks {
has reports: list[list[Task]]; # typed at the source
can collect with Root entry {
report [-->][?:Task];
}
}
with entry {
result = root spawn ListTasks();
tasks: list[Task] = result.reports[0]; # type-safe
}
For Python utilities, ship a .pyi stub alongside the module so the imported names arrive typed at the boundary.
After (escape valve): accept any explicitly. Keep the source untyped and annotate the receiving local as any, then narrow before flowing into typed destinations:
with entry {
result = root spawn ListTasks();
raw: any = result.reports[0];
if isinstance(raw, list) {
tasks: list[Task] = raw; # narrowed -- no error
}
}
Migration: For each strict-any error, choose one of three responses:
- Type the source -- add
has reports: list[T](walkers), a-> Tannotation (functions), or a.pyistub (Python utilities). Preferred for stable APIs. - Drop the annotation --
x = src();makesxinferred-anyand no check fires. - Annotate
anyexplicitly --x: any = src();documents the boundary.
See The any Type and Gradual Typing for the full rule and Walker Response Patterns for typing the walker reports channel.
Version 0.13.6#
1. cl { } / sv { } / na { } Module-Level Braced Blocks Deprecated#
Module-level braced codespace blocks (cl { ... }, sv { ... }, na { ... }) are now discouraged in favor of to cl: / to sv: / to na: section headers. A section header applies until the next header or end of file, which flattens cl/sv/na-heavy modules and eliminates the wrapping brace pair. The single-statement prefix form (cl stmt, sv stmt, na stmt) is unchanged.
The braced block form still parses and compiles, but emits deprecation warning W0064 when used at module scope. Inner-scope uses (inside a function or class body) do not emit W0064 and remain the recommended way to locally override the active codespace.
Impact: Existing module-level cl { ... } / sv { ... } / na { ... } blocks keep working but will produce W0064 diagnostics. Migrate to the section-header form to silence the warning and reduce indentation.
Before:
cl {
def:pub Greeting(props: dict) -> JsxElement {
return <h1>Hello, {props.name}!</h1>;
}
def:pub Counter() -> JsxElement {
has count: int = 0;
return <button onClick={lambda -> None { count = count + 1; }}>{count}</button>;
}
}
After:
to cl:
def:pub Greeting(props: dict) -> JsxElement {
return <h1>Hello, {props.name}!</h1>;
}
def:pub Counter() -> JsxElement {
has count: int = 0;
return <button onClick={lambda -> None { count = count + 1; }}>{count}</button>;
}
jac format automatically rewrites module-level braced blocks to section headers.
Version 0.12.4#
1. root Is a Reserved Keyword Again (SpecialVarRef)#
root is again a reserved keyword (KW_ROOT) and parses as a SpecialVarRef, mirroring how here and visitor are bound. The type checker resolves it directly to Root, the binder rejects local rebinding, and codegen lowers it to Jac.root(). This reverses the brief window in 0.12.3 where root was an ambient builtin resolved through jac_builtins.pyi.
Impact: Bare root is the canonical form in .jac source and continues to work as before in walkers, graph operations, and edge expressions. However:
- Backtick escaping is required to shadow it. Use
`rootto declare a parameter, field, or local namedroot. root()is deprecated in.jacsource. Barerootis canonical; the compiler emits W0062 when it seesroot()in a.jacfile and lowers it to the sameJac.root()call so existing code keeps working.- AST introspection sees
SpecialVarRefwithKW_ROOTagain. Code that special-cased the post-0.12.3Nameshape needs to update. - Bytecode cache must be cleared. The AST shape for
rootchanges fromNametoSpecialVarRef. Runrm -rf ~/.cache/jac/bytecode/ .jac/cache/(or your project's configured cache dir) after upgrading.
.jac source vs library mode
The deprecation applies to .jac source only. In library mode (Python files using from jaclang.lib import root, connect, spawn, ...), root is a Python function and must be called as root() -- it is not a keyword in that context. See Library Mode for the full Python-side surface.
Before (0.12.3 .jac source):
# root was an ambient builtin; backtick escaping not needed
has root: str = "default";
with entry {
r = root(); # ambient-builtin call, valid in 0.12.3
root() ++> Item(); # valid in 0.12.3
}
After (.jac source):
node Item { has name: str = ""; }
# root is a keyword again; backtick to shadow as a field
obj Settings {
has `root: str = "default";
}
with entry {
r = root; # bare reference, canonical
root ++> Item(); # works, no warning
r2 = root(); # still works but emits W0062
}
In library mode (Python):
from jaclang.lib import root, connect
# root() is the canonical call form in Python
connect(left=root(), right=Item())
Version 0.12.2#
1. Filter Comprehension Syntax Changed from (?:...) to [?:...]#
The parenthesized filter comprehension syntax (?:Type) and (?:Type, condition) is now deprecated in favor of bracket syntax [?:Type] and [?:Type, condition]. The old syntax still parses but emits deprecation warning W0061. The formatter (jac format) automatically converts old syntax to new.
Before:
# Standalone filter
my_list(?:Foo, val < 3);
# After edge traversal
visit [-->](?:MyNode);
# Inside edge ref chain
visit [-->(?:MyNode)];
After:
# Standalone filter
my_list[?:Foo, val < 3];
# After edge traversal
visit [-->][?:MyNode];
# Inside edge ref chain
visit [-->[?:MyNode]];
Why: The [? token sequence is unambiguous in all contexts, including nested inside edge ref chain brackets. Bracket syntax is consistent with how edge references already use [...].
Migration: Run jac format on your .jac files to auto-convert, or manually replace (?: with [?: and (? with [? (adjusting closing ) to ]).
Version 0.11.1 / byllm 0.5.1#
1. LiteLLM Minimum Version Raised to 1.81.15#
The litellm dependency for byllm has been bumped from >=1.75.5.post1,<1.80.0 to >=1.81.15,<1.83.0.
Impact: If your environment has other packages that pin litellm below 1.81.15, dependency resolution will fail.
Migration: Update litellm in your project or environment:
Version 0.10.3#
1. Test Syntax Changed from Identifiers to String Descriptions#
The test keyword now requires a string description instead of an identifier name. This gives tests more readable, natural-language names with spaces, punctuation, and proper casing.
Before:
test my_calculator_add {
calc = Calculator();
assert calc.add(5) == 5;
}
test walker_visits_all_nodes {
root spawn MyWalker();
assert visited_count == 3;
}
After:
test "my calculator add" {
calc = Calculator();
assert calc.add(5) == 5;
}
test "walker visits all nodes" {
root spawn MyWalker();
assert visited_count == 3;
}
Key Changes:
- Test names must be quoted strings:
test "description" { ... }instead oftest name { ... } - Spaces, punctuation, and mixed case are now allowed in test names
- The string description is displayed as-is in test output (pytest,
jac test) - A valid Python identifier is derived automatically for internal use (lowercased, non-alphanumeric replaced with
_)
Migration: Replace test identifier_name { with test "identifier name" { in all .jac files (convert underscores to spaces).
Version 0.10.2#
1. CLI Dependency Commands Redesigned#
The jac add, jac install, jac remove, and jac update commands were redesigned. Key behavioral changes:
jac addnow requires at least one package argument (previously, callingjac addwith no args silently fell through to install)jac addwithout a version spec now queries the installed version and records~=X.Y(previously recorded>=0.0.0)jac installnow syncs all dependency types (pip, git, and plugin-provided like npm)- New
jac updatecommand for updating dependencies to latest compatible versions - Virtual environment is now at
.jac/venv/instead of.jac/packages/
Version 0.10.0#
1. KWESC_NAME Syntax Changed from <> to Backtick#
Keyword-escaped names now use a backtick (`) prefix instead of the angle-bracket (<>) prefix. This affects any identifier that uses a Jac keyword as a variable, field, or parameter name.
Before:
glob <>node = 10;
glob <>walker = 30;
obj Foo {
has <>type: str = "default";
}
myobj = otherobj.<>walker.<>type;
After:
glob `node = 10;
glob `walker = 30;
obj Foo {
has `type: str = "default";
}
myobj = otherobj.`walker.`type;
Note: Builtin type names (any, list, dict, set, tuple, type, bytes, int, float, str, bool) do not need backtick escaping when used in expression contexts (function calls, type annotations, isinstance arguments). Backtick is only needed when using them as field, variable, or parameter names:
# No backtick needed (expression context)
x = list(items);
y: tuple[(int, int)] = (1, 2);
if isinstance(val, dict) { ... }
# Backtick needed (identifier context)
has `type: str = "default";
`bytes = read_data();
Migration: Find and replace all <> keyword escape prefixes with ` in your .jac files.
2. Backtick Type Operator Removed#
The backtick (`) type operator (TYPE_OP) and TypeRef AST node have been removed from the language. This affects two areas: walker event signatures and filter comprehensions.
Walker Entry/Exit Signatures#
The `root syntax for referencing the Root type is replaced with Root (capital R).
Before:
walker MyWalker {
can start with `root entry {
visit [-->];
}
can finish with `root exit {
print("done");
}
}
After:
walker MyWalker {
can start with Root entry {
visit [-->];
}
can finish with Root exit {
print("done");
}
}
Union types also use Root:
# Before: can start with `root | MyNode entry {
# After:
can start with Root | MyNode entry {
visit [-->];
}
Filter Comprehensions#
The typed filter syntax changes from (`?Type) and (`?Type:condition) to [?:Type] and [?:Type, condition].
Before:
# Type-only filter
visit [-->](`?MyNode);
# Typed filter with comparison
visit [-->](`?Year:year==2025);
After:
# Type-only filter
visit [-->][?:MyNode];
# Typed filter with comparison
visit [-->][?:Year, year==2025];
Parenthesized syntax (?:Type) is deprecated
The intermediate parenthesized syntax (?:Type) and (?:Type, condition) was introduced in v0.10.0 but has been replaced by the bracket syntax [?:Type] and [?:Type, condition] for consistency with edge reference brackets. If your code uses the (?:...) form, migrate to [?:...].
Migration Steps:
- Replace all
`rootwithRootin walkerentry/exitdeclarations - Replace
(`?Type)with[?:Type]in filter comprehensions - Replace
(`?Type:condition)with[?:Type, condition]-- note the comma separator instead of colon - Replace any
(?:Type)with[?:Type]and(?:Type, condition)with[?:Type, condition] - The
rootkeyword (lowercase, no backtick) for the root instance is unchanged --root spawn,root ++>, etc. remain the same
Version 0.9.13 / jac-client 0.2.13#
1. BrowserRouter Migration#
Client-side routing has migrated from HashRouter to BrowserRouter. URLs now use clean paths instead of hash-based URLs.
Before:
After:
Key Changes:
HashRouterreplaced withBrowserRouterin the React Router integrationnavigate()now useswindow.history.pushStateinstead ofwindow.location.hash- The vanilla runtime's
__jacGetHashPathrenamed to__jacGetPath, returnswindow.location.pathnameinstead of hash fragment - Server-side SPA catch-all automatically serves app HTML for clean URL paths when
base_route_appis configured
Migration Steps:
- Update any hardcoded hash-based URLs (
#/path) to clean paths (/path) in your code - If using the vanilla runtime's
Linkcomponent,hrefvalues no longer need a#prefix - Ensure
base_route_appis set injac.toml[serve]section for direct navigation and page refresh to work - If deploying as a static site, configure your hosting provider's SPA fallback
Version 0.9.9#
1. --cl Flag Replaced with --npm and --use client#
The --cl flag has been removed from jac-client CLI commands and replaced with more descriptive alternatives.
Before:
# Create a client project
jac create myapp --cl
# Add npm dependencies
jac add tailwind --cl
jac add typescript --cl --dev
# Remove npm dependencies
jac remove lodash --cl
After:
# Create a client project (use --use client instead of --cl)
jac create myapp --use client
# Add npm dependencies (use --npm instead of --cl)
jac add tailwind --npm
jac add typescript --npm --dev
# Remove npm dependencies (use --npm instead of --cl)
jac remove lodash --npm
Key Changes:
jac create --cl→jac create --use clientjac add --cl→jac add --npmjac remove --cl→jac remove --npm- The
--skipflag remains available forjac create --use client --skipto skip npm package installation
2. .cl.jac Files No Longer Auto-Imported as Annexes#
Client module files (.cl.jac) are now treated as standalone modules only. Previously, .cl.jac files were automatically annexed to their corresponding .jac files (similar to .impl.jac files). This dual behavior has been removed to simplify the module system.
Before:
# main.jac - automatically included main.cl.jac content
node Todo { has title: str; }
walker AddTodo { has title: str; }
# main.cl.jac - auto-annexed to main.jac (no explicit import needed)
cl {
def:pub app -> JsxElement {
return <div>Hello World</div>;
}
}
After:
# main.jac - must explicitly import client code
node Todo { has title: str; }
walker AddTodo { has title: str; }
# Explicit client block with import
cl {
import from .frontend { app as ClientApp }
def:pub app -> JsxElement {
return <ClientApp />;
}
}
# frontend.cl.jac - standalone client module (renamed from main.cl.jac)
def:pub app -> JsxElement {
return <div>Hello World</div>;
}
Key Changes:
.cl.jacfiles are no longer automatically annexed to matching.jacfiles- Client code must be explicitly imported using
cl importor imported inside acl {}block - The main entry point must re-export the client app through a
cl {}block to trigger client compilation - Use uppercase aliases when importing components (e.g.,
app as ClientApp) so JSX compiles to component references instead of strings
Migration Steps:
- Rename your
main.cl.jacto a descriptive name likefrontend.cl.jacorapp.cl.jac - Add a
cl {}block in yourmain.jacthat imports and re-exports the client app:
cl {
import from .frontend { app as ClientApp }
def:pub app -> JsxElement {
return <ClientApp />;
}
}
- If your
.cl.jacfile references walkers defined inmain.jac, add walker stub declarations in the client file:
# frontend.cl.jac
walker AddTodo { has title: str; } # Stub for RPC calls
walker ListTodos {}
def:pub app -> JsxElement { ... }
Note: .cl.jac files can still have their own .impl.jac annexes for separating declarations from implementations.
Version 0.9.8#
1. Walker Traversal Semantics Changed to Recursive DFS with Deferred Exits#
Walker traversal now uses recursive depth-first semantics where entry abilities execute when entering a node, and exit abilities execute after all descendants are visited (post-order). Previously, both entry and exit abilities executed on each node before moving to the next.
Before (v0.9.7 and earlier):
For a graph root → A → B → C, the execution order was:
Each node's entries AND exits completed before visiting the next node.
After (v0.9.8+):
Entries execute depth-first, exits execute in reverse order (LIFO/stack unwinding).
Example with sibling nodes:
# Graph: root → a, root → b, root → c (three children)
# Before: a entries, a exits, b entries, b exits, c entries, c exits
# After: a entries, b entries, c entries, c exits, b exits, a exits
Key Behavioral Changes:
- Exit abilities are deferred until all descendants of a node are visited
- If
disengageis called during entry/child traversal, exit abilities for ancestor nodes will NOT execute - Exit order is LIFO (last visited node's exits run first)
walker.pathis now populated during traversal, tracking visited nodes in order
Migration Steps:
- Review any code that relies on exit abilities running before visiting child nodes
- If your walker uses
disengageand depends on ancestor exit abilities running, refactor to use entry abilities or remove the disengage - Update tests that assert specific entry/exit execution order
Example migration for disengage pattern:
# Before: Exit ability would run before disengage stops traversal
walker MyWalker {
can process with MyNode entry {
if some_condition { disengage; }
visit [-->];
}
can cleanup with Root exit {
# This WOULD run before disengage in old semantics
print("Cleanup");
}
}
# After: Use entry ability instead, since exits won't run after disengage
walker MyWalker {
can process with MyNode entry {
if some_condition { disengage; }
visit [-->];
}
can cleanup with Root entry {
# Use entry to ensure this runs before any disengage
print("Cleanup will run");
}
}
Version 0.9.5#
1. jac serve Renamed to jac start, jac scale Now Uses --scale Flag#
The jac serve command has been renamed to jac start for better clarity. Additionally, the jac scale command (from jac-scale plugin) is now accessed via jac start --scale instead of a separate command.
Before (v0.9.4 and earlier):
# Start local server
jac serve main.jac
# Deploy to Kubernetes (jac-scale plugin)
jac scale main.jac
jac scale main.jac -b # with build
After (v0.9.5+):
# Start local server
jac start main.jac
# Deploy to Kubernetes (jac-scale plugin)
jac start main.jac --scale
jac start main.jac --scale --build # with build
Migration Steps:
- Replace all
jac servecommands withjac start - Replace
jac scalecommands withjac start --scale - Replace
jac scale -bwithjac start --scale --build - Update any CI/CD scripts or documentation that reference these commands
Key Changes:
jac serve→jac startjac scale→jac start --scalejac scale -b→jac start --scale --build(orjac start --scale -b)- The
jac destroycommand remains unchanged for removing Kubernetes deployments
2. Build Artifacts Consolidated to .jac/ Directory#
All Jac project build artifacts are now organized under a single .jac/ directory instead of being scattered across the project root. This is a breaking change for existing projects.
Before (v0.9.4 and earlier):
my-project/
├── jac.toml
├── main.jac
├── .jaccache/ # Bytecode cache
├── packages/ # Python packages
├── .client-build/ # Client build artifacts (jac-client)
├── .jac-client.configs/ # Client config files (jac-client)
└── anchor_store.db.* # ShelfDB files (jac-scale)
After (v0.9.5+):
my-project/
├── jac.toml
├── main.jac
└── .jac/ # All build artifacts
├── cache/ # Bytecode cache
├── packages/ # Python packages
├── client/ # Client build artifacts
│ ├── configs/ # Generated config files
│ ├── build/ # Build output
│ └── dist/ # Distribution files
└── data/ # Runtime data (ShelfDB)
Migration Steps:
- Delete old artifact directories from your project root:
- Update
.gitignore(simplified):
- If using custom
shelf_db_pathin jac-scale config, update the path:
- Optionally configure a custom base directory in
jac.toml:
Key Changes:
- Bytecode cache moved from
.jaccache/to.jac/cache/ - Python packages moved from
packages/to.jac/packages/ - Client build artifacts moved from
.client-build/to.jac/client/ - Client configs moved from
.jac-client.configs/to.jac/client/configs/ - ShelfDB files moved to
.jac/data/ - New
[build].dirconfig option allows customizing the base directory
Version 0.9.4#
1. let Keyword Removed - Use Direct Assignment#
The let keyword has been removed from Jaclang. Variable declarations now use direct assignment syntax, aligning with Python's approach to variable binding.
Before:
After:
Key Changes:
- Remove the
letkeyword from all variable declarations - Use direct assignment (
x = value) instead oflet x = value - This applies to all contexts including destructuring assignments
Note for client-side code: In
cl {}blocks and.cl.jacfiles, prefer usinghasfor reactive state (see v0.9.5 reactive state feature) instead of explicituseStatedestructuring.
Version 0.8.10#
1. byLLM Imports Moved to byllm.lib#
All byLLM exports have been moved under the byllm.lib module to enable lazy loading and faster startup. Direct imports from byllm are removed.
Before:
After:
Version 0.8.8#
1. check Keyword Removed - Use assert in Test Blocks#
The check keyword has been removed from Jaclang. All testing functionality is now unified under assert statements, which behave differently depending on context: raising exceptions in regular code and reporting test failures within test blocks.
Before:
glob a: int = 5;
glob b: int = 2;
test "equality" {
check a == 5;
check b == 2;
}
test "comparison" {
check a > b;
check a - b == 3;
}
test "membership" {
check "a" in "abc";
check "d" not in "abc";
}
test "function result" {
check almostEqual(a + b, 7);
}
After:
glob a: int = 5;
glob b: int = 2;
test "equality" {
assert a == 5;
assert b == 2;
}
test "comparison" {
assert a > b;
assert a - b == 3;
}
test "membership" {
assert "a" in "abc";
assert "d" not in "abc";
}
test "function result" {
assert almostEqual(a + b, 7);
}
Key Changes:
- Replace all
checkstatements withassertstatements in test blocks assertstatements in test blocks report test failures without raising exceptionsassertstatements outside test blocks continue to raiseAssertionErroras before- Optional error messages can be added:
assert condition, "Error message";
This change unifies the testing and validation syntax, making the language more consistent while maintaining all testing capabilities.
Version 0.8.4#
1. Global, Nonlocal Operators Updated to global, nonlocal#
This renaming aims to make the operator's purpose align with python, as global, nonlocal more aligned with python.
Before:
glob x = "Jaclang ";
def outer_func -> None {
:global: x; # :g: also correct
x = 'Jaclang is ';
y = 'Awesome';
def inner_func -> tuple[str, str] {
:nonlocal: y; #:nl: also correct
y = "Fantastic";
return (x, y);
}
print(x, y);
print(inner_func());
}
with entry {
outer_func();
}
After:
glob x = "Jaclang ";
def outer_func -> None {
global x;
x = 'Jaclang is ';
y = 'Awesome';
def inner_func -> tuple[str, str] {
nonlocal y;
y = "Fantastic";
return (x, y);
}
print(x, y);
print(inner_func());
}
with entry {
outer_func();
}
2. mtllm.llms Module Replaced with Unified mtllm.llm {Model}#
The mtllm library now uses a single unified Model class under the mtllm.llm module, instead of separate classes like Gemini and OpenAI. This simplifies usage and aligns model loading with HuggingFace-style naming conventions.
Before:
import from mtllm.llms { Gemini, OpenAI }
glob llm1 = Gemini(model_name="gemini-2.0-flash");
glob llm2 = OpenAI();
After:
import from mtllm.llm { Model }
glob llm1 = Model(model_name="gemini/gemini-2.0-flash");
glob llm2 = Model(model_name="gpt-4o");
Version 0.8.1#
1. dotgen Builtin Function Renamed to printgraph#
This renaming aims to make the function's purpose clearer, as printgraph more accurately reflects its action of outputting graph data, similar to how it can also output in JSON format. Also other formats may be added (like mermaid).
Before:
node N {has val: int;}
edge E {has val: int = 0;}
with entry {
end = root;
for i in range(0, 2) {
end +>: E : val=i :+> (end := [ N(val=i) for i in range(0, 2) ]);
}
data = dotgen(node=root);
print(data);
}
After:
node N {has val: int;}
edge E {has val: int = 0;}
with entry {
end = root;
for i in range(0, 2) {
end +>: E : val=i :+> (end := [ N(val=i) for i in range(0, 2) ]);
}
data = printgraph(node=root);
print(data);
}
2. ignore Feature Removed#
This removal aims to avoid being over specific with object-spatial features.
Before:
node MyNode {
has val:int;
}
walker MyWalker {
can func1 with MyNode entry {
ignore [here];
visit [-->]; # before
print(here);
}
}
with entry {
n1 = MyNode(5);
n1 ++> MyNode(10) ++> MyNode(15) ++> n1; # will result circular
n1 spawn MyWalker();
}
After:
node MyNode {
has val:int;
}
walker MyWalker {
has Ignore: list = [];
can func1 with MyNode entry {
self.Ignore.append(here); # comment here to check the circular graph
visit [i for i in [-->] if i not in self.Ignore]; # now
print(here);
}
}
with entry {
n1 = MyNode(5);
n1 ++> MyNode(10) ++> MyNode(15) ++> n1; # will result circular
n1 spawn MyWalker();
}
Version 0.8.0#
1. impl Keyword Introduced to Simplify Implementation#
The new impl keyword provides a simpler and more explicit way to implement abilities and methods for objects, nodes, edges, and other types. This replaces the previous more complex colon-based syntax for implementation.
Before (v0.7.x):
:obj:Circle:def:area -> float {
return math.pi * self.radius * self.radius;
}
:node:Person:can:greet with Room entry {
print("Hello, I am " + self.name);
}
:def:calculate_distance(x: float, y: float) -> float {
return math.sqrt(x*x + y*y);
}
After (v0.8.0+):
impl Circle.area -> float {
return math.pi * self.radius * self.radius;
}
impl Person.greet with Room entry {
return "Hello, I am " + self.name;
}
impl calculate_distance(x: float, y: float) -> float {
return math.sqrt(x*x + y*y);
}
This change makes the implementation syntax more readable, eliminates ambiguity, and better aligns with object-oriented programming conventions by using the familiar dot notation to indicate which type a method belongs to.
2. Inheritance Base Classes Specification Syntax Changed#
The syntax for specifying inheritance has been updated from using colons to using parentheses, which better aligns with common object-oriented programming languages.
Before (v0.7.x):
obj Vehicle {
has wheels: int;
}
obj Car :Vehicle: {
has doors: int = 4;
}
node BaseUser {
has username: str;
}
node AdminUser :BaseUser: {
has is_admin: bool = true;
}
After (v0.8.0+):
obj Vehicle {
has wheels: int;
}
obj Car(Vehicle) {
has doors: int = 4;
}
node BaseUser {
has username: str;
}
node AdminUser(BaseUser) {
has is_admin: bool = true;
}
This change makes the inheritance syntax more intuitive and consistent with languages like Python, making it easier for developers to understand class hierarchies at a glance.
3. def Keyword Introduced#
Instead of using can keyword for all functions and abilities, can statements are only used for object-spatial abilities and def keyword must be used for traditional python like functions and methods.
Before (v0.7.x and earlier):
can add(x: int, y: int) -> int {
return x + y;
}
node Person {
has name;
has age;
can get_name {
return self.name;
}
can greet with speak_to {
return "Hello " + visitor.name + ", my name is " + self.name;
}
can calculate_birth_year {
return 2025 - self.age;
}
}
After (v0.8.0+):
def add(x: int, y: int) -> int {
return x + y;
}
node Person {
has name;
has age;
def get_name {
return self.name;
}
can greet with speak_to entry {
return "Hello " + visitor.name + ", my name is " + self.name;
}
def calculate_birth_year {
return 2025 - self.age;
}
}
4. visitor Keyword Introduced#
Instead of using here keyword to represent the other object context while self is the self referential context. Now here can only be used in walker abilities to reference a node or edge, and visitor must be used in nodes/edges to reference the walker context.
Before (v0.7.x and earlier):
node Person {
has name;
can greet {
self.name = self.name.upper();
return "Hello, I am " + self.name;
}
can update_walker_info {
here.age = 25; # 'here' refers to the walker
}
}
walker PersonVisitor {
has age;
can visit: Person {
here.name = "Visitor"; # 'here' refers to the current node
report here.greet();
}
}
After (v0.8.0+):
node Person {
has name;
can greet {
self.name = self.name.upper();
return "Hello, I am " + self.name;
}
can update_walker_info {
visitor.age = 25; # 'visitor' refers to the walker
}
}
walker PersonVisitor {
has age;
can visit: Person {
here.name = "Visitor"; # 'here' still refers to the current node in walker context
report here.greet();
}
}
This change makes the code more intuitive by clearly distinguishing between:
self: The current object (node or edge) referring to itselfvisitor: The walker interacting with a node/edgehere: Used only in walker abilities to reference the current node/edge being visited
5. Lambda Syntax Updated#
Instead of using the with x: int can x; type syntax the updated lambda syntax now replaces with and can with lambda and : respectively.
Before (v0.7.x):
After (v0.8.0+):
This change brings Jac's lambda syntax closer to Python's familiar lambda parameter: expression pattern, making it more intuitive for developers coming from Python backgrounds while maintaining Jac's type annotations.
6. Object-Spatial Arrow Notation Updated#
The syntax for typed arrow notations are updated as -:MyEdge:-> and +:MyEdge:+> is now ->:MyEdge:-> and +>:MyEdge:+> for reference and creations.
Before (v0.7.x):
After (v0.8.0+):
This change was made to eliminate syntax conflicts with Python-style list slicing operations (e.g., my_list[:-1] was forced to be written my_list[: -1]). The new arrow notation provides clearer directional indication while ensuring that object-spatial operations don't conflict with the token parsing for common list operations.
7. Import from Syntax Updated for Clarity#
The syntax for importing specific modules or components from a package has been updated to use curly braces for better readability and to align with modern language conventions.
Before (v0.7.x):
After (v0.8.0+):
import from pygame_mock { color, display };
import from utils { helper, math_utils, string_formatter };
This new syntax using curly braces makes it clearer which modules are being imported from which package, especially when importing multiple items from different packages.
8. Import Statements Auto-Resolved (No Language Hints Needed)#
The language-specific import syntax has been simplified by removing the explicit language annotations (:py and :jac). The compiler now automatically resolves imports based on context and file extensions.
Before (v0.7.x):
After (v0.8.0+):
This change simplifies the import syntax, making code cleaner while still maintaining the ability to import from both Python and Jac modules. The Jac compiler now intelligently determines the appropriate language context for each import.
9. restrict and unrestrict Renamed to perm_grant and perm_revoke#
The permission management API has been renamed to better reflect its purpose and functionality.
Before (v0.7.x):
walker create_item {
can create with Root entry {
new_item = spawn Item(name="New Item");
Jac.unrestrict(new_item, level="CONNECT"); # Grant permissions
Jac.restrict(new_item, level="WRITE"); # Revoke permissions
}
}
After (v0.8.0+):
walker create_item {
can create with Root entry {
new_item = spawn Item(name="New Item");
Jac.perm_grant(new_item, level="CONNECT"); # Grant permissions
Jac.perm_revoke(new_item, level="WRITE"); # Revoke permissions
}
}
This change makes the permission management API more intuitive by using verbs that directly describe the actions being performed.