Nodlin Starlark Language Extensions

Feb 3, 2026 ยท 14 min read

This document describes the Nodlin-specific extensions to the Starlark language. This supplements the standard Starlark syntax documentation available at:

Overview

Nodlin extends Starlark with specific types and methods for interacting with nodes, relationships, actions, and events within the Nodlin environment. These extensions enable scripts to create workflows, manage actions, link nodes, and respond to events.


Pre-declared Variables

The following variables are pre-declared in every script execution and are reserved keywords:

C or context (NodeContext)

Provides details about the current request context.

Properties:

  • C.userID - Tuple of (userid, domain) for the user making the request

Methods:

  • C.checkpoint() - Creates a new checkpoint in the flow (no parameters)
  • C.newNodeID() - Generates a new random node ID (no parameters)
    • Returns: String containing new node ID
  • C.newNodeVersion() - Generates a new random node version ID (no parameters)
    • Returns: String containing new version ID

N or node (Node)

Represents the current recorded version of the node (prior to any update). This is read-only and cannot be changed.

Properties:

  • N.nodeID - The unique identifier of the node
  • N.nodeType - The FQN type of the node
  • N.nodeSubType - The subtype of the node (if specified)
  • N.label - The user-defined label for the node
  • N.alias - The alias for the node
  • N.related or N.R - The relationships from this node (see Relationships)
  • N.data - The data properties of the node (NodePayload)
  • N.<property> - Direct access to any data property by name

Methods:

  • See Node Methods section below

O or operation (Action or Event)

Represents the operation that triggered script execution (either an Action or Event).

Common Properties:

  • O.name - Name of the action or event
  • O.nodeID - The node ID
  • O.nodeType - The node type
  • O.data - The payload data

Action-specific Properties:

  • O.graphID - The graph ID
  • O.domainID - The domain ID

Event-specific Properties:

  • O.domain - Source domain
  • O.graphID - Source graph ID
  • O.fromNode - Source node ID
  • O.fromType - Source node type
  • O.overRelation - The relationship over which the event was triggered

Methods:

  • O.isActionName(name) - Check if this is an action with the specified name
    • Parameters: name (string) - Action name to check
    • Returns: Boolean
  • O.isEventName(name) - Check if this is an event with the specified name
    • Parameters: name (string) - Event name to check
    • Returns: Boolean

V or value (Node)

Represents the result node that will be recorded after successful execution. It is initialized with:

  • For actions: the user-provided payload
  • For events: a copy of the current node that can be modified

Special Properties (can be set on value):

  • value.image - Data URI or SVG element to display
  • value.sizeX - Width in pixels
  • value.sizeY - Height in pixels
  • value.label - Short label description
  • value.summary - Description for tooltips and book view
  • value.help - Help text specific to this node
  • value.hasError - Boolean indicating input/validation error
  • value.alias - Alias for the node (used in named references)
  • value.hidden - Boolean to hide the node by default

All other properties and methods are the same as Node (N).

named (Dictionary)

Dictionary of nodes indexed by their user-defined names (aliases). Only includes directly related nodes and the current node.

Usage:

# Check if a named node exists
if 'taskNode' in named:
    total = total + named['taskNode'].effort

# Iterate through all named nodes
for name in named:
    print(name, named[name])

# Access node properties
myNode = named['expense']
amount = myNode.amount

values (Dictionary)

Dictionary containing the evaluated values from expression nodes. For expression nodes, the value property is stored as JSON - this dictionary provides the unmarshalled values.

Usage:

# Access an expression value
if 'credit' in values:
    budget = budget + values['credit']

Node Methods

Methods available on any Node object (N, V, or nodes from relationships):

hasOne(relation, [type])

Returns exactly one related node, or fails if zero or multiple nodes exist.

Parameters:

  • relation (string, required) - Relationship name
  • type (string, optional) - Node type filter

Returns: Node object or raises error

Examples:

# Get one node over a relationship (any type)
payment = mortgage.hasOne("has_schedule")

# Get one node of specific type over relationship
payment = mortgage.hasOne("has_schedule", "paymentSchedule")

