synth-ai 0.18.0.dev590__tar.gz → 0.18.1.dev595__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 (250) hide show
  1. {synth_ai-0.18.0.dev590/synth_ai.egg-info → synth_ai-0.18.1.dev595}/PKG-INFO +2 -2
  2. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/README.md +1 -1
  3. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/pyproject.toml +1 -1
  4. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/__init__.py +14 -0
  5. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/actor_images.py +21 -5
  6. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/factories.py +5 -1
  7. synth_ai-0.18.1.dev595/synth_ai/sdk/research/contracts/failure.py +173 -0
  8. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/image_releases.py +38 -7
  9. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/run_observability.py +9 -5
  10. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/image_releases.py +142 -0
  11. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/public.py +10 -0
  12. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/runs.py +8 -13
  13. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595/synth_ai.egg-info}/PKG-INFO +2 -2
  14. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai.egg-info/SOURCES.txt +1 -0
  15. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/LICENSE +0 -0
  16. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/MANIFEST.in +0 -0
  17. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/setup.cfg +0 -0
  18. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/README.md +0 -0
  19. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/__init__.py +0 -0
  20. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/__main__.py +0 -0
  21. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/cli/AGENTS.md +0 -0
  22. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/cli/README.md +0 -0
  23. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/cli/__init__.py +0 -0
  24. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/cli/__main__.py +0 -0
  25. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/cli/main.py +0 -0
  26. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/cli/research.py +0 -0
  27. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/cli/research_environments.py +0 -0
  28. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/cli/research_image_releases.py +0 -0
  29. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/cli/research_projects.py +0 -0
  30. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/client.py +0 -0
  31. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/README.md +0 -0
  32. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/__init__.py +0 -0
  33. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/auth/__init__.py +0 -0
  34. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/auth/credentials.py +0 -0
  35. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/contracts/__init__.py +0 -0
  36. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/contracts/json_value.py +0 -0
  37. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/contracts/pagination.py +0 -0
  38. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/errors.py +0 -0
  39. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/http/__init__.py +0 -0
  40. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/http/async_transport.py +0 -0
  41. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/http/request.py +0 -0
  42. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/http/retry.py +0 -0
  43. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/http/streaming.py +0 -0
  44. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/http/transport.py +0 -0
  45. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/research/__init__.py +0 -0
  46. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/utils/__init__.py +0 -0
  47. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/utils/env.py +0 -0
  48. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/utils/log_filter.py +0 -0
  49. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/utils/paths.py +0 -0
  50. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/core/utils/urls.py +0 -0
  51. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/README.md +0 -0
  52. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/__init__.py +0 -0
  53. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/__main__.py +0 -0
  54. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/objective_tools.py +0 -0
  55. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/registry.py +0 -0
  56. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/request_models.py +0 -0
  57. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/server.py +0 -0
  58. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/__init__.py +0 -0
  59. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/approvals.py +0 -0
  60. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/artifacts.py +0 -0
  61. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/datasets.py +0 -0
  62. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/environments.py +0 -0
  63. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/exports.py +0 -0
  64. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/factories.py +0 -0
  65. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/factory_results.py +0 -0
  66. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/files.py +0 -0
  67. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/image_releases.py +0 -0
  68. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/integrations.py +0 -0
  69. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/logs.py +0 -0
  70. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/models.py +0 -0
  71. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/outputs.py +0 -0
  72. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/policy_schemas.py +0 -0
  73. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/progress.py +0 -0
  74. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/project_data.py +0 -0
  75. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/projects.py +0 -0
  76. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/prs.py +0 -0
  77. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/readiness.py +0 -0
  78. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/repos.py +0 -0
  79. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/resources.py +0 -0
  80. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/runs.py +0 -0
  81. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/trained_models.py +0 -0
  82. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/usage.py +0 -0
  83. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/visuals.py +0 -0
  84. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/mcp/research/tools/workspace_inputs.py +0 -0
  85. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/py.typed +0 -0
  86. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/README.md +0 -0
  87. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/__init__.py +0 -0
  88. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/pagination.py +0 -0
  89. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/__init__.py +0 -0
  90. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/_internal/__init__.py +0 -0
  91. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/_internal/crypto.py +0 -0
  92. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/_internal/env.py +0 -0
  93. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/_internal/urls.py +0 -0
  94. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/account.py +0 -0
  95. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/advanced.py +0 -0
  96. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/advanced_factories.py +0 -0
  97. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/advanced_swarms.py +0 -0
  98. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/artifacts.py +0 -0
  99. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/auth.py +0 -0
  100. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/client.py +0 -0
  101. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/_activity_wire.py +0 -0
  102. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/_environment_wire.py +0 -0
  103. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/_transcript_wire.py +0 -0
  104. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/_wire.py +0 -0
  105. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/activity.py +0 -0
  106. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/activity_items.py +0 -0
  107. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/billing.py +0 -0
  108. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/canonical_usage.py +0 -0
  109. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/checkpoints.py +0 -0
  110. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/cloud_deployment_claims.py +0 -0
  111. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/cloud_deployments.py +0 -0
  112. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/common.py +0 -0
  113. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/dataset_revisions.py +0 -0
  114. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/deliveries.py +0 -0
  115. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/dev_environment_evidence.py +0 -0
  116. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/economics.py +0 -0
  117. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/environment_manifest.py +0 -0
  118. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/environments.py +0 -0
  119. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/evidence.py +0 -0
  120. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/factory_evidence.py +0 -0
  121. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/factory_lenses.py +0 -0
  122. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/factory_operations.py +0 -0
  123. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/factory_role_receipts.py +0 -0
  124. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/local_execution_profile.py +0 -0
  125. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/operator_evidence.py +0 -0
  126. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/project.py +0 -0
  127. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/project_data.py +0 -0
  128. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/project_runtime.py +0 -0
  129. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/project_workspace.py +0 -0
  130. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/project_workspace_evidence.py +0 -0
  131. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/projects.py +0 -0
  132. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/promotions.py +0 -0
  133. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/research_intern.py +0 -0
  134. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/run_authority.py +0 -0
  135. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/run_control.py +0 -0
  136. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/run_diagnostics.py +0 -0
  137. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/run_events.py +0 -0
  138. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/run_execution.py +0 -0
  139. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/run_launch.py +0 -0
  140. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/run_state.py +0 -0
  141. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/run_timeline.py +0 -0
  142. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/runtime_image.py +0 -0
  143. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/runtime_intent.py +0 -0
  144. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/runtime_stream.py +0 -0
  145. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/runtime_stream_snapshot.py +0 -0
  146. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/scientific_integrity.py +0 -0
  147. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_actor_models.py +0 -0
  148. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_actor_policy_data.py +0 -0
  149. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_agent_harnesses.py +0 -0
  150. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_agent_kinds.py +0 -0
  151. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_agent_models.py +0 -0
  152. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_branching.py +0 -0
  153. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_credential_providers.py +0 -0
  154. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_environment_kinds.py +0 -0
  155. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_evidence_obligations.py +0 -0
  156. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_funding_sources.py +0 -0
  157. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_horizons.py +0 -0
  158. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_host_kinds.py +0 -0
  159. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_inference_providers.py +0 -0
  160. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_network_topology.py +0 -0
  161. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_providers.py +0 -0
  162. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_resource_kinds.py +0 -0
  163. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_roles.py +0 -0
  164. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_run_policy.py +0 -0
  165. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_runbooks.py +0 -0
  166. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_runnable_project.py +0 -0
  167. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_runtime_kinds.py +0 -0
  168. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_tool_providers.py +0 -0
  169. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/smr_work_modes.py +0 -0
  170. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/status.py +0 -0
  171. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/swarms.py +0 -0
  172. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/tag.py +0 -0
  173. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/traces.py +0 -0
  174. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/transcript.py +0 -0
  175. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/types.py +0 -0
  176. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/usage.py +0 -0
  177. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/visuals.py +0 -0
  178. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/wire_models.py +0 -0
  179. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/wire_models_README.md +0 -0
  180. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/work_products.py +0 -0
  181. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/contracts/workspaces.py +0 -0
  182. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/economics.py +0 -0
  183. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/efforts.py +0 -0
  184. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/environments.py +0 -0
  185. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/errors.py +0 -0
  186. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/events.py +0 -0
  187. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/experiments.py +0 -0
  188. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/facade.py +0 -0
  189. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/factories.py +0 -0
  190. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/factory_storage.py +0 -0
  191. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/knowledge.py +0 -0
  192. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/models.py +0 -0
  193. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/operations.py +0 -0
  194. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/project_data.py +0 -0
  195. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/project_deliveries.py +0 -0
  196. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/project_workspaces.py +0 -0
  197. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/projects.py +0 -0
  198. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/readouts.py +0 -0
  199. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/research_intern.py +0 -0
  200. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/secrets.py +0 -0
  201. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/README.md +0 -0
  202. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/__init__.py +0 -0
  203. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/_base.py +0 -0
  204. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/_client_helpers.py +0 -0
  205. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/_run_authority_mixin.py +0 -0
  206. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/approvals.py +0 -0
  207. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/billing.py +0 -0
  208. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/client.py +0 -0
  209. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/cloud_deployments.py +0 -0
  210. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/config.py +0 -0
  211. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/cost.py +0 -0
  212. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/datasets.py +0 -0
  213. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/dev_environments.py +0 -0
  214. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/environments.py +0 -0
  215. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/exports.py +0 -0
  216. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/factories.py +0 -0
  217. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/factory_evidence.py +0 -0
  218. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/files.py +0 -0
  219. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/github.py +0 -0
  220. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/image_releases.py +0 -0
  221. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/images.py +0 -0
  222. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/logs.py +0 -0
  223. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/models.py +0 -0
  224. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/outputs.py +0 -0
  225. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/progress.py +0 -0
  226. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/project.py +0 -0
  227. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/projects.py +0 -0
  228. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/prs.py +0 -0
  229. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/readiness.py +0 -0
  230. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/repositories.py +0 -0
  231. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/secrets.py +0 -0
  232. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/setup.py +0 -0
  233. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/tag.py +0 -0
  234. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/trained_models.py +0 -0
  235. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/transport.py +0 -0
  236. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/usage.py +0 -0
  237. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/work_products.py +0 -0
  238. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/session/workspace_inputs.py +0 -0
  239. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/swarms.py +0 -0
  240. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/traces.py +0 -0
  241. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/transport/__init__.py +0 -0
  242. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/transport/http.py +0 -0
  243. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/transport/pagination.py +0 -0
  244. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/version.py +0 -0
  245. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/visuals.py +0 -0
  246. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai/sdk/research/wiki.py +0 -0
  247. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai.egg-info/dependency_links.txt +0 -0
  248. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai.egg-info/entry_points.txt +0 -0
  249. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai.egg-info/requires.txt +0 -0
  250. {synth_ai-0.18.0.dev590 → synth_ai-0.18.1.dev595}/synth_ai.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synth-ai
