bazaar-compute-node 0.1.15__tar.gz → 0.1.16__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 (95) hide show
  1. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/PKG-INFO +1 -1
  2. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/pyproject.toml +1 -1
  3. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/pyproject.toml.orig +1 -1
  4. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/codex/runtime.py +10 -26
  5. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/orchestration/session.py +85 -26
  6. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/LICENSE +0 -0
  7. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/README.md +0 -0
  8. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/__init__.py +0 -0
  9. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/__init__.py +0 -0
  10. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/agent.py +0 -0
  11. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/agent_management.py +0 -0
  12. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/application.py +0 -0
  13. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/attachments.py +0 -0
  14. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/command.py +0 -0
  15. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/config.py +0 -0
  16. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/registry.py +0 -0
  17. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/reminder_dispatch.py +0 -0
  18. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/system_service.py +0 -0
  19. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/transport.py +0 -0
  20. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
  21. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/app/wrapper.py +0 -0
  22. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/bcc.py +0 -0
  23. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/cli.py +0 -0
  24. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/__init__.py +0 -0
  25. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
  26. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/codex/approval.py +0 -0
  27. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/codex/client.py +0 -0
  28. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/codex/events.py +0 -0
  29. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
  30. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/codex/process.py +0 -0
  31. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/codex/protocol.py +0 -0
  32. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
  33. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
  34. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
  35. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
  36. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/agent_migration.py +0 -0
  37. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/codec.py +0 -0
  38. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
  39. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/migrations.py +0 -0
  40. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
  41. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -0
  42. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/reminder_database.py +0 -0
  43. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/reminder_migration.py +0 -0
  44. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/reminder_repository.py +0 -0
  45. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/repository.py +0 -0
  46. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/sqlite/scoped_repository.py +0 -0
  47. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/telegram/__init__.py +0 -0
  48. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/telegram/api.py +0 -0
  49. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/telegram/approval.py +0 -0
  50. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/telegram/attachments.py +0 -0
  51. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/telegram/channel.py +0 -0
  52. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/telegram/identity.py +0 -0
  53. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/telegram/markdown.py +0 -0
  54. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/telegram/outbound.py +0 -0
  55. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/telegram/plugin.py +0 -0
  56. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
  57. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/wecom/channel.py +0 -0
  58. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/wecom/markdown.py +0 -0
  59. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
  60. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
  61. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/__init__.py +0 -0
  62. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/approval.py +0 -0
  63. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/audit.py +0 -0
  64. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/channel.py +0 -0
  65. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/client.py +0 -0
  66. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/command.py +0 -0
  67. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/concurrency.py +0 -0
  68. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/correlation.py +0 -0
  69. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/instruction.py +0 -0
  70. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/lifecycle.py +0 -0
  71. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/models/__init__.py +0 -0
  72. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/models/entities.py +0 -0
  73. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/models/reminder.py +0 -0
  74. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/models/reminder_owner.py +0 -0
  75. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/models/states.py +0 -0
  76. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/observability.py +0 -0
  77. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
  78. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/orchestration/command.py +0 -0
  79. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/orchestration/delivery.py +0 -0
  80. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/orchestration/error_feedback.py +0 -0
  81. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/orchestration/reminder.py +0 -0
  82. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/orchestration/reminder_command.py +0 -0
  83. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/orchestration/services.py +0 -0
  84. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/orchestration/turn.py +0 -0
  85. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/outcomes.py +0 -0
  86. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/paths.py +0 -0
  87. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/reminder.py +0 -0
  88. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/reminder_schedule.py +0 -0
  89. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/runtime.py +0 -0
  90. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/storage.py +0 -0
  91. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/core/timerwheel.py +0 -0
  92. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/i18n/__init__.py +0 -0
  93. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/i18n/catalog.py +0 -0
  94. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/i18n/english.py +0 -0
  95. {bazaar_compute_node-0.1.15 → bazaar_compute_node-0.1.16}/src/bazaar_compute_node/i18n/schinese.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bazaar-compute-node
