mainsequence 8.0.7__tar.gz → 8.1.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 (139) hide show
  1. {mainsequence-8.0.7/mainsequence.egg-info → mainsequence-8.1.0}/PKG-INFO +1 -1
  2. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/agent_runtime_models.py +165 -13
  3. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/base.py +0 -1
  4. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/metatables/core.py +0 -282
  5. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -4
  6. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/time_index_table_updates/runner.py +30 -89
  7. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -19
  8. {mainsequence-8.0.7 → mainsequence-8.1.0/mainsequence.egg-info}/PKG-INFO +1 -1
  9. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence.egg-info/SOURCES.txt +0 -1
  10. {mainsequence-8.0.7 → mainsequence-8.1.0}/pyproject.toml +1 -1
  11. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_code_repository_runtime_context.py +0 -1
  12. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_environment_response_contracts.py +0 -6
  13. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_filter_normalization.py +211 -0
  14. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_time_index_table_update_configuration_client.py +27 -26
  15. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_time_index_table_update_flow.py +0 -2
  16. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_update_runner_uid_runtime.py +52 -67
  17. mainsequence-8.0.7/tests/test_scheduler_client_model.py +0 -172
  18. {mainsequence-8.0.7 → mainsequence-8.1.0}/LICENSE +0 -0
  19. {mainsequence-8.0.7 → mainsequence-8.1.0}/MANIFEST.in +0 -0
  20. {mainsequence-8.0.7 → mainsequence-8.1.0}/README.md +0 -0
  21. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/AGENTS.md +0 -0
  22. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  23. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  24. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  25. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  26. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  27. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
  28. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  29. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  30. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  31. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  32. {mainsequence-8.0.7 → mainsequence-8.1.0}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  33. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/__init__.py +0 -0
  34. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/__main__.py +0 -0
  35. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/bootstrap.py +0 -0
  36. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/__init__.py +0 -0
  37. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/api.py +0 -0
  38. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/browser_auth.py +0 -0
  39. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/cli.py +0 -0
  40. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/code_repository_status.py +0 -0
  41. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/config.py +0 -0
  42. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/docker_utils.py +0 -0
  43. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/doctor.py +0 -0
  44. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/local_ops.py +0 -0
  45. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/migrations.py +0 -0
  46. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/model_filters.py +0 -0
  47. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/pydantic_cli.py +0 -0
  48. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/sdk_utils.py +0 -0
  49. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/ssh_utils.py +0 -0
  50. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/cli/ui.py +0 -0
  51. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/__init__.py +0 -0
  52. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/client.py +0 -0
  53. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/compute_validation.py +0 -0
  54. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  55. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  56. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  57. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  58. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/dtype_codec.py +0 -0
  59. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/exceptions.py +0 -0
  60. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/metatables/__init__.py +0 -0
  61. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/models_foundry.py +0 -0
  62. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/models_helpers.py +0 -0
  63. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/models_user.py +0 -0
  64. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/observability.py +0 -0
  65. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/client/utils.py +0 -0
  66. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/code_repository_context.py +0 -0
  67. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/code_repository_skills.py +0 -0
  68. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/defaults.py +0 -0
  69. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/instrumentation/__init__.py +0 -0
  70. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/instrumentation/utils.py +0 -0
  71. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/logconf.py +0 -0
  72. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/__init__.py +0 -0
  73. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/__main__.py +0 -0
  74. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  75. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  76. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/future_registry.py +0 -0
  77. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/hashing.py +0 -0
  78. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  79. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  80. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/migrations/env.py +0 -0
  81. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/migrations/provider.py +0 -0
  82. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/migrations/registry.py +0 -0
  83. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  84. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  85. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  86. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  87. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  88. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/schema_names.py +0 -0
  89. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  90. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
  91. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
  92. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
  93. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
  94. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
  95. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
  96. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/repository_identity_security.py +0 -0
  97. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/runtime_flags.py +0 -0
  98. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence/scaffold_skills.py +0 -0
  99. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence.egg-info/dependency_links.txt +0 -0
  100. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence.egg-info/entry_points.txt +0 -0
  101. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence.egg-info/requires.txt +0 -0
  102. {mainsequence-8.0.7 → mainsequence-8.1.0}/mainsequence.egg-info/top_level.txt +0 -0
  103. {mainsequence-8.0.7 → mainsequence-8.1.0}/setup.cfg +0 -0
  104. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
  105. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_auth_precedence.py +0 -0
  106. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_canonical_api_route_contract.py +0 -0
  107. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_cli.py +0 -0
  108. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_cli_browser_auth.py +0 -0
  109. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_cli_migrations.py +0 -0
  110. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_client.py +0 -0
  111. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_code_repository_batch_jobs_from_file.py +0 -0
  112. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_code_repository_branch_contract.py +0 -0
  113. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_code_repository_documentation_contract.py +0 -0
  114. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_code_repository_skills.py +0 -0
  115. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_dependency_extras.py +0 -0
  116. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_duckdb_interface_dimensions.py +0 -0
  117. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_environment_resource_context.py +0 -0
  118. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_instrumentation.py +0 -0
  119. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_local_ops.py +0 -0
  120. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_logconf.py +0 -0
  121. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_meta_table_migrations.py +0 -0
  122. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_meta_tables_client_models.py +0 -0
  123. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  124. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_models_user_request_bound_auth.py +0 -0
  125. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_notification_uid_contract.py +0 -0
  126. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_observability_client.py +0 -0
  127. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_pod_project_resolution.py +0 -0
  128. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_scaffold_skills.py +0 -0
  129. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_schema_names.py +0 -0
  130. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_secret_client_model.py +0 -0
  131. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_source_table_configuration.py +0 -0
  132. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_sqlite_interface_dimensions.py +0 -0
  133. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_ssh_utils.py +0 -0
  134. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_time_index_table_access_dimension_audit.py +0 -0
  135. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_time_index_table_dimension_queries.py +0 -0
  136. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_time_index_table_update_configuration.py +0 -0
  137. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_time_index_table_update_hard_cut.py +0 -0
  138. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_update_statistics.py +0 -0
  139. {mainsequence-8.0.7 → mainsequence-8.1.0}/tests/test_update_uid_guards.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.0.7