3
- Version: 0.18.0.dev590
3
+ Version: 0.18.1.dev595
4
4
  Summary: Python SDK and CLI for Synth Managed Research and Research Factory
5
5
  Author-email: Synth AI <josh@usesynth.ai>
6
6
  License-Expression: MIT
@@ -27,7 +27,7 @@ Dynamic: license-file
27
27
 
28
28
  # Synth AI SDK
29
29
 
30
- <!-- CI release pins: PyPI-0.18.0-orange synth-ai==0.18.0 -->
30
+ <!-- CI release pins: PyPI-0.18.1-orange synth-ai==0.18.1 -->
31
31
 
32
32
  [![PyPI version](https://img.shields.io/pypi/v/synth-ai.svg)](https://pypi.org/project/synth-ai/)
33
33
  [![License](https://img.shields.io/pypi/l/synth-ai.svg)](https://pypi.org/project/synth-ai/)
@@ -1,6 +1,6 @@
1
1
  # Synth AI SDK
2
2
 
3
- <!-- CI release pins: PyPI-0.18.0-orange synth-ai==0.18.0 -->
3
+ <!-- CI release pins: PyPI-0.18.1-orange synth-ai==0.18.1 -->
4
4
 
5
5
  [![PyPI version](https://img.shields.io/pypi/v/synth-ai.svg)](https://pypi.org/project/synth-ai/)
6
6
  [![License](https://img.shields.io/pypi/l/synth-ai.svg)](https://pypi.org/project/synth-ai/)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "synth-ai"
3
- version = "0.18.0.dev590"
3
+ version = "0.18.1.dev595"
4
4
  description = "Python SDK and CLI for Synth Managed Research and Research Factory"
5
5
  authors = [{name = "Synth AI", email = "josh@usesynth.ai"}]
6
6
  license = "MIT"
@@ -177,7 +177,15 @@ from synth_ai.sdk.research.contracts.factory_role_receipts import (
177
177
  FactoryRoleReceiptResponse,
178
178
  FactoryRoleReceiptRuntimeEvidence,
179
179
  )
180
+ from synth_ai.sdk.research.contracts.failure import (
181
+ ActorFailureReason,
182
+ ManagedResearchFailureClassification,
183
+ ManagedResearchFailureFamily,
184
+ ManagedResearchFailureScope,
185
+ ManagedResearchFailureSeverity,
186
+ )
180
187
  from synth_ai.sdk.research.contracts.image_releases import (
188
+ ActorImageCapability,
181
189
  ActorRuntimeImageMaterialization,
182
190
  ActorRuntimeImageRelease,
183
191
  ActorRuntimeImageReleaseArchive,
@@ -475,6 +483,7 @@ def __getattr__(name: str) -> object:
475
483
 
476
484
 
477
485
  __all__ = [
486
+ "ActorFailureReason",
478
487
  "DataBindingCreateRequest",
479
488
  "DataBindingResponse",
480
489
  "DatasetRevisionContent",
@@ -512,6 +521,10 @@ __all__ = [
512
521
  "MagiDecisionReceiptResponse",
513
522
  "MagiDecisionRequest",
514
523
  "MagiMode",
524
+ "ManagedResearchFailureClassification",
525
+ "ManagedResearchFailureFamily",
526
+ "ManagedResearchFailureScope",
527
+ "ManagedResearchFailureSeverity",
515
528
  "ActivityActor",
516
529
  "ActivityActorState",
517
530
  "ActivityCursor",
@@ -530,6 +543,7 @@ __all__ = [
530
543
  "ActiveActorModel",
531
544
  "ActorHarness",
532
545
  "ActorImageBinding",
546
+ "ActorImageCapability",
533
547
  "ActorModel",
534
548
  "ActorModelAssignment",
535
549
  "ActorSubtype",
@@ -11,6 +11,7 @@ from __future__ import annotations
11
11
  from collections.abc import Mapping
12
12
  from dataclasses import dataclass
13
13
  from typing import TypeAlias
14
+ from uuid import UUID
14
15
 
15
16
  ACTOR_IMAGE_ROLES: tuple[str, ...] = (
16
17
  "orchestrator",
@@ -28,14 +29,21 @@ class ActorImageBinding:
28
29
  notes: str | None = None
29
30
 
30
31
  def __post_init__(self) -> None:
31
- if not isinstance(self.release_id, str) or not self.release_id.strip():
32
- raise ValueError("ActorImageBinding.release_id must be a nonempty string")
32
+ if not isinstance(self.release_id, str):
33
+ raise ValueError("ActorImageBinding.release_id must be a UUID string")
34
+ try:
35
+ release_id = str(UUID(self.release_id.strip()))
36
+ except (AttributeError, ValueError) as exc:
37
+ raise ValueError("ActorImageBinding.release_id must be a UUID string") from exc
38
+ object.__setattr__(self, "release_id", release_id)
33
39
  for field_name in ("reason", "notes"):
34
40
  value = getattr(self, field_name)
35
41
  if value is not None and (not isinstance(value, str) or not value.strip()):
36
42
  raise ValueError(
37
43
  f"ActorImageBinding.{field_name} must be a nonempty string when set"
38
44
  )
45
+ if value is not None:
46
+ object.__setattr__(self, field_name, value.strip())
39
47
 
40
48
  def to_wire(self) -> dict[str, object]:
41
49
  payload: dict[str, object] = {"release_id": self.release_id.strip()}
@@ -60,10 +68,14 @@ class ActorImageBinding:
60
68
  raise ValueError("actor image binding release_id must be a string")
61
69
  reason = payload.get("reason")
62
70
  notes = payload.get("notes")
71
+ if reason is not None and not isinstance(reason, str):
72
+ raise ValueError("actor image binding reason must be a string when set")
73
+ if notes is not None and not isinstance(notes, str):
74
+ raise ValueError("actor image binding notes must be a string when set")
63
75
  return cls(
64
76
  release_id=release_id,
65
- reason=reason if isinstance(reason, str) or reason is None else None,
66
- notes=notes if isinstance(notes, str) or notes is None else None,
77
+ reason=reason,
78
+ notes=notes,
67
79
  )
68
80
  raise ValueError(
69
81
  "actor image binding must be an ActorImageBinding, release id string, or mapping"
@@ -83,10 +95,14 @@ def actor_image_overrides_payload(
83
95
  raise ValueError("actor_image_overrides must map actor roles to bindings")
84
96
  payload: dict[str, dict[str, object]] = {}
85
97
  for raw_role, raw_binding in overrides.items():
86
- role = str(raw_role or "").strip().lower()
98
+ if not isinstance(raw_role, str):
99
+ raise ValueError("actor_image_overrides roles must be strings")
100
+ role = raw_role.strip().lower()
87
101
  if role not in ACTOR_IMAGE_ROLES:
88
102
  allowed = ", ".join(ACTOR_IMAGE_ROLES)
89
103
  raise ValueError(f"actor_image_overrides role must be one of: {allowed}")
104
+ if role in payload:
105
+ raise ValueError(f"actor_image_overrides repeats role: {role}")
90
106
  payload[role] = ActorImageBinding.from_wire(raw_binding).to_wire()
91
107
  return payload or None
92
108
 
@@ -550,7 +550,6 @@ class FactorySpec:
550
550
  "name": self.name,
551
551
  "kind": self.kind.value,
552
552
  "status": self.state.value,
553
- "result_authority_generation": self.result_authority_generation.value,
554
553
  "budget_policy": self.budget.to_wire() if self.budget is not None else {},
555
554
  "cap_policy": self.capacity.to_wire() if self.capacity is not None else {},
556
555
  "homeostasis_policy": {},
@@ -558,6 +557,11 @@ class FactorySpec:
558
557
  "authorization_policy": {},
559
558
  "metadata": dict(self.metadata),
560
559
  }
560
+ # Older production control planes predate this opt-in field and reject
561
+ # unknown keys. Legacy is the backend default, so only transmit an
562
+ # explicit non-legacy authority choice.
563
+ if self.result_authority_generation is not FactoryResultAuthorityGeneration.LEGACY:
564
+ value["result_authority_generation"] = self.result_authority_generation.value
561
565
  if self.description is not None:
562
566
  value["description"] = self.description
563
567
  return value
@@ -0,0 +1,173 @@
1
+ """Public Managed Research failure vocabulary and wire contract."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import StrEnum
7
+ from typing import Any
8
+
9
+
10
+ class ActorFailureReason(StrEnum):
11
+ ACTOR_STARTUP_RETRY_BUDGET_EXHAUSTED = "actor_startup_retry_budget_exhausted"
12
+ TASK_BLOCKED = "task_blocked"
13
+ TASK_FAILED = "task_failed"
14
+ RUN_FAILED = "run_failed"
15
+ ORCHESTRATOR_SESSION_START_FAILED = "orchestrator_session_start_failed"
16
+ ORCHESTRATOR_SESSION_FAILED = "orchestrator_session_failed"
17
+ TASK_CLAIM_MATERIALIZATION_FAILED = "task_claim_materialization_failed"
18
+ CLAIMED_ACTOR_MISSING_TASK_BINDING = "claimed_actor_missing_task_binding"
19
+ WORKER_HOST_CLAIMED_NON_TASK_ACTOR = "worker_host_claimed_non_task_actor"
20
+ WORKER_HOST_ACTOR_RUN_MISSING = "worker_host_actor_run_missing"
21
+ WORKER_HOST_CLAIMED_ACTOR_TASK_MISSING = "worker_host_claimed_actor_task_missing"
22
+ TASK_ALREADY_TERMINAL = "task_already_terminal"
23
+ DB_POOL_PRESSURE = "db_pool_pressure"
24
+ CONTROL_PLANE_PRESSURE = "control_plane_pressure"
25
+ PARTICIPANT_START_BACKPRESSURE = "participant_start_backpressure"
26
+ PARTICIPANT_SYNC_DEFERRED_UNCONSUMED = "participant_sync_deferred_unconsumed"
27
+ TERMINAL_TRANSITION_DEFERRED_UNCONSUMED = "terminal_transition_deferred_unconsumed"
28
+ PARTICIPANT_LIVE_CAPACITY_EXHAUSTED = "participant_live_capacity_exhausted"
29
+ PARTICIPANT_MEMORY_CAPACITY_EXHAUSTED = "participant_memory_capacity_exhausted"
30
+ EXTERNAL_IO_DEFERRED = "external_io_deferred"
31
+ OPENCODE_ENDPOINT_UNREACHABLE = "opencode_endpoint_unreachable"
32
+ OPENCODE_ENDPOINT_REFUSED = "opencode_endpoint_refused"
33
+ OPENCODE_ENDPOINT_LOST = "opencode_endpoint_lost"
34
+ OPENCODE_ENDPOINT_HEALTH_TIMEOUT = "opencode_endpoint_health_timeout"
35
+ OPENCODE_CONTAINER_OOM = "opencode_container_oom"
36
+ OPENCODE_EVENT_STREAM_ROUTE_UNREACHABLE = "opencode_event_stream_route_unreachable"
37
+ OPENCODE_EVENT_STREAM_ENDPOINT_REFUSED = "opencode_event_stream_endpoint_refused"
38
+ OPENCODE_EVENT_STREAM_READ_TIMEOUT = "opencode_event_stream_read_timeout"
39
+ OPENCODE_EVENT_STREAM_URL_ERROR = "opencode_event_stream_url_error"
40
+ OPENCODE_EVENT_STREAM_RECONNECT_GRACE_EXHAUSTED = (
41
+ "opencode_event_stream_reconnect_grace_exhausted"
42
+ )
43
+ PARTICIPANT_SESSION_MISSING = "participant_session_missing"
44
+ PARTICIPANT_SESSION_START_FAILED = "participant_session_start_failed"
45
+ PARTICIPANT_LIVE_UNBOUND = "participant_live_unbound"
46
+ PARTICIPANT_LIVE_HANDLE_MISSING = "participant_live_handle_missing"
47
+ PARTICIPANT_BINDING_FAILED = "participant_binding_failed"
48
+ CLAIMED_TASK_WITHOUT_PARTICIPANT = "claimed_task_without_participant"
49
+ STALE_CLAIM_REAPED = "stale_claim_reaped"
50
+ RUNTIME_INTENT_BACKLOG = "runtime_intent_backlog"
51
+ UNKNOWN_UNHEALTHY_EXPIRED = "unknown_unhealthy_expired"
52
+ RUNTIME_NO_PRODUCTIVE_PROGRESS = "runtime_no_productive_progress"
53
+
54
+
55
+ class ManagedResearchFailureFamily(StrEnum):
56
+ UNKNOWN = "unknown"
57
+ VALIDATION = "validation"
58
+ BOOTSTRAP = "bootstrap"
59
+ DISPATCH = "dispatch"
60
+ RUNTIME = "runtime"
61
+ RUNTIME_RECOVERY = "runtime_recovery"
62
+ TERMINALIZATION = "terminalization"
63
+ CLEANUP = "cleanup"
64
+ INFRASTRUCTURE = "infrastructure"
65
+ USAGE_CAPTURE = "usage_capture"
66
+ PARTICIPANT_START = "participant_start"
67
+ ORCHESTRATOR_CYCLE = "orchestrator_cycle"
68
+ ORCHESTRATOR_PROVIDER = "orchestrator_provider"
69
+ ORCHESTRATOR_CODEX = "orchestrator_codex"
70
+
71
+
72
+ class ManagedResearchFailureSeverity(StrEnum):
73
+ WARNING = "warning"
74
+ ERROR = "error"
75
+ FATAL = "fatal"
76
+
77
+
78
+ class ManagedResearchFailureScope(StrEnum):
79
+ ACTOR = "actor"
80
+ TASK = "task"
81
+ RUN = "run"
82
+
83
+
84
+ @dataclass(frozen=True)
85
+ class ManagedResearchFailureClassification:
86
+ family: ManagedResearchFailureFamily
87
+ code: str
88
+ severity: ManagedResearchFailureSeverity
89
+ retryable: bool
90
+ scope: ManagedResearchFailureScope | None = None
91
+ operator_action: str | None = None
92
+ detail: str | None = None
93
+ actor_id: str | None = None
94
+ actor_key: str | None = None
95
+ task_id: str | None = None
96
+ task_key: str | None = None
97
+ recorded_at: str | None = None
98
+ family_raw: str | None = None
99
+ raw: dict[str, Any] = field(default_factory=dict, repr=False, compare=False)
100
+
101
+ @classmethod
102
+ def from_wire(cls, payload: object) -> ManagedResearchFailureClassification:
103
+ if not isinstance(payload, dict):
104
+ raise ValueError("run failure classification must be an object")
105
+ family = payload.get("family")
106
+ code = payload.get("code")
107
+ severity = payload.get("severity")
108
+ retryable = payload.get("retryable")
109
+ if not isinstance(family, str) or not isinstance(code, str):
110
+ raise ValueError("run failure classification requires family and code")
111
+ if not isinstance(severity, str) or not isinstance(retryable, bool):
112
+ raise ValueError("run failure classification requires severity and retryable")
113
+ try:
114
+ parsed_family = ManagedResearchFailureFamily(family)
115
+ except ValueError:
116
+ parsed_family = ManagedResearchFailureFamily.UNKNOWN
117
+ return cls(
118
+ family=parsed_family,
119
+ code=code,
120
+ severity=ManagedResearchFailureSeverity(severity),
121
+ retryable=retryable,
122
+ scope=(
123
+ ManagedResearchFailureScope(payload["scope"])
124
+ if isinstance(payload.get("scope"), str)
125
+ else None
126
+ ),
127
+ operator_action=payload.get("operator_action")
128
+ if isinstance(payload.get("operator_action"), str)
129
+ else None,
130
+ detail=payload.get("detail") if isinstance(payload.get("detail"), str) else None,
131
+ actor_id=payload.get("actor_id") if isinstance(payload.get("actor_id"), str) else None,
132
+ actor_key=payload.get("actor_key")
133
+ if isinstance(payload.get("actor_key"), str)
134
+ else None,
135
+ task_id=payload.get("task_id") if isinstance(payload.get("task_id"), str) else None,
136
+ task_key=payload.get("task_key") if isinstance(payload.get("task_key"), str) else None,
137
+ recorded_at=payload.get("recorded_at")
138
+ if isinstance(payload.get("recorded_at"), str)
139
+ else None,
140
+ family_raw=family if parsed_family is ManagedResearchFailureFamily.UNKNOWN else None,
141
+ raw={str(key): value for key, value in payload.items()},
142
+ )
143
+
144
+ def to_wire(self) -> dict[str, Any]:
145
+ return {
146
+ key: value
147
+ for key, value in {
148
+ **self.raw,
149
+ "family": self.family.value,
150
+ "code": self.code,
151
+ "severity": self.severity.value,
152
+ "retryable": self.retryable,
153
+ "scope": self.scope.value if self.scope is not None else None,
154
+ "operator_action": self.operator_action,
155
+ "detail": self.detail,
156
+ "actor_id": self.actor_id,
157
+ "actor_key": self.actor_key,
158
+ "task_id": self.task_id,
159
+ "task_key": self.task_key,
160
+ "recorded_at": self.recorded_at,
161
+ "family_raw": self.family_raw,
162
+ }.items()
163
+ if value is not None
164
+ }
165
+
166
+
167
+ __all__ = [
168
+ "ActorFailureReason",
169
+ "ManagedResearchFailureClassification",
170
+ "ManagedResearchFailureFamily",
171
+ "ManagedResearchFailureScope",
172
+ "ManagedResearchFailureSeverity",
173
+ ]
@@ -82,6 +82,26 @@ class ImageReleaseKind(StrEnum):
82
82
  CRAFTAX_SCORER = "craftax_scorer"
83
83
 
84
84
 
85
+ class ActorImageCapability(StrEnum):
86
+ """Capabilities admitted by the backend actor-runtime image contract."""
87
+
88
+ CODEX_CLI = "codex_cli"
89
+ CRAFTAX_EVAL = "craftax_eval"
90
+ CRAFTER_EVAL = "crafter_eval"
91
+ DUNGEONGRID_EVAL = "dungeongrid_eval"
92
+ IMAGE_ARTIFACTS = "image_artifacts"
93
+ JAX_CPU = "jax_cpu"
94
+ MANAGED_RESEARCH_SDK = "managed_research_sdk"
95
+ MCP_CLIENT = "mcp_client"
96
+ MODAL_CLIENT = "modal_client"
97
+ NETHACK_EVAL = "nethack_eval"
98
+ NUMPY_STACK = "numpy_stack"
99
+ OPENCODE = "opencode"
100
+ OPENROUTER_CLIENT = "openrouter_client"
101
+ SYNTH_SDK = "synth_sdk"
102
+ VIDEO_ARTIFACTS = "video_artifacts"
103
+
104
+
85
105
  class RuntimeImageReleaseStatus(StrEnum):
86
106
  ACTIVE = "active"
87
107
  ARCHIVED = "archived"
@@ -149,6 +169,15 @@ def _strings(
149
169
  return tuple(out)
150
170
 
151
171
 
172
+ def _capabilities(value: JsonValue) -> tuple[str, ...]:
173
+ capabilities = _strings(value, "capabilities", minimum=1)
174
+ allowed = frozenset(capability.value for capability in ActorImageCapability)
175
+ unexpected = sorted(set(capabilities) - allowed)
176
+ if unexpected:
177
+ raise ValueError(f"capabilities contain unsupported values: {unexpected!r}")
178
+ return capabilities
179
+
180
+
152
181
  def _datetime(value: object, field: str) -> datetime:
153
182
  parsed = (
154
183
  value
@@ -260,7 +289,9 @@ class CraftaxScorerImageReleaseDeclaration(_DeclarationBase):
260
289
  fixture_binary_sha256: str
261
290
 
262
291
  def __post_init__(self) -> None:
263
- super().__post_init__()
292
+ # Explicit base dispatch is required for frozen slotted dataclasses on
293
+ # Python 3.12: zero-argument super() can retain the pre-slots class.
294
+ _DeclarationBase.__post_init__(self)
264
295
  if self.kind is not ImageReleaseKind.CRAFTAX_SCORER:
265
296
  raise ValueError("craftax scorer declaration kind must be craftax_scorer")
266
297
  object.__setattr__(
@@ -304,7 +335,8 @@ class ActorRuntimeImageReleaseDeclaration(_DeclarationBase):
304
335
  recipe_digest: str | None = None
305
336
 
306
337
  def __post_init__(self) -> None:
307
- super().__post_init__()
338
+ # See CraftaxScorerImageReleaseDeclaration.__post_init__.
339
+ _DeclarationBase.__post_init__(self)
308
340
  if self.kind is not ImageReleaseKind.ACTOR_RUNTIME:
309
341
  raise ValueError("actor runtime declaration kind must be actor_runtime")
310
342
  object.__setattr__(self, "actor_role", _const(self.actor_role, "actor_role", "worker"))
@@ -313,9 +345,7 @@ class ActorRuntimeImageReleaseDeclaration(_DeclarationBase):
313
345
  "interface_mode",
314
346
  _const(self.interface_mode, "interface_mode", "synth_actor_runtime"),
315
347
  )
316
- object.__setattr__(
317
- self, "capabilities", _strings(list(self.capabilities), "capabilities", minimum=1)
318
- )
348
+ object.__setattr__(self, "capabilities", _capabilities(list(self.capabilities)))
319
349
  object.__setattr__(
320
350
  self,
321
351
  "python_packages",
@@ -439,7 +469,7 @@ def declaration_from_wire(value: JsonValue) -> ImageReleaseDeclaration:
439
469
  source_commit_sha=source_commit_sha,
440
470
  actor_role=_const(payload["actor_role"], "actor_role", "worker"),
441
471
  interface_mode=_const(payload["interface_mode"], "interface_mode", "synth_actor_runtime"),
442
- capabilities=_strings(payload["capabilities"], "capabilities", minimum=1),
472
+ capabilities=_capabilities(payload["capabilities"]),
443
473
  python_packages=_strings(payload["python_packages"], "python_packages", pattern=_PACKAGE),
444
474
  recipe_digest=optional_digest(payload.get("recipe_digest"), field="recipe_digest"),
445
475
  )
@@ -717,7 +747,7 @@ class ActorRuntimeImageMaterialization:
717
747
  selection_kind=_const(
718
748
  payload["selection_kind"], "selection_kind", "customer_actor_runtime"
719
749
  ),
720
- capabilities=_strings(payload["capabilities"], "capabilities", minimum=1),
750
+ capabilities=_capabilities(payload["capabilities"]),
721
751
  python_packages=_strings(
722
752
  payload["python_packages"], "python_packages", pattern=_PACKAGE
723
753
  ),
@@ -1199,6 +1229,7 @@ __all__ = [
1199
1229
  "ActorRuntimeImageReleaseArchive",
1200
1230
  "ActorRuntimeImageReleaseDeclaration",
1201
1231
  "ActorRuntimeImageReleaseList",
1232
+ "ActorImageCapability",
1202
1233
  "CraftaxScorerImageRelease",
1203
1234
  "CraftaxScorerImageReleaseDeclaration",
1204
1235
  "ImageRelease",
@@ -6,6 +6,7 @@ from collections.abc import Mapping
6
6
  from dataclasses import dataclass, field
7
7
  from enum import StrEnum
8
8
 
9
+ from synth_ai.sdk.research.contracts.failure import ManagedResearchFailureClassification
9
10
  from synth_ai.sdk.research.contracts.run_state import (
10
11
  ManagedResearchRun,
11
12
  ManagedResearchRunLivenessPhase,
@@ -1307,7 +1308,7 @@ class ManagedResearchRunContractIncidents:
1307
1308
  class ManagedResearchRunContractDiagnostics:
1308
1309
  lifecycle_invariants: list[dict[str, object]] = field(default_factory=list)
1309
1310
  resource_wait: dict[str, object] | None = None
1310
- failure_classification: dict[str, object] | None = None
1311
+ failure_classification: ManagedResearchFailureClassification | None = None
1311
1312
 
1312
1313
  @classmethod
1313
1314
  def from_wire(cls, payload: object) -> ManagedResearchRunContractDiagnostics:
@@ -1327,10 +1328,12 @@ class ManagedResearchRunContractDiagnostics:
1327
1328
  )
1328
1329
  if resource_wait is not None
1329
1330
  else None,
1330
- failure_classification=dict(
1331
- _require_mapping(
1332
- failure_classification,
1333
- label="run_contract.diagnostics.failure_classification",
1331
+ failure_classification=ManagedResearchFailureClassification.from_wire(
1332
+ dict(
1333
+ _require_mapping(
1334
+ failure_classification,
1335
+ label="run_contract.diagnostics.failure_classification",
1336
+ )
1334
1337
  )
1335
1338
  )
1336
1339
  if failure_classification is not None
@@ -1509,6 +1512,7 @@ __all__ = [
1509
1512
  "ManagedResearchRunContractArtifacts",
1510
1513
  "ManagedResearchRunContractContainerEvalPackages",
1511
1514
  "ManagedResearchRunContractDiagnostics",
1515
+ "ManagedResearchFailureClassification",
1512
1516
  "ManagedResearchRunContractExecutionRoute",
1513
1517
  "ManagedResearchRunContractFinalization",
1514
1518
  "ManagedResearchRunContractIncidents",
@@ -5,8 +5,12 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import hashlib
9
+ from pathlib import Path
8
10
  from typing import cast
9
11
 
12
+ import httpx
13
+
10
14
  from synth_ai.core.contracts.json_value import JsonObject, JsonValue
11
15
  from synth_ai.core.http.async_transport import AsyncHttpTransport
12
16
  from synth_ai.core.http.request import HttpRequest
@@ -79,6 +83,52 @@ def _retrieve(value: object, *, release_id: ImageReleaseId) -> ImageRelease:
79
83
  return release
80
84
 
81
85
 
86
+ def _archive_path(
87
+ archive_path: str | Path,
88
+ *,
89
+ request: ImageReleaseUploadRequest,
90
+ ) -> Path:
91
+ """Verify the exact bytes that the backend will materialize.
92
+
93
+ The signed upload URL is intentionally the only direct-storage capability
94
+ used here. Authentication for the Research API is never forwarded to the
95
+ object store.
96
+ """
97
+ path = Path(archive_path).expanduser().resolve()
98
+ if not path.is_file():
99
+ raise ValueError(f"image release archive is not a file: {path}")
100
+ if path.stat().st_size != request.declaration.archive_size_bytes:
101
+ raise ValueError("image release archive size does not match its declaration")
102
+ digest = hashlib.sha256()
103
+ with path.open("rb") as handle:
104
+ for block in iter(lambda: handle.read(1024 * 1024), b""):
105
+ digest.update(block)
106
+ if digest.hexdigest() != request.declaration.archive_sha256:
107
+ raise ValueError("image release archive digest does not match its declaration")
108
+ return path
109
+
110
+
111
+ def _upload_timeout_seconds(value: float) -> float:
112
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
113
+ raise ValueError("upload_timeout_seconds must be a number")
114
+ timeout_seconds = float(value)
115
+ if not 1.0 <= timeout_seconds <= 7200.0:
116
+ raise ValueError("upload_timeout_seconds must be between 1 and 7200")
117
+ return timeout_seconds
118
+
119
+
120
+ def _verify_finalized_upload(
121
+ finalized: ImageReleaseFinalize,
122
+ *,
123
+ upload: ImageReleaseUpload,
124
+ ) -> ImageReleaseFinalize:
125
+ if finalized.release.release_id != upload.release_id:
126
+ raise ValueError("image finalize response changed its release identity")
127
+ if finalized.upload_reconciliation.upload_id != upload.upload_id:
128
+ raise ValueError("image finalize response changed its upload identity")
129
+ return finalized
130
+
131
+
82
132
  class ImageReleasesAPI:
83
133
  """Immutable uploads plus executable actor-image materializations."""
84
134
 
@@ -115,6 +165,48 @@ class ImageReleasesAPI:
115
165
  )
116
166
  return _finalize(value, request=request)
117
167
 
168
+ def upload_archive(
169
+ self,
170
+ archive_path: str | Path,
171
+ request: ImageReleaseUploadRequest,
172
+ *,
173
+ upload_timeout_seconds: float = 1800.0,
174
+ ) -> ImageReleaseFinalize:
175
+ """Upload and materialize an exact declared OCI archive.
176
+
177
+ The local archive is validated before requesting a presigned URL. A
178
+ non-2xx object-store response is terminal: finalize is never attempted
179
+ against bytes the SDK did not observe as successfully uploaded.
180
+ """
181
+ if not isinstance(request, ImageReleaseUploadRequest):
182
+ raise ValueError("request must be ImageReleaseUploadRequest")
183
+ path = _archive_path(archive_path, request=request)
184
+ timeout_seconds = _upload_timeout_seconds(upload_timeout_seconds)
185
+ upload = self.create_upload(request)
186
+ if upload.upload_required:
187
+ with (
188
+ httpx.Client(timeout=timeout_seconds, follow_redirects=False) as client,
189
+ path.open("rb") as archive,
190
+ ):
191
+ response = client.put(
192
+ upload.upload_url,
193
+ content=archive,
194
+ headers={"Content-Length": str(request.declaration.archive_size_bytes)},
195
+ )
196
+ if not response.is_success:
197
+ raise RuntimeError(
198
+ f"image release archive upload failed with HTTP {response.status_code}"
199
+ )
200
+ return _verify_finalized_upload(
201
+ self.finalize(
202
+ ImageReleaseFinalizeRequest(
203
+ upload_id=upload.upload_id,
204
+ declaration=request.declaration,
205
+ )
206
+ ),
207
+ upload=upload,
208
+ )
209
+
118
210
  def list(self) -> ActorRuntimeImageReleaseList:
119
211
  value = self._transport.execute(
120
212
  _request(
@@ -147,6 +239,14 @@ class ImageReleasesAPI:
147
239
  )
148
240
  return _retrieve(value, release_id=release_id)
149
241
 
242
+ def get(self, release_id: ImageReleaseId) -> ImageRelease:
243
+ """Retrieve one immutable image-release receipt.
244
+
245
+ ``retrieve`` remains available for callers on the earlier core-client
246
+ surface; ``get`` matches the lifecycle verb used by the session API.
247
+ """
248
+ return self.retrieve(release_id)
249
+
150
250
 
151
251
  class AsyncImageReleasesAPI:
152
252
  """Native-async peer of :class:`ImageReleasesAPI`."""
@@ -184,6 +284,44 @@ class AsyncImageReleasesAPI:
184
284
  )
185
285
  return _finalize(value, request=request)
186
286
 
287
+ async def upload_archive(
288
+ self,
289
+ archive_path: str | Path,
290
+ request: ImageReleaseUploadRequest,
291
+ *,
292
+ upload_timeout_seconds: float = 1800.0,
293
+ ) -> ImageReleaseFinalize:
294
+ """Native-async upload and materialization of an exact OCI archive."""
295
+ if not isinstance(request, ImageReleaseUploadRequest):
296
+ raise ValueError("request must be ImageReleaseUploadRequest")
297
+ path = _archive_path(archive_path, request=request)
298
+ timeout_seconds = _upload_timeout_seconds(upload_timeout_seconds)
299
+ upload = await self.create_upload(request)
300
+ if upload.upload_required:
301
+ async with httpx.AsyncClient(
302
+ timeout=timeout_seconds,
303
+ follow_redirects=False,
304
+ ) as client:
305
+ with path.open("rb") as archive:
306
+ response = await client.put(
307
+ upload.upload_url,
308
+ content=archive,
309
+ headers={"Content-Length": str(request.declaration.archive_size_bytes)},
310
+ )
311
+ if not response.is_success:
312
+ raise RuntimeError(
313
+ f"image release archive upload failed with HTTP {response.status_code}"
314
+ )
315
+ return _verify_finalized_upload(
316
+ await self.finalize(
317
+ ImageReleaseFinalizeRequest(
318
+ upload_id=upload.upload_id,
319
+ declaration=request.declaration,
320
+ )
321
+ ),
322
+ upload=upload,
323
+ )
324
+
187
325
  async def list(self) -> ActorRuntimeImageReleaseList:
188
326
  value = await self._transport.execute(
189
327
  _request(
@@ -216,5 +354,9 @@ class AsyncImageReleasesAPI:
216
354
  )
217
355
  return _retrieve(value, release_id=release_id)
218
356
 
357
+ async def get(self, release_id: ImageReleaseId) -> ImageRelease:
358
+ """Native-async peer of :meth:`ImageReleasesAPI.get`."""
359
+ return await self.retrieve(release_id)
360
+
219
361
 
220
362
  __all__ = ["AsyncImageReleasesAPI", "ImageReleasesAPI"]