setName(name)

Sets the alias/name for the node (stored in __name property).

Parameters:

  • name (string, required) - Alphanumeric name, must start with letter, underscores allowed

Returns: None

Example:

value.setName("myTaskNode")

linkToNewNode(...)

Creates a new node and establishes a link from the current node.

Parameters (for scripts):

  • scriptID (string, optional) - Specific script version ID
  • scriptFQN (string, optional) - Fully qualified script name
  • payload (dict, optional) - Initial data for the new node
  • relation (string, optional) - Relationship name (stored in metadata for scripts)
  • label (string, optional) - User-defined label for the relationship
  • reverse (bool, optional) - Create relationship in reverse direction (default: False)
  • weight (int, optional) - Relationship display priority weight
  • reverseRelation (string, optional) - Relationship name for reverse link (scripts only)

Parameters (for internal types):

  • FQN (string, optional) - Fully qualified internal node type name
  • payload (dict, optional) - Initial data for the new node
  • relation (string, optional) - Full relationship FQN
  • label (string, optional) - User-defined label
  • reverse (bool, optional) - Create relationship in reverse direction
  • weight (int, optional) - Relationship display priority weight

Returns: String containing the new node ID

Examples:

# Create and link to a script node
newID = node.linkToNewNode(
    scriptFQN="group.owner.domain.package.task.v1",
    label="Sub-task",
    payload={"name": "My Task"}
)

# Create with reverse relationship
newID = node.linkToNewNode(
    scriptFQN="group.owner.domain.expense",
    label="Expense",
    reverse=True
)

# Create internal type node
newID = node.linkToNewNode(
    FQN="agt_core_all_internalTest_task",
    label="Internal Task"
)

Creates or removes a link between two existing nodes.

Parameters:

  • fromNodeID (string, optional) - Source node ID (defaults to current node)
  • fromFQN (string, optional) - Source node type (defaults to script type)
  • toNodeID (string, optional) - Target node ID (defaults to current node)
  • toNodeFQN (string, optional) - Target node type (defaults to script type)
  • relation (string, optional) - Relationship name
  • label (string, optional) - User-defined label
  • reverse (bool, optional) - Create in reverse direction (default: False)
  • remove (bool, optional) - Remove existing relationship (default: False)
  • weight (int, optional) - Relationship display priority weight

Returns: None

Example:

# Create link after checkpoint
newID = node.linkToNewNode(scriptFQN=TASK_TYPE, label="New Task", reverse=True)
C.checkpoint()
node.link(toNodeID=newID, label="depends on")

# Remove a link
node.link(toNodeID=targetID, remove=True)

actionNode(...)

Performs an action on a node (create, delete, or custom action).

Parameters:

  • action (string, required) - Action name (e.g., “create”, “delete”)
  • nodeid (string, required for non-create) - Target node ID
  • nodeVersion (string, optional) - Current version of the node
  • newNodeVersion (string, optional) - New version ID to assign
  • scriptID (string, optional) - Script version ID (for script nodes)
  • scriptFQN (string, optional) - Script FQN (for script nodes)
  • FQN (string, optional) - Node type FQN (for internal types)
  • payload (dict, optional) - Action payload

Returns: String containing the node ID

Example:

# Delete a node
node.actionNode(action="delete", nodeid="abc123", nodeVersion="v1")

# Create a new node with specific action
nodeID = node.actionNode(
    action="create",
    nodeid=C.newNodeID(),
    scriptFQN="group.owner.domain.task",
    payload={"name": "New Task"}
)

getActions()

Retrieves all actions associated with the node.

Parameters: None

Returns: UserActionList (iterable collection) or None

Example:

actions = N.getActions()
if actions:
    for action in actions:
        print(action.actionID, action.comment, action.state)

getFYIs()

Retrieves all FYI (For Your Information) items for the node.

Parameters: None

Returns: List of FYI items or None

Example:

fyis = N.getFYIs()
if fyis:
    for fyi in fyis:
        print(fyi)

createAction(...)

Creates a new user action for a node.

