Jac Library Mode#
Part of: Part IX: Deployment
Related: Python Integration | Part III: OSP
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.
This page focuses on what is unique to library mode: the jaclang.lib API surface and the jac tool jac2py workflow. For how Jac compiles to and interoperates with Python in general (bytecode transpilation, bidirectional imports, type-only imports), see Python Integration. Library mode itself corresponds to adoption Pattern 5: Pure Python + Jac Library.
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
root is a function in library mode
In .jac source, root is a reserved keyword and writing root() emits warning W0062 (deprecated; use bare root). In library mode, root is a Python function imported from jaclang.lib, so it must be called as root() -- the bare reference is just the function object. The same applies to other graph builtins (spawn, connect, get_all_root). The deprecation in breaking-changes.md only governs .jac source.
Converting Jac Code to Pure Python#
The jac tool jac2py command transpiles Jac source files into equivalent Python code, emitting clean jaclang.lib imports with type hints and docstrings so the output works with standard Python IDEs, linters, and formatters. (The same transpilation pipeline underpins all Jac→Python compilation -- see How it Works: Transpilation to Native Python.)
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 tool jac2py friends.jac to generate:
Generated Python code
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)
Note
The transpiler outputs from jaclang.jac0core.jaclib import ... internally; the public jaclang.lib re-exports the same symbols and is the recommended import path. See Python Integration.
Key Concepts Explained#
The mappings below show how each Jac construct is expressed against the jaclang.lib API. This is the inverse of the general transpilation reference in Python Integration -- here the focus is the hand-written library-mode form, not the compiler output.
1. Nodes and Edges#
In Jac:
In Library Mode:
Subclass Node for graph nodes and Edge for relationships; declare data fields as standard typed class attributes.
2. Walkers#
In Jac:
In Library Mode:
Subclass Walker for traversal agents that navigate the graph and run 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")
@on_entry/@on_exit mark methods that run when a walker enters or exits a node or edge. here is the current node/edge; visitor (in node/edge abilities) is the traversing walker.
4. Connecting Nodes#
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)
connect() creates directed edges; edge names the edge class (generic if omitted), and right accepts a single node or a list.
5. Spawning Walkers#
In Jac:
walker FriendFinder {
can find with Root entry {
visit [-->];
}
}
with entry {
result = FriendFinder() spawn root;
}
In Library Mode:
spawn() starts a walker at a node and returns the walker after traversal; root() returns the current graph's root node.
6. Visiting Nodes#
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())
)
OPath(node) builds a traversal path: edge_out()/edge_in() select direction, edge() keeps edges only (no destination nodes), and visit() marks the path; refs() resolves it to concrete node/edge references for visit().
Complete Library Interface Reference#
API Scope Notice
The following reference includes both public API functions available via from jaclang.lib import ... and internal runtime functions that may not be directly importable. Core functions available for import include: connect, disconnect, spawn, root, node, edge, walker, obj, Anchor, NodeAnchor, EdgeAnchor, WalkerAnchor, Root. Other functions listed below may be internal to the runtime and subject to change.
Type Aliases & Constants#
| Name | Type | Description |
|---|---|---|
TYPE_CHECKING |
bool | Python typing constant; guards type-only references to break circular imports (see Type-Only Imports). |
EdgeDir |
Enum | Edge direction enum (IN, OUT, ANY) |
DSFunc |
Type | Object 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 |
OPath |
Object-spatial path builder | OPath(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 OPath class)#
| Method | Description | Returns |
|---|---|---|
OPath(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 |
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_operator(model) |
Decorator for LLM-powered functions | @by_operator(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 |
Best Practices#
Standard Python hygiene applies: annotate fields for IDE support, and import only the jaclang.lib names you use (avoid from jaclang.lib import *). The patterns below are specific to library-mode object-spatial code.
1. 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
2. Path Filtering#
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()),
)
The full Jac syntax → library-mode mapping is covered in Key Concepts Explained above. For the general Jac↔Python interop story (importing
.jacfiles, inline Python, type compatibility), see Python Integration.
Summary#
Library mode provides a pure Python implementation of Jac's object-spatial programming model through the jaclang.lib package, distinguished by:
- Complete Feature Parity: All Jac language features are accessible through the library interface
- Idiomatic Python: Standard Python classes, decorators, and functions with full tooling support (type hints, IDE autocomplete, static analysis)
- Zero-Friction Integration: Drop into existing Python projects with no build-system changes
Use it when you want Jac's graph-native and AI-integrated programming model while staying entirely in Python syntax.