tetra-rp 0.20.0__tar.gz → 0.23.0__tar.gz

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 (111) hide show
  1. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/PKG-INFO +2 -2
  2. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/pyproject.toml +38 -10
  3. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/__init__.py +5 -0
  4. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/build.py +50 -11
  5. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/build_utils/lb_handler_generator.py +14 -66
  6. tetra_rp-0.23.0/src/tetra_rp/cli/commands/build_utils/manifest.py +430 -0
  7. tetra_rp-0.23.0/src/tetra_rp/cli/commands/build_utils/mothership_handler_generator.py +75 -0
  8. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/build_utils/scanner.py +204 -1
  9. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/deploy.py +61 -20
  10. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/init.py +4 -0
  11. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/run.py +25 -9
  12. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/test_mothership.py +6 -5
  13. tetra_rp-0.23.0/src/tetra_rp/cli/utils/deployment.py +517 -0
  14. tetra_rp-0.23.0/src/tetra_rp/cli/utils/skeleton_template/mothership.py +55 -0
  15. tetra_rp-0.23.0/src/tetra_rp/cli/utils/skeleton_template/pyproject.toml +58 -0
  16. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/workers/cpu/__init__.py +0 -1
  17. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/workers/cpu/endpoint.py +1 -3
  18. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/workers/gpu/__init__.py +0 -1
  19. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/workers/gpu/endpoint.py +1 -2
  20. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/api/runpod.py +4 -1
  21. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/app.py +54 -3
  22. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/constants.py +15 -0
  23. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/cpu.py +0 -10
  24. tetra_rp-0.23.0/src/tetra_rp/core/resources/gpu.py +219 -0
  25. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/load_balancer_sls_resource.py +9 -4
  26. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/serverless.py +159 -10
  27. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/serverless_cpu.py +2 -12
  28. tetra_rp-0.23.0/src/tetra_rp/core/resources/template.py +36 -0
  29. tetra_rp-0.23.0/src/tetra_rp/runtime/circuit_breaker.py +274 -0
  30. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/lb_handler.py +0 -50
  31. tetra_rp-0.23.0/src/tetra_rp/runtime/load_balancer.py +160 -0
  32. tetra_rp-0.23.0/src/tetra_rp/runtime/metrics.py +325 -0
  33. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/mothership_provisioner.py +60 -15
  34. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/production_wrapper.py +4 -2
  35. tetra_rp-0.23.0/src/tetra_rp/runtime/reliability_config.py +149 -0
  36. tetra_rp-0.23.0/src/tetra_rp/runtime/retry_manager.py +118 -0
  37. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/service_registry.py +68 -38
  38. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp.egg-info/PKG-INFO +2 -2
  39. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp.egg-info/SOURCES.txt +8 -3
  40. tetra_rp-0.20.0/src/tetra_rp/cli/commands/build_utils/manifest.py +0 -170
  41. tetra_rp-0.20.0/src/tetra_rp/cli/utils/deployment.py +0 -157
  42. tetra_rp-0.20.0/src/tetra_rp/core/resources/gpu.py +0 -68
  43. tetra_rp-0.20.0/src/tetra_rp/core/resources/template.py +0 -107
  44. tetra_rp-0.20.0/src/tetra_rp/core/resources/utils.py +0 -50
  45. tetra_rp-0.20.0/src/tetra_rp/core/utils/json.py +0 -33
  46. tetra_rp-0.20.0/src/tetra_rp/runtime/manifest_client.py +0 -141
  47. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/README.md +0 -0
  48. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/setup.cfg +0 -0
  49. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/__init__.py +0 -0
  50. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/__init__.py +0 -0
  51. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/apps.py +0 -0
  52. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/build_utils/__init__.py +0 -0
  53. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/build_utils/handler_generator.py +0 -0
  54. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/resource.py +0 -0
  55. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/commands/undeploy.py +0 -0
  56. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/main.py +0 -0
  57. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/__init__.py +0 -0
  58. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/app.py +0 -0
  59. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/conda.py +0 -0
  60. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/ignore.py +0 -0
  61. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton.py +0 -0
  62. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/.env.example +0 -0
  63. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/.flashignore +0 -0
  64. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/.gitignore +0 -0
  65. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/README.md +0 -0
  66. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/main.py +3 -3
  67. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/requirements.txt +0 -0
  68. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/cli/utils/skeleton_template/workers/__init__.py +0 -0
  69. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/client.py +0 -0
  70. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/config.py +0 -0
  71. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/__init__.py +0 -0
  72. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/api/__init__.py +0 -0
  73. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/deployment.py +0 -0
  74. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/discovery.py +0 -0
  75. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/exceptions.py +0 -0
  76. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/__init__.py +0 -0
  77. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/base.py +0 -0
  78. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/cloud.py +0 -0
  79. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/environment.py +0 -0
  80. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/live_serverless.py +0 -0
  81. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/network_volume.py +0 -0
  82. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/resources/resource_manager.py +0 -0
  83. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/utils/__init__.py +0 -0
  84. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/utils/backoff.py +0 -0
  85. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/utils/constants.py +0 -0
  86. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/utils/file_lock.py +0 -0
  87. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/utils/http.py +0 -0
  88. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/utils/lru_cache.py +0 -0
  89. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/utils/singleton.py +0 -0
  90. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/core/validation.py +0 -0
  91. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/execute_class.py +0 -0
  92. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/logger.py +0 -0
  93. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/protos/__init__.py +0 -0
  94. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/protos/remote_execution.py +0 -0
  95. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/__init__.py +0 -0
  96. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/config.py +0 -0
  97. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/exceptions.py +0 -0
  98. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/generic_handler.py +0 -0
  99. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/manifest_fetcher.py +0 -0
  100. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/models.py +0 -0
  101. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/serialization.py +0 -0
  102. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/runtime/state_manager_client.py +0 -0
  103. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/stubs/__init__.py +0 -0
  104. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/stubs/live_serverless.py +0 -0
  105. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/stubs/load_balancer_sls.py +0 -0
  106. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/stubs/registry.py +0 -0
  107. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp/stubs/serverless.py +0 -0
  108. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp.egg-info/dependency_links.txt +0 -0
  109. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp.egg-info/entry_points.txt +0 -0
  110. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp.egg-info/requires.txt +0 -0
  111. {tetra_rp-0.20.0 → tetra_rp-0.23.0}/src/tetra_rp.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tetra_rp
