Jac Library Mode: Pure Python with Jac's Power#
Introduction#
Jac provides a library mode that enables developers to express all Jac language features as standard Python code. This mode provides complete access to Jac's object-spatial programming capabilities through the jaclang.lib
package, allowing developers to work entirely within Python syntax.
Library mode is designed for:
- Python-first teams wanting to adopt Jac's graph-native and AI capabilities without learning new syntax
- Existing Python codebases that need object-spatial architectures and AI integration with zero migration friction
- Understanding Jac's architecture by exploring how its transpilation to Python works under the hood
- Enterprise and corporate environments where introducing standard Python libraries is more acceptable than adopting new language syntax
Converting Jac Code to Pure Python#
The jac jac2py
command transpiles Jac source files into equivalent Python code. The generated output:
- Provides clean, ergonomic imports from
jaclang.lib
with full IDE autocomplete support - Generates idiomatic Python code with proper type hints and docstrings
- Ensures full compatibility with Python tooling, linters, formatters, and static analyzers
The Friends Network Example#
This section demonstrates Jac's object-spatial programming model through a complete example implementation in library mode.
The Jac Code#
The following example implements a social network graph with person nodes connected by friendship and family relationship edges:
node Person {
has name: str;
can announce with FriendFinder entry {
print(f"{visitor} is checking me out");
}
}
edge Friend {}
edge Family {
can announce with FriendFinder entry {
print(f"{visitor} is traveling to family member");
}
}
with entry {
# Build the graph
p1 = Person(name="John");
p2 = Person(name="Susan");
p3 = Person(name="Mike");
p4 = Person(name="Alice");
root ++> p1;
p1 +>: Friend :+> p2;
p2 +>: Family :+> [p1, p3];
p2 +>: Friend :+> p3;
}
walker FriendFinder {
has started: bool = False;
can report_friend with Person entry {
if self.started {
print(f"{here.name} is a friend of friend, or family");
} else {
self.started = True;
visit [-->];
}
visit [edge ->:Family :->];
}
can move_to_person with `root entry {
visit [-->];
}
}
with entry {
result = FriendFinder() spawn root;
print(result);
}
The Library Mode Python Equivalent#
Run jac jac2py friends.jac
to generate:
from __future__ import annotations
from jaclang.lib import (
Edge,
Node,
Path,
Root,
Walker,
build_edge,
connect,
on_entry,
refs,
root,
spawn,
visit,
)
class Person(Node):
name: str
@on_entry
def announce(self, visitor: FriendFinder) -> None:
print(f"{visitor} is checking me out")
class Friend(Edge):
pass
class Family(Edge):
@on_entry
def announce(self, visitor: FriendFinder) -> None:
print(f"{visitor} is traveling to family member")
# Build the graph
p1 = Person(name="John")
p2 = Person(name="Susan")
p3 = Person(name="Mike")
p4 = Person(name="Alice")
connect(left=root(), right=p1)
connect(left=p1, right=p2, edge=Friend)
connect(left=p2, right=[p1, p3], edge=Family)
connect(left=p2, right=p3, edge=Friend)
class FriendFinder(Walker):
started: bool = False
@on_entry
def report_friend(self, here: Person) -> None:
if self.started:
print(f"{here.name} is a friend of friend, or family")
else:
self.started = True
visit(self, refs(Path(here).edge_out().visit()))
visit(
self,
refs(
Path(here).edge_out(edge=lambda i: isinstance(i, Family)).edge().visit()
),
)
@on_entry
def move_to_person(self, here: Root) -> None:
visit(self, refs(Path(here).edge_out().visit()))
result = spawn(FriendFinder(), root())
print(result)
Key Concepts Explained#
1. Nodes and Edges#
In Jac:
In Library Mode:
Graph nodes are implemented by inheriting from the Node
base class, while relationships between nodes inherit from the Edge
base class. Data fields are defined using standard Python class attributes with type annotations.
2. Walkers#
In Jac:
In Library Mode:
Walkers are graph traversal agents implemented by inheriting from the Walker
base class. Walkers navigate through the graph structure and execute logic at each visited node or edge.
3. Abilities (Event Handlers)#
In Jac:
In Library Mode:
from jaclang.lib import on_entry
@on_entry
def report_friend(self, here: Person) -> None:
print(f"{here.name} is a friend")
Abilities define event handlers that execute when a walker interacts with nodes or edges. The @on_entry
decorator marks methods that execute when a walker enters a node or edge, while @on_exit
marks exit handlers. The here
parameter represents the current node or edge being visited, and the visitor
parameter (in node/edge abilities) represents the traversing walker.
4. Connecting Nodes#
In Jac:
root ++> p1; # Connect root to p1
p1 +>: Friend :+> p2; # Connect p1 to p2 with Friend edge
p2 +>: Family :+> [p1, p3]; # Connect p2 to multiple nodes
In Library Mode:
from jaclang.lib import connect, root
connect(left=root(), right=p1)
connect(left=p1, right=p2, edge=Friend)
connect(left=p2, right=[p1, p3], edge=Family)
The connect()
function creates directed edges between nodes. The edge
parameter specifies the edge type class, defaulting to a generic edge if omitted. The function supports connecting a single source node to either a single target node or a list of target nodes.
5. Spawning Walkers#
In Jac:
In Library Mode:
The spawn()
function initiates a walker at a specified node and begins traversal. The root()
function returns the root node of the current graph. The spawn()
function returns the walker instance after traversal completion.
6. Visiting Nodes#
In Jac:
In Library Mode:
from jaclang.lib import visit, refs, Path
visit(self, refs(Path(here).edge_out().visit()))
visit(
self, refs(Path(here).edge_out(edge=lambda i: isinstance(i, Family)).edge().visit())
)
The Path()
class constructs traversal paths from a given node. The edge_out()
method specifies outgoing edges to follow, while edge_in()
specifies incoming edges. The edge()
method filters the path to include only edges, excluding destination nodes. The visit()
method marks the constructed path for the walker to traverse, and refs()
converts the path into concrete node or edge references.
Complete Library Interface Reference#
Type Aliases & Constants#
Name | Type | Description |
---|---|---|
TYPE_CHECKING |
bool | Python typing constant for type checking blocks |
EdgeDir |
Enum | Edge direction enum (IN, OUT, ANY) |
DSFunc |
Type | Data spatial function type alias |
Base Classes#
Class | Description | Usage |
---|---|---|
Obj |
Base class for all archetypes | Generic archetype base |
Node |
Graph node archetype | class MyNode(Node): |
Edge |
Graph edge archetype | class MyEdge(Edge): |
Walker |
Graph traversal agent | class MyWalker(Walker): |
Root |
Root node type | Entry point for graphs |
GenericEdge |
Generic edge when no type specified | Default edge type |
Path |
Object-spatial path builder | Path(node).edge_out() |
Decorators#
Decorator | Description | Usage |
---|---|---|
@on_entry |
Entry ability decorator | Executes when walker enters node/edge |
@on_exit |
Exit ability decorator | Executes when walker exits node/edge |
@sem(doc, fields) |
Semantic string decorator | AI/LLM integration metadata |
Graph Construction#
Function | Description | Parameters |
---|---|---|
connect(left, right, edge, undir, conn_assign, edges_only) |
Connect nodes with edge | left : source node(s)right : target node(s)edge : edge class (optional)undir : undirected flagconn_assign : attribute assignmentsedges_only : return edges instead of nodes |
disconnect(left, right, dir, filter) |
Remove edges between nodes | left : source node(s)right : target node(s)dir : edge directionfilter : edge filter function |
build_edge(is_undirected, conn_type, conn_assign) |
Create edge builder function | is_undirected : bidirectional flagconn_type : edge classconn_assign : initial attributes |
assign_all(target, attr_val) |
Assign attributes to list of objects | target : list of objectsattr_val : tuple of (attrs, values) |
Graph Traversal & Walker Operations#
Function | Description | Parameters |
---|---|---|
spawn(walker, node) |
Start walker at node | walker : Walker instancenode : Starting node |
spawn_call(walker, node) |
Internal spawn execution (sync) | walker : Walker anchornode : Node/edge anchor |
async_spawn_call(walker, node) |
Internal spawn execution (async) | Same as spawn_call (async version) |
visit(walker, nodes) |
Visit specified nodes | walker : Walker instancenodes : Node/edge references |
disengage(walker) |
Stop walker traversal | walker : Walker to stop |
refs(path) |
Convert path to node/edge references | path : ObjectSpatialPath |
arefs(path) |
Async path references (placeholder) | path : ObjectSpatialPath |
filter_on(items, func) |
Filter archetype list by predicate | items : list of archetypesfunc : filter function |
Path Building (Methods on Path class)#
Method | Description | Returns |
---|---|---|
Path(node) |
Create path from node | ObjectSpatialPath |
.edge_out(edge, node) |
Filter outgoing edges | Self (chainable) |
.edge_in(edge, node) |
Filter incoming edges | Self (chainable) |
.edge_any(edge, node) |
Filter any direction | Self (chainable) |
.edge() |
Edges only (no nodes) | Self (chainable) |
.visit() |
Mark for visit traversal | Self (chainable) |
Node & Edge Operations#
Function | Description | Parameters |
---|---|---|
get_edges(origin, destination) |
Get edges connected to nodes | origin : list of nodesdestination : ObjectSpatialDestination |
get_edges_with_node(origin, destination, from_visit) |
Get edges and connected nodes | origin : list of nodesdestination : destination specfrom_visit : include nodes flag |
edges_to_nodes(origin, destination) |
Get nodes connected via edges | origin : list of nodesdestination : destination spec |
remove_edge(node, edge) |
Remove edge reference from node | node : NodeAnchoredge : EdgeAnchor |
detach(edge) |
Detach edge from both nodes | edge : EdgeAnchor |
Data Access & Persistence#
Function | Description | Returns |
---|---|---|
root() |
Get current root node | Root node instance |
get_all_root() |
Get all root nodes | List of roots |
get_object(id) |
Get archetype by ID string | Archetype or None |
object_ref(obj) |
Get hex ID string of archetype | String |
save(obj) |
Persist archetype to database | None |
destroy(objs) |
Delete archetype(s) from memory | None |
commit(anchor) |
Commit data to datasource | None |
reset_graph(root) |
Purge graph from memory | Count of deleted items |
Access Control & Permissions#
Function | Description | Parameters |
---|---|---|
perm_grant(archetype, level) |
Grant public access to archetype | archetype : Target archetypelevel : AccessLevel (READ/CONNECT/WRITE) |
perm_revoke(archetype) |
Revoke public access | archetype : Target archetype |
allow_root(archetype, root_id, level) |
Allow specific root access | archetype : Targetroot_id : Root UUIDlevel : Access level |
disallow_root(archetype, root_id, level) |
Disallow specific root access | Same as allow_root |
elevate_root() |
Elevate context to system root | No parameters (uses context) |
check_read_access(anchor) |
Check read permission | anchor : Target anchor |
check_write_access(anchor) |
Check write permission | anchor : Target anchor |
check_connect_access(anchor) |
Check connect permission | anchor : Target anchor |
check_access_level(anchor, no_custom) |
Get access level for anchor | anchor : Targetno_custom : skip custom check |
Module Management & Archetypes#
Function | Description | Parameters |
---|---|---|
jac_import(target, base_path, ...) |
Import Jac/Python module | target : Module namebase_path : Search pathabsorb , mdl_alias , override_name , items , reload_module , lng : import options |
load_module(module_name, module, force) |
Load module into machine | module_name : Namemodule : Module objectforce : reload flag |
attach_program(program) |
Attach JacProgram to runtime | program : JacProgram instance |
list_modules() |
List all loaded modules | Returns list of names |
list_nodes(module_name) |
List nodes in module | module_name : Module to inspect |
list_walkers(module_name) |
List walkers in module | module_name : Module to inspect |
list_edges(module_name) |
List edges in module | module_name : Module to inspect |
get_archetype(module_name, archetype_name) |
Get archetype class from module | module_name : Modulearchetype_name : Class name |
make_archetype(cls) |
Convert class to archetype | cls : Class to convert |
spawn_node(node_name, attributes, module_name) |
Create node instance by name | node_name : Node class nameattributes : Init dictmodule_name : Source module |
spawn_walker(walker_name, attributes, module_name) |
Create walker instance by name | walker_name : Walker classattributes : Init dictmodule_name : Source module |
update_walker(module_name, items) |
Reload walker from module | module_name : Moduleitems : Items to update |
create_archetype_from_source(source_code, ...) |
Create archetype from Jac source | source_code : Jac code stringmodule_name , base_path , cachable , keep_temporary_files : options |
Testing & Debugging#
Function | Description | Parameters |
---|---|---|
jac_test(func) |
Mark function as test | func : Test function |
run_test(filepath, ...) |
Run test suite | filepath : Test filefunc_name , filter , xit , maxfail , directory , verbose : test options |
report(expr, custom) |
Report value from walker | expr : Value to reportcustom : custom report flag |
printgraph(node, depth, traverse, edge_type, bfs, edge_limit, node_limit, file, format) |
Generate graph visualization | node : Start nodedepth : Max depthtraverse : traversal flagedge_type : filter edgesbfs : breadth-first flagedge_limit , node_limit : limitsfile : output pathformat : 'dot' or 'mermaid' |
LLM & AI Integration#
Function | Description | Use Case |
---|---|---|
by(model) |
Decorator for LLM-powered functions | @by(model) def func(): ... |
call_llm(model, mtir) |
Direct LLM invocation | Advanced LLM usage |
get_mtir(caller, args, call_params) |
Get method IR for LLM | LLM internal representation |
sem(semstr, inner_semstr) |
Semantic metadata decorator | @sem("doc", {"field": "desc"}) |
Runtime & Threading#
Function | Description | Parameters |
---|---|---|
setup() |
Initialize class references | No parameters |
get_context() |
Get current execution context | Returns ExecutionContext |
field(factory, init) |
Define dataclass field | factory : Default factoryinit : Include in init |
impl_patch_filename(file_loc) |
Patch function file location | file_loc : File path for stack traces |
thread_run(func, *args) |
Run function in thread | func : Functionargs : Arguments |
thread_wait(future) |
Wait for thread completion | future : Future object |
create_cmd() |
Create CLI commands | No parameters (placeholder) |
Best Practices#
1. Type Hints#
Always use type hints for better IDE support:
2. Walker State#
Keep walker state minimal and immutable when possible:
class Counter(Walker):
count: int = 0 # Simple state
@on_entry
def increment(self, here: Node) -> None:
self.count += 1
3. Path Filtering#
Use lambda functions for flexible filtering:
# Filter by edge type
visit(
self,
refs(Path(here).edge_out(edge=lambda e: isinstance(e, (Friend, Family))).visit()),
)
# Filter by node attribute
visit(
self,
refs(Path(here).edge_out(node=lambda n: hasattr(n, "active") and n.active).visit()),
)
4. Clean Imports#
Import only what you need:
# Good
from jaclang.lib import Node, Walker, spawn, visit, on_entry
# Avoid
from jaclang.lib import *
Migration Guide#
From Jac to Library Mode#
Jac Syntax | Library Mode Python |
---|---|
node Person { has name: str; } |
class Person(Node): name: str |
edge Friend {} |
class Friend(Edge): pass |
walker W { has x: int; } |
class W(Walker): x: int |
root ++> node |
connect(root(), node) |
a +>: Edge :+> b |
connect(a, b, Edge) |
W() spawn root |
spawn(W(), root()) |
visit [-->] |
visit(self, refs(Path(here).edge_out().visit())) |
visit [<--] |
visit(self, refs(Path(here).edge_in().visit())) |
visit [--] |
visit(self, refs(Path(here).edge_any().visit())) |
can f with T entry {} |
@on_entry def f(self, here: T): ... |
disengage; |
disengage(self) |
Summary#
Library mode provides a pure Python implementation of Jac's object-spatial programming model through the jaclang.lib
package. This approach offers several advantages:
- Complete Feature Parity: All Jac language features are accessible through the library interface
- Idiomatic Python: Implementation uses standard Python classes, decorators, and functions without runtime magic
- Full Tooling Support: Generated code includes proper type hints, enabling IDE autocomplete, static analysis, and debugging
- Seamless Integration: The library can be incorporated into existing Python projects without requiring build system modifications
- Maintainable Output: Transpiled code is readable and follows Python best practices
Library mode enables developers to leverage Jac's graph-native and AI-integrated programming model while maintaining full compatibility with the Python ecosystem and development workflow.