plato-sdk-v2 2.0.50__py3-none-any.whl → 2.2.4__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.
Files changed (158) hide show
  1. plato/__init__.py +7 -6
  2. plato/_generated/__init__.py +1 -1
  3. plato/_generated/api/v1/env/evaluate_session.py +3 -3
  4. plato/_generated/api/v1/env/log_state_mutation.py +4 -4
  5. plato/_generated/api/v1/sandbox/checkpoint_vm.py +3 -3
  6. plato/_generated/api/v1/sandbox/save_vm_snapshot.py +3 -3
  7. plato/_generated/api/v1/sandbox/setup_sandbox.py +8 -8
  8. plato/_generated/api/v1/session/__init__.py +2 -0
  9. plato/_generated/api/v1/session/get_sessions_for_archival.py +100 -0
  10. plato/_generated/api/v1/testcases/__init__.py +6 -2
  11. plato/_generated/api/v1/testcases/get_mutation_groups_for_testcase.py +98 -0
  12. plato/_generated/api/v1/testcases/{get_next_output_testcase_for_scoring.py → get_next_testcase_for_scoring.py} +23 -10
  13. plato/_generated/api/v1/testcases/get_testcase_metadata_for_scoring.py +74 -0
  14. plato/_generated/api/v2/__init__.py +2 -1
  15. plato/_generated/api/v2/jobs/__init__.py +4 -0
  16. plato/_generated/api/v2/jobs/checkpoint.py +3 -3
  17. plato/_generated/api/v2/jobs/disk_snapshot.py +3 -3
  18. plato/_generated/api/v2/jobs/log_for_job.py +4 -39
  19. plato/_generated/api/v2/jobs/make.py +4 -4
  20. plato/_generated/api/v2/jobs/setup_sandbox.py +97 -0
  21. plato/_generated/api/v2/jobs/snapshot.py +3 -3
  22. plato/_generated/api/v2/jobs/snapshot_store.py +91 -0
  23. plato/_generated/api/v2/sessions/__init__.py +4 -0
  24. plato/_generated/api/v2/sessions/checkpoint.py +3 -3
  25. plato/_generated/api/v2/sessions/disk_snapshot.py +3 -3
  26. plato/_generated/api/v2/sessions/evaluate.py +3 -3
  27. plato/_generated/api/v2/sessions/log_job_mutation.py +4 -39
  28. plato/_generated/api/v2/sessions/make.py +4 -4
  29. plato/_generated/api/v2/sessions/setup_sandbox.py +98 -0
  30. plato/_generated/api/v2/sessions/snapshot.py +3 -3
  31. plato/_generated/api/v2/sessions/snapshot_store.py +94 -0
  32. plato/_generated/api/v2/user/__init__.py +7 -0
  33. plato/_generated/api/v2/user/get_current_user.py +76 -0
  34. plato/_generated/models/__init__.py +174 -23
  35. plato/_sims_generator/__init__.py +19 -4
  36. plato/_sims_generator/instruction.py +203 -0
  37. plato/_sims_generator/templates/instruction/helpers.py.jinja +161 -0
  38. plato/_sims_generator/templates/instruction/init.py.jinja +43 -0
  39. plato/agents/__init__.py +107 -517
  40. plato/agents/base.py +145 -0
  41. plato/agents/build.py +61 -0
  42. plato/agents/config.py +160 -0
  43. plato/agents/logging.py +401 -0
  44. plato/agents/runner.py +161 -0
  45. plato/agents/trajectory.py +266 -0
  46. plato/chronos/__init__.py +37 -0
  47. plato/chronos/api/__init__.py +3 -0
  48. plato/chronos/api/agents/__init__.py +13 -0
  49. plato/chronos/api/agents/create_agent.py +63 -0
  50. plato/chronos/api/agents/delete_agent.py +61 -0
  51. plato/chronos/api/agents/get_agent.py +62 -0
  52. plato/chronos/api/agents/get_agent_schema.py +72 -0
  53. plato/chronos/api/agents/get_agent_versions.py +62 -0
  54. plato/chronos/api/agents/list_agents.py +57 -0
  55. plato/chronos/api/agents/lookup_agent.py +74 -0
  56. plato/chronos/api/auth/__init__.py +9 -0
  57. plato/chronos/api/auth/debug_auth_api_auth_debug_get.py +43 -0
  58. plato/chronos/api/auth/get_auth_status_api_auth_status_get.py +61 -0
  59. plato/chronos/api/auth/get_current_user_route_api_auth_me_get.py +60 -0
  60. plato/chronos/api/callback/__init__.py +11 -0
  61. plato/chronos/api/callback/push_agent_logs.py +61 -0
  62. plato/chronos/api/callback/update_agent_status.py +57 -0
  63. plato/chronos/api/callback/upload_artifacts.py +59 -0
  64. plato/chronos/api/callback/upload_logs_zip.py +57 -0
  65. plato/chronos/api/callback/upload_trajectory.py +57 -0
  66. plato/chronos/api/default/__init__.py +7 -0
  67. plato/chronos/api/default/health.py +43 -0
  68. plato/chronos/api/jobs/__init__.py +7 -0
  69. plato/chronos/api/jobs/launch_job.py +63 -0
  70. plato/chronos/api/registry/__init__.py +19 -0
  71. plato/chronos/api/registry/get_agent_schema_api_registry_agents__agent_name__schema_get.py +62 -0
  72. plato/chronos/api/registry/get_agent_versions_api_registry_agents__agent_name__versions_get.py +52 -0
  73. plato/chronos/api/registry/get_world_schema_api_registry_worlds__package_name__schema_get.py +68 -0
  74. plato/chronos/api/registry/get_world_versions_api_registry_worlds__package_name__versions_get.py +52 -0
  75. plato/chronos/api/registry/list_registry_agents_api_registry_agents_get.py +44 -0
  76. plato/chronos/api/registry/list_registry_worlds_api_registry_worlds_get.py +44 -0
  77. plato/chronos/api/runtimes/__init__.py +11 -0
  78. plato/chronos/api/runtimes/create_runtime.py +63 -0
  79. plato/chronos/api/runtimes/delete_runtime.py +61 -0
  80. plato/chronos/api/runtimes/get_runtime.py +62 -0
  81. plato/chronos/api/runtimes/list_runtimes.py +57 -0
  82. plato/chronos/api/runtimes/test_runtime.py +67 -0
  83. plato/chronos/api/secrets/__init__.py +11 -0
  84. plato/chronos/api/secrets/create_secret.py +63 -0
  85. plato/chronos/api/secrets/delete_secret.py +61 -0
  86. plato/chronos/api/secrets/get_secret.py +62 -0
  87. plato/chronos/api/secrets/list_secrets.py +57 -0
  88. plato/chronos/api/secrets/update_secret.py +68 -0
  89. plato/chronos/api/sessions/__init__.py +10 -0
  90. plato/chronos/api/sessions/get_session.py +62 -0
  91. plato/chronos/api/sessions/get_session_logs.py +72 -0
  92. plato/chronos/api/sessions/get_session_logs_download.py +62 -0
  93. plato/chronos/api/sessions/list_sessions.py +57 -0
  94. plato/chronos/api/status/__init__.py +8 -0
  95. plato/chronos/api/status/get_status_api_status_get.py +44 -0
  96. plato/chronos/api/status/get_version_info_api_version_get.py +44 -0
  97. plato/chronos/api/templates/__init__.py +11 -0
  98. plato/chronos/api/templates/create_template.py +63 -0
  99. plato/chronos/api/templates/delete_template.py +61 -0
  100. plato/chronos/api/templates/get_template.py +62 -0
  101. plato/chronos/api/templates/list_templates.py +57 -0
  102. plato/chronos/api/templates/update_template.py +68 -0
  103. plato/chronos/api/trajectories/__init__.py +8 -0
  104. plato/chronos/api/trajectories/get_trajectory.py +62 -0
  105. plato/chronos/api/trajectories/list_trajectories.py +62 -0
  106. plato/chronos/api/worlds/__init__.py +10 -0
  107. plato/chronos/api/worlds/create_world.py +63 -0
  108. plato/chronos/api/worlds/delete_world.py +61 -0
  109. plato/chronos/api/worlds/get_world.py +62 -0
  110. plato/chronos/api/worlds/list_worlds.py +57 -0
  111. plato/chronos/client.py +171 -0
  112. plato/chronos/errors.py +141 -0
  113. plato/chronos/models/__init__.py +647 -0
  114. plato/chronos/py.typed +0 -0
  115. plato/sims/cli.py +299 -123
  116. plato/sims/registry.py +77 -4
  117. plato/v1/cli/agent.py +88 -84
  118. plato/v1/cli/main.py +2 -0
  119. plato/v1/cli/pm.py +441 -119
  120. plato/v1/cli/sandbox.py +747 -191
  121. plato/v1/cli/sim.py +11 -0
  122. plato/v1/cli/verify.py +1269 -0
  123. plato/v1/cli/world.py +3 -0
  124. plato/v1/flow_executor.py +21 -17
  125. plato/v1/models/env.py +11 -11
  126. plato/v1/sdk.py +2 -2
  127. plato/v1/sync_env.py +11 -11
  128. plato/v1/sync_flow_executor.py +21 -17
  129. plato/v1/sync_sdk.py +4 -2
  130. plato/v2/__init__.py +2 -0
  131. plato/v2/async_/environment.py +20 -1
  132. plato/v2/async_/session.py +54 -3
  133. plato/v2/sync/environment.py +2 -1
  134. plato/v2/sync/session.py +52 -2
  135. plato/worlds/README.md +218 -0
  136. plato/worlds/__init__.py +54 -18
  137. plato/worlds/base.py +304 -93
  138. plato/worlds/config.py +239 -73
  139. plato/worlds/runner.py +391 -80
  140. {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/METADATA +1 -3
  141. {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/RECORD +143 -68
  142. {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/entry_points.txt +1 -0
  143. plato/_generated/api/v2/interfaces/__init__.py +0 -27
  144. plato/_generated/api/v2/interfaces/v2_interface_browser_create.py +0 -68
  145. plato/_generated/api/v2/interfaces/v2_interface_cdp_url.py +0 -65
  146. plato/_generated/api/v2/interfaces/v2_interface_click.py +0 -64
  147. plato/_generated/api/v2/interfaces/v2_interface_close.py +0 -59
  148. plato/_generated/api/v2/interfaces/v2_interface_computer_create.py +0 -68
  149. plato/_generated/api/v2/interfaces/v2_interface_cursor.py +0 -64
  150. plato/_generated/api/v2/interfaces/v2_interface_key.py +0 -68
  151. plato/_generated/api/v2/interfaces/v2_interface_screenshot.py +0 -65
  152. plato/_generated/api/v2/interfaces/v2_interface_scroll.py +0 -70
  153. plato/_generated/api/v2/interfaces/v2_interface_type.py +0 -64
  154. plato/world/__init__.py +0 -44
  155. plato/world/base.py +0 -267
  156. plato/world/config.py +0 -139
  157. plato/world/types.py +0 -47
  158. {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/WHEEL +0 -0
plato/v1/cli/agent.py CHANGED
@@ -6,13 +6,16 @@ import re
6
6
  import shutil
7
7
  import subprocess
8
8
  import sys
9
- import tempfile
10
9
  from pathlib import Path
10
+ from typing import TYPE_CHECKING
11
11
 
12
12
  import typer
13
13
 
14
14
  from plato.v1.cli.utils import console, require_api_key
15
15
 
16
+ if TYPE_CHECKING:
17
+ from plato.agents.build import BuildConfig
18
+
16
19
 
17
20
  def _extract_schemas(pkg_path: Path, package_name: str) -> tuple[dict | None, dict | None, dict | None]:
18
21
  """Extract Config, BuildConfig, and SecretsConfig schemas from the agent package.
@@ -279,8 +282,7 @@ def _publish_agent_image(
279
282
  description: str,
280
283
  dry_run: bool,
281
284
  schema_data: dict | None = None,
282
- template_variables: dict[str, str] | None = None,
283
- secrets_schema: dict | None = None,
285
+ build_config: "BuildConfig | None" = None,
284
286
  ) -> None:
285
287
  """Common logic for publishing an agent Docker image to ECR."""
286
288
  import httpx
@@ -298,9 +300,27 @@ def _publish_agent_image(
298
300
  console.print("[cyan]Building Docker image...[/cyan]")
299
301
  local_tag = f"{agent_name}:{version}"
300
302
 
303
+ # Build docker command with build args from config
304
+ docker_cmd = ["docker", "build", "--progress=plain", "-t", local_tag]
305
+
306
+ # Add --target prod if Dockerfile has multi-stage builds
307
+ dockerfile_path = build_path / "Dockerfile"
308
+ if dockerfile_path.exists():
309
+ dockerfile_content = dockerfile_path.read_text()
310
+ if "FROM" in dockerfile_content and "AS prod" in dockerfile_content:
311
+ docker_cmd.extend(["--target", "prod"])
312
+ console.print("[cyan]Using target: prod[/cyan]")
313
+
314
+ # Add build args from build config's env dict
315
+ if build_config and build_config.env:
316
+ for key, value in build_config.env.items():
317
+ docker_cmd.extend(["--build-arg", f"{key}={value}"])
318
+
319
+ docker_cmd.append(str(build_path))
320
+
301
321
  # Use Popen to stream output in real-time
302
322
  process = subprocess.Popen(
303
- ["docker", "build", "--progress=plain", "-t", local_tag, str(build_path)],
323
+ docker_cmd,
304
324
  stdout=subprocess.PIPE,
305
325
  stderr=subprocess.STDOUT,
306
326
  text=True,
@@ -309,6 +329,9 @@ def _publish_agent_image(
309
329
 
310
330
  # Stream build output
311
331
  build_output = []
332
+ if process.stdout is None:
333
+ console.print("[red]Error: Failed to capture Docker build output[/red]")
334
+ raise typer.Exit(1)
312
335
  for line in iter(process.stdout.readline, ""):
313
336
  line = line.rstrip()
314
337
  if line:
@@ -405,10 +428,6 @@ def _publish_agent_image(
405
428
  "description": description,
406
429
  "config_schema": schema_data,
407
430
  }
408
- if template_variables:
409
- registration_data["template_variables"] = template_variables
410
- if secrets_schema:
411
- registration_data["secrets_schema"] = secrets_schema
412
431
  response = client.post(
413
432
  "/v2/agents/register",
414
433
  json=registration_data,
@@ -431,6 +450,11 @@ def _publish_agent_image(
431
450
  console.print(f"[cyan]Artifact ID:[/cyan] {reg_result['artifact_id']}")
432
451
  console.print(f"[cyan]Image:[/cyan] {ecr_image}")
433
452
 
453
+ # Clean up local images after successful push
454
+ console.print("[dim]Cleaning up local images...[/dim]")
455
+ subprocess.run(["docker", "rmi", local_tag], capture_output=True)
456
+ subprocess.run(["docker", "rmi", ecr_image], capture_output=True)
457
+
434
458
 
435
459
  def _publish_package(path: str, repo: str, dry_run: bool = False):
436
460
  """
@@ -545,6 +569,8 @@ def _publish_package(path: str, repo: str, dry_run: bool = False):
545
569
  upload_url = f"{api_url}/v2/pypi/{repo}/"
546
570
  console.print(f"\n[cyan]Uploading to {upload_url}...[/cyan]")
547
571
 
572
+ # api_key is guaranteed to be set (checked earlier when not dry_run)
573
+ assert api_key is not None, "api_key must be set when not in dry_run mode"
548
574
  try:
549
575
  result = subprocess.run(
550
576
  [
@@ -560,6 +586,7 @@ def _publish_package(path: str, repo: str, dry_run: bool = False):
560
586
  ],
561
587
  capture_output=True,
562
588
  text=True,
589
+ check=False,
563
590
  )
564
591
 
565
592
  if result.returncode == 0:
@@ -797,39 +824,10 @@ def agent_push(
797
824
  console.print(f"[yellow]Failed:[/yellow] {', '.join(failed)}")
798
825
  return
799
826
 
800
- # Check if target is a Harbor agent name
801
- if target in HARBOR_AGENTS:
802
- harbor_version = _get_harbor_version()
803
- console.print(f"[cyan]Harbor agent:[/cyan] {target}")
804
- console.print(f"[cyan]Harbor version:[/cyan] {harbor_version}")
805
-
806
- install_script = _get_harbor_install_script(target)
807
- if not install_script:
808
- console.print(f"[red]Error: Could not get install script for '{target}'[/red]")
809
- console.print("[yellow]Make sure harbor is installed: pip install 'plato-sdk-v2[agents]'[/yellow]")
810
- raise typer.Exit(1)
811
-
812
- with tempfile.TemporaryDirectory() as tmpdir:
813
- tmppath = Path(tmpdir)
814
- aux_files = HARBOR_AGENT_AUX_FILES.get(target, [])
815
- (tmppath / "Dockerfile").write_text(_build_harbor_dockerfile(aux_files))
816
- (tmppath / "install.sh").write_text(install_script)
817
- _copy_harbor_aux_files(target, tmppath)
818
-
819
- _publish_agent_image(
820
- agent_name=target,
821
- version=harbor_version,
822
- build_path=tmppath,
823
- description=f"Harbor agent: {target}",
824
- dry_run=dry_run,
825
- )
826
- return
827
-
828
- # Otherwise, treat as a path to custom agent
827
+ # Treat target as a path to agent directory
829
828
  pkg_path = Path(target).resolve()
830
829
  if not pkg_path.exists():
831
- console.print(f"[red]Error: '{target}' is not a Harbor agent name or valid path[/red]")
832
- console.print(f"\n[yellow]Harbor agents:[/yellow] {', '.join(HARBOR_AGENTS.keys())}")
830
+ console.print(f"[red]Error: '{target}' is not a valid path[/red]")
833
831
  raise typer.Exit(1)
834
832
 
835
833
  _push_single_agent(pkg_path, dry_run)
@@ -837,6 +835,8 @@ def agent_push(
837
835
 
838
836
  def _push_single_agent(pkg_path: Path, dry_run: bool) -> None:
839
837
  """Push a single custom agent from a directory."""
838
+ from plato.agents.build import BuildConfig
839
+
840
840
  # Check for Dockerfile
841
841
  if not (pkg_path / "Dockerfile").exists():
842
842
  console.print(f"[red]Error: No Dockerfile found at {pkg_path}[/red]")
@@ -866,56 +866,61 @@ def _push_single_agent(pkg_path: Path, dry_run: bool) -> None:
866
866
  console.print("[red]Error: No version in pyproject.toml[/red]")
867
867
  raise typer.Exit(1)
868
868
 
869
- # Extract short name
869
+ # Extract short name (remove common prefixes)
870
870
  short_name = package_name
871
- for prefix in ("plato-agent-", "plato-"):
872
- if short_name.startswith(prefix):
873
- short_name = short_name[len(prefix) :]
871
+ for suffix in ("-agent",):
872
+ if short_name.endswith(suffix):
873
+ short_name = short_name[: -len(suffix)]
874
874
  break
875
875
 
876
- # Extract Config (runtime), BuildConfig (build-time), and SecretsConfig schemas
877
- console.print("[cyan]Looking for Config, BuildConfig, and SecretsConfig classes...[/cyan]")
878
- schema_data, build_config_schema, secrets_schema = _extract_schemas(pkg_path, package_name)
876
+ # Load build config from pyproject.toml (optional, for build args)
877
+ build_config = None
878
+ try:
879
+ build_config = BuildConfig.from_pyproject(pkg_path)
880
+ if build_config.env:
881
+ console.print(f"[cyan]Build args:[/cyan] {list(build_config.env.keys())}")
882
+ except Exception:
883
+ pass # Build config is optional
879
884
 
880
- # Fall back to schema.json file for config_schema
881
- if schema_data is None:
882
- schema_file = pkg_path / "schema.json"
883
- if schema_file.exists():
884
- console.print(f"[cyan]Loading schema from {schema_file}[/cyan]")
885
- try:
886
- with open(schema_file) as f:
887
- schema_data = json.load(f)
888
- except Exception as e:
889
- console.print(f"[yellow]Warning: Failed to load schema.json: {e}[/yellow]")
885
+ # Load schema from entry point defined in pyproject.toml
886
+ schema_data = None
887
+ entry_points_config = pyproject.get("project", {}).get("entry-points", {}).get("plato.agents", {})
890
888
 
891
- if schema_data:
892
- console.print("[green]Config schema found (runtime config)[/green]")
889
+ if not entry_points_config:
890
+ console.print("[yellow]No plato.agents entry point in pyproject.toml - agent will have no schema[/yellow]")
893
891
  else:
894
- console.print("[yellow]No Config schema found[/yellow]")
892
+ # Get the first (and typically only) entry point
893
+ # Format is: agent_name = "module_name:ClassName"
894
+ for ep_name, ep_value in entry_points_config.items():
895
+ try:
896
+ if ":" not in ep_value:
897
+ console.print(f"[yellow]Invalid entry point format '{ep_value}' - expected 'module:Class'[/yellow]")
898
+ continue
895
899
 
896
- if secrets_schema:
897
- console.print("[green]SecretsConfig schema found[/green]")
898
- else:
899
- console.print("[yellow]No SecretsConfig schema found[/yellow]")
900
-
901
- # Extract template variables from BuildConfig schema
902
- template_variables = _extract_template_variables(build_config_schema)
903
- if template_variables:
904
- console.print(f"[cyan]Template variables (from BuildConfig):[/cyan] {template_variables}")
905
- elif build_config_schema:
906
- console.print("[yellow]BuildConfig found but no template variables extracted[/yellow]")
907
-
908
- # If this is a Harbor agent wrapper, generate install.sh from Harbor template
909
- if short_name in HARBOR_AGENTS:
910
- console.print(f"[cyan]Generating install.sh from Harbor template for {short_name}...[/cyan]")
911
- install_script = _get_harbor_install_script(short_name, template_variables)
912
- if install_script:
913
- (pkg_path / "install.sh").write_text(install_script)
914
- console.print("[green]install.sh generated[/green]")
915
- # Copy auxiliary files if needed
916
- _copy_harbor_aux_files(short_name, pkg_path)
917
- else:
918
- console.print("[yellow]Warning: Could not generate install.sh from Harbor template[/yellow]")
900
+ module_name, class_name = ep_value.split(":", 1)
901
+
902
+ # Add src/ to path and import
903
+ import sys
904
+
905
+ src_path = pkg_path / "src"
906
+ if src_path.exists():
907
+ sys.path.insert(0, str(src_path))
908
+
909
+ try:
910
+ module = __import__(module_name, fromlist=[class_name])
911
+ agent_cls = getattr(module, class_name)
912
+ schema_data = agent_cls.get_schema()
913
+ console.print(f"[green]Loaded schema from {class_name}[/green]")
914
+ break
915
+ finally:
916
+ if src_path.exists() and str(src_path) in sys.path:
917
+ sys.path.remove(str(src_path))
918
+
919
+ except Exception as e:
920
+ console.print(f"[yellow]Failed to load schema from entry point: {e}[/yellow]")
921
+
922
+ if not schema_data:
923
+ console.print("[yellow]No schema found (agent will have no config validation)[/yellow]")
919
924
 
920
925
  _publish_agent_image(
921
926
  agent_name=short_name,
@@ -924,8 +929,7 @@ def _push_single_agent(pkg_path: Path, dry_run: bool) -> None:
924
929
  description=description or f"Custom agent: {short_name}",
925
930
  dry_run=dry_run,
926
931
  schema_data=schema_data,
927
- template_variables=template_variables,
928
- secrets_schema=secrets_schema,
932
+ build_config=build_config,
929
933
  )
930
934
 
931
935
 
plato/v1/cli/main.py CHANGED
@@ -11,6 +11,7 @@ from dotenv import load_dotenv
11
11
  from plato.v1.cli.agent import agent_app
12
12
  from plato.v1.cli.pm import pm_app
13
13
  from plato.v1.cli.sandbox import sandbox_app
14
+ from plato.v1.cli.sim import sim_app
14
15
  from plato.v1.cli.utils import console
15
16
  from plato.v1.cli.world import world_app
16
17
 
@@ -69,6 +70,7 @@ app = typer.Typer(help="[bold blue]Plato CLI[/bold blue] - Manage Plato environm
69
70
  # Register sub-apps
70
71
  app.add_typer(sandbox_app, name="sandbox")
71
72
  app.add_typer(pm_app, name="pm")
73
+ app.add_typer(sim_app, name="sim")
72
74
  app.add_typer(agent_app, name="agent")
73
75
  app.add_typer(world_app, name="world")
74
76