3
+ Version: 8.1.0
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -36,6 +36,11 @@ STANDARD_A2A_OUTPUT_CONTRACT_METADATA_KEY = (
36
36
  )
37
37
  STANDARD_AGENT_INFERENCE_METADATA_KEY = "https://mainsequence.ai/a2a/extensions/agent-inference/v1"
38
38
  MAX_INLINE_A2A_FILE_BYTES = 15 * 1024 * 1024
39
+ TRANSIENT_RUNTIME_INTERACTION_STATES = frozenset(
40
+ {"checking", "starting", "waking", "updating"}
41
+ )
42
+ MIN_RUNTIME_INTERACTION_RETRY_SECONDS = 0.5
43
+ MAX_RUNTIME_INTERACTION_RETRY_SECONDS = 30.0
39
44
 
40
45
 
41
46
  class AgentSessionStatus(str, Enum):
@@ -229,6 +234,93 @@ class AgentRuntimePaths(BasePydanticModel):
229
234
  pass
230
235
 
231
236
 
237
+ class AgentRuntimeInteractionNotice(BasePydanticModel):
238
+ model_config = ConfigDict(extra="allow")
239
+
240
+ code: str
241
+ severity: str
242
+ title: str
243
+ message: str
244
+
245
+
246
+ class AgentRuntimeInteractionAction(BasePydanticModel):
247
+ model_config = ConfigDict(extra="allow")
248
+
249
+ type: Literal["update_agent_runtime", "retry_runtime_wake"]
250
+ label: str
251
+ confirmation_message: str
252
+ interaction_revision: str
253
+
254
+
255
+ class AgentRuntimeInteractionOperation(BasePydanticModel):
256
+ model_config = ConfigDict(extra="allow")
257
+
258
+ uid: str
259
+ status: str
260
+ created_at: str
261
+ started_at: str | None = None
262
+ finished_at: str | None = None
263
+ support_reference: str
264
+
265
+
266
+ class AgentRuntimeInteraction(BasePydanticModel):
267
+ model_config = ConfigDict(extra="allow")
268
+
269
+ state: Literal[
270
+ "ready",
271
+ "warning",
272
+ "checking",
273
+ "starting",
274
+ "waking",
275
+ "update_required",
276
+ "updating",
277
+ "update_failed",
278
+ "unavailable",
279
+ ]
280
+ can_submit: bool
281
+ notice: AgentRuntimeInteractionNotice | None
282
+ action: AgentRuntimeInteractionAction | None
283
+ operation: AgentRuntimeInteractionOperation | None
284
+ retry_after_ms: int | None = Field(None, ge=0)
285
+
286
+
287
+ class AgentRuntimePresenceReplicas(BasePydanticModel):
288
+ desired: int | None = Field(None, ge=0)
289
+ actual: int | None = Field(None, ge=0)
290
+
291
+
292
+ class AgentRuntimePresenceWake(BasePydanticModel):
293
+ operation_uid: str
294
+ state: Literal[
295
+ "requested",
296
+ "in_progress",
297
+ "serving",
298
+ "failed",
299
+ "expired",
300
+ "superseded",
301
+ ]
302
+ requested_at: str
303
+ deadline_at: str
304
+
305
+
306
+ class AgentRuntimePresence(BasePydanticModel):
307
+ phase: Literal[
308
+ "not_deployed",
309
+ "observing",
310
+ "idle",
311
+ "provisioning",
312
+ "pulling_image",
313
+ "starting",
314
+ "serving",
315
+ "redeploying",
316
+ "failed",
317
+ ]
318
+ replicas: AgentRuntimePresenceReplicas
319
+ detail: str
320
+ observed_at: str | None
321
+ wake: AgentRuntimePresenceWake | None
322
+
323
+
232
324
  class AgentSessionRuntimeAccess(BasePydanticModel):
