plato-sdk-v2 2.8.7__py3-none-any.whl → 2.9.0__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/_generated/models/__init__.py +20 -8
- plato/agents/base.py +3 -9
- plato/agents/config.py +7 -64
- plato/agents/markers.py +18 -0
- plato/agents/otel.py +22 -60
- plato/agents/schema.py +96 -0
- plato/cli/chronos.py +78 -113
- plato/cli/compose.py +1379 -0
- plato/cli/main.py +4 -0
- plato/cli/sandbox.py +62 -14
- plato/cli/session.py +492 -0
- plato/v2/async_/environment.py +572 -0
- plato/v2/async_/session.py +45 -0
- plato/v2/sync/environment.py +6 -0
- plato/v2/sync/sandbox.py +235 -37
- plato/v2/sync/session.py +9 -0
- plato/v2/types.py +46 -15
- plato/worlds/__init__.py +2 -6
- plato/worlds/base.py +44 -130
- plato/worlds/config.py +5 -166
- plato/worlds/markers.py +63 -0
- plato/worlds/models.py +23 -0
- plato/worlds/schema.py +154 -0
- {plato_sdk_v2-2.8.7.dist-info → plato_sdk_v2-2.9.0.dist-info}/METADATA +1 -1
- {plato_sdk_v2-2.8.7.dist-info → plato_sdk_v2-2.9.0.dist-info}/RECORD +27 -20
- {plato_sdk_v2-2.8.7.dist-info → plato_sdk_v2-2.9.0.dist-info}/WHEEL +0 -0
- {plato_sdk_v2-2.8.7.dist-info → plato_sdk_v2-2.9.0.dist-info}/entry_points.txt +0 -0
plato/cli/chronos.py
CHANGED
|
@@ -6,11 +6,13 @@ import asyncio
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
+
import platform as plat
|
|
9
10
|
import subprocess
|
|
10
11
|
import tempfile
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from typing import Annotated
|
|
13
14
|
|
|
15
|
+
import httpx
|
|
14
16
|
import typer
|
|
15
17
|
|
|
16
18
|
from plato.cli.utils import console
|
|
@@ -64,8 +66,6 @@ def launch(
|
|
|
64
66
|
The config file should contain world.package (required) and optionally world.config,
|
|
65
67
|
runtime.artifact_id, and tags.
|
|
66
68
|
"""
|
|
67
|
-
import httpx
|
|
68
|
-
|
|
69
69
|
# Set defaults
|
|
70
70
|
if not chronos_url:
|
|
71
71
|
chronos_url = "https://chronos.plato.so"
|
|
@@ -126,83 +126,9 @@ def launch(
|
|
|
126
126
|
raise typer.Exit(1)
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
@chronos_app.command()
|
|
130
|
-
def example(
|
|
131
|
-
world: str = typer.Argument(
|
|
132
|
-
"structured-execution",
|
|
133
|
-
help="World to generate example config for",
|
|
134
|
-
),
|
|
135
|
-
output: Path = typer.Option(
|
|
136
|
-
None,
|
|
137
|
-
"--output",
|
|
138
|
-
"-o",
|
|
139
|
-
help="Output file path (prints to stdout if not specified)",
|
|
140
|
-
),
|
|
141
|
-
):
|
|
142
|
-
"""Generate an example job config file.
|
|
143
|
-
|
|
144
|
-
Creates a sample JSON configuration for launching Chronos jobs, which can be
|
|
145
|
-
customized for your use case.
|
|
146
|
-
|
|
147
|
-
Arguments:
|
|
148
|
-
world: World type to generate example for (default: "structured-execution")
|
|
149
|
-
|
|
150
|
-
Options:
|
|
151
|
-
-o, --output: Output file path. If not specified, prints to stdout.
|
|
152
|
-
|
|
153
|
-
Available worlds: structured-execution, code-world
|
|
154
|
-
"""
|
|
155
|
-
examples = {
|
|
156
|
-
"structured-execution": {
|
|
157
|
-
"world_package": "plato-world-structured-execution",
|
|
158
|
-
"world_version": "latest",
|
|
159
|
-
"world_config": {
|
|
160
|
-
"sim_name": "my-sim",
|
|
161
|
-
"github_url": "https://github.com/example/repo",
|
|
162
|
-
"max_attempts": 3,
|
|
163
|
-
"use_backtrack": True,
|
|
164
|
-
"skill_runner": {
|
|
165
|
-
"image": "claude-code:2.1.5",
|
|
166
|
-
"config": {"model_name": "anthropic/claude-sonnet-4-20250514", "max_turns": 100},
|
|
167
|
-
},
|
|
168
|
-
"plato_api_key": "pk_xxx",
|
|
169
|
-
"anthropic_api_key": "sk-ant-xxx",
|
|
170
|
-
},
|
|
171
|
-
"_comment": "Agents and secrets are embedded directly in world_config",
|
|
172
|
-
},
|
|
173
|
-
"code-world": {
|
|
174
|
-
"world_package": "plato-world-code",
|
|
175
|
-
"world_config": {
|
|
176
|
-
"task": "Fix the bug in src/main.py",
|
|
177
|
-
"repo_url": "https://github.com/example/repo",
|
|
178
|
-
"coder": {
|
|
179
|
-
"image": "claude-code:latest",
|
|
180
|
-
"config": {"model_name": "anthropic/claude-sonnet-4-20250514"},
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
"_comment": "world_version is optional - uses latest if not specified",
|
|
184
|
-
},
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if world not in examples:
|
|
188
|
-
console.print(f"[red]❌ Unknown world: {world}[/red]")
|
|
189
|
-
console.print(f"Available examples: {list(examples.keys())}")
|
|
190
|
-
raise typer.Exit(1)
|
|
191
|
-
|
|
192
|
-
example_config = examples[world]
|
|
193
|
-
json_output = json.dumps(example_config, indent=2)
|
|
194
|
-
|
|
195
|
-
if output:
|
|
196
|
-
with open(output, "w") as f:
|
|
197
|
-
f.write(json_output)
|
|
198
|
-
console.print(f"[green]✅ Example config written to {output}[/green]")
|
|
199
|
-
else:
|
|
200
|
-
console.print(json_output)
|
|
201
|
-
|
|
202
|
-
|
|
203
129
|
def _get_world_runner_dockerfile() -> Path:
|
|
204
130
|
"""Get the path to the world runner Dockerfile template."""
|
|
205
|
-
return Path(__file__).parent / "templates" / "world-runner.Dockerfile"
|
|
131
|
+
return Path(__file__).parent.parent / "v1" / "cli" / "templates" / "world-runner.Dockerfile"
|
|
206
132
|
|
|
207
133
|
|
|
208
134
|
def _build_world_runner_image(platform_override: str | None = None) -> str:
|
|
@@ -253,8 +179,6 @@ def _get_docker_platform(override: str | None = None) -> str:
|
|
|
253
179
|
if override:
|
|
254
180
|
return override
|
|
255
181
|
|
|
256
|
-
import platform as plat
|
|
257
|
-
|
|
258
182
|
system = plat.system()
|
|
259
183
|
machine = plat.machine().lower()
|
|
260
184
|
|
|
@@ -338,8 +262,22 @@ def _build_agent_image(
|
|
|
338
262
|
return True
|
|
339
263
|
|
|
340
264
|
|
|
341
|
-
def
|
|
342
|
-
"""Extract
|
|
265
|
+
def _extract_agent_name_from_image(image: str) -> str:
|
|
266
|
+
"""Extract agent name from image reference (local or full registry path)."""
|
|
267
|
+
# Remove tag if present (e.g., "explorer:1.0.6" -> "explorer")
|
|
268
|
+
image_without_tag = image.split(":")[0]
|
|
269
|
+
# Get the last path component (e.g., "383806609161.dkr.ecr.../agents/plato/explorer" -> "explorer")
|
|
270
|
+
return image_without_tag.split("/")[-1]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _extract_agent_images_from_config(config_data: dict, extract_all: bool = False) -> list[str]:
|
|
274
|
+
"""Extract agent image names from config data.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
config_data: The world configuration data
|
|
278
|
+
extract_all: If True, extract agent names from ALL images (including registry paths).
|
|
279
|
+
If False, only extract from local images (no registry prefix).
|
|
280
|
+
"""
|
|
343
281
|
images = []
|
|
344
282
|
|
|
345
283
|
# Check agents section
|
|
@@ -347,9 +285,14 @@ def _extract_agent_images_from_config(config_data: dict) -> list[str]:
|
|
|
347
285
|
for agent_config in agents.values():
|
|
348
286
|
if isinstance(agent_config, dict):
|
|
349
287
|
image = agent_config.get("image", "")
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
288
|
+
if image:
|
|
289
|
+
if extract_all:
|
|
290
|
+
name = _extract_agent_name_from_image(image)
|
|
291
|
+
elif "/" not in image.split(":")[0]:
|
|
292
|
+
# Only include local images (no registry prefix)
|
|
293
|
+
name = image.split(":")[0]
|
|
294
|
+
else:
|
|
295
|
+
continue
|
|
353
296
|
if name not in images:
|
|
354
297
|
images.append(name)
|
|
355
298
|
|
|
@@ -358,14 +301,44 @@ def _extract_agent_images_from_config(config_data: dict) -> list[str]:
|
|
|
358
301
|
agent_config = config_data.get(field, {})
|
|
359
302
|
if isinstance(agent_config, dict):
|
|
360
303
|
image = agent_config.get("image", "")
|
|
361
|
-
if image
|
|
362
|
-
|
|
304
|
+
if image:
|
|
305
|
+
if extract_all:
|
|
306
|
+
name = _extract_agent_name_from_image(image)
|
|
307
|
+
elif "/" not in image.split(":")[0]:
|
|
308
|
+
name = image.split(":")[0]
|
|
309
|
+
else:
|
|
310
|
+
continue
|
|
363
311
|
if name not in images:
|
|
364
312
|
images.append(name)
|
|
365
313
|
|
|
366
314
|
return images
|
|
367
315
|
|
|
368
316
|
|
|
317
|
+
def _update_config_with_local_images(config_data: dict, built_agents: list[str]) -> dict:
|
|
318
|
+
"""Update config to use local image tags for built agents."""
|
|
319
|
+
config_data = config_data.copy()
|
|
320
|
+
|
|
321
|
+
# Update agents section
|
|
322
|
+
if "agents" in config_data:
|
|
323
|
+
config_data["agents"] = config_data["agents"].copy()
|
|
324
|
+
for agent_name, agent_config in config_data["agents"].items():
|
|
325
|
+
if isinstance(agent_config, dict) and "image" in agent_config:
|
|
326
|
+
image_agent_name = _extract_agent_name_from_image(agent_config["image"])
|
|
327
|
+
if image_agent_name in built_agents:
|
|
328
|
+
config_data["agents"][agent_name] = agent_config.copy()
|
|
329
|
+
config_data["agents"][agent_name]["image"] = f"{image_agent_name}:latest"
|
|
330
|
+
|
|
331
|
+
# Update direct fields
|
|
332
|
+
for field in ["coder", "verifier", "skill_runner"]:
|
|
333
|
+
if field in config_data and isinstance(config_data[field], dict) and "image" in config_data[field]:
|
|
334
|
+
image_agent_name = _extract_agent_name_from_image(config_data[field]["image"])
|
|
335
|
+
if image_agent_name in built_agents:
|
|
336
|
+
config_data[field] = config_data[field].copy()
|
|
337
|
+
config_data[field]["image"] = f"{image_agent_name}:latest"
|
|
338
|
+
|
|
339
|
+
return config_data
|
|
340
|
+
|
|
341
|
+
|
|
369
342
|
async def _create_chronos_session(
|
|
370
343
|
chronos_url: str,
|
|
371
344
|
api_key: str,
|
|
@@ -375,8 +348,6 @@ async def _create_chronos_session(
|
|
|
375
348
|
tags: list[str] | None = None,
|
|
376
349
|
) -> dict:
|
|
377
350
|
"""Create a session in Chronos."""
|
|
378
|
-
import httpx
|
|
379
|
-
|
|
380
351
|
url = f"{chronos_url.rstrip('/')}/api/sessions"
|
|
381
352
|
|
|
382
353
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
@@ -394,25 +365,6 @@ async def _create_chronos_session(
|
|
|
394
365
|
return response.json()
|
|
395
366
|
|
|
396
367
|
|
|
397
|
-
async def _close_chronos_session(
|
|
398
|
-
chronos_url: str,
|
|
399
|
-
api_key: str,
|
|
400
|
-
session_id: str,
|
|
401
|
-
) -> None:
|
|
402
|
-
"""Close a Chronos session."""
|
|
403
|
-
import httpx
|
|
404
|
-
|
|
405
|
-
url = f"{chronos_url.rstrip('/')}/api/sessions/{session_id}/close"
|
|
406
|
-
|
|
407
|
-
try:
|
|
408
|
-
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
409
|
-
response = await client.post(url, headers={"x-api-key": api_key})
|
|
410
|
-
response.raise_for_status()
|
|
411
|
-
logger.info(f"Closed Chronos session: {session_id}")
|
|
412
|
-
except Exception as e:
|
|
413
|
-
logger.warning(f"Failed to close Chronos session: {e}")
|
|
414
|
-
|
|
415
|
-
|
|
416
368
|
async def _complete_chronos_session(
|
|
417
369
|
chronos_url: str,
|
|
418
370
|
api_key: str,
|
|
@@ -422,7 +374,6 @@ async def _complete_chronos_session(
|
|
|
422
374
|
error_message: str | None = None,
|
|
423
375
|
) -> None:
|
|
424
376
|
"""Complete a Chronos session with final status."""
|
|
425
|
-
import httpx
|
|
426
377
|
|
|
427
378
|
url = f"{chronos_url.rstrip('/')}/api/sessions/{session_id}/complete"
|
|
428
379
|
|
|
@@ -493,15 +444,29 @@ async def _run_dev_impl(
|
|
|
493
444
|
world_name = world_package_name or "unknown"
|
|
494
445
|
|
|
495
446
|
# Build local agent images if agents_dir is provided
|
|
447
|
+
built_agents: list[str] = []
|
|
496
448
|
if agents_dir:
|
|
497
449
|
agents_dir = agents_dir.expanduser().resolve()
|
|
498
|
-
|
|
450
|
+
# Extract all agent names (including from full registry paths) when agents_dir is provided
|
|
451
|
+
agent_images = _extract_agent_images_from_config(config_data, extract_all=True)
|
|
499
452
|
if agent_images:
|
|
500
|
-
console.print(f"[blue]
|
|
453
|
+
console.print(f"[blue]Checking for local agent builds: {agent_images}[/blue]")
|
|
501
454
|
for agent_name in agent_images:
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
455
|
+
# Check if agent exists locally
|
|
456
|
+
agent_path = agents_dir / agent_name
|
|
457
|
+
if agent_path.exists() and (agent_path / "Dockerfile").exists():
|
|
458
|
+
console.print(f"[blue]Building local agent: {agent_name}[/blue]")
|
|
459
|
+
success = _build_agent_image(agent_name, agents_dir, platform_override)
|
|
460
|
+
if not success:
|
|
461
|
+
raise RuntimeError(f"Failed to build agent image: {agent_name}")
|
|
462
|
+
built_agents.append(agent_name)
|
|
463
|
+
else:
|
|
464
|
+
console.print(f"[dim]No local agent found for '{agent_name}', using remote image[/dim]")
|
|
465
|
+
|
|
466
|
+
# Update config to use local images for built agents
|
|
467
|
+
if built_agents:
|
|
468
|
+
config_data = _update_config_with_local_images(config_data, built_agents)
|
|
469
|
+
console.print(f"[green]✅ Using local builds for: {built_agents}[/green]")
|
|
505
470
|
|
|
506
471
|
# Import world module to get config class for environment detection
|
|
507
472
|
# We need to dynamically load the world from world_dir
|