plato-sdk-v2 2.3.6__py3-none-any.whl → 2.3.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
plato/worlds/base.py CHANGED
@@ -28,6 +28,17 @@ from plato.agents.otel import (
28
28
 
29
29
  logger = logging.getLogger(__name__)
30
30
 
31
+
32
+ def _get_plato_version() -> str:
33
+ """Get the installed plato SDK version."""
34
+ try:
35
+ from importlib.metadata import version
36
+
37
+ return version("plato")
38
+ except Exception:
39
+ return "unknown"
40
+
41
+
31
42
  # Global registry of worlds
32
43
  _WORLD_REGISTRY: dict[str, type[BaseWorld]] = {}
33
44
 
@@ -417,7 +428,7 @@ class BaseWorld(ABC, Generic[ConfigT]):
417
428
  content_type=content_type,
418
429
  )
419
430
 
420
- async def _create_and_upload_checkpoint(self) -> dict[str, str] | None:
431
+ async def _create_and_upload_checkpoint(self) -> tuple[dict[str, str], bool]:
421
432
  """Create a full checkpoint including env snapshots and state bundle.
422
433
 
423
434
  This method:
@@ -426,7 +437,7 @@ class BaseWorld(ABC, Generic[ConfigT]):
426
437
  3. Creates and uploads state bundle to S3
427
438
 
428
439
  Returns:
429
- Dict mapping env alias to artifact_id if successful, None otherwise
440
+ Tuple of (env_snapshots dict, state_bundle_uploaded bool)
430
441
  """
431
442
  # Commit state changes first
432
443
  self._commit_state(f"Checkpoint at step {self._step_count}")
@@ -436,6 +447,8 @@ class BaseWorld(ABC, Generic[ConfigT]):
436
447
  if env_snapshots is None:
437
448
  env_snapshots = {}
438
449
 
450
+ state_bundle_uploaded = True # Default to True if state not enabled
451
+
439
452
  # Create and upload state bundle
440
453
  if self.config.state.enabled:
441
454
  bundle_data = self._create_state_bundle()
@@ -446,11 +459,12 @@ class BaseWorld(ABC, Generic[ConfigT]):
446
459
  )
447
460
  if success:
448
461
  self.logger.info(f"Uploaded state bundle at step {self._step_count}")
462
+ state_bundle_uploaded = True
449
463
  else:
450
464
  self.logger.warning(f"Failed to upload state bundle at step {self._step_count}")
451
- return None
465
+ state_bundle_uploaded = False
452
466
 
453
- return env_snapshots
467
+ return env_snapshots, state_bundle_uploaded
454
468
 
455
469
  def get_env(self, alias: str) -> Environment | None:
