Skip to content

Commit a01cdee

Browse files
Feats: Event system;Update docsing
1 parent 7dedc6a commit a01cdee

67 files changed

Lines changed: 3287 additions & 2745 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,4 +363,7 @@ test-results.xml
363363

364364
# Pytest
365365

366-
test-results.xml
366+
test-results.xml
367+
368+
# Other
369+
temp/

docs/.vitepress/config.mts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ export default withMermaid({
119119
text: "Dependency Injection",
120120
link: "/guide/advanced/dependency_injection",
121121
},
122+
{
123+
text: "Event System",
124+
link: "/guide/advanced/event_system",
125+
},
122126
{
123127
text: "Locating & Scope",
124128
link: "/guide/advanced/locating_and_space",
@@ -178,6 +182,10 @@ export default withMermaid({
178182
text: "Self-Compile Instructions",
179183
link: "/reference/api/self-compile",
180184
},
185+
{
186+
text: "SuspendObjectStream",
187+
link: "/reference/api/suspend-object-stream",
188+
},
181189
],
182190
},
183191
{ text: "Appendix", link: "/guide/appendix" },
@@ -257,6 +265,10 @@ export default withMermaid({
257265
text: "依赖注入",
258266
link: "/zh/guide/advanced/dependency_injection",
259267
},
268+
{
269+
text: "事件系统",
270+
link: "/zh/guide/advanced/event_system",
271+
},
260272
{
261273
text: "定位与空间",
262274
link: "/zh/guide/advanced/locating_and_space",
@@ -310,6 +322,10 @@ export default withMermaid({
310322
{ text: "类型系统", link: "/zh/reference/api/types" },
311323
{ text: "异常系统", link: "/zh/reference/api/exceptions" },
312324
{ text: "自编译指令", link: "/zh/reference/api/self-compile" },
325+
{
326+
text: "SuspendObjectStream",
327+
link: "/zh/reference/api/suspend-object-stream",
328+
},
313329
],
314330
},
315331
{ text: "附录", link: "/zh/guide/appendix" },

docs/guide/advanced/built-in_instruction_set/jump_clause.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
> **Common foundation**
66
> Both rely on the same alias registry (`ALIAS`), perform address resolution during `_pre_check`, and use the `@markup` decorator to manage jump markers. Understanding these common mechanisms helps clarify the difference between them.
77
8-
---
9-
108
## Non-self-compiled direct nodes
119

1210
`GOTO` and `CALL` are **not** `SelfCompileInstruction`. They do not expand into `NodeCompose` at compile time; instead, they exist as single jump nodes in the workflow array.
@@ -17,8 +15,6 @@ That means:
1715
- runtime behavior is expressed entirely through address resolution and pointer rewriting
1816
- they do not create new Bubbles or nesting scopes
1917

20-
---
21-
2218
## GOTO
2319

2420
`GOTO` is a factory wrapper for `JumpNode`. At runtime, it calls the interpreter’s `jump_to` method and performs a **one-way, non-returning** pointer rewrite.
@@ -42,8 +38,6 @@ That means:
4238
- **Branch merging**: multiple conditional branches converge on the same `NOP` point.
4339
- **State machine transitions**: jump to different next states based on runtime conditions.
4440

45-
---
46-
4741
## CALL
4842

4943
`CALL` is a factory wrapper for `CallNode`. At runtime, it calls the interpreter’s `call_sub` method and performs a **push → jump → execute → pop** subroutine call.
@@ -69,8 +63,6 @@ That means:
6963
- **Modular decomposition**: split complex workflows into independent subprocedures and keep the main flow simple.
7064
- **Interrupt handling**: external systems can invoke predefined interrupt handlers via `call_sub(interrupt=True)`.
7165

72-
---
73-
7466
## GOTO vs CALL: comparison
7567

7668
| Feature | GOTO | CALL |
@@ -81,17 +73,13 @@ That means:
8173
| Use cases | one-way jump, branch merge, error handling | subroutine reuse, modular flow, interrupts |
8274
| Underlying API | `pc.jump_to(addr)` | `pc.call_sub(addr)` |
8375

84-
---
85-
8676
## Usage notes
8777

8878
- **GOTO is not a substitute for loops**: `GOTO` does not provide return semantics. Jumping out of a loop with `GOTO` will not correctly manage the loop state. Use `BreakLoop` for loop exit and `CALL` for reusable subroutines.
8979
- **CALL targets must be addressable**: the target node or entry node must have `address_able=True`, which is required by `ALIAS`.
9080
- **GOTO and CALL share alias space**: both look up aliases in `alias2vector_map`. Avoid alias name conflicts.
9181
- **CALL return depends on stack integrity**: a `GOTO` inside a subroutine sets `_jump_marked`, which can cause `call_sub` to skip stack restoration. Understand that `GOTO` inside a subroutine can override normal return behavior.
9282

93-
---
94-
9583
## Example
9684

9785
```python

docs/guide/advanced/built-in_instruction_set/sentinel_clause.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
`NOP` and `INTERRUPT` are two special atomic instructions in AmritaSense’s instruction set. They are not `SelfCompileInstruction` and have no compile-time expansion. Instead, they exist as individual nodes in the workflow, and their behavior is defined purely at runtime.
44

5-
---
6-
75
## NOP sentinel instruction
86

97
`NOP` (No Operation) is an **empty sentinel node**. Its significance is not in doing work, but in **standing in place** to provide a valid jump target.
@@ -37,8 +35,6 @@ IF(cond, GOTO("then")) >> ... >> ALIAS(NOP, "end_if")
3735

3836
**As an empty ELSE branch**: `IF(cond, do).ELSE(NOP)` expresses “do nothing when the condition is false” explicitly.
3937

40-
---
41-
4238
## INTERRUPT forced termination instruction
4339

4440
`INTERRUPT` is the workflow’s **emergency stop button**. When executed, the workflow terminates immediately and unconditionally.
@@ -76,8 +72,6 @@ The only exception is when `InterruptNotice` is explicitly included in the `exce
7672
- **Emergency safety stop**: insert `INTERRUPT` in the workflow when an unrecoverable error or dangerous condition occurs.
7773
- **Timeout handling**: a node can check timeout conditions before execution and raise `INTERRUPT` to force termination.
7874

79-
---
80-
8175
## Comparison summary
8276

8377
| | NOP | INTERRUPT |

docs/guide/advanced/built-in_instruction_set/try_clause.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
AmritaSense provides a complete exception handling instruction system that aligns closely with Python’s `try-except-else-finally`. Before using it, ask yourself: **when should you use instruction-based exception handling, and when should you write `try-except` inside a node?**
44

5-
---
6-
75
## Choosing between two exception handling modes
86

97
### Inline try-catch inside a node
@@ -56,8 +54,6 @@ TRY(call_api).CATCH(TimeoutError, use_cache).CATCH(AuthError, refresh_token).FIN
5654
> **Core principle**
5755
> Use Python for exceptions that belong inside a node. Use instructions for workflow-level exception control. They can be mixed — a TRY block may contain nodes that use their own inline try-catch.
5856
59-
---
60-
6157
## Instruction syntax and semantics
6258

6359
### Full syntax
@@ -86,8 +82,6 @@ TRY(do).CATCH(exc1, handler1).CATCH(exc2, handler2).FINALLY(cleanup)
8682
2. A single `TRY` structure may define at most one `FINALLY` and one `THEN`.
8783
3. `CATCH` may appear multiple times and is matched top-to-bottom with short-circuit behavior.
8884

89-
---
90-
9185
## Runtime execution logic
9286

9387
`TryClause` is a `SelfCompileInstruction` and expands at compile time into:
@@ -110,8 +104,6 @@ The runtime behavior of `TryNode` is:
110104
- if no catch matches, re-raise the exception
111105
4. **Regardless of exception**: the `finally` block executes via `call_near` if defined.
112106

113-
---
114-
115107
## Exception penetration rules
116108

117109
When the `WorkflowInterpreter` is initialized with `exception_ignored`, those exception types are not caught by any `CATCH` block:
@@ -129,8 +121,6 @@ When `TryNode` encounters one of these exceptions, it re-raises it immediately,
129121
- **`BreakLoop`** can jump out of the innermost loop and is not intercepted by intermediate exception handlers.
130122
- **Critical errors** can bypass local fault tolerance and reach a global handler.
131123

132-
---
133-
134124
## Usage examples
135125

136126
### Instruction orchestration: API call fault tolerance
@@ -178,8 +168,6 @@ TRY(risky_op)\
178168
TRY(acquire_resource).FINALLY(release_resource)
179169
```
180170

181-
---
182-
183171
## Dependency injection in exception handlers
184172

185173
Nodes in `CATCH`, `THEN`, and `FINALLY` blocks can use `Depends` normally. The caught exception object itself can also be injected into a handler node — this is a unique advantage of instruction-based exception handling over inline try-catch.

docs/guide/advanced/built-in_instruction_set/while_clause.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
AmritaSense provides two standard loop paradigms: `WHILE` (check before executing) and `DO-WHILE` (execute before checking). Both are `SelfCompileInstruction` and expand at compile time into a fixed structure containing jump nodes. At runtime, the loop semantics are implemented entirely through pointer offsets and `jump_near`, without any external state flags.
44

5-
---
6-
75
## Compile-time expansion structure
86

97
### WHILE loop
@@ -41,8 +39,6 @@ AmritaSense provides two standard loop paradigms: `WHILE` (check before executin
4139
> **Key difference**
4240
> WHILE checks the condition before the loop body; DO-WHILE executes the loop body before the condition. Both use `NOP` as the unified loop exit.
4341
44-
---
45-
4642
## Runtime execution flow
4743

4844
### WHILE
@@ -75,8 +71,6 @@ Within `action` or `do_node`, you can `raise BreakLoop` to implement `break` sem
7571

7672
Returning early from `action` or `do_node` ends the current node execution. The interpreter then naturally advances to `CheckUpNode` (for `WHILE`) or `DowhileNode` (for `DO-WHILE`), starting the next iteration. This behavior is equivalent to `continue`.
7773

78-
---
79-
8074
## `GOTO` restrictions inside loops
8175

8276
The compile-time structure of `WHILE` and `DO-WHILE` is fixed. `WhileNode` and `DONode` rely on `call_offset` and `jump_near` relative offsets to perform condition checks and body execution.
@@ -89,8 +83,6 @@ If a `GOTO` inside the loop body jumps outside the loop structure:
8983

9084
Therefore, **do not use `GOTO` to jump out of a loop**. Use `BreakLoop` to exit the loop, and use `CALL` for reusable subroutine execution.
9185

92-
---
93-
9486
## Usage example
9587

9688
```python
@@ -125,8 +117,6 @@ def fetch():
125117
retry = DO(fetch).WHILE(has_more)
126118
```
127119

128-
---
129-
130120
## When to use WHILE vs DO-WHILE
131121

132122
| Scenario | Recommended |
@@ -136,7 +126,5 @@ retry = DO(fetch).WHILE(has_more)
136126
| The condition must be evaluated before the body | `WHILE` |
137127
| The condition only becomes available after the body | `DO-WHILE` |
138128

139-
---
140-
141129
> **Condition nodes are nodes**
142130
> Unlike graph model frameworks that hardcode conditions into routing functions, AmritaSense treats the loop condition itself as a `Node[bool]`. That means the condition can be asynchronous, can accept dependency injection, and can be suspended before evaluation. This is the direct embodiment of the “everything is a node” philosophy in loop structures.

docs/guide/advanced/child_node.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ AmritaSense provides a complete subroutine call mechanism. Unlike `GOTO`’s one
44

55
This chapter starts from the interpreter’s low-level API and explains call stack management, argument passing, and how to invoke subroutines in node code.
66

7-
---
8-
97
## 4.3.1 `call_sub`: the interpreter’s low-level call primitive
108

119
`call_sub` is a low-level call primitive provided by `WorkflowInterpreter`. Both the composition-level `CALL` instruction and node-internal subroutine calls ultimately use it. Its core workflow is:
@@ -30,8 +28,6 @@ This design lets the same call primitive serve both internal reuse and external
3028

3129
After the subroutine completes, `call_sub` checks the `_jump_marked` flag. If the subroutine executed a jump operation such as `GOTO`, that flag is set to `True`. In that case, the `finally` block **does not restore the original execution pointer** — the new jump target is preserved, and the interpreter continues from there. This ensures subroutine-internal jumps can correctly affect the main workflow control flow.
3230

33-
---
34-
3531
## 4.3.2 Passing arguments to subroutines
3632

3733
`call_sub` supports passing extra positional and keyword arguments directly. These arguments are merged into the available parameter pool for the subroutine’s entry node during dependency resolution.
@@ -61,8 +57,6 @@ Subroutine nodes can use both operands passed via `call_sub` and dependencies de
6157
If a subroutine entry node declares a dependency via `Depends` and that provider returns `None`, the workflow raises an exception and terminates. This is different from an event system where a `None` return might be treated as a “skip.” Node execution is atomic, and failed dependency resolution means the node cannot run.
6258
:::
6359

64-
---
65-
6660
## 4.3.3 Call stack and return address restoration
6761

6862
The call stack is the core data structure of AmritaSense’s subroutine mechanism, ensuring correct return behavior for nested calls.
@@ -99,8 +93,6 @@ Every `call_sub` pushes the current address, and every return pops the top addre
9993

10094
If the subroutine executes `GOTO` or another jump operation, `_jump_marked` is set to `True`. In that case, `call_sub` skips restoring the saved pointer and does not pop the return address. This means the subroutine’s internal jump can “override” normal return behavior. Developers should understand that `GOTO` inside a subroutine may prevent automatic return stack cleanup and may require explicit stack management.
10195

102-
---
103-
10496
### Summary
10597

10698
`call_sub` and the call stack form the low-level foundation of AmritaSense’s subroutine system. The composition-level `CALL` instruction is a declarative wrapper around this primitive, while node-internal `call_sub` gives developers full freedom to construct dynamic call chains in code. In the next chapter, we’ll explore how to combine these features with external interruption to build subroutine libraries that can be safely injected from outside.

docs/guide/advanced/custom_instruction.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
AmritaSense’s built-in instruction set already covers core control flow such as conditionals, loops, and exception handling. But when those basic instructions repeatedly appear in fixed patterns, you can encapsulate them as **new instructions** with `SelfCompileInstruction`. This extension does not modify the interpreter; it only expands into standard node compositions at compile time, and at runtime it behaves exactly like built-in instructions.
44

5-
---
6-
75
## 4.7.1 Selfcompiled instruction interface: `SelfCompileInstruction`
86

97
`SelfCompileInstruction` is an abstract base class that defines the unified entry for all self-compiled instructions:
@@ -32,8 +30,6 @@ class SelfCompileInstruction(ABC):
3230
3. If the expanded structure includes jumps, address calculation must be handled inside `extract()`.
3331
4. The returned `NodeCompose` is automatically rendered; you do not need to call `render()` manually.
3432

35-
---
36-
3733
## 4.7.2 Implementation pattern: `extract()` and address calculation
3834

3935
The core task in implementing a custom instruction is mapping an “intention” to a concrete sequence of nodes. This mapping involves three steps:
@@ -82,8 +78,6 @@ This is equivalent to writing:
8278
workflow = start >> log_start >> process_data >> log_end >> end
8379
```
8480

85-
---
86-
8781
## 4.7.3 Example 1: retry wrapper
8882

8983
Wrapping a potentially failing node with retry logic is a typical use case for self-compiled instructions.
@@ -153,8 +147,6 @@ WHILE(lambda: retries < self._max).ACTION(TRY(call_api).CATCH(Exception, on_erro
153147
- Jump addresses are handled by the built-in instructions, so `RetryClause` does not need to manage offsets manually.
154148
- Users see only `RetryClause(...)`, while the expansion remains transparent.
155149

156-
---
157-
158150
## 4.7.4 Example 2: conditional execution wrapper
159151

160152
Encapsulate the common pattern “execute a node when a condition is true, otherwise skip it” as a single instruction.
@@ -195,8 +187,6 @@ class ExecuteWhenElse(SelfCompileInstruction):
195187
)
196188
```
197189

198-
---
199-
200190
## Design principles for custom instructions
201191

202192
1. **Encapsulate patterns, not logic**: custom instructions should encapsulate recurring composition patterns (retry, conditional execution, timeout protection), not concrete business logic. Business logic belongs inside nodes.

docs/guide/advanced/custom_node.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
In AmritaSense, nodes are the basic units of execution flow. Built-in instructions are ultimately expanded into node compositions, while custom nodes are the direct way developers encapsulate their business logic. This chapter explains node essence, lifecycle, and how to use `POINTER_DEPENDS` to gain full interpreter control when necessary.
44

5-
---
6-
75
## 4.6.1 The `@Node` decorator and node essence
86

97
The `@Node()` decorator converts an ordinary Python function or coroutine into a workflow node. It does not do anything complex — it simply packages the function object, signature metadata, and a few data fields into a `Node` instance.
@@ -34,8 +32,6 @@ Each node is essentially a thin wrapper around the original function. It preserv
3432

3533
**Everything is a node** — this is AmritaSense’s core philosophy. Conditionals, loop bodies, exception handlers, and GOTO targets are all `Node` or `BaseNode` instances. Custom nodes are no exception.
3634

37-
---
38-
3935
## 4.6.2 Handling sync and async nodes
4036

4137
AmritaSense unifies synchronous and asynchronous nodes. In `_call()`, it decides how to execute based on `iscoroutinefunction` and `wrap_to_async`.
@@ -84,8 +80,6 @@ This is appropriate for extremely lightweight sync operations, such as simple co
8480

8581
The `Node` class is also memory-optimized using `__slots__`, avoiding a default `__dict__` and keeping each node as lightweight as possible.
8682

87-
---
88-
8983
## 4.6.3 Node lifecycle and atomicity
9084

9185
### Lifecycle
@@ -108,8 +102,6 @@ Node atomicity is guaranteed by the **interpreter lock** and **cooperative inter
108102

109103
If a custom node class inherits from `BaseNode`, it can override `_pre_check`. This method is called before each node execution and can access the current interpreter instance to perform address validation, alias lookup, or other checks. `CallNode` and `JumpNode` both use this mechanism to resolve aliases to addresses before their first execution.
110104

111-
---
112-
113105
## 4.6.4 `POINTER_DEPENDS`: access to the interpreter
114106

115107
`POINTER_DEPENDS` is a special dependency injection factory that allows a node to obtain the current `WorkflowInterpreter` instance.
@@ -140,7 +132,22 @@ With interpreter access, nodes can directly manipulate pointers and the call sta
140132

141133
Therefore, **inject `POINTER_DEPENDS` only when necessary**. Most nodes should use normal Python logic and composition-level instructions (`IF`, `WHILE`, `CALL`) to express control flow, and only directly access the interpreter when instructions cannot express the desired behavior.
142134

143-
---
135+
## 4.6.5 Safe runtime integration
136+
137+
Custom nodes that need runtime context should use `POINTER_DEPENDS` to access `WorkflowInterpreter` rather than manipulating internal interpreter state directly. The interpreter exposes safe APIs such as:
138+
139+
- `jump_to(addr)`
140+
- `jump_near(addr)`
141+
- `jump_offset(offset)`
142+
- `call_sub(addr, *args, interrupt=False)`
143+
- `find_addr_alias(alias)`
144+
- `object_io`
145+
146+
Because `object_io` is a generic `SuspendObjectStream` subclass, custom nodes can also participate in external suspend/resume or streaming I/O patterns without changing the core interpreter loop.
147+
148+
::: tip
149+
If you only need data dependencies, prefer `Depends(...)` with provider functions. Use `POINTER_DEPENDS` only for workflow control or low-level runtime inspection.
150+
:::
144151

145152
### Summary
146153

0 commit comments

Comments
 (0)