3
- Version: 0.20.0
3
+ Version: 0.23.0
4
4
  Summary: A Python library for distributed inference and serving of machine learning models
5
5
  Author-email: Marut Pandya <pandyamarut@gmail.com>, Patrick Rachford <prachford@icloud.com>, Dean Quinanola <dean.quinanola@runpod.io>
6
6
  License: MIT
@@ -8,7 +8,7 @@ Classifier: Development Status :: 3 - Alpha
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: <3.14,>=3.9
11
+ Requires-Python: <3.15,>=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  Requires-Dist: cloudpickle>=3.1.1
14
14
  Requires-Dist: runpod
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tetra_rp"
3
- version = "0.20.0"
3
+ version = "0.23.0"
4
4
  description = "A Python library for distributed inference and serving of machine learning models"
5
5
  authors = [
6
6
  { name = "Marut Pandya", email = "pandyamarut@gmail.com" },
@@ -15,7 +15,7 @@ classifiers = [
15
15
  "License :: OSI Approved :: MIT License",
16
16
  "Operating System :: OS Independent",
17
17
  ]
18
- requires-python = ">=3.9,<3.14"
18
+ requires-python = ">=3.10,<3.15"
19
19
 
20
20
  dependencies = [
21
21
  "cloudpickle>=3.1.1",
@@ -39,6 +39,7 @@ test = [
39
39
  "pytest-mock>=3.14.0",
40
40
  "pytest-asyncio>=1.0.0",
41
41
  "pytest-cov>=6.2.1",
42
+ "pytest-xdist>=3.6.1",
42
43
  "twine>=6.1.0",
43
44
  ]
44
45
 
@@ -68,14 +69,15 @@ addopts = [
68
69
  "--tb=short",
69
70
  "--cov=tetra_rp",
70
71
  "--cov-report=term-missing",
71
- "--cov-fail-under=35"
72
+ "--cov-fail-under=65"
72
73
  ]
73
74
  asyncio_mode = "auto"
74
75
  asyncio_default_fixture_loop_scope = "function"
75
76
  markers = [
76
77
  "unit: Unit tests",
77
78
  "integration: Integration tests",
78
- "slow: Slow tests"
79
+ "slow: Slow tests",
80
+ "serial: Tests that must run serially (not parallelized)"
79
81
  ]
80
82
  filterwarnings = [
81
83
  "ignore::DeprecationWarning",
@@ -85,14 +87,10 @@ filterwarnings = [
85
87
  ]
86
88
 
87
89
  [tool.ruff]
88
- # Exclude tetra-examples directory since it's a separate repository
89
- exclude = [
90
- "tetra-examples/",
91
- ]
92
90
 
93
91
  [tool.mypy]
94
92
  # Basic configuration
95
- python_version = "3.9"
93
+ python_version = "3.10"
96
94
  warn_return_any = true
97
95
  warn_unused_configs = true
98
96
  disallow_untyped_defs = false # Start lenient, can be stricter later
@@ -110,7 +108,6 @@ pretty = true
110
108
 
111
109
  # Exclude directories
112
110
  exclude = [
113
- "tetra-examples/",
114
111
  "tests/", # Start by excluding tests, can add later
115
112
  ]
116
113
 
@@ -122,5 +119,36 @@ module = [
122
119
  ]
123
120
  ignore_missing_imports = true
124
121
 
122
+ [tool.coverage.run]
123
+ parallel = true
124
+ branch = false
125
+ source = ["tetra_rp"]
126
+ omit = [
127
+ "*/tests/*",
128
+ "*/test_*.py",
129
+ "*/__pycache__/*",
130
+ "*/site-packages/*",
131
+ ]
132
+
133
+ [tool.coverage.report]
134
+ precision = 2
135
+ show_missing = true
136
+ skip_covered = false
137
+ exclude_lines = [
138
+ "pragma: no cover",
139
+ "def __repr__",
140
+ "raise AssertionError",
141
+ "raise NotImplementedError",
142
+ "if __name__ == .__main__.:",
143
+ "if TYPE_CHECKING:",
144
+ "@abstractmethod",
145
+ ]
146
+
147
+ [tool.coverage.paths]
148
+ source = [
149
+ "src/tetra_rp",
150
+ "*/site-packages/tetra_rp",
151
+ ]
152
+
125
153
  [tool.uv.sources]
126
154
  runpod = { git = "https://github.com/runpod/runpod-python", rev = "main" }
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
22
22
  CudaVersion,
23
23
  DataCenter,
24
24
  GpuGroup,
25
+ GpuType,
25
26
  LiveLoadBalancer,
26
27
  LiveServerless,
27
28
  LoadBalancerSlsResource,
@@ -49,6 +50,7 @@ def __getattr__(name):
49
50
  "CudaVersion",
50
51
  "DataCenter",
51
52
  "GpuGroup",
53
+ "GpuType",
52
54
  "LiveLoadBalancer",
53
55
  "LiveServerless",
54
56
  "LoadBalancerSlsResource",
@@ -68,6 +70,7 @@ def __getattr__(name):
68
70
  CudaVersion,
69
71
  DataCenter,
70
72
  GpuGroup,
73
+ GpuType,
71
74
  LiveLoadBalancer,
72
75
  LiveServerless,
73
76
  LoadBalancerSlsResource,
@@ -88,6 +91,7 @@ def __getattr__(name):
88
91
  "CudaVersion": CudaVersion,
89
92
  "DataCenter": DataCenter,
90
93
  "GpuGroup": GpuGroup,
94
+ "GpuType": GpuType,
91
95
  "LiveLoadBalancer": LiveLoadBalancer,
92
96
  "LiveServerless": LiveServerless,
93
97
  "LoadBalancerSlsResource": LoadBalancerSlsResource,
@@ -112,6 +116,7 @@ __all__ = [
112
116
  "CudaVersion",
113
117
  "DataCenter",
114
118
  "GpuGroup",
119
+ "GpuType",
115
120
  "LiveLoadBalancer",
116
121
  "LiveServerless",
117
122
  "LoadBalancerSlsResource",
@@ -27,6 +27,7 @@ from ..utils.ignore import get_file_tree, load_ignore_patterns
27
27
  from .build_utils.handler_generator import HandlerGenerator
28
28
  from .build_utils.lb_handler_generator import LBHandlerGenerator
29
29
  from .build_utils.manifest import ManifestBuilder
30
+ from .build_utils.mothership_handler_generator import generate_mothership_handler
30
31
  from .build_utils.scanner import RemoteDecoratorScanner
31
32
 
32
33
  logger = logging.getLogger(__name__)
@@ -285,26 +286,35 @@ def build_command(
285
286
  scanner = RemoteDecoratorScanner(build_dir)
286
287
  remote_functions = scanner.discover_remote_functions()
287
288
 
288
- if remote_functions:
289
- # Build and write manifest
290
- manifest_builder = ManifestBuilder(app_name, remote_functions)
291
- manifest = manifest_builder.build()
292
- manifest_path = build_dir / "flash_manifest.json"
293
- manifest_path.write_text(json.dumps(manifest, indent=2))
289
+ # Always build manifest (includes mothership even without @remote functions)
290
+ manifest_builder = ManifestBuilder(
291
+ app_name, remote_functions, scanner, build_dir=build_dir
292
+ )
293
+ manifest = manifest_builder.build()
294
+ manifest_path = build_dir / "flash_manifest.json"
295
+ manifest_path.write_text(json.dumps(manifest, indent=2))
296
+
297
+ # Copy manifest to .flash/ directory for deployment reference
298
+ # This avoids needing to extract from tarball during deploy
299
+ flash_dir = project_dir / ".flash"
300
+ deployment_manifest_path = flash_dir / "flash_manifest.json"
301
+ shutil.copy2(manifest_path, deployment_manifest_path)
294
302
 
295
- # Generate handler files based on resource type
296
- handler_paths = []
303
+ # Generate handler files if there are resources
304
+ handler_paths = []
305
+ manifest_resources = manifest.get("resources", {})
297
306
 
307
+ if manifest_resources:
298
308
  # Separate resources by type
299
309
  # Use flag determined by isinstance() at scan time
300
310
  lb_resources = {
301
311
  name: data
302
- for name, data in manifest.get("resources", {}).items()
312
+ for name, data in manifest_resources.items()
303
313
  if data.get("is_load_balanced", False)
304
314
  }
305
315
  qb_resources = {
306
316
  name: data
307
- for name, data in manifest.get("resources", {}).items()
317
+ for name, data in manifest_resources.items()
308
318
  if not data.get("is_load_balanced", False)
309
319
  }
310
320
 
@@ -318,14 +328,43 @@ def build_command(
318
328
  qb_gen = HandlerGenerator(manifest, build_dir)
319
329
  handler_paths.extend(qb_gen.generate_handlers())
320
330
 
331
+ # Generate mothership handler if present in manifest
332
+ mothership_resources = {
333
+ name: data
334
+ for name, data in manifest_resources.items()
335
+ if data.get("is_mothership", False)
336
+ }
337
+ if mothership_resources:
338
+ for (
339
+ resource_name,
340
+ resource_data,
341
+ ) in mothership_resources.items():
342
+ mothership_handler_path = (
343
+ build_dir / "handlers" / "handler_mothership.py"
344
+ )
345
+ generate_mothership_handler(
346
+ main_file=resource_data.get("main_file", "main.py"),
347
+ app_variable=resource_data.get(
348
+ "app_variable", "app"
349
+ ),
350
+ output_path=mothership_handler_path,
351
+ )
352
+ handler_paths.append(str(mothership_handler_path))
353
+
354
+ if handler_paths:
321
355
  progress.update(
322
356
  manifest_task,
323
357
  description=f"[green]✓ Generated {len(handler_paths)} handlers and manifest",
324
358
  )
359
+ elif manifest_resources:
360
+ progress.update(
361
+ manifest_task,
362
+ description=f"[green]✓ Generated manifest with {len(manifest_resources)} resources",
363
+ )
325
364
  else:
326
365
  progress.update(
327
366
  manifest_task,
328
- description="[yellow]⚠ No @remote functions found",
367
+ description="[yellow]⚠ No resources detected",
329
368
  )
330
369
 
331
370
  except (ImportError, SyntaxError) as e:
@@ -40,9 +40,6 @@ ROUTE_REGISTRY = {{
40
40
  {registry}
41
41
  }}
42
42
 
43
- # Module-level state for /manifest endpoint
44
- _state_client: Optional[StateManagerClient] = None
45
-
46
43
 
47
44
  # Lifespan context manager for startup/shutdown
48
45
  @asynccontextmanager
@@ -51,43 +48,46 @@ async def lifespan(app: FastAPI):
51
48
  # Startup
52
49
  logger.info("Starting {resource_name} endpoint")
53
50
 
54
- # Check if this is the mothership and initiate provisioning
51
+ # Check if this is the mothership and run reconciliation
52
+ # Note: Resources are now provisioned upfront by the CLI during deployment.
53
+ # This background task runs reconciliation on mothership startup to ensure
54
+ # all resources are still deployed and in sync with the manifest.
55
55
  try:
56
56
  from tetra_rp.runtime.mothership_provisioner import (
57
57
  is_mothership,
58
- provision_children,
58
+ reconcile_children,
59
59
  get_mothership_url,
60
60
  )
61
61
  from tetra_rp.runtime.state_manager_client import StateManagerClient
62
62
 
63
63
  if is_mothership():
64
64
  logger.info("=" * 60)
65
- logger.info("Mothership detected - Starting auto-provisioning")
66
- logger.info("Test phase: Deploying child endpoints with 'tmp-' prefix")
65
+ logger.info("Mothership detected - Starting reconciliation task")
66
+ logger.info("Resources are provisioned upfront by the CLI")
67
+ logger.info("This task ensures all resources remain in sync")
67
68
  logger.info("=" * 60)
68
69
  try:
69
70
  mothership_url = get_mothership_url()
70
71
  logger.info(f"Mothership URL: {{mothership_url}}")
71
72
 
72
- # Initialize State Manager client and store in module-level state
73
+ # Initialize State Manager client for reconciliation
73
74
  state_client = StateManagerClient()
74
- global _state_client
75
- _state_client = state_client
76
75
 
77
- # Spawn background provisioning task (non-blocking)
76
+ # Spawn background reconciliation task (non-blocking)
77
+ # This will verify all resources from manifest are deployed
78
78
  manifest_path = Path(__file__).parent / "flash_manifest.json"
79
79
  task = asyncio.create_task(
80
- provision_children(manifest_path, mothership_url, state_client)
80
+ reconcile_children(manifest_path, mothership_url, state_client)
81
81
  )
82
82
  # Add error callback to catch and log background task exceptions
83
83
  task.add_done_callback(
84
- lambda t: logger.error(f"Background provisioning failed: {{t.exception()}}")
84
+ lambda t: logger.error(f"Reconciliation task failed: {{t.exception()}}")
85
85
  if t.exception()
86
86
  else None
87
87
  )
88
88
 
89
89
  except Exception as e:
90
- logger.error(f"Failed to start mothership provisioning: {{e}}")
90
+ logger.error(f"Failed to start reconciliation task: {{e}}")
91
91
  # Don't fail startup - continue serving traffic
92
92
 
93
93
  except ImportError:
@@ -117,58 +117,6 @@ def ping():
117
117
  return {{"status": "healthy"}}
118
118
 
119
119
 
120
- # Manifest endpoint for service discovery
121
- @app.get("/manifest")
122
- async def manifest():
123
- """Return complete authoritative manifest for service discovery.
124
-
125
- Fetches the full manifest from State Manager, allowing child endpoints
126
- to synchronize their configuration.
127
-
128
- Returns:
129
- dict: Complete manifest with version, generated_at, project_name,
130
- function_registry, resources, and routes
131
- """
132
- try:
133
- import os
134
- from tetra_rp.runtime.mothership_provisioner import is_mothership
135
-
136
- # Only mothership serves manifest
137
- if not is_mothership():
138
- return {{"error": "Only mothership serves manifest"}}, 403
139
-
140
- # Check state client initialized
141
- global _state_client
142
- if _state_client is None:
143
- return {{"error": "State Manager not initialized"}}, 500
144
-
145
- # Get mothership ID
146
- mothership_id = os.getenv("RUNPOD_ENDPOINT_ID")
147
- if not mothership_id:
148
- return {{"error": "RUNPOD_ENDPOINT_ID not set"}}, 500
149
-
150
- # Fetch persisted manifest from State Manager (single source of truth)
151
- persisted_manifest = await _state_client.get_persisted_manifest(mothership_id)
152
-
153
- # First boot: no manifest yet, return minimal structure
154
- if persisted_manifest is None:
155
- return {{
156
- "version": "1.0",
157
- "generated_at": "",
158
- "project_name": "",
159
- "function_registry": {{}},
160
- "resources": {{}},
161
- "routes": {{}}
162
- }}
163
-
164
- # Return complete manifest
165
- return persisted_manifest
166
-
167
- except Exception as e:
168
- logger.error(f"Failed to get manifest: {{e}}")
169
- return {{"error": str(e)}}, 500
170
-
171
-
172
120
  if __name__ == "__main__":
173
121
  import uvicorn
174
122
  # Local development server for testing