Skip to content

Library Mode

Part of: Part IX: Deployment

Related: Python Integration | Part III: OSP


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

The jac jac2py command transpiles Jac source files into equivalent Python code. The generated output:

  1. Provides clean, ergonomic imports from jaclang.lib with full IDE autocomplete support
  2. Generates idiomatic Python code with proper type hints and docstrings
  3. Ensures full compatibility with Python tooling, linters, formatters, and static analyzers

This section demonstrates Jac’s object-spatial programming model through a complete example implementation in library mode.

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);
}

Run jac jac2py friends.jac to generate:

from __future__ import annotations
from jaclang.lib import (
Edge,
Node,
OPath,
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(OPath(here).edge_out().visit()))
visit(
self,
refs(
OPath(here).edge_out(edge=lambda i: isinstance(i, Family)).edge().visit()
),
)
@on_entry
def move_to_person(self, here: Root) -> None:
visit(self, refs(OPath(here).edge_out().visit()))
result = spawn(FriendFinder(), root())
print(result)

In Jac:

node Person {
has name: str;
}
edge Friend {}

In Library Mode:

from jaclang.lib import Node, Edge
class Person(Node):
name: str
class Friend(Edge):
pass

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.

In Jac:

walker FriendFinder {
has started: bool = False;
}

In Library Mode:

from jaclang.lib import Walker
class FriendFinder(Walker):
started: bool = False

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.

In Jac:

can report_friend with Person entry {
print(f"{here.name} is a friend");
}

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.

In Jac:

