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.
Files changed (41) hide show
  1. mycorrhizal/_version.py +1 -1
  2. mycorrhizal/common/__init__.py +15 -3
  3. mycorrhizal/common/cache.py +114 -0
  4. mycorrhizal/common/compilation.py +263 -0
  5. mycorrhizal/common/interface_detection.py +159 -0
  6. mycorrhizal/common/interfaces.py +3 -50
  7. mycorrhizal/common/mermaid.py +124 -0
  8. mycorrhizal/common/wrappers.py +1 -1
  9. mycorrhizal/hypha/core/builder.py +11 -1
  10. mycorrhizal/hypha/core/runtime.py +242 -107
  11. mycorrhizal/mycelium/__init__.py +174 -0
  12. mycorrhizal/mycelium/core.py +619 -0
  13. mycorrhizal/mycelium/exceptions.py +30 -0
  14. mycorrhizal/mycelium/hypha_bridge.py +1143 -0
  15. mycorrhizal/mycelium/instance.py +440 -0
  16. mycorrhizal/mycelium/pn_context.py +276 -0
  17. mycorrhizal/mycelium/runner.py +165 -0
  18. mycorrhizal/mycelium/spores_integration.py +655 -0
  19. mycorrhizal/mycelium/tree_builder.py +102 -0
  20. mycorrhizal/mycelium/tree_spec.py +197 -0
  21. mycorrhizal/rhizomorph/README.md +82 -33
  22. mycorrhizal/rhizomorph/core.py +287 -119
  23. mycorrhizal/septum/TRANSITION_REFERENCE.md +385 -0
  24. mycorrhizal/{enoki → septum}/core.py +326 -100
  25. mycorrhizal/{enoki → septum}/testing_utils.py +7 -7
  26. mycorrhizal/{enoki → septum}/util.py +44 -21
  27. mycorrhizal/spores/__init__.py +3 -3
  28. mycorrhizal/spores/core.py +149 -28
  29. mycorrhizal/spores/dsl/__init__.py +8 -8
  30. mycorrhizal/spores/dsl/hypha.py +3 -15
  31. mycorrhizal/spores/dsl/rhizomorph.py +3 -11
  32. mycorrhizal/spores/dsl/{enoki.py → septum.py} +26 -77
  33. mycorrhizal/spores/encoder/json.py +21 -12
  34. mycorrhizal/spores/extraction.py +14 -11
  35. mycorrhizal/spores/models.py +53 -20
  36. mycorrhizal-0.2.0.dist-info/METADATA +335 -0
  37. mycorrhizal-0.2.0.dist-info/RECORD +54 -0
  38. mycorrhizal-0.1.2.dist-info/METADATA +0 -198
  39. mycorrhizal-0.1.2.dist-info/RECORD +0 -39
  40. /mycorrhizal/{enoki → septum}/__init__.py +0 -0
  41. {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