Exception System
AmritaSense defines a lean, purpose-specific exception hierarchy for handling various errors and interruptions during workflow execution. Each exception type has clear semantic boundaries and usage constraints.
InterruptNotice
class InterruptNotice(BaseException):
"""Special exception for immediate workflow termination.
Raised by the INTERRUPT node or external systems. As a BaseException
subclass, it bypasses regular CATCH blocks and penetrates directly to
the interpreter's top-level handler, ensuring clean and unconditional
termination.
"""Why inherit from BaseException
Python's except Exception does not catch BaseException subclasses. Therefore, no TRY/CATCH block in the workflow can intercept InterruptNotice by default. This is an intentional design choice — INTERRUPT must be an "uncatchable" emergency termination signal. The only exception is explicitly adding InterruptNotice to exception_ignored, at which point it becomes catchable as a regular exception.
Trigger methods
- Composition level: Automatically raised when execution reaches an
INTERRUPTnode. - External injection: External systems can directly
raise InterruptNotice(); the interpreter catches and terminates at the next node boundary.
Interpreter response
When the interpreter main loop catches InterruptNotice:
- Logs the current pointer position and notification message
- Clears
_ret_addr_stack(the call stack) - Resets
_pointer(the pointer vector) - Resets the
_jump_markedflag - The workflow exits cleanly with no residual state
InterruptKeepContext (v0.4.x+)
InterruptKeepContext is a subclass of InterruptNotice that terminates workflow execution while preserving interpreter state. Unlike InterruptNotice, which triggers a full reset(), this exception leaves the pointer, call stack, and dependency injection parameters intact.
Use cases:
- Pause-inspect-resume debugging
- Checkpoint-restart workflows
- Triggered by the
INTERRUPT_KEEP_CTXinstruction node
NullPointerException
class NullPointerException(Exception):
"""Raised when a node cannot be located at a given address.
This occurs when jump operations reference non-existent nodes,
invalid address vectors, or aliases that failed to resolve.
"""Trigger scenarios
- A GOTO, CALL, or other jump instruction's target address does not exist in the
NodeComposeRendered. - An alias lookup fails in
alias2vector_map(at which pointJumpNodeandCallNode's_post_compileraisesAliasNotFoundError). - An out-of-bounds index is accessed at runtime via
find_addr.
Relationship with alias validation
NullPointerException is the fallback exception for runtime address failures. In practice, when using ALIAS + GOTO/CALL for normal addressing, typos are caught at compile time during _post_compile with correction suggestions. Only raw list[int] addresses that are invalid at runtime will raise this exception.
BreakLoop
class BreakLoop(Exception):
"""Exception used to break out of WHILE or DO-WHILE loop constructs.
When raised within a loop body, the loop terminates immediately and
execution continues after the loop's NOP exit point.
"""Automatic penetration mechanism
BreakLoop is automatically added to the _exc_ignored tuple during WorkflowInterpreter initialization. This means:
- Any
TRY/CATCHblock inside a loop body cannot catchBreakLoop. - It penetrates all intermediate exception handling layers and reaches the innermost
WhileNodeorDONode. - The loop node catches
BreakLoopand executesjump_near(NOP), exiting cleanly.
v0.3.0+: This auto-inclusion can be disabled via
__flags__.DISABLE_EXC_IGNORED = Truefromamrita_sense._unsafe. See Unsafe Features for details.
Usage
Simply raise it inside the ACTION or DO node of a loop body:
@Node()
def process_item():
if item is None:
raise BreakLoop # No more data; exit the loop
if item.should_skip:
return # Equivalent to continue
handle(item)Note: Developers should not manually add BreakLoop to exception_ignored — it is already added automatically during interpreter initialization. Adding it again has no additional effect; attempting to remove it would cause CATCH blocks inside loops to accidentally capture BreakLoop, breaking loop semantics.
IllegalState (v0.3.0+)
Raised when an operation is attempted in an invalid state. Common triggers:
- Calling
terminate_all()orwait_all()on a non-top-level interpreter - Starting
run()on an interpreter that is already running - Accessing
waiton an interpreter that is not running - Violating TRY/CATCH syntax constraints (e.g., adding CATCH after FINALLY, duplicate THEN)
See Subgraph Isolation for correct usage patterns.
DependsException and subclasses
All dependency injection exceptions inherit from DependsException.
class DependsException(Exception):
"""Base class for all dependency injection related exceptions."""AliasNotFoundError (v0.4.x+)
Raised when a GOTO or CALL instruction references an alias that does not exist in the workflow graph's alias registry. Detected at compile time during _post_compile. Replaces the generic RuntimeError / ValueError previously used for alias resolution failures.
DependsResolveFailed
class DependsResolveFailed(Exception):
"""Raised when a node's dependencies cannot be resolved.
This occurs when required parameters in the function signature
cannot be matched to any available dependency source.
"""Trigger conditions
- A node's function signature has an unmatched parameter (no dependency source of matching type, and no default value).
- Multiple dependency sources match the same parameter and cannot be disambiguated.
DependsInjectFailed
class DependsInjectFailed(Exception):
"""Raised when runtime dependency injection fails during node execution.
This typically occurs when a Depends factory function raises an
exception that is not in the exception_ignored tuple.
"""Trigger conditions
- A
Dependsfactory function raises an exception at runtime that is not in_exc_ignored. - When resolving multiple dependencies concurrently, all failures are collected into an
ExceptionGroupand re-raised.
Critical behavior: Depends returning None terminates immediately
Unlike the event system's "return None to skip" behavior, in node execution, if a dependency factory declared via Depends returns None, the workflow raises an exception and terminates immediately. Nodes are atomic execution units, and dependency resolution failure means the node cannot run — this is not a "skip" scenario. Therefore, dependency factory functions designed for nodes should always return a valid value (or raise an explicit exception when unable to provide one, rather than returning None).
GraphBuildError (v0.4.x+)
Raised when workflow graph building or rendering fails. Common triggers:
- Duplicate alias names in the same composition
- Attempting to build an already-built
NodeComposeRendered - Missing original graph during rendering
StreamStateError (v0.4.x+)
Raised when a SuspendObjectStream operation is attempted in an invalid state. Common triggers:
- Pushing to a closed queue
- Calling
wait_to_suspend()when already waiting - Setting a callback function twice
- Calling
get_response_generator()when already being consumed
Exception Hierarchy
BaseException
├── InterruptNotice # Inherits BaseException; uncatchable by CATCH by default
│ └── InterruptKeepContext # Interrupts but preserves interpreter context
Exception
├── IllegalState # Invalid state operation
├── NullPointerException # Invalid address
├── BreakLoop # Loop exit signal
├── AliasNotFoundError # Alias resolution failure
├── GraphBuildError # Graph build/render failure
├── StreamStateError # Stream operation in illegal state
└── DependsException # Dependency injection base class
├── DependsResolveFailed # Dependencies cannot be resolved
└── DependsInjectFailed # Exception during dependency injectionDesign principles:
InterruptNoticeinherits fromBaseException, achieving natural uncatchability.InterruptKeepContextinherits fromInterruptNotice, preserving context for later recovery.BreakLoopinherits fromException, but gains equivalent penetration capability through automatic inclusion in_exc_ignored.IllegalStatewas added in v0.3.0 to protect interpreter tree API call validity and instruction syntax constraints.AliasNotFoundError/GraphBuildError/StreamStateErrorare fine-grained exception types added in v0.4.x+.- All dependency-related exceptions inherit from
DependsException, allowing users to catch the entire dependency error category as needed.
search_exceptions() (v0.3.0+)
from amrita_sense.utils import search_exceptions
def search_exceptions(
seq: Sequence[BaseException | list | None],
) -> list[BaseException]Recursively searches a sequence (potentially containing nested lists of exceptions) and returns a flat list of all BaseException instances. Used internally by FUN_BLOCK to collect exceptions from sub-interpreter trees.