node Person {
has name: str;
}
edge Friend {}
edge Family {}
with entry {
p1 = Person(name="John");
p2 = Person(name="Susan");
p3 = Person(name="Mike");
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.

In Jac:

walker FriendFinder {
can find with Root entry {
visit [-->];
}
}
with entry {
result = FriendFinder() spawn root;
}

In Library Mode:

from jaclang.lib import spawn, root
result = spawn(FriendFinder(), root())

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.

In Jac:

edge Family {}
walker Visitor {
can traverse with Root entry {
visit [-->]; # Visit all outgoing edges
visit [->:Family:->]; # Visit only Family edges
}
}

In Library Mode:

from jaclang.lib import visit, refs, OPath
visit(self, refs(OPath(here).edge_out().visit()))
visit(
self, refs(OPath(here).edge_out(edge=lambda i: isinstance(i, Family)).edge().visit())
)

The OPath() 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.


NameTypeDescription
TYPE_CHECKINGboolPython typing constant for type checking blocks
EdgeDirEnumEdge direction enum (IN, OUT, ANY)
DSFuncTypeData spatial function type alias
ClassDescriptionUsage
ObjBase class for all archetypesGeneric archetype base
NodeGraph node archetypeclass MyNode(Node):
EdgeGraph edge archetypeclass MyEdge(Edge):
WalkerGraph traversal agentclass MyWalker(Walker):
RootRoot node typeEntry point for graphs
GenericEdgeGeneric edge when no type specifiedDefault edge type
OPathObject-spatial path builderOPath(node).edge_out()
DecoratorDescriptionUsage
@on_entryEntry ability decoratorExecutes when walker enters node/edge
@on_exitExit ability decoratorExecutes when walker exits node/edge
@sem(doc, fields)Semantic string decoratorAI/LLM integration metadata
FunctionDescriptionParameters
connect(left, right, edge, undir, conn_assign, edges_only)Connect nodes with edgeleft: source node(s)
right: target node(s)
edge: edge class (optional)
undir: undirected flag
conn_assign: attribute assignments
edges_only: return edges instead of nodes
disconnect(left, right, dir, filter)Remove edges between nodesleft: source node(s)
right: target node(s)
dir: edge direction
filter: edge filter function
build_edge(is_undirected, conn_type, conn_assign)Create edge builder functionis_undirected: bidirectional flag
conn_type: edge class
conn_assign: initial attributes
assign_all(target, attr_val)Assign attributes to list of objectstarget: list of objects
attr_val: tuple of (attrs, values)
FunctionDescriptionParameters
spawn(walker, node)Start walker at nodewalker: Walker instance
node: Starting node
spawn_call(walker, node)Internal spawn execution (sync)walker: Walker anchor
node: Node/edge anchor
async_spawn_call(walker, node)Internal spawn execution (async)Same as spawn_call (async version)
visit(walker, nodes)Visit specified nodeswalker: Walker instance
nodes: Node/edge references
disengage(walker)Stop walker traversalwalker: Walker to stop
refs(path)Convert path to node/edge referencespath: ObjectSpatialPath
arefs(path)Async path references (placeholder)path: ObjectSpatialPath
filter_on(items, func)Filter archetype list by predicateitems: list of archetypes
func: filter function
MethodDescriptionReturns
OPath(node)Create path from nodeObjectSpatialPath
.edge_out(edge, node)Filter outgoing edgesSelf (chainable)
.edge_in(edge, node)Filter incoming edgesSelf (chainable)
.edge_any(edge, node)Filter any directionSelf (chainable)
.edge()Edges only (no nodes)Self (chainable)
.visit()Mark for visit traversalSelf (chainable)
FunctionDescriptionParameters
get_edges(origin, destination)Get edges connected to nodesorigin: list of nodes
destination: ObjectSpatialDestination
get_edges_with_node(origin, destination, from_visit)Get edges and connected nodesorigin: list of nodes
destination: destination spec
from_visit: include nodes flag
edges_to_nodes(origin, destination)Get nodes connected via edgesorigin: list of nodes
destination: destination spec
remove_edge(node, edge)Remove edge reference from nodenode: NodeAnchor
edge: EdgeAnchor
detach(edge)Detach edge from both nodesedge: EdgeAnchor
FunctionDescriptionReturns
root()Get current root nodeRoot node instance
get_all_root()Get all root nodesList of roots
get_object(id)Get archetype by ID stringArchetype or None
object_ref(obj)Get hex ID string of archetypeString
save(obj)Persist archetype to databaseNone
destroy(objs)Delete archetype(s) from memoryNone
commit(anchor)Commit data to datasourceNone
reset_graph(root)Purge graph from memoryCount of deleted items
FunctionDescriptionParameters
perm_grant(archetype, level)Grant public access to archetypearchetype: Target archetype
level: AccessLevel (READ/CONNECT/WRITE)
perm_revoke(archetype)Revoke public accessarchetype: Target archetype
allow_root(archetype, root_id, level)Allow specific root accessarchetype: Target
root_id: Root UUID
level: Access level
disallow_root(archetype, root_id, level)Disallow specific root accessSame as allow_root
check_read_access(anchor)Check read permissionanchor: Target anchor
check_write_access(anchor)Check write permissionanchor: Target anchor
check_connect_access(anchor)Check connect permissionanchor: Target anchor
check_access_level(anchor, no_custom)Get access level for anchoranchor: Target
no_custom: skip custom check
FunctionDescriptionParameters
jac_import(target, base_path, ...)Import Jac/Python moduletarget: Module name
base_path: Search path
absorb, mdl_alias, override_name, items, reload_module, lng: import options
load_module(module_name, module, force)Load module into machinemodule_name: Name
module: Module object
force: reload flag
attach_program(program)Attach JacProgram to runtimeprogram: JacProgram instance
list_modules()List all loaded modulesReturns list of names
list_nodes(module_name)List nodes in modulemodule_name: Module to inspect
list_walkers(module_name)List walkers in modulemodule_name: Module to inspect
list_edges(module_name)List edges in modulemodule_name: Module to inspect
get_archetype(module_name, archetype_name)Get archetype class from modulemodule_name: Module
archetype_name: Class name
make_archetype(cls)Convert class to archetypecls: Class to convert
spawn_node(node_name, attributes, module_name)Create node instance by namenode_name: Node class name
attributes: Init dict
module_name: Source module
spawn_walker(walker_name, attributes, module_name)Create walker instance by namewalker_name: Walker class
attributes: Init dict
module_name: Source module
update_walker(module_name, items)Reload walker from modulemodule_name: Module
items: Items to update
create_archetype_from_source(source_code, ...)Create archetype from Jac sourcesource_code: Jac code string
module_name, base_path, cachable, keep_temporary_files: options
FunctionDescriptionParameters
jac_test(func)Mark function as testfunc: Test function
run_test(filepath, ...)Run test suitefilepath: Test file
func_name, filter, xit, maxfail, directory, verbose: test options
report(expr, custom)Report value from walkerexpr: Value to report
custom: custom report flag
printgraph(node, depth, traverse, edge_type, bfs, edge_limit, node_limit, file, format)Generate graph visualizationnode: Start node
depth: Max depth
traverse: traversal flag
edge_type: filter edges
bfs: breadth-first flag
edge_limit, node_limit: limits
file: output path
format: ‘dot’ or ‘mermaid’
FunctionDescriptionUse Case
by(model)Decorator for LLM-powered functions@by(model) def func(): ...
call_llm(model, mtir)Direct LLM invocationAdvanced LLM usage
get_mtir(caller, args, call_params)Get method IR for LLMLLM internal representation
sem(semstr, inner_semstr)Semantic metadata decorator@sem("doc", {"field": "desc"})
FunctionDescriptionParameters
setup()Initialize class referencesNo parameters
get_context()Get current execution contextReturns ExecutionContext
field(factory, init)Define dataclass fieldfactory: Default factory
init: Include in init
impl_patch_filename(file_loc)Patch function file locationfile_loc: File path for stack traces
thread_run(func, *args)Run function in threadfunc: Function
args: Arguments
thread_wait(future)Wait for thread completionfuture: Future object
create_cmd()Create CLI commandsNo parameters (placeholder)

Always use type hints for better IDE support:

from typing import Optional
class Person(Node):
name: str
age: Optional[int] = None

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

Use lambda functions for flexible filtering:

# Filter by edge type
visit(
self,
refs(OPath(here).edge_out(edge=lambda e: isinstance(e, (Friend, Family))).visit()),
)
# Filter by node attribute
visit(
self,
refs(OPath(here).edge_out(node=lambda n: hasattr(n, "active") and n.active).visit()),
)

Import only what you need:

# Good
from jaclang.lib import Node, Walker, spawn, visit, on_entry
# Avoid
from jaclang.lib import *

Jac SyntaxLibrary 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 ++> nodeconnect(root(), node)
a +>: Edge :+> bconnect(a, b, Edge)
W() spawn rootspawn(W(), root())
visit [-->]visit(self, refs(OPath(here).edge_out().visit()))
visit [<--]visit(self, refs(OPath(here).edge_in().visit()))
visit [--]visit(self, refs(OPath(here).edge_any().visit()))
can f with T entry {}@on_entry
def f(self, here: T): ...
disengage;disengage(self)

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.