mycorrhizal 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mycorrhizal/_version.py +1 -1
- mycorrhizal/common/__init__.py +15 -3
- mycorrhizal/common/cache.py +114 -0
- mycorrhizal/common/compilation.py +263 -0
- mycorrhizal/common/interface_detection.py +159 -0
- mycorrhizal/common/interfaces.py +3 -50
- mycorrhizal/common/mermaid.py +124 -0
- mycorrhizal/common/wrappers.py +1 -1
- mycorrhizal/hypha/core/builder.py +11 -1
- mycorrhizal/hypha/core/runtime.py +242 -107
- mycorrhizal/mycelium/__init__.py +174 -0
- mycorrhizal/mycelium/core.py +619 -0
- mycorrhizal/mycelium/exceptions.py +30 -0
- mycorrhizal/mycelium/hypha_bridge.py +1143 -0
- mycorrhizal/mycelium/instance.py +440 -0
- mycorrhizal/mycelium/pn_context.py +276 -0
- mycorrhizal/mycelium/runner.py +165 -0
- mycorrhizal/mycelium/spores_integration.py +655 -0
- mycorrhizal/mycelium/tree_builder.py +102 -0
- mycorrhizal/mycelium/tree_spec.py +197 -0
- mycorrhizal/rhizomorph/README.md +82 -33
- mycorrhizal/rhizomorph/core.py +287 -119
- mycorrhizal/septum/TRANSITION_REFERENCE.md +385 -0
- mycorrhizal/{enoki → septum}/core.py +326 -100
- mycorrhizal/{enoki → septum}/testing_utils.py +7 -7
- mycorrhizal/{enoki → septum}/util.py +44 -21
- mycorrhizal/spores/__init__.py +3 -3
- mycorrhizal/spores/core.py +149 -28
- mycorrhizal/spores/dsl/__init__.py +8 -8
- mycorrhizal/spores/dsl/hypha.py +3 -15
- mycorrhizal/spores/dsl/rhizomorph.py +3 -11
- mycorrhizal/spores/dsl/{enoki.py → septum.py} +26 -77
- mycorrhizal/spores/encoder/json.py +21 -12
- mycorrhizal/spores/extraction.py +14 -11
- mycorrhizal/spores/models.py +53 -20
- mycorrhizal-0.2.0.dist-info/METADATA +335 -0
- mycorrhizal-0.2.0.dist-info/RECORD +54 -0
- mycorrhizal-0.1.2.dist-info/METADATA +0 -198
- mycorrhizal-0.1.2.dist-info/RECORD +0 -39
- /mycorrhizal/{enoki → septum}/__init__.py +0 -0
- {mycorrhizal-0.1.2.dist-info → mycorrhizal-0.2.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# Septum Transition Types - Complete Reference
|
|
2
|
+
|
|
3
|
+
This document provides a comprehensive reference for all transition types in the Septum FSM system.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
In Septum, state transitions determine how the state machine moves between states. Each transition type has specific semantics for lifecycle methods (`on_enter`, `on_leave`, `on_state`) and return values that control execution flow.
|
|
8
|
+
|
|
9
|
+
## Return Value Meaning
|
|
10
|
+
|
|
11
|
+
In the original implementation, `_process_transition` returns a boolean:
|
|
12
|
+
- **True**: Check the queue (continue the loop to wait for/process next message)
|
|
13
|
+
- **False**: Don't check queue (stop the loop, return to caller)
|
|
14
|
+
|
|
15
|
+
## Transition Types Table
|
|
16
|
+
|
|
17
|
+
| Transition | Type | Retry Counter | on_leave | on_enter | Next Step | Returns | Notes |
|
|
18
|
+
|------------|------|---------------|----------|----------|-----------|---------|-------|
|
|
19
|
+
| **StateSpec** | Transition | No change | ✅ called | ✅ called | Continue processing | False | Normal state transition |
|
|
20
|
+
| **Again** | Continuation | No change | ❌ not called | ❌ not called | Re-execute on_state immediately | False + background task | Originally: `asyncio.create_task(self.tick(timeout=0))` |
|
|
21
|
+
| **Unhandled** | Continuation | No change | ❌ not called | ❌ not called | Wait for message | True* | Only if can_dwell=True or has timeout, else raises BlockedInUntimedState |
|
|
22
|
+
| **Repeat** | Renewal | Reset | ✅ called | ✅ called | Continue processing | False | Re-enters state from on_enter, resets retry counter |
|
|
23
|
+
| **Restart** | Renewal | Reset | ✅ called | ✅ called | Wait for message | True | Re-enters state from on_enter, resets retry counter, then waits |
|
|
24
|
+
| **Retry** | Continuation | Increment | ✅ called | ✅ called | Continue processing | False | Decrements retry counter, fails if exceeded |
|
|
25
|
+
| **Push(states...)** | Transition | No change | ✅ called | ✅ called | Continue processing | False | Pushes states to stack, enters first state |
|
|
26
|
+
| **Pop** | Transition | No change | ✅ called | ✅ called | Continue processing | False | Pops from stack, enters popped state |
|
|
27
|
+
| **None** | - | No change | ❌ not called | ❌ not called | Wait for message | True* | Same as Unhandled |
|
|
28
|
+
|
|
29
|
+
## Key Behaviors
|
|
30
|
+
|
|
31
|
+
### Continuation (stay in same state)
|
|
32
|
+
|
|
33
|
+
**Again**: Stay in state, re-execute on_state immediately (no on_leave/on_enter)
|
|
34
|
+
- Use for: Immediate re-execution without waiting for events
|
|
35
|
+
- Does NOT call lifecycle methods
|
|
36
|
+
- Creates background task for continuous execution
|
|
37
|
+
|
|
38
|
+
**Unhandled**: Stay in state, wait for next message
|
|
39
|
+
- Use for: Waiting for events/messages
|
|
40
|
+
- Does NOT call lifecycle methods
|
|
41
|
+
- Requires `can_dwell=True` or timeout, otherwise raises `BlockedInUntimedState`
|
|
42
|
+
|
|
43
|
+
**Retry**: Stay in state, re-enter from on_enter, increment retry counter
|
|
44
|
+
- Use for: Retry logic with failure handling
|
|
45
|
+
- Calls lifecycle methods
|
|
46
|
+
- Decrements retry counter, fails if exceeded
|
|
47
|
+
|
|
48
|
+
### Renewal (re-enter current state)
|
|
49
|
+
|
|
50
|
+
**Repeat**: Re-enter from on_enter, reset retry counter, continue immediately
|
|
51
|
+
- Use for: Reset state and continue processing
|
|
52
|
+
- Calls lifecycle methods
|
|
53
|
+
- Resets retry counter
|
|
54
|
+
- Returns False (continues processing)
|
|
55
|
+
|
|
56
|
+
**Restart**: Re-enter from on_enter, reset retry counter, then wait for message
|
|
57
|
+
- Use for: Reset state and wait for next event
|
|
58
|
+
- Calls lifecycle methods
|
|
59
|
+
- Resets retry counter
|
|
60
|
+
- Returns True (waits for message)
|
|
61
|
+
|
|
62
|
+
### State Change
|
|
63
|
+
|
|
64
|
+
**StateSpec**: Leave current state, enter new state
|
|
65
|
+
- Calls `on_leave` of current state
|
|
66
|
+
- Transitions to new state
|
|
67
|
+
- Calls `on_enter` of new state
|
|
68
|
+
- Returns False (doesn't check queue)
|
|
69
|
+
|
|
70
|
+
**Push**: Stack states, enter first pushed state
|
|
71
|
+
- Pushes states onto stack
|
|
72
|
+
- Transitions to first pushed state (calls on_leave/on_enter)
|
|
73
|
+
- Returns False (continues processing)
|
|
74
|
+
|
|
75
|
+
**Pop**: Return to previous stacked state
|
|
76
|
+
- Pops state from stack
|
|
77
|
+
- Transitions to popped state (calls on_leave/on_enter)
|
|
78
|
+
- Returns False (continues processing)
|
|
79
|
+
|
|
80
|
+
## The Again Problem
|
|
81
|
+
|
|
82
|
+
### Original Implementation
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
elif target == Again:
|
|
86
|
+
# Schedule another tick without blocking
|
|
87
|
+
asyncio.create_task(self.tick(timeout=0))
|
|
88
|
+
return False # Always returns False
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Issue:** The background task approach works for `run()` but causes issues with manual `tick()` calls.
|
|
92
|
+
|
|
93
|
+
### Current Decorator-Based Implementation Issue
|
|
94
|
+
|
|
95
|
+
- Manual `tick(timeout=0)` expects one state execution per call
|
|
96
|
+
- But `Again` means "keep going immediately"
|
|
97
|
+
- These are conflicting requirements!
|
|
98
|
+
|
|
99
|
+
## Proposed Solution
|
|
100
|
+
|
|
101
|
+
The `Again` transition needs different behavior based on context:
|
|
102
|
+
|
|
103
|
+
1. **When called from `run()`**: Keep the state machine running continuously
|
|
104
|
+
2. **When called from manual `tick()`**: Execute once and return
|
|
105
|
+
|
|
106
|
+
The key insight is that `Again` should return `True` (continue loop) when we want continuous execution, but return `False` (stop loop) when we want manual control.
|
|
107
|
+
|
|
108
|
+
The `block` parameter indicates the mode:
|
|
109
|
+
- `block=True` (automatic/run mode): Again returns True, keeps looping
|
|
110
|
+
- `block=False` (manual/tick mode): Again returns False, stops after one execution
|
|
111
|
+
|
|
112
|
+
## Implementation Guide
|
|
113
|
+
|
|
114
|
+
For each transition type, the implementation should:
|
|
115
|
+
|
|
116
|
+
### 1. StateSpec
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# Call on_leave of current state
|
|
120
|
+
await current_state.on_leave(ctx)
|
|
121
|
+
# Transition to new state
|
|
122
|
+
self.current_state = new_state
|
|
123
|
+
# Call on_enter of new state
|
|
124
|
+
await new_state.on_enter(ctx)
|
|
125
|
+
# Return False (don't check queue)
|
|
126
|
+
return False
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 2. Again
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# Do NOT call on_leave/on_enter
|
|
133
|
+
# Re-execute on_state immediately
|
|
134
|
+
if block:
|
|
135
|
+
# Run mode: keep looping
|
|
136
|
+
return True
|
|
137
|
+
else:
|
|
138
|
+
# Manual mode: stop after this execution
|
|
139
|
+
return False
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 3. Unhandled/None
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# Do NOT call on_leave/on_enter
|
|
146
|
+
if can_dwell or has_timeout:
|
|
147
|
+
# Wait for message
|
|
148
|
+
return True
|
|
149
|
+
else:
|
|
150
|
+
# Can't wait in untimed state
|
|
151
|
+
raise BlockedInUntimedState()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 4. Repeat
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
# Call on_leave
|
|
158
|
+
await current_state.on_leave(ctx)
|
|
159
|
+
# Reset retry counter
|
|
160
|
+
self.retry_count = 0
|
|
161
|
+
# Call on_enter
|
|
162
|
+
await current_state.on_enter(ctx)
|
|
163
|
+
# Return False (continue processing)
|
|
164
|
+
return False
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 5. Restart
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
# Call on_leave
|
|
171
|
+
await current_state.on_leave(ctx)
|
|
172
|
+
# Reset retry counter
|
|
173
|
+
self.retry_count = 0
|
|
174
|
+
# Call on_enter
|
|
175
|
+
await current_state.on_enter(ctx)
|
|
176
|
+
# Return True (wait for message)
|
|
177
|
+
return True
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 6. Retry
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
# Increment retry counter
|
|
184
|
+
self.retry_count += 1
|
|
185
|
+
# Check if exceeded
|
|
186
|
+
if self.retry_count > self.max_retries:
|
|
187
|
+
# Call on_fail with its transition
|
|
188
|
+
return await self.on_fail(ctx)
|
|
189
|
+
# Else: Call on_leave, call on_enter
|
|
190
|
+
await current_state.on_leave(ctx)
|
|
191
|
+
await current_state.on_enter(ctx)
|
|
192
|
+
# Return False (continue processing)
|
|
193
|
+
return False
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 7. Push
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
# Push states to stack
|
|
200
|
+
for state in reversed(states):
|
|
201
|
+
self.state_stack.append(state)
|
|
202
|
+
# Transition to first pushed state (calls on_leave/on_enter)
|
|
203
|
+
new_state = states[0]
|
|
204
|
+
await current_state.on_leave(ctx)
|
|
205
|
+
self.current_state = new_state
|
|
206
|
+
await new_state.on_enter(ctx)
|
|
207
|
+
# Return False (continue processing)
|
|
208
|
+
return False
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 8. Pop
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
# Pop state from stack
|
|
215
|
+
new_state = self.state_stack.pop()
|
|
216
|
+
# Transition to popped state (calls on_leave/on_enter)
|
|
217
|
+
await current_state.on_leave(ctx)
|
|
218
|
+
self.current_state = new_state
|
|
219
|
+
await new_state.on_enter(ctx)
|
|
220
|
+
# Return False (continue processing)
|
|
221
|
+
return False
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Usage Examples
|
|
225
|
+
|
|
226
|
+
### Basic State Transition
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
@septum.state
|
|
230
|
+
def StateA():
|
|
231
|
+
class Events(Enum):
|
|
232
|
+
GO_TO_B = auto()
|
|
233
|
+
|
|
234
|
+
@septum.on_state
|
|
235
|
+
async def on_state(ctx):
|
|
236
|
+
return Events.GO_TO_B
|
|
237
|
+
|
|
238
|
+
@septum.transitions
|
|
239
|
+
def transitions():
|
|
240
|
+
return [
|
|
241
|
+
LabeledTransition(Events.GO_TO_B, StateB),
|
|
242
|
+
]
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Retry with Counter
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
@septum.state(config=StateConfiguration(max_retries=3))
|
|
249
|
+
def AttemptOperation():
|
|
250
|
+
class Events(Enum):
|
|
251
|
+
SUCCESS = auto()
|
|
252
|
+
RETRY = auto()
|
|
253
|
+
FAIL = auto()
|
|
254
|
+
|
|
255
|
+
@septum.on_state
|
|
256
|
+
async def on_state(ctx):
|
|
257
|
+
try:
|
|
258
|
+
# Attempt operation
|
|
259
|
+
result = await risky_operation()
|
|
260
|
+
return Events.SUCCESS
|
|
261
|
+
except Exception:
|
|
262
|
+
if ctx.retry_count < 3:
|
|
263
|
+
return Events.RETRY
|
|
264
|
+
else:
|
|
265
|
+
return Events.FAIL
|
|
266
|
+
|
|
267
|
+
@septum.on_fail
|
|
268
|
+
async def on_fail(ctx):
|
|
269
|
+
# Called when retries exceeded
|
|
270
|
+
logger.error("Operation failed after retries")
|
|
271
|
+
return StateShutdown
|
|
272
|
+
|
|
273
|
+
@septum.transitions
|
|
274
|
+
def transitions():
|
|
275
|
+
return [
|
|
276
|
+
LabeledTransition(Events.SUCCESS, NextState),
|
|
277
|
+
LabeledTransition(Events.RETRY, Retry),
|
|
278
|
+
LabeledTransition(Events.FAIL, ErrorHandler),
|
|
279
|
+
]
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Push/Pop for Sub-states
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
@septum.state
|
|
286
|
+
def MainMenu():
|
|
287
|
+
class Events(Enum):
|
|
288
|
+
START_SETTINGS = auto()
|
|
289
|
+
EXIT = auto()
|
|
290
|
+
|
|
291
|
+
@septum.on_state
|
|
292
|
+
async def on_state(ctx):
|
|
293
|
+
# Push SettingsMenu state
|
|
294
|
+
return Push(SettingsMenu)
|
|
295
|
+
|
|
296
|
+
@septum.transitions
|
|
297
|
+
def transitions():
|
|
298
|
+
return [
|
|
299
|
+
LabeledTransition(Events.START_SETTINGS, Push(SettingsMenu)),
|
|
300
|
+
LabeledTransition(Events.EXIT, Pop),
|
|
301
|
+
]
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Best Practices
|
|
305
|
+
|
|
306
|
+
1. **Use StateSpec for normal transitions** - Most transitions should be direct state-to-state
|
|
307
|
+
2. **Use Retry for failure recovery** - Leverage retry counter for transient failures
|
|
308
|
+
3. **Use Push/Pop for hierarchical navigation** - Stack-based navigation for menus/wizards
|
|
309
|
+
4. **Use Repeat/Restart sparingly** - Only when you need to reset state
|
|
310
|
+
5. **Avoid Again in most cases** - Use proper state transitions instead
|
|
311
|
+
|
|
312
|
+
## Common Patterns
|
|
313
|
+
|
|
314
|
+
### Pattern 1: Timeout with Retry
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
@septum.state(config=StateConfiguration(timeout=5.0, max_retries=3))
|
|
318
|
+
def WaitForResponse():
|
|
319
|
+
class Events(Enum):
|
|
320
|
+
RECEIVED = auto()
|
|
321
|
+
TIMEOUT = auto()
|
|
322
|
+
|
|
323
|
+
@septum.on_timeout
|
|
324
|
+
async def on_timeout(ctx):
|
|
325
|
+
return Events.TIMEOUT
|
|
326
|
+
|
|
327
|
+
@septum.on_state
|
|
328
|
+
async def on_state(ctx):
|
|
329
|
+
# Wait for response message
|
|
330
|
+
return Events.RECEIVED
|
|
331
|
+
|
|
332
|
+
@septum.transitions
|
|
333
|
+
def transitions():
|
|
334
|
+
return [
|
|
335
|
+
LabeledTransition(Events.RECEIVED, ProcessResponse),
|
|
336
|
+
LabeledTransition(Events.TIMEOUT, Retry),
|
|
337
|
+
]
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Pattern 2: Hierarchical Menu
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
@septum.state
|
|
344
|
+
def MainMenu():
|
|
345
|
+
@septum.on_state
|
|
346
|
+
async def on_state(ctx):
|
|
347
|
+
# Show menu options
|
|
348
|
+
return Events.SHOW_SETTINGS
|
|
349
|
+
|
|
350
|
+
@septum.transitions
|
|
351
|
+
def transitions():
|
|
352
|
+
return [
|
|
353
|
+
LabeledTransition(Events.SHOW_SETTINGS, Push(SettingsMenu)),
|
|
354
|
+
LabeledTransition(Events.BACK, Pop),
|
|
355
|
+
]
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Pattern 3: Error Recovery with Restart
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
@septum.state
|
|
362
|
+
def ProcessingState():
|
|
363
|
+
class Events(Enum):
|
|
364
|
+
ERROR = auto()
|
|
365
|
+
|
|
366
|
+
@septum.on_state
|
|
367
|
+
async def on_state(ctx):
|
|
368
|
+
try:
|
|
369
|
+
await process()
|
|
370
|
+
return Events.DONE
|
|
371
|
+
except Exception:
|
|
372
|
+
return Events.ERROR
|
|
373
|
+
|
|
374
|
+
@septum.transitions
|
|
375
|
+
def transitions():
|
|
376
|
+
return [
|
|
377
|
+
LabeledTransition(Events.ERROR, Restart),
|
|
378
|
+
]
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## References
|
|
382
|
+
|
|
383
|
+
- Septum library documentation
|
|
384
|
+
- State machine design patterns
|
|
385
|
+
- Asyncio best practices
|