233
325
  model_config = ConfigDict(extra="allow")
234
326
 
@@ -287,6 +379,16 @@ class AgentSessionRuntimeAccess(BasePydanticModel):
287
379
  None,
288
380
  description="Runtime image drift payload for the resolved coding-agent runtime.",
289
381
  )
382
+ runtime_interaction: AgentRuntimeInteraction = Field(
383
+ ...,
384
+ description=(
385
+ "Authoritative backend decision for whether a new runtime message may be submitted."
386
+ ),
387
+ )
388
+ runtime_presence: AgentRuntimePresence = Field(
389
+ ...,
390
+ description="Sanitized diagnostic runtime-presence evidence.",
391
+ )
290
392
 
291
393
  @property
292
394
  def image_drift_dict(self) -> dict[str, Any] | None:
@@ -313,6 +415,27 @@ def _join_runtime_url(rpc_url: str, runtime_path: str) -> str:
313
415
  return f"{rpc_url.rstrip('/')}/{runtime_path.lstrip('/')}"
314
416
 
315
417
 
418
+ def _runtime_access_blocked_message(access: AgentSessionRuntimeAccess) -> str:
419
+ notice = access.runtime_interaction.notice
420
+ if notice is not None and notice.message.strip():
421
+ return notice.message.strip()
422
+ if access.detail and access.detail.strip():
423
+ return f"Coding-agent runtime access is unavailable. {access.detail.strip()}"
424
+ return "Coding-agent runtime access is unavailable."
425
+
426
+
427
+ def _runtime_access_retry_seconds(access: AgentSessionRuntimeAccess) -> float:
428
+ retry_after_ms = access.runtime_interaction.retry_after_ms
429
+ if retry_after_ms is None:
430
+ raise ApiError(
431
+ "Transient runtime access response is missing runtime_interaction.retry_after_ms."
432
+ )
433
+ return min(
434
+ MAX_RUNTIME_INTERACTION_RETRY_SECONDS,
435
+ max(MIN_RUNTIME_INTERACTION_RETRY_SECONDS, retry_after_ms / 1000.0),
436
+ )
437
+
438
+
316
439
  class Agent(
317
440
  OwnerLogMixin,
318
441
  OwnerResourceUsageMixin,
@@ -481,6 +604,22 @@ class Agent(
481
604
  raise TypeError("Agent runtime access response must be a JSON object")
482
605
  return AgentSessionRuntimeAccess(**data)
483
606
 
607
+ def _resolve_runtime_access_for_message_send(
608
+ self,
609
+ *,
610
+ timeout=None,
611
+ ) -> AgentSessionRuntimeAccess:
612
+ """Wait only while an active backend-owned transition blocks submission."""
613
+
614
+ while True:
615
+ access = self.resolve_runtime_access(timeout=timeout)
616
+ interaction = access.runtime_interaction
617
+ if interaction.can_submit:
618
+ return access
619
+ if interaction.state not in TRANSIENT_RUNTIME_INTERACTION_STATES:
620
+ raise ApiError(_runtime_access_blocked_message(access))
621
+ time.sleep(_runtime_access_retry_seconds(access))
622
+
484
623
  def _build_response_body(
485
624
  self,
486
625
  *,
@@ -601,7 +740,7 @@ class Agent(
601
740
  timeout_seconds=timeout_seconds,
602
741
  )
603
742
  response = self._post_response(
604
- access=self.resolve_runtime_access(timeout=timeout),
743
+ access=self._resolve_runtime_access_for_message_send(timeout=timeout),
605
744
  body=body,
606
745
  stream=False,
607
746
  timeout=timeout,
@@ -643,7 +782,7 @@ class Agent(
643
782
  timeout_seconds=timeout_seconds,
644
783
  )
645
784
  response = self._post_response(
646
- access=self.resolve_runtime_access(timeout=timeout),
785
+ access=self._resolve_runtime_access_for_message_send(timeout=timeout),
647
786
  body=body,
648
787
  stream=True,
649
788
  timeout=timeout,
@@ -1303,14 +1442,25 @@ class AgentSession(OwnerLogMixin, BaseObjectOrm, BasePydanticModel):
1303
1442
  timeout=None,
1304
1443
  ) -> AgentSessionRuntimeAccess:
1305
1444
  session_uid = cls._resolve_agent_session_uid(agent_session)
1306
- cached = cls.get_cached_runtime_access(session_uid)
1307
- if cached is not None:
1308
- return cached
1309
- return cls.resolve_runtime_access(
1310
- session_uid,
1311
- cache=True,
1312
- timeout=timeout,
1313
- )
1445
+ access = cls.get_cached_runtime_access(session_uid)
1446
+ while True:
1447
+ if access is None:
1448
+ access = cls.resolve_runtime_access(
1449
+ session_uid,
1450
+ cache=True,
1451
+ timeout=timeout,
1452
+ )
1453
+ interaction = access.runtime_interaction
1454
+ if interaction.can_submit:
1455
+ return access
1456
+ if interaction.state not in TRANSIENT_RUNTIME_INTERACTION_STATES:
1457
+ raise ApiError(_runtime_access_blocked_message(access))
1458
+ time.sleep(_runtime_access_retry_seconds(access))
1459
+ access = cls.resolve_runtime_access(
1460
+ session_uid,
1461
+ cache=True,
1462
+ timeout=timeout,
1463
+ )
1314
1464
 
1315
1465
  @staticmethod
1316
1466
  def _build_standard_a2a_message_send_body(
@@ -1513,9 +1663,8 @@ class AgentSession(OwnerLogMixin, BaseObjectOrm, BasePydanticModel):
1513
1663
  )
1514
1664
  if response.status_code in (401, 403):
1515
1665
  cls.clear_cached_runtime_access(session_uid)
1516
- access = cls.resolve_runtime_access(
1666
+ access = cls._resolve_runtime_access_for_message_send(
1517
1667
  session_uid,
1518
- cache=True,
1519
1668
  timeout=timeout,
1520
1669
  )
1521
1670
  response = cls._post_standard_a2a_message(
@@ -1594,7 +1743,10 @@ class AgentSession(OwnerLogMixin, BaseObjectOrm, BasePydanticModel):
1594
1743
  response = request(access)
1595
1744
  if response.status_code in (401, 403):
1596
1745
  cls.clear_cached_runtime_access(session_uid)
1597
- access = cls.resolve_runtime_access(session_uid, cache=True, timeout=timeout)
1746
+ access = cls._resolve_runtime_access_for_message_send(
1747
+ session_uid,
1748
+ timeout=timeout,
1749
+ )
1598
1750
  response = request(access)
1599
1751
  if response.status_code in (401, 403):
1600
1752
  cls.clear_cached_runtime_access(session_uid)
@@ -134,7 +134,6 @@ class BaseObjectOrm:
134
134
  END_POINTS = {
135
135
  "User": "users",
136
136
  # TDAG
137
- "Scheduler": "schedulers",
138
137
  "TimeIndexMetaTable": "time-index-meta-tables",
139
138
  "TimeIndexTableUpdate": "time-index-table-updates",
140
139
  "TimeIndexTableUpdateDetails": "time-index-table-update-details",
@@ -9,7 +9,6 @@ import json
9
9
  import math
10
10
  import os
11
11
  import re
12
- import time
13
12
  from collections.abc import Callable, Mapping, Sequence
14
13
  from dataclasses import dataclass, field
15
14
  from typing import Any, ClassVar, Literal, TypedDict
@@ -57,11 +56,8 @@ from ..dtype_codec import (
57
56
  )
58
57
  from ..exceptions import raise_for_response
59
58
  from ..utils import (
60
- TDAG_CONSTANTS,
61
59
  DateInfo,
62
60
  bios_uuid,
63
- get_network_ip,
64
- is_process_running,
65
61
  make_request,
66
62
  serialize_to_json,
67
63
  )
@@ -2114,10 +2110,6 @@ class AlreadyExist(Exception):
2114
2110
  pass
2115
2111
 
2116
2112
 
2117
- class SchedulerDoesNotExist(Exception):
2118
- pass
2119
-
2120
-
2121
2113
  class TimeIndexTableUpdateDoesNotExist(Exception):
2122
2114
  pass
2123
2115
 
@@ -2532,7 +2524,6 @@ class TimeIndexTableUpdate(TableUpdateNode, BaseObjectOrm):
2532
2524
  table_updater_source_code_git_hash: str | None = None
2533
2525
  table_updater_source_code: str | None = None
2534
2526
  update_details: TimeIndexTableUpdateDetails | None = None
2535
- run_configuration: TimeIndexTableUpdateConfiguration | None = None
2536
2527
 
2537
2528
  @property
2538
2529
  def data_source_uid(self):
@@ -2618,7 +2609,6 @@ class TimeIndexTableUpdate(TableUpdateNode, BaseObjectOrm):
2618
2609
  index_min=index_min,
2619
2610
  multi_index_column_stats=result.get("multi_index_column_stats"),
2620
2611
  ),
2621
- must_update=result["must_update"],
2622
2612
  direct_dependency_uids=result.get("direct_dependency_uids"),
2623
2613
  )
2624
2614
  return table_update_run
@@ -3136,31 +3126,6 @@ class TimeIndexTableUpdate(TableUpdateNode, BaseObjectOrm):
3136
3126
  )
3137
3127
  return table_update
3138
3128
 
3139
- def get_update_time_to_wait(self):
3140
- next_update = self.update_details.next_update
3141
- time_to_wait = 0.0
3142
- if next_update is not None:
3143
- time_to_wait = (
3144
- pd.to_datetime(next_update) - datetime.datetime.now(datetime.UTC)
3145
- ).total_seconds()
3146
- time_to_wait = max(0, time_to_wait)
3147
- return time_to_wait, next_update
3148
-
3149
- def wait_for_update_time(
3150
- self,
3151
- ):
3152
- if self.update_details.error_on_last_update or self.update_details.last_update is None:
3153
- return None
3154
-
3155
- time_to_wait, next_update = self.get_update_time_to_wait()
3156
- if time_to_wait > 0:
3157
- logger.info(f"Scheduler Waiting for ts update time at {next_update} {time_to_wait}")
3158
- time.sleep(time_to_wait)
3159
- else:
3160
- time_to_wait = max(0, 60 - datetime.datetime.now(datetime.UTC).second)
3161
- logger.info("Scheduler Waiting for ts update at start of minute")
3162
- time.sleep(time_to_wait)
3163
-
3164
3129
 
3165
3130
  class BaseUpdateDetails:
3166
3131
  active_update: bool = Field(default=False, description="Flag to indicate if update is active")
@@ -3169,16 +3134,12 @@ class BaseUpdateDetails:
3169
3134
  default=False, description="Flag to indicate if there was an error in the last update"
3170
3135
  )
3171
3136
  last_update: datetime.datetime | None = Field(None, description="Timestamp of the last update")
3172
- next_update: datetime.datetime | None = Field(None, description="Timestamp of the next update")
3173
3137
  update_statistics: dict[str, Any] | None = Field(
3174
3138
  None, description="JSON field for update statistics"
3175
3139
  )
3176
3140
  active_update_status: str = Field(
3177
3141
  default="Q", max_length=20, description="Current update status"
3178
3142
  )
3179
- active_update_scheduler_uid: str | None = Field(
3180
- None, description="UID reference to the scheduler for active update"
3181
- )
3182
3143
  update_priority: int = Field(default=0, description="Priority level of the update")
3183
3144
  last_updated_by_user_uid: str | None = Field(
3184
3145
  None, description="UID reference to the user that last updated this record"
@@ -3189,7 +3150,6 @@ class TimeIndexTableUpdateDetails(BaseUpdateDetails, BasePydanticModel, BaseObje
3189
3150
  table_update_uid: str | None = Field(
3190
3151
  None, description="Public uid of the related TimeIndexTableUpdate"
3191
3152
  )
3192
- run_configuration: TimeIndexTableUpdateConfiguration | None = None
3193
3153
 
3194
3154
  @classmethod
3195
3155
  def patch_for_table_update_uid(
@@ -3843,242 +3803,6 @@ class TimeIndexMetaTable(MetaTable):
3843
3803
  )
3844
3804
 
3845
3805
 
3846
- class ScheduledUpdateNode(BasePydanticModel):
3847
- """Strict read projection for an update node attached to a Scheduler.
3848
-
3849
- Scheduler endpoints return a flattened polymorphic node projection with
3850
- output-table identity. This is intentionally separate from
3851
- :class:`TableUpdateNode`, which models the full update graph resource.
3852
- """
3853
-
3854
- model_config = ConfigDict(extra="forbid")
3855
-
3856
- uid: str = Field(..., description="Public uid of the scheduled update node")
3857
- node_type: str = Field(..., min_length=1, description="Scheduled-node discriminator")
3858
- update_hash: str | None = Field(
3859
- ...,
3860
- max_length=63,
3861
- description="Update identity when exposed by the concrete scheduled node",
3862
- )
3863
- build_configuration: dict[str, Any] | None = Field(
3864
- ...,
3865
- description="Serialized build configuration exposed by the concrete node",
3866
- )
3867
- output_table_uid: str | None = Field(
3868
- ...,
3869
- description="Canonical public uid of the scheduled node's output table",
3870
- )
3871
- output_table_type: str | None = Field(
3872
- ...,
3873
- description="Output-table discriminator exposed by the backend",
3874
- )
3875
- output_table_physical_table_name: str | None = Field(
3876
- ...,
3877
- description="Physical table name of the scheduled node's output",
3878
- )
3879
- output_table_identifier: str | None = Field(
3880
- ...,
3881
- description="Published identifier of the scheduled node's output table",
3882
- )
3883
- output_table_data_source_uid: str | None = Field(
3884
- ...,
3885
- description="Canonical data-source uid of the scheduled node's output table",
3886
- )
3887
-
3888
-
3889
- class Scheduler(BasePydanticModel, BaseObjectOrm):
3890
- uid: str | None = Field(None, description="Public uid of this scheduler")
3891
- organization_environment_uid: str = Field(
3892
- ...,
3893
- description=(
3894
- "Read-only public UID of the Organization Environment derived by the "
3895
- "backend from the scheduler's update nodes."
3896
- ),
3897
- )
3898
- name: str
3899
- is_running: bool
3900
- running_process_pid: int | None
3901
- running_in_debug_mode: bool
3902
- updates_halted: bool
3903
- host: str | None
3904
- api_address: str | None
3905
- api_port: int | None
3906
- last_heart_beat: datetime.datetime | None = None
3907
- pre_loads_in_tree: list[ScheduledUpdateNode] | None = None
3908
- in_active_tree: list[ScheduledUpdateNode] | None = None
3909
- schedules_to: list[ScheduledUpdateNode] | None = None
3910
- # for heartbeat
3911
- _stop_heart_beat: bool = False
3912
- _executor: object | None = None
3913
-
3914
- def _public_uid(self) -> str:
3915
- return _require_public_uid(self, "Scheduler")
3916
-
3917
- @classmethod
3918
- def get_scheduler_for_update_node(cls, update_node_uid: str):
3919
- """GET the scheduler assigned to a TimeIndexTableUpdate uid."""
3920
- if update_node_uid in (None, ""):
3921
- raise ValueError("update_node_uid is required.")
3922
- s = cls.build_session()
3923
- url = cls.get_object_url() + "/for-update-node/"
3924
- r = make_request(
3925
- s=s,
3926
- r_type="GET",
3927
- url=url,
3928
- payload={"params": {"update_node_uid": str(update_node_uid)}},
3929
- loaders=cls.LOADERS,
3930
- )
3931
- if r.status_code == 404:
3932
- raise SchedulerDoesNotExist(r.json().get("detail", r.text))
3933
- r.raise_for_status()
3934
- return cls(**r.json())
3935
-
3936
- @classmethod
3937
- def build_and_assign_to_update_nodes(
3938
- cls,
3939
- scheduler_name: str,
3940
- update_node_uids: list[str],
3941
- delink_all_ts: bool = False,
3942
- remove_from_other_schedulers: bool = True,
3943
- timeout=None,
3944
- **kwargs,
3945
- ):
3946
- """
3947
- POST /schedulers/build-and-assign-to-update-nodes/
3948
- body: {
3949
- scheduler_name, update_node_uids, delink_all_ts?,
3950
- remove_from_other_schedulers?, scheduler_kwargs?
3951
- }
3952
- """
3953
- s = cls.build_session()
3954
- url = cls.get_object_url() + "/build-and-assign-to-update-nodes/"
3955
- request_body = {
3956
- "scheduler_name": scheduler_name,
3957
- "delink_all_update_nodes": delink_all_ts,
3958
- "remove_from_other_schedulers": remove_from_other_schedulers,
3959
- "scheduler_kwargs": kwargs or {},
3960
- }
3961
- request_body["update_node_uids"] = [str(uid) for uid in update_node_uids]
3962
- payload = {"json": request_body}
3963
- r = make_request(
3964
- s=s, r_type="POST", url=url, payload=payload, time_out=timeout, loaders=cls.LOADERS
3965
- )
3966
- if r.status_code not in [200, 201]:
3967
- r.raise_for_status()
3968
- return cls(**r.json())
3969
-
3970
- def in_active_tree_connect(self, update_node_uids: list[str]):
3971
- """
3972
- PATCH /schedulers/{uid}/in-active-tree/
3973
- body: { update_node_uids }
3974
- """
3975
- s = self.build_session()
3976
- url = f"{self.get_object_url()}/{self._public_uid()}/in-active-tree/"
3977
- r = make_request(
3978
- s=s,
3979
- r_type="PATCH",
3980
- url=url,
3981
- payload={"json": {"update_node_uids": [str(uid) for uid in update_node_uids]}},
3982
- loaders=self.LOADERS,
3983
- )
3984
- if r.status_code not in (200, 204):
3985
- raise Exception(f"Error in request {r.text}")
3986
-
3987
- def assign_to_scheduler(self, update_node_uids: list[str]):
3988
- """
3989
- PATCH /schedulers/{uid}/assign/
3990
- body: { update_node_uids }
3991
- """
3992
- s = self.build_session()
3993
- url = f"{self.get_object_url()}/{self._public_uid()}/assign/"
3994
- r = make_request(
3995
- s=s,
3996
- r_type="PATCH",
3997
- url=url,
3998
- payload={"json": {"update_node_uids": [str(uid) for uid in update_node_uids]}},
3999
- loaders=self.LOADERS,
4000
- )
4001
- r.raise_for_status()
4002
- return Scheduler(**r.json())
4003
-
4004
- def is_scheduler_running_in_process(self):
4005
- # test call
4006
- if self.is_running and hasattr(self, "api_address"):
4007
- # verify scheduler host is the same
4008
- if self.api_address == get_network_ip() and is_process_running(
4009
- self.running_process_pid
4010
- ):
4011
- return True
4012
- return False
4013
-
4014
- def _heart_beat_patch(self):
4015
- try:
4016
- scheduler = self.patch(
4017
- is_running=True,
4018
- running_process_pid=os.getpid(),
4019
- running_in_debug_mode=self.running_in_debug_mode,
4020
- last_heart_beat=datetime.datetime.now(datetime.UTC).timestamp(),
4021
- )
4022
- for field, value in scheduler.__dict__.items():
4023
- setattr(self, field, value)
4024
- except Exception as e:
4025
- logger.error(e)
4026
-
4027
- def _heartbeat_runner(self, run_interval):
4028
- """
4029
- Runs forever (until the main thread ends),
4030
- calling _scheduler_heart_beat_patch every 30 seconds.
4031
- """
4032
- logger.debug("Heartbeat thread started with interval = %d seconds", run_interval)
4033
-
4034
- while True:
4035
- self._heart_beat_patch()
4036
- # Sleep in a loop so that if we ever decide to
4037
- # add a cancellation event, we can check it in smaller intervals
4038
- for _ in range(run_interval):
4039
- # could check for a stop event here if not daemon
4040
- if self._stop_heart_beat:
4041
- return
4042
- time.sleep(1)
4043
-
4044
- def start_heart_beat(self):
4045
- from concurrent.futures import ThreadPoolExecutor
4046
-
4047
- if self._executor is None:
4048
- self._executor = ThreadPoolExecutor(max_workers=1)
4049
- run_interval = TDAG_CONSTANTS.SCHEDULER_HEART_BEAT_FREQUENCY_SECONDS
4050
- self._heartbeat_future = self._executor.submit(self._heartbeat_runner, run_interval)
4051
-
4052
- def stop_heart_beat(self):
4053
- """
4054
- Stop the heartbeat gracefully.
4055
- """
4056
- # Signal the runner loop to exit
4057
- self._stop_heart_beat = True
4058
-
4059
- # Optionally wait for the future to complete
4060
- if hasattr(self, "heartbeat_future") and self._heartbeat_future:
4061
- logger.info("Waiting for the heartbeat thread to finish...")
4062
- self._heartbeat_future.result() # or .cancel() if you prefer
4063
-
4064
- # Shut down the executor if no longer needed
4065
- if self._executor:
4066
- self._executor.shutdown(wait=True)
4067
- self._executor = None
4068
-
4069
- logger.info("Heartbeat thread stopped.")
4070
-
4071
-
4072
- class TimeIndexTableUpdateConfiguration(BasePydanticModel, BaseObjectOrm):
4073
- update_schedule: str = "*/1 * * * *"
4074
- table_update_details_uid: str | None = None
4075
-
4076
- @classmethod
4077
- @property
4078
- def ROOT_URL(cls):
4079
- return None
4080
-
4081
-
4082
3806
  class BaseUpdateStatistics(BaseModel):
4083
3807
  """
4084
3808
  Backend-agnostic update-state envelope shared by updater types.
@@ -4886,7 +4610,6 @@ class _TableUpdateRunFields:
4886
4610
  )
4887
4611
  # extra fields for local control
4888
4612
  update_statistics: BaseUpdateStatistics | None = None
4889
- must_update: bool | None = None
4890
4613
  direct_dependency_uids: list[str] | None = None
4891
4614
 
4892
4615
 
@@ -5104,7 +4827,6 @@ def get_session_data_source() -> DataSource:
5104
4827
 
5105
4828
  TimeIndexTableUpdateDetails.model_rebuild()
5106
4829
  TimeIndexTableUpdate.model_rebuild()
5107
- TimeIndexTableUpdateConfiguration.model_rebuild()
5108
4830
  TimeIndexedProfile.model_rebuild()
5109
4831
  TimeIndexMetaTable.model_rebuild()
5110
4832
  DataSource.model_rebuild()
@@ -5163,10 +4885,6 @@ __all__ = [
5163
4885
  "MetaTableStatementPayload",
5164
4886
  "MetaTableValidateContractRequest",
5165
4887
  "PodDataSource",
5166
- "TimeIndexTableUpdateConfiguration",
5167
- "ScheduledUpdateNode",
5168
- "Scheduler",
5169
- "SchedulerDoesNotExist",
5170
4888
  "SessionDataSource",
5171
4889
  "SQLITE",
5172
4890
  "COMPILED_SQL_V1",
@@ -621,10 +621,6 @@ class BaseTimeIndexTableUpdateManager:
621
621
  def update_details(self) -> Any | None:
622
622
  return self.table_update.update_details
623
623
 
624
- @property
625
- def run_configuration(self) -> dict | None:
626
- return self.table_update.run_configuration
627
-
628
624
  @property
629
625
  def time_indexed_profile(self) -> Any | None:
630
626
  return self._get_time_indexed_profile()