456
470
  """Get an environment by alias.
@@ -645,25 +659,35 @@ The following services are available for your use:
645
659
  if config.session_id:
646
660
  self._session_id = config.session_id
647
661
 
648
- # Set environment variables for agent runners
662
+ # Set environment variables for agent runners (which run in Docker)
649
663
  os.environ["SESSION_ID"] = config.session_id
650
664
  if config.otel_url:
651
- os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = config.otel_url
652
- # Use JSON protocol (not protobuf) for the OTLP exporter
653
- os.environ["OTEL_EXPORTER_OTLP_PROTOCOL"] = "http/json"
665
+ # For agents in Docker, convert localhost to host.docker.internal
666
+ # so they can reach the host machine's Chronos instance
667
+ agent_otel_url = config.otel_url
668
+ if "localhost" in agent_otel_url or "127.0.0.1" in agent_otel_url:
669
+ agent_otel_url = agent_otel_url.replace("localhost", "host.docker.internal")
670
+ agent_otel_url = agent_otel_url.replace("127.0.0.1", "host.docker.internal")
671
+ os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = agent_otel_url
672
+ os.environ["OTEL_EXPORTER_OTLP_PROTOCOL"] = "http/protobuf"
654
673
  if config.upload_url:
655
674
  os.environ["UPLOAD_URL"] = config.upload_url
656
675
 
657
- # Initialize OTel tracing if otel_url is provided
658
- print(f"[World] OTel URL from config: {config.otel_url!r}")
676
+ # Initialize OTel tracing for the world itself (runs on host, not in Docker)
659
677
  if config.otel_url:
678
+ logger.debug(f"Initializing OTel tracing with endpoint: {config.otel_url}")
660
679
  init_tracing(
661
680
  service_name=f"world-{self.name}",
662
681
  session_id=config.session_id,
663
682
  otlp_endpoint=config.otel_url,
664
683
  )
665
684
  else:
666
- print("[World] No otel_url in config - OTel tracing disabled")
685
+ logger.debug("No otel_url in config - OTel tracing disabled")
686
+
687
+ # Log version info (goes to OTel after init_tracing)
688
+ plato_version = _get_plato_version()
689
+ world_version = self.get_version()
690
+ self.logger.info(f"World version: {world_version}, Plato SDK version: {plato_version}")
667
691
 
668
692
  # Connect to Plato session if configured (for heartbeats)
669
693
  await self._connect_plato_session()
@@ -671,85 +695,72 @@ The following services are available for your use:
671
695
  # Get tracer for spans
672
696
  tracer = get_tracer("plato.world")
673
697
 
674
- # Log session start
675
- with tracer.start_as_current_span("session_start") as span:
676
- span.set_attribute("span.type", "session_start")
677
- span.set_attribute("source", "world")
678
- span.set_attribute("world_name", self.name)
679
- span.set_attribute("world_version", self.get_version())
680
- span.set_attribute("content", f"World '{self.name}' started")
698
+ # Create root session span that encompasses everything
699
+ # This ensures all child spans share the same trace_id
700
+ with tracer.start_as_current_span("session") as session_span:
701
+ session_span.set_attribute("plato.world.name", self.name)
702
+ session_span.set_attribute("plato.world.version", self.get_version())
703
+ session_span.set_attribute("plato.session.id", config.session_id)
681
704
 
682
- try:
683
- # Execute reset with OTel span
684
- with tracer.start_as_current_span("reset") as reset_span:
685
- reset_span.set_attribute("span.type", "reset")
686
- reset_span.set_attribute("source", "world")
687
- reset_span.set_attribute("content", f"Resetting world '{self.name}'")
688
- obs = await self.reset()
689
- obs_data = obs.model_dump() if hasattr(obs, "model_dump") else str(obs)
690
- reset_span.set_attribute("observation", str(obs_data)[:1000]) # Truncate for OTel
691
- self.logger.info(f"World reset complete: {obs}")
692
-
693
- while True:
694
- self._step_count += 1
695
-
696
- # Execute step with OTel span
697
- with tracer.start_as_current_span(f"step_{self._step_count}") as step_span:
698
- step_span.set_attribute("span.type", "step")
699
- step_span.set_attribute("source", "world")
700
- step_span.set_attribute("step_number", self._step_count)
701
- step_span.set_attribute("content", f"Step {self._step_count} started")
702
-
703
- # Store span context for nested agent spans
704
-
705
- self._current_step_id = format(step_span.get_span_context().span_id, "016x")
706
-
707
- result = await self.step()
708
-
709
- step_span.set_attribute("done", result.done)
710
- obs_data = (
711
- result.observation.model_dump()
712
- if hasattr(result.observation, "model_dump")
713
- else str(result.observation)
714
- )
715
- step_span.set_attribute("observation", str(obs_data)[:1000])
716
-
717
- self.logger.info(f"Step {self._step_count}: done={result.done}")
718
-
719
- # Create checkpoint if enabled and interval matches
720
- if self.config.checkpoint.enabled and self._step_count % self.config.checkpoint.interval == 0:
721
- self.logger.info(f"Creating checkpoint after step {self._step_count}")
722
- env_snapshots = await self._create_and_upload_checkpoint()
723
-
724
- # Emit checkpoint span for UI visibility
725
- with tracer.start_as_current_span(f"checkpoint_{self._step_count}") as checkpoint_span:
726
- checkpoint_span.set_attribute("span.type", "checkpoint")
727
- checkpoint_span.set_attribute("source", "world")
728
- checkpoint_span.set_attribute("step_number", self._step_count)
729
- checkpoint_span.set_attribute("content", f"Checkpoint created at step {self._step_count}")
730
- if env_snapshots:
731
- # Serialize env_snapshots for OTel attribute
732
- import json
733
-
734
- checkpoint_span.set_attribute("env_snapshots", json.dumps(env_snapshots))
735
- checkpoint_span.set_attribute("success", env_snapshots is not None)
736
-
737
- if result.done:
738
- break
739
-
740
- finally:
741
- await self.close()
742
- await self._disconnect_plato_session()
743
-
744
- # Log session end
745
- with tracer.start_as_current_span("session_end") as span:
746
- span.set_attribute("span.type", "session_end")
747
- span.set_attribute("source", "world")
748
- span.set_attribute("total_steps", self._step_count)
749
- span.set_attribute("content", f"World '{self.name}' completed after {self._step_count} steps")
750
-
751
- # Shutdown OTel tracing and clear session info
752
- shutdown_tracing()
753
- self._session_id = None
754
-
755
- self.logger.info(f"World '{self.name}' completed after {self._step_count} steps")
705
+ try:
706
+ # Execute reset with OTel span
707
+ with tracer.start_as_current_span("reset") as reset_span:
708
+ obs = await self.reset()
709
+ obs_data = obs.model_dump() if hasattr(obs, "model_dump") else str(obs)
710
+ reset_span.set_attribute("plato.observation", str(obs_data)[:1000])
711
+ self.logger.info(f"World reset complete: {obs}")
712
+
713
+ while True:
714
+ self._step_count += 1
715
+
716
+ # Execute step with OTel span
717
+ with tracer.start_as_current_span(f"step_{self._step_count}") as step_span:
718
+ step_span.set_attribute("plato.step.number", self._step_count)
719
+
720
+ # Store span context for nested agent spans
721
+ self._current_step_id = format(step_span.get_span_context().span_id, "016x")
722
+
723
+ result = await self.step()
724
+
725
+ step_span.set_attribute("plato.step.done", result.done)
726
+ obs_data = (
727
+ result.observation.model_dump()
728
+ if hasattr(result.observation, "model_dump")
729
+ else str(result.observation)
730
+ )
731
+ step_span.set_attribute("plato.step.observation", str(obs_data)[:1000])
732
+
733
+ self.logger.info(f"Step {self._step_count}: done={result.done}")
734
+
735
+ # Create checkpoint if enabled and interval matches
736
+ if self.config.checkpoint.enabled and self._step_count % self.config.checkpoint.interval == 0:
737
+ self.logger.info(f"Creating checkpoint after step {self._step_count}")
738
+ with tracer.start_as_current_span("checkpoint") as checkpoint_span:
739
+ checkpoint_span.set_attribute("plato.checkpoint.step", self._step_count)
740
+ env_snapshots, state_bundle_uploaded = await self._create_and_upload_checkpoint()
741
+
742
+ checkpoint_span.set_attribute("plato.checkpoint.success", len(env_snapshots) > 0)
743
+ checkpoint_span.set_attribute(
744
+ "plato.checkpoint.state_bundle_uploaded", state_bundle_uploaded
745
+ )
746
+
747
+ if env_snapshots:
748
+ checkpoint_span.set_attribute(
749
+ "plato.checkpoint.environments", list(env_snapshots.keys())
750
+ )
751
+ checkpoint_span.set_attribute(
752
+ "plato.checkpoint.artifact_ids", list(env_snapshots.values())
753
+ )
754
+
755
+ if result.done:
756
+ break
757
+
758
+ finally:
759
+ await self.close()
760
+ await self._disconnect_plato_session()
761
+
762
+ # Shutdown OTel tracing and clear session info (outside the span)
763
+ shutdown_tracing()
764
+ self._session_id = None
765
+
766
+ self.logger.info(f"World '{self.name}' completed after {self._step_count} steps")
plato/worlds/runner.py CHANGED
@@ -6,7 +6,6 @@ import asyncio
6
6
  import json
7
7
  import logging
8
8
  import os
9
- import platform
10
9
  from pathlib import Path
11
10
  from typing import Annotated
12
11
 
@@ -136,6 +135,28 @@ def list_worlds(
136
135
  typer.echo(f" {name} (v{version}): {desc}")
137
136
 
138
137
 
138
+ def _get_docker_platform() -> str:
139
+ """Get the appropriate Docker platform for the current system.
140
+
141
+ Returns:
142
+ Docker platform string (e.g., "linux/arm64" or "linux/amd64")
143
+ """
144
+ import platform as plat
145
+
146
+ system = plat.system()
147
+ machine = plat.machine().lower()
148
+
149
+ # On macOS with Apple Silicon (arm64/aarch64), use linux/arm64
150
+ if system == "Darwin" and machine in ("arm64", "aarch64"):
151
+ return "linux/arm64"
152
+ # On Linux ARM
153
+ elif system == "Linux" and machine in ("arm64", "aarch64"):
154
+ return "linux/arm64"
155
+ # Default to amd64 for x86_64 or other architectures
156
+ else:
157
+ return "linux/amd64"
158
+
159
+
139
160
  async def _build_agent_image(
140
161
  agent_name: str,
141
162
  agents_dir: Path,
@@ -181,9 +202,17 @@ async def _build_agent_image(
181
202
  target = "prod"
182
203
  logger.info(f"Building {image_tag} (prod mode from {build_context})...")
183
204
 
205
+ # Detect platform for ARM Mac support
206
+ docker_platform = _get_docker_platform()
207
+ logger.info(f"Building for platform: {docker_platform}")
208
+
184
209
  cmd = [
185
210
  "docker",
186
211
  "build",
212
+ "--platform",
213
+ docker_platform,
214
+ "--build-arg",
215
+ f"PLATFORM={docker_platform}",
187
216
  "--target",
188
217
  target,
189
218
  "-t",
@@ -192,10 +221,6 @@ async def _build_agent_image(
192
221
  dockerfile_abs,
193
222
  ]
194
223
 
195
- # Use native platform for local dev on ARM Macs (avoids slow emulation)
196
- if platform.machine() == "arm64":
197
- cmd.extend(["--build-arg", "PLATFORM=linux/arm64"])
198
-
199
224
  cmd.append(build_context)
200
225
 
201
226
  logger.debug(f"Build command: {' '.join(cmd)}")
@@ -405,6 +430,7 @@ async def _run_dev(
405
430
  plato = AsyncPlato()
406
431
  session = None
407
432
  plato_session_id: str | None = None
433
+ chronos_session_id: str | None = None
408
434
 
409
435
  try:
410
436
  if env_configs:
@@ -435,18 +461,10 @@ async def _run_dev(
435
461
 
436
462
  # Update run_config with session info from Chronos
437
463
  run_config.session_id = chronos_session_id
438
- run_config.otel_url = chronos_session.otel_url
464
+ # Use base chronos URL for OTEL endpoint (more reliable than session-provided URL)
465
+ run_config.otel_url = f"{chronos_url.rstrip('/')}/api/otel"
439
466
  run_config.upload_url = chronos_session.upload_url
440
467
 
441
- # For local dev, override otel_url to use localhost directly
442
- # (Chronos may return a tunnel URL that's meant for remote VMs)
443
- if "localhost" in chronos_url or "127.0.0.1" in chronos_url:
444
- run_config.otel_url = f"{chronos_url.rstrip('/')}/api/otel"
445
- logger.info(f"Local dev: using OTel URL {run_config.otel_url}")
446
-
447
- print(f"[Runner] run_config.otel_url = {run_config.otel_url!r}")
448
- print(f"[Runner] run_config.upload_url = {run_config.upload_url!r}")
449
-
450
468
  # Run the world
451
469
  logger.info(f"Starting world '{world_name}'...")
452
470
  world_instance = world_cls()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plato-sdk-v2
3
- Version: 2.3.6
3
+ Version: 2.3.8
4
4
  Summary: Python SDK for the Plato API
5
5
  Author-email: Plato <support@plato.so>
6
6
  License-Expression: MIT
@@ -47,7 +47,6 @@ Description-Content-Type: text/markdown
47
47
 
48
48
  # Plato Python SDK
49
49
 
50
-
51
50
  Python SDK for the Plato platform. Uses [Harbor](https://harborframework.com) for agent execution.
52
51
 
53
52
  ## Installation
@@ -302,8 +302,8 @@ plato/agents/artifacts.py,sha256=ljeI0wzsp7Q6uKqMb-k7kTb680Vizs54ohtM-d7zvOg,292
302
302
  plato/agents/base.py,sha256=vUbPQuNSo6Ka2lIB_ZOXgi4EoAjtAD7GIj9LnNotam0,4577
303
303
  plato/agents/build.py,sha256=CNMbVQFs2_pYit1dA29Davve28Yi4c7TNK9wBB7odrE,1621
304
304
  plato/agents/config.py,sha256=CmRS6vOAg7JeqX4Hgp_KpA1YWBX_LuMicHm7SBjQEbs,5077
305
- plato/agents/otel.py,sha256=xhsqJrfD9s3tyiSyZPFkTPS0wma71v_TS5BbGiL6lmQ,8168
306
- plato/agents/runner.py,sha256=1qOCYVgT6m4zsRM8f0JXOYIg6smCyI-cuGZgVVae7aM,16244
305
+ plato/agents/otel.py,sha256=UOEBeMyyfbffsC3tRXlHAydoj9bXwJupA_UeLZ5h97w,8585
306
+ plato/agents/runner.py,sha256=Ei20Ib-Fn5XOaS6V1Rtw0UEw34XflEWaXMpazPjmnrE,6061
307
307
  plato/agents/trajectory.py,sha256=WdiBmua0KvCrNaM3qgPI7-7B4xmSkfbP4oZ_9_8qHzU,10529
308
308
  plato/chronos/__init__.py,sha256=RHMvSrQS_-vkKOyTRuAkp2gKDP1HEuBLDnw8jcZs1Jg,739
309
309
  plato/chronos/client.py,sha256=YcOGtHWERyOD9z8LKt8bRMVL0cEwL2hiAP4qQgdZlUI,5495
@@ -389,9 +389,9 @@ plato/v1/cli/__init__.py,sha256=om4b7PxgsoI7rEwuQelmQkqPdhMVn53_5qEN8kvksYw,105
389
389
  plato/v1/cli/agent.py,sha256=G6TV3blG_BqMDBWS-CG7GwzqoqcJTMsIKQ88jvLXb4k,43745
390
390
  plato/v1/cli/main.py,sha256=ktPtBvMwykR7AjXmTQ6bmZkHdzpAjhX5Fq66cDbGSzA,6844
391
391
  plato/v1/cli/pm.py,sha256=uLM6WszKqxq9Czg1FraDyWb9_INUuHZq63imvRYfRLw,49734
392
- plato/v1/cli/sandbox.py,sha256=5rth_jL73L72GC0VJ0meXRgZo2EpsJ_qI3ipFjfXzJY,95185
393
- plato/v1/cli/ssh.py,sha256=10ag6S1sxMcAmvcg24qy-yYwXb1miWfqxkXOs4QX8u0,6623
394
- plato/v1/cli/utils.py,sha256=be-llK6T6NHnIQl_Kfs-8EPu9JhIuZ_k9tJ3Ts-AKt4,3887
392
+ plato/v1/cli/sandbox.py,sha256=N7DIpXsxExtZB47tWKxp-3cV0_gLnI7C-mTKW3tTi8Y,95360
393
+ plato/v1/cli/ssh.py,sha256=enrf7Y01ZeRIyHDEX0Yt7up5zEe7MCvE9u8SP4Oqiz4,6926
394
+ plato/v1/cli/utils.py,sha256=ba7Crv4OjDmgCv4SeB8UeZDin-iOdQw_3N6fd-g5XVk,4572
395
395
  plato/v1/cli/verify.py,sha256=7QmQwfOOkr8a51f8xfVIr2zif7wGl2E8HOZTbOaIoV0,20671
396
396
  plato/v1/cli/world.py,sha256=yBUadOJs1QYm6Jmx_ACDzogybRq5x4B-BnTvGO_ulQk,9757
397
397
  plato/v1/examples/doordash_tasks.py,sha256=8Sz9qx-vTmiOAiCAbrDRvZGsA1qQQBr1KHbxXdjr7OI,23233
@@ -458,11 +458,11 @@ plato/v2/utils/models.py,sha256=PwehSSnIRG-tM3tWL1PzZEH77ZHhIAZ9R0UPs6YknbM,1441
458
458
  plato/v2/utils/proxy_tunnel.py,sha256=8ZTd0jCGSfIHMvSv1fgEyacuISWnGPHLPbDglWroTzY,10463
459
459
  plato/worlds/README.md,sha256=XFOkEA3cNNcrWkk-Cxnsl-zn-y0kvUENKQRSqFKpdqw,5479
460
460
  plato/worlds/__init__.py,sha256=ALoou3l5lXvs_YZc5eH6HdMHpvhnpzKWqz__aSC1jFc,2152
461
- plato/worlds/base.py,sha256=pYBuXn7zZ3n-ys4XkEVEUL1poq1lWTNH9qYbMzWRLfE,27694
461
+ plato/worlds/base.py,sha256=kIX02gMQR43ZsZK25vZvCoNkYtICVRMeofNk-im5IRM,28215
462
462
  plato/worlds/build_hook.py,sha256=KSoW0kqa5b7NyZ7MYOw2qsZ_2FkWuz0M3Ru7AKOP7Qw,3486
463
463
  plato/worlds/config.py,sha256=a5frj3mt06rSlT25kE-L8Q2b2MTWkR-8cUoBKpC8tG4,11036
464
- plato/worlds/runner.py,sha256=vrgKStT8fs3HuOGcPL5obZt-BLdLlQ0CVBUQZWyOOis,19023
465
- plato_sdk_v2-2.3.6.dist-info/METADATA,sha256=mt3gSWRwTjcgKse_WxVlNF1_bRtZjdjzWiIdJJ2ecFU,8653
466
- plato_sdk_v2-2.3.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
467
- plato_sdk_v2-2.3.6.dist-info/entry_points.txt,sha256=upGMbJCx6YWUTKrPoYvYUYfFCqYr75nHDwhA-45m6p8,136
468
- plato_sdk_v2-2.3.6.dist-info/RECORD,,
464
+ plato/worlds/runner.py,sha256=2H5EV77bTYrMyI7qez0kwxOp9EApQxG19Ob9a_GTdbw,19383
465
+ plato_sdk_v2-2.3.8.dist-info/METADATA,sha256=GnoZAAlPmFfWxSwHFjkab7s6MmWfk8rXOvOynTb2r28,8652
466
+ plato_sdk_v2-2.3.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
467
+ plato_sdk_v2-2.3.8.dist-info/entry_points.txt,sha256=upGMbJCx6YWUTKrPoYvYUYfFCqYr75nHDwhA-45m6p8,136
468
+ plato_sdk_v2-2.3.8.dist-info/RECORD,,