Parameters:

  • nodeid (string, optional) - Node ID (defaults to current node)
  • comment (string, optional) - Action comment
  • priority (int, optional) - Priority: 1 (high), 2 (medium), 3 (low). Default: 3
  • forDate (Time, optional) - Target completion date (defaults to now)
  • successCondition (string, optional) - Completion condition (default: “ALL”)

Returns: String containing the new action ID

Example:

actionID = node.createAction(
    nodeid=newNodeID,
    comment="Review and approve",
    priority=1,
    forDate=time.parse_duration("48h") + time.now()
)

createAssignment(...)

Creates an assignment for an existing action.

Parameters:

  • nodeid (string, optional) - Node ID (defaults to current node)
  • actionid (string, required) - The action ID to assign
  • user (tuple, optional) - User tuple: (userid, domain)
  • group (tuple, optional) - Group tuple: (groupid, domain)

Note: Either user or group must be specified, but not both.

Returns: String containing the new assignment ID

Example:

# Create action first
actionID = node.createAction(nodeid=nodeID, comment="Review")
C.checkpoint()

# Then create assignment
assignmentID = node.createAssignment(
    nodeid=nodeID,
    actionid=actionID,
    user=("john.doe", "example.com")
)

assignNewAction(...)

Creates both an action and an assignment in one call (with automatic checkpoint).

Parameters:

  • nodeid (string, optional) - Node ID (defaults to current node)
  • comment (string, optional) - Action comment
  • priority (int, optional) - Priority: 1 (high), 2 (medium), 3 (low). Default: 3
  • forDate (Time, optional) - Target completion date
  • successCondition (string, optional) - Completion condition (default: “ALL”)
  • user (tuple, optional) - User tuple: (userid, domain)
  • group (tuple, optional) - Group tuple: (groupid, domain)

Returns: Tuple of (actionID, assignmentID)

Example:

actionID, assignmentID = node.assignNewAction(
    comment="Approve expense",
    priority=1,
    user=("manager", "company.com")
)

getAffordances()

Returns list of possible relationships (affordances) for the node.

Parameters: None

Returns: List of Affordance objects or None

Example:

affordances = node.getAffordances()
if affordances:
    for aff in affordances:
        print(aff.overRelation, aff.toType)

setAffordance(...)

Adds a possible relationship to the node’s spec (the typical relationships). Nodes in nodlin can be connected to any other node, but highlighting the typical relationships (where the node may be dependent on information in related node) prioritises the display and creation of these nodes and relationships.

Parameters:

  • overRelation (string, optional) - Relationship name from this node.
  • toType (string, optional) - Target node type FQN. If not specified then toSubType must be specified (script FQN).
  • toSubType (string, optional) - Target node subtype
  • inverseRelation (string, optional) - Reverse relationship name.
  • label (string, optional) - Relationship label
  • inverseLabel (string, optional) - Reverse relationship label

Note: Either toType or toSubType must be specified. If toType is not specified, it is assumed to be a script type node.

If relation (overRelation or inverseRelation) is not a fully qualified relationship name, then its inferred to be relationship for current node type.

Note that the new node created will have no payload (just defaulted properties from execution of the target type code or script). If you require to create the new node with a payload, then this must be created in the script.

Having setAffordance in the script (ie, specific to the instance of the node) allows you to provide context specific affordances. For example, if only 1 instance of a connected node type is allowed, then you can avoid setting the affordance if one instance of a connected node already exists.

Returns: The created Affordance object

Example:

# Define possible relationship to another script
aff = node.setAffordance(
    overRelation="hasDependency",
    toSubType="taskScript",
    label="Depends On"
)

# Define relationship to internal type
aff = node.setAffordance(
    overRelation="hasTask",
    toType="agt_core_all_task",
    inverseRelation="taskOwner"
)

Relationships

Access relationships using: node.related.<relationship>.<type> or node.R.<relationship>.<type>

Accessing Relationships

# Get all nodes of a specific type over a relationship
tasks = N.related.depends_on.task

# Check if a relationship exists
if 'expense' in N.related:
    expenses = N.related.expense