3
- Version: 0.1.15
3
+ Version: 0.1.16
4
4
  Summary: Runtime-agnostic computer node daemon for agents and channels.
5
5
  License-Expression: AGPL-3.0-only
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.15"
3
+ version = "0.1.16"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.15"
3
+ version = "0.1.16"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -104,7 +104,6 @@ class Runtime(IRuntime, IAsyncLifecycle):
104
104
  self._model = model
105
105
  self._effort = effort
106
106
  self._connections: dict[str, _Connection] = {}
107
- self._teardown_tasks: set[asyncio.Task[None]] = set()
108
107
  self._expire_events: asyncio.Queue[RuntimeExpire] = asyncio.Queue()
109
108
  self._logger = logging.getLogger("bazaar_compute_node.runtime.codex")
110
109
  self._started = False
@@ -114,6 +113,11 @@ class Runtime(IRuntime, IAsyncLifecycle):
114
113
  del timeout
115
114
  if self._stopping:
116
115
  raise RuntimeError("Codex App Server runtime is stopping")
116
+ if os.name == "nt":
117
+ self._logger.warning(
118
+ "Codex idle timeout is not supported on Windows; "
119
+ "see openai/codex#15461."
120
+ )
117
121
  self._started = True
118
122
 
119
123
  async def stop(self, *, timeout: float) -> None:
@@ -524,6 +528,9 @@ class Runtime(IRuntime, IAsyncLifecycle):
524
528
  *,
525
529
  timeout: float,
526
530
  ) -> bool:
531
+ if os.name == "nt":
532
+ # Detached OS processes are not exposed by the Windows Codex provider.
533
+ return True
527
534
  connection = self._connections.get(session.id)
528
535
  provider_thread_id = session.provider_thread_id
529
536
  if connection is None or not provider_thread_id:
@@ -549,11 +556,10 @@ class Runtime(IRuntime, IAsyncLifecycle):
549
556
  status=ProviderCallStatus.CONFIRMED,
550
557
  value=session,
551
558
  )
552
- self._schedule_connection_teardown(connection, timeout=timeout)
559
+ await self._stop_connection(connection, timeout=timeout)
553
560
  return ProviderCallResult(
554
- status=ProviderCallStatus.QUEUED,
561
+ status=ProviderCallStatus.CONFIRMED,
555
562
  value=session,
556
- receipt={"teardown": "scheduled"},
557
563
  )
558
564
 
559
565
  async def _open_connection(
@@ -660,28 +666,6 @@ class Runtime(IRuntime, IAsyncLifecycle):
660
666
  except OSError, TimeoutError, JsonlTransportError:
661
667
  return
662
668
 
663
- def _schedule_connection_teardown(
664
- self,
665
- connection: _Connection,
666
- *,
667
- timeout: float,
668
- ) -> None:
669
- task = asyncio.create_task(
670
- self._stop_connection(connection, timeout=timeout),
671
- name=f"bcn-codex-teardown-{connection.provider_thread_id}",
672
- )
673
- self._teardown_tasks.add(task)
674
- task.add_done_callback(self._forget_teardown_task)
675
-
676
- def _forget_teardown_task(self, task: asyncio.Task[None]) -> None:
677
- self._teardown_tasks.discard(task)
678
- if task.cancelled():
679
- return
680
- try:
681
- task.result()
682
- except Exception:
683
- self._logger.exception("Codex runtime connection teardown failed")
684
-
685
669
  def _clear_active_turn(self, session_id: str, turn_id: str) -> None:
686
670
  connection = self._connections.get(session_id)
687
671
  if connection is not None and connection.active_turn_id == turn_id:
@@ -204,6 +204,7 @@ class SessionOrchestrator(IAsyncLifecycle):
204
204
  clock=self._clock,
205
205
  )
