synapse-sdk 1.0.0a11__py3-none-any.whl → 2026.1.1b2__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.

Potentially problematic release.


This version of synapse-sdk might be problematic. Click here for more details.

Files changed (261) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +9 -8
  3. synapse_sdk/cli/agent/__init__.py +25 -0
  4. synapse_sdk/cli/agent/config.py +104 -0
  5. synapse_sdk/cli/agent/select.py +197 -0
  6. synapse_sdk/cli/auth.py +104 -0
  7. synapse_sdk/cli/main.py +1025 -0
  8. synapse_sdk/cli/plugin/__init__.py +58 -0
  9. synapse_sdk/cli/plugin/create.py +566 -0
  10. synapse_sdk/cli/plugin/job.py +196 -0
  11. synapse_sdk/cli/plugin/publish.py +322 -0
  12. synapse_sdk/cli/plugin/run.py +131 -0
  13. synapse_sdk/cli/plugin/test.py +200 -0
  14. synapse_sdk/clients/README.md +239 -0
  15. synapse_sdk/clients/__init__.py +5 -0
  16. synapse_sdk/clients/_template.py +266 -0
  17. synapse_sdk/clients/agent/__init__.py +84 -29
  18. synapse_sdk/clients/agent/async_ray.py +289 -0
  19. synapse_sdk/clients/agent/container.py +83 -0
  20. synapse_sdk/clients/agent/plugin.py +101 -0
  21. synapse_sdk/clients/agent/ray.py +296 -39
  22. synapse_sdk/clients/backend/__init__.py +152 -12
  23. synapse_sdk/clients/backend/annotation.py +164 -22
  24. synapse_sdk/clients/backend/core.py +101 -0
  25. synapse_sdk/clients/backend/data_collection.py +292 -0
  26. synapse_sdk/clients/backend/hitl.py +87 -0
  27. synapse_sdk/clients/backend/integration.py +374 -46
  28. synapse_sdk/clients/backend/ml.py +134 -22
  29. synapse_sdk/clients/backend/models.py +247 -0
  30. synapse_sdk/clients/base.py +538 -59
  31. synapse_sdk/clients/exceptions.py +35 -7
  32. synapse_sdk/clients/pipeline/__init__.py +5 -0
  33. synapse_sdk/clients/pipeline/client.py +636 -0
  34. synapse_sdk/clients/protocols.py +178 -0
  35. synapse_sdk/clients/utils.py +86 -8
  36. synapse_sdk/clients/validation.py +58 -0
  37. synapse_sdk/enums.py +76 -0
  38. synapse_sdk/exceptions.py +168 -0
  39. synapse_sdk/integrations/__init__.py +74 -0
  40. synapse_sdk/integrations/_base.py +119 -0
  41. synapse_sdk/integrations/_context.py +53 -0
  42. synapse_sdk/integrations/ultralytics/__init__.py +78 -0
  43. synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
  44. synapse_sdk/integrations/ultralytics/_patches.py +124 -0
  45. synapse_sdk/loggers.py +476 -95
  46. synapse_sdk/mcp/MCP.md +69 -0
  47. synapse_sdk/mcp/__init__.py +48 -0
  48. synapse_sdk/mcp/__main__.py +6 -0
  49. synapse_sdk/mcp/config.py +349 -0
  50. synapse_sdk/mcp/prompts/__init__.py +4 -0
  51. synapse_sdk/mcp/resources/__init__.py +4 -0
  52. synapse_sdk/mcp/server.py +1352 -0
  53. synapse_sdk/mcp/tools/__init__.py +6 -0
  54. synapse_sdk/plugins/__init__.py +133 -9
  55. synapse_sdk/plugins/action.py +229 -0
  56. synapse_sdk/plugins/actions/__init__.py +82 -0
  57. synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
  58. synapse_sdk/plugins/actions/dataset/action.py +471 -0
  59. synapse_sdk/plugins/actions/export/__init__.py +55 -0
  60. synapse_sdk/plugins/actions/export/action.py +183 -0
  61. synapse_sdk/plugins/actions/export/context.py +59 -0
  62. synapse_sdk/plugins/actions/inference/__init__.py +84 -0
  63. synapse_sdk/plugins/actions/inference/action.py +285 -0
  64. synapse_sdk/plugins/actions/inference/context.py +81 -0
  65. synapse_sdk/plugins/actions/inference/deployment.py +322 -0
  66. synapse_sdk/plugins/actions/inference/serve.py +252 -0
  67. synapse_sdk/plugins/actions/train/__init__.py +54 -0
  68. synapse_sdk/plugins/actions/train/action.py +326 -0
  69. synapse_sdk/plugins/actions/train/context.py +57 -0
  70. synapse_sdk/plugins/actions/upload/__init__.py +49 -0
  71. synapse_sdk/plugins/actions/upload/action.py +165 -0
  72. synapse_sdk/plugins/actions/upload/context.py +61 -0
  73. synapse_sdk/plugins/config.py +98 -0
  74. synapse_sdk/plugins/context/__init__.py +109 -0
  75. synapse_sdk/plugins/context/env.py +113 -0
  76. synapse_sdk/plugins/datasets/__init__.py +113 -0
  77. synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
  78. synapse_sdk/plugins/datasets/converters/base.py +347 -0
  79. synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
  80. synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
  81. synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
  82. synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
  83. synapse_sdk/plugins/datasets/formats/dm.py +351 -0
  84. synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
  85. synapse_sdk/plugins/decorators.py +83 -0
  86. synapse_sdk/plugins/discovery.py +790 -0
  87. synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
  88. synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
  89. synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
  90. synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
  91. synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
  92. synapse_sdk/plugins/docs/README.md +513 -0
  93. synapse_sdk/plugins/docs/STEP.md +656 -0
  94. synapse_sdk/plugins/enums.py +70 -10
  95. synapse_sdk/plugins/errors.py +92 -0
  96. synapse_sdk/plugins/executors/__init__.py +43 -0
  97. synapse_sdk/plugins/executors/local.py +99 -0
  98. synapse_sdk/plugins/executors/ray/__init__.py +18 -0
  99. synapse_sdk/plugins/executors/ray/base.py +282 -0
  100. synapse_sdk/plugins/executors/ray/job.py +298 -0
  101. synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
  102. synapse_sdk/plugins/executors/ray/packaging.py +137 -0
  103. synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
  104. synapse_sdk/plugins/executors/ray/task.py +257 -0
  105. synapse_sdk/plugins/models/__init__.py +26 -0
  106. synapse_sdk/plugins/models/logger.py +173 -0
  107. synapse_sdk/plugins/models/pipeline.py +25 -0
  108. synapse_sdk/plugins/pipelines/__init__.py +81 -0
  109. synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
  110. synapse_sdk/plugins/pipelines/context.py +107 -0
  111. synapse_sdk/plugins/pipelines/display.py +311 -0
  112. synapse_sdk/plugins/runner.py +114 -0
  113. synapse_sdk/plugins/schemas/__init__.py +19 -0
  114. synapse_sdk/plugins/schemas/results.py +152 -0
  115. synapse_sdk/plugins/steps/__init__.py +63 -0
  116. synapse_sdk/plugins/steps/base.py +128 -0
  117. synapse_sdk/plugins/steps/context.py +90 -0
  118. synapse_sdk/plugins/steps/orchestrator.py +128 -0
  119. synapse_sdk/plugins/steps/registry.py +103 -0
  120. synapse_sdk/plugins/steps/utils/__init__.py +20 -0
  121. synapse_sdk/plugins/steps/utils/logging.py +85 -0
  122. synapse_sdk/plugins/steps/utils/timing.py +71 -0
  123. synapse_sdk/plugins/steps/utils/validation.py +68 -0
  124. synapse_sdk/plugins/templates/__init__.py +50 -0
  125. synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
  126. synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
  127. synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
  128. synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
  129. synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
  130. synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
  131. synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
  132. synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
  133. synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
  134. synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
  135. synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
  136. synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
  137. synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
  138. synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
  139. synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
  140. synapse_sdk/plugins/testing/__init__.py +25 -0
  141. synapse_sdk/plugins/testing/sample_actions.py +98 -0
  142. synapse_sdk/plugins/types.py +206 -0
  143. synapse_sdk/plugins/upload.py +595 -64
  144. synapse_sdk/plugins/utils.py +325 -37
  145. synapse_sdk/shared/__init__.py +25 -0
  146. synapse_sdk/utils/__init__.py +1 -0
  147. synapse_sdk/utils/auth.py +74 -0
  148. synapse_sdk/utils/file/__init__.py +58 -0
  149. synapse_sdk/utils/file/archive.py +449 -0
  150. synapse_sdk/utils/file/checksum.py +167 -0
  151. synapse_sdk/utils/file/download.py +286 -0
  152. synapse_sdk/utils/file/io.py +129 -0
  153. synapse_sdk/utils/file/requirements.py +36 -0
  154. synapse_sdk/utils/network.py +168 -0
  155. synapse_sdk/utils/storage/__init__.py +238 -0
  156. synapse_sdk/utils/storage/config.py +188 -0
  157. synapse_sdk/utils/storage/errors.py +52 -0
  158. synapse_sdk/utils/storage/providers/__init__.py +13 -0
  159. synapse_sdk/utils/storage/providers/base.py +76 -0
  160. synapse_sdk/utils/storage/providers/gcs.py +168 -0
  161. synapse_sdk/utils/storage/providers/http.py +250 -0
  162. synapse_sdk/utils/storage/providers/local.py +126 -0
  163. synapse_sdk/utils/storage/providers/s3.py +177 -0
  164. synapse_sdk/utils/storage/providers/sftp.py +208 -0
  165. synapse_sdk/utils/storage/registry.py +125 -0
  166. synapse_sdk/utils/websocket.py +99 -0
  167. synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
  168. synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
  169. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
  170. synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
  171. locale/en/LC_MESSAGES/messages.mo +0 -0
  172. locale/en/LC_MESSAGES/messages.po +0 -39
  173. locale/ko/LC_MESSAGES/messages.mo +0 -0
  174. locale/ko/LC_MESSAGES/messages.po +0 -34
  175. synapse_sdk/cli/create_plugin.py +0 -10
  176. synapse_sdk/clients/agent/core.py +0 -7
  177. synapse_sdk/clients/agent/service.py +0 -15
  178. synapse_sdk/clients/backend/dataset.py +0 -51
  179. synapse_sdk/clients/ray/__init__.py +0 -6
  180. synapse_sdk/clients/ray/core.py +0 -22
  181. synapse_sdk/clients/ray/serve.py +0 -20
  182. synapse_sdk/i18n.py +0 -35
  183. synapse_sdk/plugins/categories/__init__.py +0 -0
  184. synapse_sdk/plugins/categories/base.py +0 -235
  185. synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
  186. synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
  187. synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
  188. synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
  189. synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
  190. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
  191. synapse_sdk/plugins/categories/decorators.py +0 -13
  192. synapse_sdk/plugins/categories/export/__init__.py +0 -0
  193. synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
  194. synapse_sdk/plugins/categories/export/actions/export.py +0 -10
  195. synapse_sdk/plugins/categories/import/__init__.py +0 -0
  196. synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
  197. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  198. synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
  199. synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
  200. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
  201. synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
  202. synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
  203. synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
  204. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
  205. synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
  206. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
  207. synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
  208. synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
  209. synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
  210. synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
  211. synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
  212. synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
  213. synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
  214. synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
  215. synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
  216. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
  217. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
  218. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
  219. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
  220. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
  221. synapse_sdk/plugins/categories/registry.py +0 -16
  222. synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
  223. synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
  224. synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
  225. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
  226. synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
  227. synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
  228. synapse_sdk/plugins/categories/templates.py +0 -32
  229. synapse_sdk/plugins/cli/__init__.py +0 -21
  230. synapse_sdk/plugins/cli/publish.py +0 -37
  231. synapse_sdk/plugins/cli/run.py +0 -67
  232. synapse_sdk/plugins/exceptions.py +0 -22
  233. synapse_sdk/plugins/models.py +0 -121
  234. synapse_sdk/plugins/templates/cookiecutter.json +0 -11
  235. synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
  236. synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
  237. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  238. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  239. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
  240. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
  241. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
  242. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
  243. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  244. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
  245. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
  246. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
  247. synapse_sdk/shared/enums.py +0 -8
  248. synapse_sdk/utils/debug.py +0 -5
  249. synapse_sdk/utils/file.py +0 -87
  250. synapse_sdk/utils/module_loading.py +0 -29
  251. synapse_sdk/utils/pydantic/__init__.py +0 -0
  252. synapse_sdk/utils/pydantic/config.py +0 -4
  253. synapse_sdk/utils/pydantic/errors.py +0 -33
  254. synapse_sdk/utils/pydantic/validators.py +0 -7
  255. synapse_sdk/utils/storage.py +0 -91
  256. synapse_sdk/utils/string.py +0 -11
  257. synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
  258. synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
  259. synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
  260. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
  261. {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,656 @@
1
+ # Step Implementation Guide
2
+
3
+ Step-based workflows enable complex multi-phase operations with progress tracking, conditional execution, and automatic rollback on failure.
4
+
5
+ ## Overview
6
+
7
+ Steps are composable building blocks for workflows. Each step:
8
+ - Has a unique name and progress weight
9
+ - Executes independently with shared context
10
+ - Can be skipped conditionally
11
+ - Supports rollback on workflow failure
12
+
13
+ ```mermaid
14
+ flowchart LR
15
+ subgraph Workflow["Step-Based Workflow"]
16
+ S1["Step 1<br/>weight: 0.2"] --> S2["Step 2<br/>weight: 0.5"]
17
+ S2 --> S3["Step 3<br/>weight: 0.3"]
18
+ end
19
+
20
+ Context["Shared Context"] -.-> S1
21
+ Context -.-> S2
22
+ Context -.-> S3
23
+
24
+ style Workflow fill:#e1f5fe
25
+ style Context fill:#fff3e0
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Core Components
31
+
32
+ ### BaseStep[C]
33
+
34
+ Location: `synapse_sdk/plugins/steps/base.py`
35
+
36
+ Abstract base class for all workflow steps. Generic type `C` must extend `BaseStepContext`.
37
+
38
+ ```python
39
+ from abc import ABC, abstractmethod
40
+ from synapse_sdk.plugins.steps import BaseStep, StepResult
41
+
42
+ class BaseStep[C: BaseStepContext](ABC):
43
+ @property
44
+ @abstractmethod
45
+ def name(self) -> str:
46
+ """Unique step identifier."""
47
+ ...
48
+
49
+ @property
50
+ @abstractmethod
51
+ def progress_weight(self) -> float:
52
+ """Weight for progress calculation (0.0-1.0)."""
53
+ ...
54
+
55
+ @abstractmethod
56
+ def execute(self, context: C) -> StepResult:
57
+ """Execute the step logic."""
58
+ ...
59
+
60
+ def can_skip(self, context: C) -> bool:
61
+ """Optional: Return True to skip this step."""
62
+ return False
63
+
64
+ def rollback(self, context: C, result: StepResult) -> None:
65
+ """Optional: Cleanup when a later step fails."""
66
+ pass
67
+ ```
68
+
69
+ ### StepResult
70
+
71
+ Location: `synapse_sdk/plugins/steps/base.py`
72
+
73
+ Dataclass representing step execution outcome.
74
+
75
+ ```python
76
+ from dataclasses import dataclass, field
77
+ from datetime import datetime
78
+ from typing import Any
79
+
80
+ @dataclass
81
+ class StepResult:
82
+ success: bool = True # Did step complete successfully?
83
+ data: dict[str, Any] = field(default_factory=dict) # Output data
84
+ error: str | None = None # Error message if failed
85
+ rollback_data: dict[str, Any] = field(default_factory=dict) # Data for rollback
86
+ skipped: bool = False # Was step skipped?
87
+ timestamp: datetime = field(default_factory=datetime.now)
88
+ ```
89
+
90
+ ### BaseStepContext
91
+
92
+ Location: `synapse_sdk/plugins/steps/context.py`
93
+
94
+ Base class for shared state between steps.
95
+
96
+ ```python
97
+ from dataclasses import dataclass, field
98
+ from synapse_sdk.plugins.context import RuntimeContext
99
+
100
+ @dataclass
101
+ class BaseStepContext:
102
+ runtime_ctx: RuntimeContext # Logger, env, clients
103
+ step_results: list[StepResult] = field(default_factory=list)
104
+ errors: list[str] = field(default_factory=list)
105
+ current_step: str | None = None # Set by Orchestrator
106
+
107
+ def log(self, event: str, data: dict, file: str | None = None) -> None:
108
+ """Log an event via runtime context."""
109
+ self.runtime_ctx.log(event, data, file)
110
+
111
+ def set_progress(self, current: int, total: int, category: str | None = None) -> None:
112
+ """Set progress. Uses current_step as category if not provided."""
113
+ effective_category = category or self.current_step
114
+ self.runtime_ctx.set_progress(current, total, effective_category)
115
+
116
+ def set_metrics(self, value: dict, category: str | None = None) -> None:
117
+ """Set metrics. Uses current_step as category if not provided."""
118
+ effective_category = category or self.current_step
119
+ self.runtime_ctx.set_metrics(value, effective_category)
120
+ ```
121
+
122
+ ### StepRegistry[C]
123
+
124
+ Location: `synapse_sdk/plugins/steps/registry.py`
125
+
126
+ Manages ordered collection of steps.
127
+
128
+ ```python
129
+ from synapse_sdk.plugins.steps import StepRegistry, BaseStep
130
+
131
+ registry = StepRegistry[MyContext]()
132
+
133
+ # Register steps in order
134
+ registry.register(Step1())
135
+ registry.register(Step2())
136
+ registry.register(Step3())
137
+
138
+ # Dynamic insertion
139
+ registry.insert_after('step1', NewStep()) # Insert after step1
140
+ registry.insert_before('step3', NewStep()) # Insert before step3
141
+
142
+ # Remove step
143
+ registry.unregister('step_name')
144
+
145
+ # Access
146
+ steps = registry.get_steps() # Returns copy of step list
147
+ total = registry.total_weight # Sum of all progress weights
148
+ ```
149
+
150
+ ### Orchestrator[C]
151
+
152
+ Location: `synapse_sdk/plugins/steps/orchestrator.py`
153
+
154
+ Executes steps with progress tracking and automatic rollback.
155
+
156
+ ```python
157
+ from synapse_sdk.plugins.steps import Orchestrator, StepRegistry
158
+
159
+ orchestrator = Orchestrator(
160
+ registry=registry,
161
+ context=context,
162
+ progress_callback=lambda curr, total: print(f"{curr}%"),
163
+ )
164
+
165
+ result = orchestrator.execute()
166
+ # Returns: {'success': True, 'steps_executed': 3, 'steps_total': 3}
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Creating Custom Steps
172
+
173
+ ### Step 1: Define Context
174
+
175
+ Create a context class with fields for shared state:
176
+
177
+ ```python
178
+ from dataclasses import dataclass, field
179
+ from synapse_sdk.plugins.steps import BaseStepContext
180
+
181
+ @dataclass
182
+ class ImageProcessingContext(BaseStepContext):
183
+ """Context for image processing workflow."""
184
+
185
+ # Input parameters
186
+ input_dir: str = ''
187
+ output_dir: str = ''
188
+ resize_width: int = 800
189
+
190
+ # Shared state populated by steps
191
+ image_paths: list[str] = field(default_factory=list)
192
+ processed_images: list[str] = field(default_factory=list)
193
+ failed_images: list[str] = field(default_factory=list)
194
+ total_bytes_saved: int = 0
195
+ ```
196
+
197
+ ### Step 2: Implement Steps
198
+
199
+ ```python
200
+ import os
201
+ from synapse_sdk.plugins.steps import BaseStep, StepResult
202
+
203
+ class DiscoverImagesStep(BaseStep[ImageProcessingContext]):
204
+ """Find all images in input directory."""
205
+
206
+ @property
207
+ def name(self) -> str:
208
+ return 'discover_images'
209
+
210
+ @property
211
+ def progress_weight(self) -> float:
212
+ return 0.1 # 10% of workflow
213
+
214
+ def execute(self, context: ImageProcessingContext) -> StepResult:
215
+ # Find images
216
+ images = []
217
+ for file in os.listdir(context.input_dir):
218
+ if file.lower().endswith(('.jpg', '.jpeg', '.png')):
219
+ images.append(os.path.join(context.input_dir, file))
220
+
221
+ if not images:
222
+ return StepResult(
223
+ success=False,
224
+ error=f"No images found in {context.input_dir}",
225
+ )
226
+
227
+ # Store in context for next steps
228
+ context.image_paths = images
229
+
230
+ return StepResult(
231
+ success=True,
232
+ data={'image_count': len(images)},
233
+ )
234
+
235
+
236
+ class ResizeImagesStep(BaseStep[ImageProcessingContext]):
237
+ """Resize all discovered images."""
238
+
239
+ @property
240
+ def name(self) -> str:
241
+ return 'resize_images'
242
+
243
+ @property
244
+ def progress_weight(self) -> float:
245
+ return 0.7 # 70% of workflow
246
+
247
+ def execute(self, context: ImageProcessingContext) -> StepResult:
248
+ processed = []
249
+ failed = []
250
+ bytes_saved = 0
251
+
252
+ for i, path in enumerate(context.image_paths):
253
+ # Report progress within step
254
+ context.set_progress(i + 1, len(context.image_paths))
255
+
256
+ try:
257
+ output_path, saved = self._resize_image(
258
+ path,
259
+ context.output_dir,
260
+ context.resize_width,
261
+ )
262
+ processed.append(output_path)
263
+ bytes_saved += saved
264
+ except Exception as e:
265
+ failed.append(path)
266
+ context.log('resize_failed', {'path': path, 'error': str(e)})
267
+
268
+ # Update context
269
+ context.processed_images = processed
270
+ context.failed_images = failed
271
+ context.total_bytes_saved = bytes_saved
272
+
273
+ return StepResult(
274
+ success=True,
275
+ data={
276
+ 'processed': len(processed),
277
+ 'failed': len(failed),
278
+ 'bytes_saved': bytes_saved,
279
+ },
280
+ rollback_data={'output_files': processed}, # For cleanup
281
+ )
282
+
283
+ def rollback(self, context: ImageProcessingContext, result: StepResult) -> None:
284
+ """Delete processed files on failure."""
285
+ for path in result.rollback_data.get('output_files', []):
286
+ try:
287
+ os.remove(path)
288
+ except OSError:
289
+ context.errors.append(f"Failed to delete: {path}")
290
+
291
+ def _resize_image(self, path: str, output_dir: str, width: int) -> tuple[str, int]:
292
+ # Image resizing implementation
293
+ ...
294
+
295
+
296
+ class GenerateReportStep(BaseStep[ImageProcessingContext]):
297
+ """Generate summary report."""
298
+
299
+ @property
300
+ def name(self) -> str:
301
+ return 'generate_report'
302
+
303
+ @property
304
+ def progress_weight(self) -> float:
305
+ return 0.2 # 20% of workflow
306
+
307
+ def can_skip(self, context: ImageProcessingContext) -> bool:
308
+ # Skip if nothing was processed
309
+ return len(context.processed_images) == 0
310
+
311
+ def execute(self, context: ImageProcessingContext) -> StepResult:
312
+ report = {
313
+ 'total_images': len(context.image_paths),
314
+ 'processed': len(context.processed_images),
315
+ 'failed': len(context.failed_images),
316
+ 'bytes_saved': context.total_bytes_saved,
317
+ }
318
+
319
+ # Log report
320
+ context.log('processing_complete', report)
321
+
322
+ return StepResult(success=True, data=report)
323
+ ```
324
+
325
+ ### Step 3: Run Workflow
326
+
327
+ ```python
328
+ from synapse_sdk.plugins.steps import StepRegistry, Orchestrator
329
+
330
+ def process_images(runtime_ctx, input_dir: str, output_dir: str) -> dict:
331
+ # Create registry and register steps
332
+ registry = StepRegistry[ImageProcessingContext]()
333
+ registry.register(DiscoverImagesStep())
334
+ registry.register(ResizeImagesStep())
335
+ registry.register(GenerateReportStep())
336
+
337
+ # Create context
338
+ context = ImageProcessingContext(
339
+ runtime_ctx=runtime_ctx,
340
+ input_dir=input_dir,
341
+ output_dir=output_dir,
342
+ )
343
+
344
+ # Execute with progress callback
345
+ def on_progress(current: int, total: int):
346
+ runtime_ctx.set_progress(current, total, category='overall')
347
+
348
+ orchestrator = Orchestrator(
349
+ registry=registry,
350
+ context=context,
351
+ progress_callback=on_progress,
352
+ )
353
+
354
+ try:
355
+ result = orchestrator.execute()
356
+ return {
357
+ 'success': True,
358
+ 'processed': len(context.processed_images),
359
+ 'failed': len(context.failed_images),
360
+ }
361
+ except RuntimeError as e:
362
+ return {
363
+ 'success': False,
364
+ 'error': str(e),
365
+ 'errors': context.errors,
366
+ }
367
+ ```
368
+
369
+ ---
370
+
371
+ ## Orchestrator Execution Flow
372
+
373
+ ```mermaid
374
+ flowchart TD
375
+ A["orchestrator.execute()"] --> B["For each step in registry"]
376
+ B --> C["Set context.current_step = step.name"]
377
+ C --> D{"step.can_skip(context)?"}
378
+
379
+ D -->|Yes| E["Create skipped StepResult"]
380
+ D -->|No| F["Try: result = step.execute(context)"]
381
+
382
+ F -->|Exception| G["result = StepResult(success=False, error)"]
383
+ F -->|Success| H["result from execute()"]
384
+
385
+ E --> I["Append result to context.step_results"]
386
+ G --> I
387
+ H --> I
388
+
389
+ I --> J{"result.success?"}
390
+
391
+ J -->|No| K["_rollback() in reverse order"]
392
+ K --> L["Raise RuntimeError"]
393
+
394
+ J -->|Yes| M["Update progress via callback"]
395
+ M --> N{"More steps?"}
396
+
397
+ N -->|Yes| B
398
+ N -->|No| O["Clear context.current_step"]
399
+ O --> P["Return success summary"]
400
+
401
+ style A fill:#e8f5e9
402
+ style P fill:#e8f5e9
403
+ style L fill:#ffcdd2
404
+ style D fill:#fff9c4
405
+ style J fill:#fff9c4
406
+ style N fill:#fff9c4
407
+ ```
408
+
409
+ ### Key Behaviors
410
+
411
+ 1. **Sequential Execution**: Steps run in registration order
412
+ 2. **Auto-Category**: `context.current_step` is set automatically for logging/metrics
413
+ 3. **Progress Calculation**: Based on cumulative step weights
414
+ 4. **Automatic Rollback**: On failure, `rollback()` called in reverse order
415
+ 5. **Best-Effort Rollback**: Rollback errors logged but don't stop other rollbacks
416
+
417
+ ---
418
+
419
+ ## Utility Steps
420
+
421
+ ### LoggingStep
422
+
423
+ Wraps any step with start/end logging:
424
+
425
+ ```python
426
+ from synapse_sdk.plugins.steps.utils import LoggingStep
427
+
428
+ # Wrap a step
429
+ logged_step = LoggingStep(ResizeImagesStep())
430
+
431
+ # Logs:
432
+ # - step_start: {step: 'logged_resize_images'}
433
+ # - step_end: {step: 'logged_resize_images', elapsed: 1.23, success: True}
434
+ ```
435
+
436
+ ### TimingStep
437
+
438
+ Wraps any step to measure execution duration:
439
+
440
+ ```python
441
+ from synapse_sdk.plugins.steps.utils import TimingStep
442
+
443
+ timed_step = TimingStep(ResizeImagesStep())
444
+
445
+ # Result includes:
446
+ # result.data['duration_seconds'] = 1.234567
447
+ ```
448
+
449
+ ### ValidationStep
450
+
451
+ Validates context state before proceeding:
452
+
453
+ ```python
454
+ from synapse_sdk.plugins.steps.utils import ValidationStep
455
+
456
+ def validate_input(context: ImageProcessingContext) -> tuple[bool, str | None]:
457
+ if not context.input_dir:
458
+ return False, "input_dir is required"
459
+ if not os.path.isdir(context.input_dir):
460
+ return False, f"Directory not found: {context.input_dir}"
461
+ return True, None
462
+
463
+ validate_step = ValidationStep(
464
+ validator=validate_input,
465
+ name='validate_input',
466
+ progress_weight=0.05,
467
+ )
468
+
469
+ registry.register(validate_step)
470
+ registry.register(DiscoverImagesStep())
471
+ # ...
472
+ ```
473
+
474
+ ### Composing Utility Steps
475
+
476
+ ```python
477
+ from synapse_sdk.plugins.steps.utils import LoggingStep, TimingStep
478
+
479
+ # Combine wrappers
480
+ step = LoggingStep(TimingStep(ResizeImagesStep()))
481
+
482
+ # Result: logged + timed step
483
+ # Logs start/end AND measures duration
484
+ ```
485
+
486
+ ---
487
+
488
+ ## Integration with Actions
489
+
490
+ ### BaseTrainAction Example
491
+
492
+ ```python
493
+ from synapse_sdk.plugins.actions.train import BaseTrainAction, TrainContext
494
+ from synapse_sdk.plugins.steps import StepRegistry
495
+
496
+ class MyTrainAction(BaseTrainAction[TrainParams]):
497
+ def setup_steps(self, registry: StepRegistry[TrainContext]) -> None:
498
+ """Override to register training steps."""
499
+ registry.register(PrepareDatasetStep())
500
+ registry.register(InitializeModelStep())
501
+ registry.register(TrainModelStep())
502
+ registry.register(SaveCheckpointStep())
503
+ registry.register(UploadModelStep())
504
+
505
+ def execute(self) -> dict:
506
+ # BaseTrainAction handles orchestration
507
+ return super().execute()
508
+ ```
509
+
510
+ ### BaseUploadAction Example
511
+
512
+ ```python
513
+ from synapse_sdk.plugins.actions.upload import BaseUploadAction, UploadContext
514
+ from synapse_sdk.plugins.steps import StepRegistry
515
+
516
+ class MyUploadAction(BaseUploadAction[UploadParams]):
517
+ def setup_steps(self, registry: StepRegistry[UploadContext]) -> None:
518
+ registry.register(ValidateFilesStep())
519
+ registry.register(OrganizeFilesStep())
520
+ registry.register(UploadFilesStep())
521
+ registry.register(VerifyUploadStep())
522
+ ```
523
+
524
+ ---
525
+
526
+ ## Progress Weight Guidelines
527
+
528
+ Progress weights should reflect **perceived duration** for better UX:
529
+
530
+ | Step Type | Typical Weight | Notes |
531
+ |-----------|---------------|-------|
532
+ | Validation | 0.05 - 0.10 | Quick checks |
533
+ | Data loading | 0.10 - 0.20 | I/O bound |
534
+ | Processing | 0.50 - 0.80 | Main work |
535
+ | Cleanup/Report | 0.05 - 0.10 | Finalization |
536
+
537
+ **Example Distribution:**
538
+
539
+ ```python
540
+ class ValidateStep(BaseStep):
541
+ progress_weight = 0.05 # 5%
542
+
543
+ class LoadDataStep(BaseStep):
544
+ progress_weight = 0.15 # 15%
545
+
546
+ class ProcessStep(BaseStep):
547
+ progress_weight = 0.70 # 70%
548
+
549
+ class SaveResultsStep(BaseStep):
550
+ progress_weight = 0.10 # 10%
551
+ # Total: 1.0 (100%)
552
+ ```
553
+
554
+ ---
555
+
556
+ ## Best Practices
557
+
558
+ ### Context Design
559
+
560
+ ```python
561
+ # Good: Clear field purposes
562
+ @dataclass
563
+ class GoodContext(BaseStepContext):
564
+ # Input (set at creation)
565
+ config_path: str
566
+
567
+ # Intermediate state (populated by steps)
568
+ loaded_data: dict = field(default_factory=dict)
569
+
570
+ # Output (final results)
571
+ results: list = field(default_factory=list)
572
+ ```
573
+
574
+ ### Error Handling
575
+
576
+ ```python
577
+ def execute(self, context: MyContext) -> StepResult:
578
+ try:
579
+ result = self._do_work(context)
580
+ return StepResult(success=True, data=result)
581
+ except ValidationError as e:
582
+ # Expected error - return failure
583
+ return StepResult(success=False, error=str(e))
584
+ except Exception as e:
585
+ # Unexpected error - log and return failure
586
+ context.log('unexpected_error', {'error': str(e)})
587
+ return StepResult(success=False, error=f"Unexpected: {e}")
588
+ ```
589
+
590
+ ### Rollback Design
591
+
592
+ ```python
593
+ def execute(self, context: MyContext) -> StepResult:
594
+ created_files = []
595
+
596
+ for item in context.items:
597
+ path = self._create_file(item)
598
+ created_files.append(path)
599
+
600
+ return StepResult(
601
+ success=True,
602
+ rollback_data={'files': created_files}, # Save for cleanup
603
+ )
604
+
605
+ def rollback(self, context: MyContext, result: StepResult) -> None:
606
+ for path in result.rollback_data.get('files', []):
607
+ try:
608
+ os.remove(path)
609
+ except OSError as e:
610
+ # Log but continue rollback
611
+ context.errors.append(f"Rollback failed for {path}: {e}")
612
+ ```
613
+
614
+ ### Step Independence
615
+
616
+ ```python
617
+ # Good: Step reads from context, writes to context
618
+ class ProcessStep(BaseStep[MyContext]):
619
+ def execute(self, context: MyContext) -> StepResult:
620
+ # Read from context (set by previous step)
621
+ data = context.loaded_data
622
+
623
+ # Process
624
+ results = self._process(data)
625
+
626
+ # Write to context (for next step)
627
+ context.results = results
628
+
629
+ return StepResult(success=True)
630
+ ```
631
+
632
+ ---
633
+
634
+ ## File Structure
635
+
636
+ ```
637
+ synapse_sdk/plugins/steps/
638
+ ├── __init__.py # Public exports
639
+ ├── base.py # BaseStep, StepResult
640
+ ├── context.py # BaseStepContext
641
+ ├── registry.py # StepRegistry
642
+ ├── orchestrator.py # Orchestrator
643
+ └── utils/
644
+ ├── __init__.py
645
+ ├── logging.py # LoggingStep
646
+ ├── timing.py # TimingStep
647
+ └── validation.py # ValidationStep
648
+ ```
649
+
650
+ ---
651
+
652
+ ## Related Documentation
653
+
654
+ - **[ARCHITECTURE.md](ARCHITECTURE.md)** - Overall plugin system architecture
655
+ - **[OVERVIEW.md](OVERVIEW.md)** - Getting started with plugins
656
+ - **[README.md](README.md)** - Quick reference and extension guide