# Iterate through all relationships
for relation in N.related:
    print("Relationship:", relation.hasName)
    for nodeType in relation:
        nodes = nodeType  # This is a NodeList

Relationship Naming

Relationships can be referenced by:

  1. Formal FQN: e.g., agr_core_all_workflowAgent_next
  2. Short form: e.g., next (the reference part of the FQN)
  3. CamelCase short: e.g., openQuestion for agr_core_all_workflowAgent_open_question
  4. User label: The exact user-defined label (case-sensitive)

Methods on RelatedTypes

all()

Returns list of all nodes across all types for a relationship.

Example:

# Get all related nodes over a relationship (any type)
allNodes = mortgage.R.has_schedule.all()

# Get all relationships and types
allRelated = mortgage.R.all()

hasOne([type])

Returns exactly one node, errors if zero or multiple.

Example:

# One node of any type
schedule = mortgage.R.has_schedule.hasOne()

# One node of specific type
schedule = mortgage.R.has_schedule.paymentSchedule.hasOne()

Action Properties

When O (operation) is an Action, the following properties are available:

Via . accessor:

  • O.actionID
  • O.active
  • O.comment
  • O.priority
  • O.forDate
  • O.state
  • O.successCondition
  • O.completed
  • O.createdAt
  • O.updatedAt
  • O.startedAt
  • O.completedAt
  • O.assignments - List of assignment objects
  • O.createdBy

UserAction Methods:

  • action.allUsers() - Returns list of all users involved
  • action.allGroups() - Returns list of all groups involved

Assignment Properties

Each assignment in action.assignments has:

  • assignment.assignmentID
  • assignment.active
  • assignment.assignedAt
  • assignment.assignee
  • assignment.assignedTo
  • assignment.assignedToGroup

Affordance Properties

Affordance objects have the following properties:

  • affordance.overRelation - Relationship name from source
  • affordance.toType - Target node type
  • affordance.toSubType - Target node subtype
  • affordance.inverseRelation - Reverse relationship name
  • affordance.label - Relationship label
  • affordance.inverseLabel - Reverse relationship label

Standard Starlark Libraries

The following standard libraries are available:

json

JSON encoding/decoding operations.

import json

data = json.encode({"key": "value"})
obj = json.decode('{"key": "value"}')

math

Mathematical functions.

import math

result = math.sqrt(16)
result = math.ceil(4.3)

time

Time and duration operations.

Functions:

  • time.now() - Current time
  • time.parse_duration(d) - Parse duration string (e.g., “24h”, “30m”)
  • time.from_timestamp(sec, nsec) - Create time from Unix timestamp
  • time.is_valid_timezone(loc) - Check if timezone is valid
  • time.parseTime(timeStr, format, location) - Parse time string
  • time.time(year, month, day, hour, minute, second, nanosecond, location) - Create time

Duration units: “ns”, “us” (or “ยตs”), “ms”, “s”, “m”, “h”

Examples:

# Current time
now = time.now()

# Time in 24 hours
tomorrow = time.parse_duration("24h") + time.now()

# Time in 2 days and 3 hours
future = time.parse_duration("51h") + time.now()

# Access time properties
print(now.month, now.day, now.year)

Nodlin-specific Libraries

webform

Create web forms for node UI.

# Create form
form = webform.WebForm()

# Add user input control
userOpts = webform.nodlinUserOpts(help="Select user", singleContactOnly=False)
form.addUserInput('responsible', 'Responsible', userOpts)

# Access selected values
if 'responsible' in value:
    for u in value.responsible['users']:
        print("USER %s in domain %s" % (u['userid'], u['domain']))
    for g in value.responsible['groups']:
        print("GROUP %s in domain %s" % (g['group'], g['domain']))

gridder

Grid layout utilities for SVG generation.

color

Color manipulation utilities.

font

Font utilities for text rendering.

textbox

Text box utilities for SVG text.

image

Image manipulation utilities.

datetime

Additional date/time utilities.


Best Practices

Checkpoints

Use checkpoints to ensure operations complete successfully before dependent operations:

# Create node
newID = node.linkToNewNode(scriptFQN="...", label="Task")

# Checkpoint before creating action
C.checkpoint()

# Create action (requires node to exist)
actionID = node.createAction(nodeid=newID, comment="Review")

# Checkpoint before assignment
C.checkpoint()

# Create assignment (requires action to exist)
node.createAssignment(nodeid=newID, actionid=actionID, user=("user", "domain"))

Error Handling

Use fail() to abort script execution:

if not hasattr(value, 'requiredField'):
    fail("requiredField is required")

Default Values

Use getattr() for safe property access with defaults:

# Safe access with default
total = getattr(expense, 'value', 0)

# Dictionary safe access
creditAmount = values.get("credit", 0)

String Formatting

Use string interpolation for output:

x = 0.5
print("Value: %g" % x)  # Compact form
print("Value: %f" % x)  # Full float
print("Name: %s" % name)  # String

Raw Strings

Use raw strings for SVG and special characters:

svg = r'<svg xmlns="http://www.w3.org/2000/svg">...</svg>'

Parameterized Strings

Use .format() for template strings:

template = """
<svg viewBox="0 0 {width} {height}">
    <text>{label}</text>
</svg>
"""
params = {'width': 100, 'height': 50, 'label': 'My Label'}
value.image = template.format(**params)

Reserved Properties

The following properties have special meanings on nodes and should be set appropriately:

  • __name - User-defined node name/alias
  • __affordances - List of possible relationships (set via setAffordance())
  • value - For expression nodes, contains evaluated result as JSON
  • condition - For condition nodes, contains boolean result

Types Reference

Node Types

  • Node - Represents a node in the graph
  • NodeContext - Execution context information
  • NodePayload - User-defined data on a node
  • NodeList - List of nodes

Relationship Types

  • Relationships - Dictionary of relationships from a node
  • RelatedTypes - Dictionary of node types over a relationship
  • Affordance - Possible relationship definition

Operation Types

  • Action - User-initiated operation
  • Event - System-triggered operation

Action/Assignment Types

  • UserAction - Action assigned to users/groups
  • UserActionList - Collection of actions
  • Assignment - Assignment of action to user or group

Additional Notes

  1. Immutability: The N (node) and C (context) objects are frozen and cannot be modified. Only V (value) can be modified.

  2. Case Sensitivity: Relationship names are case-sensitive when using the full form. CamelCase conversion is applied for short forms.

  3. FQN Format: Fully Qualified Names (for internal node types) follow the pattern: <type>_<domain>_<user/group>_<agentName>_<name>

type can be one of ‘agt’ to represent node type, or agr for relation.

Examples:

  • agt_core_all_expressionAgent_chart (the ‘chart’ node type)
  • agr_core_all_expressionAgent_chart_for (relation from ‘chart’ to node containing detail to chart)
  • agt_master_admin_openai_requestWithSchema (node type for an openai request with a schema)
  • agt_core_all_basicTypeAgent_basicNote (node type for a basic note)
  • agt_core_all_expressionAgent_expression (node type containing an expression)
  1. Script FQN: Fully Qualified Names (for script node types) follow the pattern: <user|group>.<user or group name>.<domain>.<package>.<nodeType>[.<version>]

Examples:

  • user.johnha.master.togaf.workPackage
  • user.johnha.master.evidenceGraph.justification.prodVersion
  1. Script Relationships: For script-to-script relationships, the formal relationship is always agt_core_all_scriptAgent_scriptNode or short form depends_on, with custom names stored in metadata.

Note that nodlin allows for agents to be defined that provide the logic for managing a group of nodes. The python (starlark) scripting agent is just one such example and conforms to the same interface as other agents. All script nodes in nodlin connect to other script nodes over the depends_on relationship. The python script agent however records additional detail in metadata on the node and relationship to allow it to function like other external and internal agents.

  1. Node Caching: Only directly related nodes are cached. Reference nodes (placeholder nodes) are created for nodes not in the cache.

  2. Value Storage: Changes to V/value are only persisted if the script completes successfully without errors.