Flow Control
After understanding the address space, pointer vectors, and node jumps, we can dive into AmritaSense’s flow control capabilities. This is the core competitive advantage of AmritaSense as a general-purpose workflow orchestration engine. It provides a complete, Turing-complete set of control flow primitives, so you can orchestrate arbitrarily complex asynchronous tasks with an intuition close to that of a programming language.
3.3.1 Conditional branching
AmritaSense is natively Turing-complete, and it includes a first-class conditional branching syntax. Unlike graph-based workflow engines that simulate branching with “routing functions + string maps,” AmritaSense makes conditional branches a built-in primitive.
Basic usage
The IF primitive supports the following patterns:
IF(condi, do) # basic IF, execute do when condition is true
IF(condi, do).ELSE(else_do) # IF-ELSE branch
IF(condi, do).ELIF(condi2, do2) # IF-ELIF chain
IF(condi, do).ELIF(condi2, do2).ELSE(else_do) # full IF-ELIF-ELSE chainIt fully reproduces Python-style elif chain syntax. The number of ELIF entries in the syntax chain is unlimited and can expand arbitrarily. Each ELIF is automatically expanded into standard address-jump triplets at compile time, so runtime execution requires no string matching or dictionary lookup.
Key features
- The underlying type of each condition is
Node[bool]: the condition expression itself is also a node. That means the condition can be a simple function, an asynchronous function, or even a complex node with dependency injection. This design ensures the philosophy of “everything is a node” is consistent throughout. - Seamless sync/async mixing: whether the condition returns
boolsynchronously or returns an awaitableboolasynchronously, the engine normalizes it to a unified execution interface automatically. - Static address calculation at compile time: all branch jump offsets are computed during
render(), so runtime execution only involves pointer vector arithmetic. There is no graph traversal or string hashing overhead.
3.3.2 Loops
AmritaSense natively includes node-level loop primitives and supports two standard loop paradigms: WHILE and DO-WHILE. Both align with classic programming language semantics and treat the loop condition itself as a composable node.
WHILE statement
The semantic behavior is the same as Python’s while ...: conditional loop: first evaluate the condition, then execute the loop body if it is true, and repeat.
WHILE(condition).ACTION(action_node)Key point: the loop condition itself is an independent composable node. This is very different from frameworks that hardcode the condition into a routing function. Your condition can be arbitrary async logic, can accept dependency injection, and can be suspended before evaluation.
DO-WHILE statement
Python does not have native do-while syntax, but its behavior is equivalent to C’s do-while. The core semantics are: execute the DO block at least once, then evaluate the condition.
DO(do_node).WHILE(condition)This is useful when you need to “execute first, then check” — for example, send an initial network request and decide whether to retry based on the response.
Breaking out of a loop
If you need to terminate a loop early (equivalent to break), you can raise BreakLoop inside the ACTION or DO node. The outer WHILE or DO-WHILE will catch that signal and cleanly terminate the loop instance.
@Node()
def early_exit():
if some_condition:
raise BreakLoop
do_something()Equivalent of continue
Sense loops do not provide a native continue keyword, but they support a zero-cost equivalent: simply return early from the current node. The interpreter will naturally advance to the next loop condition check (or to the loop entry point), yielding behavior equivalent to continue.
3.3.3 Exception handling
AmritaSense natively provides a node-domain TRY/CATCH exception handling system. This is a capability that traditional workflow engines often lack. In AmritaSense, exception handling is a first-class citizen alongside conditionals and loops.
Full usage
TRY(do).CATCH(exc, handler) # catch a specific exception
TRY(do).FINALLY(cleanup) # finally block only
TRY(do).CATCH(exc, handler).FINALLY(cleanup) # catch + cleanup
TRY(do).THEN(success).CATCH(exc, handler).FINALLY(cleanup) # full four-part structure
TRY(do).CATCH(exc, handler).THEN(success) # catch + success branch
TRY(do).CATCH(exc1, handler1).CATCH(exc2, handler2).FINALLY(cleanup) # multiple catchesThe overall logic is highly aligned with Python’s try-except-else-finally. The differences are:
- Use
CATCHto declare the exception type to catch and a corresponding handler node. - Use
THENas the equivalent of Python’selsebranch — it executes only if theTRYblock completes without an exception.
Syntax constraints
TRYmust be followed by at least oneCATCHorFINALLY.- A single
TRYstructure can define at most oneFINALLYand oneTHEN. CATCHmay be defined multiple times. The engine uses a top-to-bottom short-circuit matching rule — the first matchingCATCHis executed and later catches are ignored.
Special exception penetration rules
Unlike general-purpose languages, AmritaSense introduces an exception penetration mechanism. If a type is marked in WorkflowInterpreter’s exception_ignored parameter, that exception will be skipped by the current layer of CATCH blocks and will propagate upward to the global handler.
pc = WorkflowPC(nd, exception_ignored=(CriticalError,))This design lets developers mark exceptions as “non-recoverable” or “globally handled,” ensuring they are not accidentally swallowed by intermediate CATCH logic. This is important for complex, multi-layered workflows — local fault tolerance should not intercept critical signals.
Summary
From conditionals and loops to exception handling, AmritaSense’s flow control system covers all core structured programming paradigms. These capabilities are not “simulated” through an external DSL or graph topology; they are directly encoded as first-class primitives in the instruction set and interpreter. In the next chapter, we will explore execution and interrupt control at runtime.