206
206
  self._active_tasks: set[asyncio.Task[RuntimeTurn | None]] = set()
207
+ self._runtime_teardown_tasks: set[asyncio.Task[None]] = set()
207
208
  self._ingress_queues: dict[tuple[str, str], asyncio.Queue[_IngressItem]] = {}
208
209
  self._ingress_workers: dict[tuple[str, str], asyncio.Task[None]] = {}
209
210
  self._runtime_queues: dict[str, asyncio.Queue[_RuntimeQueueItem]] = {}
@@ -408,7 +409,7 @@ class SessionOrchestrator(IAsyncLifecycle):
408
409
  )
409
410
  except BaseException:
410
411
  try:
411
- await self._runtime.stop_session(
412
+ await self._stop_runtime_session(
412
413
  context.runtime_session,
413
414
  timeout=self._timeout_budget.provider_call_seconds,
414
415
  )
@@ -497,6 +498,8 @@ class SessionOrchestrator(IAsyncLifecycle):
497
498
  for runtime_session in tuple(self._runtime_sessions.values()):
498
499
  await self._stop_runtime_session(runtime_session, timeout=timeout)
499
500
 
501
+ await self._wait_for_runtime_teardown_tasks(timeout=timeout)
502
+
500
503
  try:
501
504
  await self._runtime.stop(timeout=timeout)
502
505
  except asyncio.CancelledError:
@@ -1515,38 +1518,45 @@ class SessionOrchestrator(IAsyncLifecycle):
1515
1518
  async with self._concurrency.for_session(runtime_session.bcn_session_id):
1516
1519
  await self._stop_runtime_session_locked(runtime_session, timeout=timeout)
1517
1520
 
1518
- async def _stop_runtime_session_locked(
1521
+ async def _wait_for_runtime_teardown_tasks(self, *, timeout: float) -> None:
1522
+ tasks = tuple(self._runtime_teardown_tasks)
1523
+ if not tasks:
1524
+ return
1525
+ gathered = asyncio.gather(*tasks, return_exceptions=True)
1526
+ try:
1527
+ await asyncio.wait_for(asyncio.shield(gathered), timeout=timeout)
1528
+ except TimeoutError:
1529
+ self._shutdown_errors.append("runtime session teardown: shutdown timeout")
1530
+
1531
+ def _schedule_runtime_session_teardown(
1519
1532
  self,
1520
1533
  runtime_session: RuntimeSession,
1521
1534
  *,
1535
+ correlation: CorrelationContext,
1522
1536
  timeout: float,
1523
1537
  ) -> None:
1524
- process_correlation = CorrelationContext(
1525
- node_id=self.agent_id,
1526
- channel_session_id=runtime_session.channel_session_id,
1527
- bcn_session_id=runtime_session.bcn_session_id,
1528
- runtime_session_id=runtime_session.id,
1529
- provider_thread_id=runtime_session.provider_thread_id,
1530
- )
1531
- await self._audit.append(
1532
- event_name="runtime.process.stop.requested",
1533
- state=RuntimeEventState.STARTED,
1534
- correlation=process_correlation,
1535
- metadata={
1536
- "runtime": runtime_session.runtime,
1537
- "workspace_id": runtime_session.workspace_id,
1538
- },
1539
- )
1540
- await self._state_machine.apply_locked(
1541
- runtime_session.bcn_session_id,
1542
- SessionRuntimeObservation(
1543
- source=SessionRuntimeObservationSource.SESSION,
1544
- signal=SessionRuntimeSignal.STOP_REQUESTED,
1545
- observed_at_ms=self._clock(),
1538
+ task = asyncio.create_task(
1539
+ self._complete_runtime_session_teardown(
1540
+ runtime_session,
1541
+ correlation=correlation,
1542
+ timeout=timeout,
1546
1543
  ),
1544
+ name=f"bcn-runtime-teardown-{runtime_session.id}",
1547
1545
  )
1546
+ self._runtime_teardown_tasks.add(task)
1547
+ task.add_done_callback(self._forget_runtime_teardown_task)
1548
+
1549
+ async def _complete_runtime_session_teardown(
1550
+ self,
1551
+ runtime_session: RuntimeSession,
1552
+ *,
1553
+ correlation: CorrelationContext,
1554
+ timeout: float,
1555
+ ) -> None:
1548
1556
  try:
1549
1557
  result = await self._runtime.stop_session(runtime_session, timeout=timeout)
1558
+ except asyncio.CancelledError:
1559
+ raise
1550
1560
  except Exception as error: # noqa: BLE001
1551
1561
  result = None
1552
1562
  stop_error = error
@@ -1563,7 +1573,6 @@ class SessionOrchestrator(IAsyncLifecycle):
1563
1573
  else ErrorKind.PROVIDER_UNKNOWN.value
1564
1574
  )
1565
1575
  error_message = result.error_message if result is not None else str(stop_error)
1566
- await self._discard_runtime_session(runtime_session)
1567
1576
  await self._audit.append(
1568
1577
  event_name=(
1569
1578
  "runtime.process.stop.completed"
@@ -1579,7 +1588,7 @@ class SessionOrchestrator(IAsyncLifecycle):
1579
1588
  if unknown
1580
1589
  else RuntimeEventState.FAILED
1581
1590
  ),
1582
- correlation=process_correlation,
1591
+ correlation=correlation,
1583
1592
  error_kind=(
1584
1593
  ErrorKind(error_kind)
1585
1594
  if error_kind in ErrorKind._value2member_map_
@@ -1595,3 +1604,53 @@ class SessionOrchestrator(IAsyncLifecycle):
1595
1604
  "workspace_id": runtime_session.workspace_id,
1596
1605
  },
1597
1606
  )
1607
+
1608
+ def _forget_runtime_teardown_task(self, task: asyncio.Task[None]) -> None:
1609
+ self._runtime_teardown_tasks.discard(task)
1610
+ if task.cancelled():
1611
+ return
1612
+ error = task.exception()
1613
+ if error is None:
1614
+ return
1615
+ self._logger.error(
1616
+ "runtime session teardown failed: %s",
1617
+ error,
1618
+ exc_info=(type(error), error, error.__traceback__),
1619
+ )
1620
+
1621
+ async def _stop_runtime_session_locked(
1622
+ self,
1623
+ runtime_session: RuntimeSession,
1624
+ *,
1625
+ timeout: float,
1626
+ ) -> None:
1627
+ process_correlation = CorrelationContext(
1628
+ node_id=self.agent_id,
1629
+ channel_session_id=runtime_session.channel_session_id,
1630
+ bcn_session_id=runtime_session.bcn_session_id,
1631
+ runtime_session_id=runtime_session.id,
1632
+ provider_thread_id=runtime_session.provider_thread_id,
1633
+ )
1634
+ await self._audit.append(
1635
+ event_name="runtime.process.stop.requested",
1636
+ state=RuntimeEventState.STARTED,
1637
+ correlation=process_correlation,
1638
+ metadata={
1639
+ "runtime": runtime_session.runtime,
1640
+ "workspace_id": runtime_session.workspace_id,
1641
+ },
1642
+ )
1643
+ await self._state_machine.apply_locked(
1644
+ runtime_session.bcn_session_id,
1645
+ SessionRuntimeObservation(
1646
+ source=SessionRuntimeObservationSource.SESSION,
1647
+ signal=SessionRuntimeSignal.STOP_REQUESTED,
1648
+ observed_at_ms=self._clock(),
1649
+ ),
1650
+ )
1651
+ self._schedule_runtime_session_teardown(
1652
+ runtime_session,
1653
+ correlation=process_correlation,
1654
+ timeout=timeout,
1655
+ )
1656
+ await self._discard_runtime_session(runtime_session)