plato-sdk-v2 2.8.8__py3-none-any.whl → 2.9.1__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.
@@ -1,4 +1,4 @@
1
- """Plato API SDK - v0.49.7"""
1
+ """Plato API SDK - v0.51.0"""
2
2
 
3
3
  from . import api, errors, models
4
4
  from .client import AsyncClient, Client
@@ -27,6 +27,9 @@ def _build_request_args(
27
27
  exclude_assigned_to_annotators: bool | None = None,
28
28
  workorder: str | None = None,
29
29
  infeasible: bool | None = None,
30
+ testcase_statuses: str | None = None,
31
+ work_order_item_id: str | None = None,
32
+ status: str | None = None,
30
33
  authorization: str | None = None,
31
34
  x_api_key: str | None = None,
32
35
  ) -> dict[str, Any]:
@@ -68,6 +71,12 @@ def _build_request_args(
68
71
  params["workorder"] = workorder
69
72
  if infeasible is not None:
70
73
  params["infeasible"] = infeasible
74
+ if testcase_statuses is not None:
75
+ params["testcase_statuses"] = testcase_statuses
76
+ if work_order_item_id is not None:
77
+ params["work_order_item_id"] = work_order_item_id
78
+ if status is not None:
79
+ params["status"] = status
71
80
 
72
81
  headers: dict[str, str] = {}
73
82
  if authorization is not None:
@@ -102,6 +111,9 @@ def sync(
102
111
  exclude_assigned_to_annotators: bool | None = None,
103
112
  workorder: str | None = None,
104
113
  infeasible: bool | None = None,
114
+ testcase_statuses: str | None = None,
115
+ work_order_item_id: str | None = None,
116
+ status: str | None = None,
105
117
  authorization: str | None = None,
106
118
  x_api_key: str | None = None,
107
119
  ) -> Any:
@@ -125,6 +137,9 @@ def sync(
125
137
  exclude_assigned_to_annotators=exclude_assigned_to_annotators,
126
138
  workorder=workorder,
127
139
  infeasible=infeasible,
140
+ testcase_statuses=testcase_statuses,
141
+ work_order_item_id=work_order_item_id,
142
+ status=status,
128
143
  authorization=authorization,
129
144
  x_api_key=x_api_key,
130
145
  )
@@ -153,6 +168,9 @@ async def asyncio(
153
168
  exclude_assigned_to_annotators: bool | None = None,
154
169
  workorder: str | None = None,
155
170
  infeasible: bool | None = None,
171
+ testcase_statuses: str | None = None,
172
+ work_order_item_id: str | None = None,
173
+ status: str | None = None,
156
174
  authorization: str | None = None,
157
175
  x_api_key: str | None = None,
158
176
  ) -> Any:
@@ -176,6 +194,9 @@ async def asyncio(
176
194
  exclude_assigned_to_annotators=exclude_assigned_to_annotators,
177
195
  workorder=workorder,
178
196
  infeasible=infeasible,
197
+ testcase_statuses=testcase_statuses,
198
+ work_order_item_id=work_order_item_id,
199
+ status=status,
179
200
  authorization=authorization,
180
201
  x_api_key=x_api_key,
181
202
  )
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: tmpu23ownyz.json
3
- # timestamp: 2026-02-02T20:25:20+00:00
2
+ # filename: tmp914w7h7j.json
3
+ # timestamp: 2026-02-03T22:34:51+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -1239,6 +1239,10 @@ class EnvFromSimulator(BaseModel):
1239
1239
  """
1240
1240
  Custom name for this environment
1241
1241
  """
1242
+ restore_memory: Annotated[bool | None, Field(title="Restore Memory")] = True
1243
+ """
1244
+ If True (default), resume from memory snapshot. If False, do a fresh boot with disk state only.
1245
+ """
1242
1246
 
1243
1247
 
1244
1248
  class EnvInfo(BaseModel):
@@ -2110,7 +2114,7 @@ class NodeSnapshotStoreConfig(BaseModel):
2110
2114
  """
2111
2115
  cache_mb: Annotated[int | None, Field(title="Cache Mb")] = 10240
2112
2116
  """
2113
- Snapshot-store userspace decompressed page cache size in MB (0 to disable). This cache deduplicates pages by content hash across all manifests. For memory mounts (mount-mem): direct_io is disabled by default, so the kernel page cache already handles caching and deduplication for mmap'd files. Multiple VMs restoring from the same snapshot share kernel-cached pages automatically. The userspace cache is less beneficial here but can still help. For disk mounts (mount-tree): direct_io is enabled by default for overlay write coherency, bypassing the kernel cache. The userspace cache is important here for read-heavy workloads (Docker builds, package installs, etc.) that read many base image pages. Without caching, every read goes through FUSE decompression. Recommendation: 512MB for most deployments, increase for heavy disk I/O workloads.
2117
+ Only used for snapshot-store (not blockdiff). Userspace decompressed page cache size in MB (0 to disable). This cache deduplicates pages by content hash across all manifests. For memory mounts (mount-mem): direct_io is disabled by default, so the kernel page cache already handles caching and deduplication for mmap'd files. Multiple VMs restoring from the same snapshot share kernel-cached pages automatically. The userspace cache is less beneficial here but can still help. For disk mounts (mount-tree): direct_io is enabled by default for overlay write coherency, bypassing the kernel cache. The userspace cache is important here for read-heavy workloads (Docker builds, package installs, etc.) that read many base image pages. Without caching, every read goes through FUSE decompression. Recommendation: 512MB for most deployments, increase for heavy disk I/O workloads.
2114
2118
  """
2115
2119
  compression_workers: Annotated[int | None, Field(title="Compression Workers")] = 1
2116
2120
  """
@@ -3376,6 +3380,7 @@ class TestCaseSetCreateRequest(BaseModel):
3376
3380
  class TestCaseStatusEnum(Enum):
3377
3381
  uploaded = "uploaded"
3378
3382
  qa_in_progress = "qa_in_progress"
3383
+ requires_recordings = "requires_recordings"
3379
3384
  self_rejected = "self_rejected"
3380
3385
  self_approved = "self_approved"
3381
3386
  submitted = "submitted"
@@ -4947,6 +4952,9 @@ class SimReview(BaseModel):
4947
4952
  review_type: Annotated[ReviewType, Field(title="Review Type")]
4948
4953
  outcome: Annotated[Outcome, Field(title="Outcome")]
4949
4954
  artifact_id: Annotated[str, Field(title="Artifact Id")]
4955
+ video_s3_path: Annotated[str | None, Field(title="Video S3 Path")] = None
4956
+ events_s3_path: Annotated[str | None, Field(title="Events S3 Path")] = None
4957
+ comments: Annotated[str | None, Field(title="Comments")] = None
4950
4958
  reviewer_user_id: Annotated[int | None, Field(title="Reviewer User Id")] = None
4951
4959
  sim_comments: Annotated[list[SimReviewComment] | None, Field(title="Sim Comments")] = None
4952
4960
 
plato/agents/base.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import importlib.metadata
5
6
  import json
6
7
  import logging
7
8
  from abc import ABC, abstractmethod
@@ -9,6 +10,7 @@ from pathlib import Path
9
10
  from typing import Any, ClassVar, Generic, TypeVar, get_args, get_origin
10
11
 
11
12
  from plato.agents.config import AgentConfig
13
+ from plato.agents.schema import get_agent_schema
12
14
 
13
15
  logger = logging.getLogger(__name__)
14
16
 
@@ -91,8 +93,6 @@ class BaseAgent(ABC, Generic[ConfigT]):
91
93
  @classmethod
92
94
  def get_version(cls) -> str:
93
95
  """Get version from package metadata."""
94
- import importlib.metadata
95
-
96
96
  for pkg_name in [cls.__module__.split(".")[0], f"plato-agent-{cls.name}"]:
97
97
  try:
98
98
  return importlib.metadata.version(pkg_name)
@@ -103,13 +103,7 @@ class BaseAgent(ABC, Generic[ConfigT]):
103
103
  @classmethod
104
104
  def get_schema(cls) -> dict:
105
105
  """Get full schema for the agent including config and build schemas."""
106
- from plato.agents.build import BuildConfig
107
-
108
- config_class = cls.get_config_class()
109
- return {
110
- "config": config_class.get_json_schema(),
111
- "build": BuildConfig.get_json_schema(),
112
- }
106
+ return get_agent_schema(cls)
113
107
 
114
108
  @abstractmethod
115
109
  async def run(self, instruction: str) -> None:
plato/agents/config.py CHANGED
@@ -17,25 +17,15 @@ Example:
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import base64
20
21
  import json
22
+ import os
21
23
  from typing import Any
22
24
 
23
25
  from pydantic_settings import BaseSettings, SettingsConfigDict
24
26
 
25
-
26
- class Secret:
27
- """Annotation marker for secret fields.
28
-
29
- Fields annotated with Secret are automatically loaded from environment variables.
30
- The env var name is the uppercase version of the field name (e.g., api_key -> API_KEY).
31
-
32
- Usage:
33
- api_key: Annotated[str, Secret(description="API key")]
34
- """
35
-
36
- def __init__(self, description: str = "", required: bool = False):
37
- self.description = description
38
- self.required = required
27
+ from plato.agents.markers import Secret
28
+ from plato.agents.schema import get_agent_config_schema, get_field_secrets
39
29
 
40
30
 
41
31
  class AgentConfig(BaseSettings):
@@ -68,56 +58,12 @@ class AgentConfig(BaseSettings):
68
58
  @classmethod
69
59
  def get_field_secrets(cls) -> dict[str, Secret]:
70
60
  """Get Secret annotations for each field."""
71
- result: dict[str, Secret] = {}
72
-
73
- for field_name, field_info in cls.model_fields.items():
74
- for meta in field_info.metadata:
75
- if isinstance(meta, Secret):
76
- result[field_name] = meta
77
- break
78
-
79
- return result
61
+ return get_field_secrets(cls)
80
62
 
81
63
  @classmethod
82
64
  def get_json_schema(cls) -> dict:
83
65
  """Get JSON schema with secrets separated."""
84
- full_schema = cls.model_json_schema()
85
- full_schema.pop("title", None)
86
-
87
- secrets_map = cls.get_field_secrets()
88
- properties = full_schema.get("properties", {})
89
-
90
- config_properties = {}
91
- secrets = []
92
-
93
- # Skip internal fields
94
- internal_fields = {"logs_dir"}
95
-
96
- for field_name, prop_schema in properties.items():
97
- if field_name in internal_fields:
98
- continue
99
-
100
- if field_name in secrets_map:
101
- secret = secrets_map[field_name]
102
- secrets.append(
103
- {
104
- "name": field_name,
105
- "description": secret.description,
106
- "required": secret.required,
107
- }
108
- )
109
- else:
110
- config_properties[field_name] = prop_schema
111
-
112
- required = [r for r in full_schema.get("required", []) if r not in internal_fields and r not in secrets_map]
113
-
114
- return {
115
- "$schema": "https://json-schema.org/draft/2020-12/schema",
116
- "type": "object",
117
- "properties": config_properties,
118
- "required": required,
119
- "secrets": secrets,
120
- }
66
+ return get_agent_config_schema(cls)
121
67
 
122
68
  def get_secrets_dict(self) -> dict[str, str]:
123
69
  """Extract secret values as a dict for environment variables."""
@@ -137,7 +83,7 @@ class AgentConfig(BaseSettings):
137
83
  internal_fields = {"logs_dir"}
138
84
 
139
85
  result: dict[str, Any] = {}
140
- for field_name in self.model_fields:
86
+ for field_name in self.__class__.model_fields:
141
87
  if field_name not in secrets_map and field_name not in internal_fields:
142
88
  value = getattr(self, field_name, None)
143
89
  if value is not None:
@@ -152,9 +98,6 @@ class AgentConfig(BaseSettings):
152
98
  The runner passes config as base64-encoded JSON in the
153
99
  AGENT_CONFIG_B64 environment variable.
154
100
  """
155
- import base64
156
- import os
157
-
158
101
  config_b64 = os.environ.get("AGENT_CONFIG_B64")
159
102
  if not config_b64:
160
103
  raise ValueError("AGENT_CONFIG_B64 environment variable not set")
@@ -0,0 +1,18 @@
1
+ """Annotation markers for Plato agent configurations."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class Secret:
7
+ """Annotation marker for secret fields.
8
+
9
+ Fields annotated with Secret are automatically loaded from environment variables.
10
+ The env var name is the uppercase version of the field name (e.g., api_key -> API_KEY).
11
+
12
+ Usage:
13
+ api_key: Annotated[str, Secret(description="API key")]
14
+ """
15
+
16
+ def __init__(self, description: str = "", required: bool = False):
17
+ self.description = description
18
+ self.required = required
plato/agents/otel.py CHANGED
@@ -26,11 +26,19 @@ Usage:
26
26
  from __future__ import annotations
27
27
 
28
28
  import logging
29
+ import os
29
30
 
31
+ from opentelemetry import context as context_api
30
32
  from opentelemetry import trace
31
- from opentelemetry.trace import Tracer
33
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
34
+ OTLPSpanExporter,
35
+ )
36
+ from opentelemetry.sdk.resources import Resource
37
+ from opentelemetry.sdk.trace import TracerProvider
38
+ from opentelemetry.sdk.trace.export import SimpleSpanProcessor
39
+ from opentelemetry.trace import NonRecordingSpan, SpanContext, TraceFlags, Tracer
32
40
 
33
- _module_logger = logging.getLogger(__name__)
41
+ logger = logging.getLogger(__name__)
34
42
 
35
43
  # Global state
36
44
  _tracer_provider = None
@@ -39,10 +47,7 @@ _log_handler = None
39
47
 
40
48
 
41
49
  class OTelSpanLogHandler(logging.Handler):
42
- """Logging handler that creates OTel spans for log messages.
43
-
44
- Converts Python log records to OTel spans with log attributes.
45
- """
50
+ """Logging handler that creates OTel spans for log messages."""
46
51
 
47
52
  def __init__(self, tracer: Tracer, level: int = logging.INFO):
48
53
  super().__init__(level)
@@ -51,9 +56,6 @@ class OTelSpanLogHandler(logging.Handler):
51
56
  def emit(self, record: logging.LogRecord) -> None:
52
57
  """Emit a log record as an OTel span."""
53
58
  try:
54
- # Debug: print that we're emitting a log span
55
- print(f"[OTel] Emitting log span: {record.name} - {record.getMessage()[:100]}")
56
- # Create a span for the log message
57
59
  with self.tracer.start_as_current_span(f"log.{record.levelname.lower()}") as span:
58
60
  span.set_attribute("log.level", record.levelname)
59
61
  span.set_attribute("log.message", record.getMessage())
@@ -66,12 +68,9 @@ class OTelSpanLogHandler(logging.Handler):
66
68
  if record.lineno:
67
69
  span.set_attribute("log.lineno", record.lineno)
68
70
 
69
- # Mark errors
70
71
  if record.levelno >= logging.ERROR:
71
72
  span.set_attribute("error", True)
72
-
73
73
  except Exception:
74
- # Don't let logging errors crash the application
75
74
  pass
76
75
 
77
76
 
@@ -94,20 +93,10 @@ def init_tracing(
94
93
  global _tracer_provider, _initialized, _log_handler
95
94
 
96
95
  if _initialized:
97
- _module_logger.debug("Tracing already initialized")
96
+ logger.debug("Tracing already initialized")
98
97
  return
99
98
 
100
99
  try:
101
- from opentelemetry import context as context_api
102
- from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
103
- OTLPSpanExporter,
104
- )
105
- from opentelemetry.sdk.resources import Resource
106
- from opentelemetry.sdk.trace import TracerProvider
107
- from opentelemetry.sdk.trace.export import SimpleSpanProcessor
108
- from opentelemetry.trace import NonRecordingSpan, SpanContext, TraceFlags
109
-
110
- # Create resource with session ID
111
100
  resource = Resource.create(
112
101
  {
113
102
  "service.name": service_name,
@@ -115,53 +104,39 @@ def init_tracing(
115
104
  }
116
105
  )
117
106
 
118
- # Create tracer provider
119
107
  _tracer_provider = TracerProvider(resource=resource)
120
108
 
121
- # Add OTLP exporter pointing to Chronos (use SimpleSpanProcessor for immediate export)
122
109
  otlp_exporter = OTLPSpanExporter(endpoint=f"{otlp_endpoint.rstrip('/')}/v1/traces")
123
110
  _tracer_provider.add_span_processor(SimpleSpanProcessor(otlp_exporter))
124
111
 
125
- # Set as global tracer provider
126
112
  trace.set_tracer_provider(_tracer_provider)
127
113
 
128
- # If parent context is provided, set it as the current context
129
- # This allows new spans to automatically link to the parent
130
114
  if parent_trace_id and parent_span_id:
131
115
  parent_context = SpanContext(
132
116
  trace_id=int(parent_trace_id, 16),
133
117
  span_id=int(parent_span_id, 16),
134
118
  is_remote=True,
135
- trace_flags=TraceFlags(0x01), # Sampled
119
+ trace_flags=TraceFlags(0x01),
136
120
  )
137
121
  parent_span = NonRecordingSpan(parent_context)
138
122
  ctx = trace.set_span_in_context(parent_span)
139
123
  context_api.attach(ctx)
140
- print(f"[OTel] Using parent context: trace_id={parent_trace_id}, span_id={parent_span_id}")
124
+ logger.debug(f"Using parent context: trace_id={parent_trace_id}, span_id={parent_span_id}")
141
125
 
142
- # Add OTel logging handler to capture logs from plato SDK
143
126
  tracer = trace.get_tracer(service_name)
144
127
  _log_handler = OTelSpanLogHandler(tracer, level=logging.INFO)
145
128
 
146
- # Add handler to plato loggers (worlds and agents)
147
- # Set level to INFO to ensure logs propagate from child loggers
148
129
  plato_logger = logging.getLogger("plato")
149
130
  plato_logger.setLevel(logging.INFO)
150
131
  plato_logger.addHandler(_log_handler)
151
- print(
152
- f"[OTel] Added log handler to 'plato' logger (level={plato_logger.level}, handlers={len(plato_logger.handlers)})"
153
- )
154
132
 
155
133
  _initialized = True
156
-
157
- print(f"[OTel] Tracing initialized: service={service_name}, session={session_id}, endpoint={otlp_endpoint}")
134
+ logger.debug(f"Tracing initialized: service={service_name}, session={session_id}")
158
135
 
159
136
  except ImportError as e:
160
- print(f"[OTel] OpenTelemetry SDK not installed: {e}")
161
- _module_logger.warning(f"OpenTelemetry SDK not installed: {e}")
137
+ logger.warning(f"OpenTelemetry SDK not installed: {e}")
162
138
  except Exception as e:
163
- print(f"[OTel] Failed to initialize tracing: {e}")
164
- _module_logger.error(f"Failed to initialize tracing: {e}")
139
+ logger.error(f"Failed to initialize tracing: {e}")
165
140
 
166
141
 
167
142
  def shutdown_tracing(timeout_millis: int = 30000) -> None:
@@ -172,7 +147,6 @@ def shutdown_tracing(timeout_millis: int = 30000) -> None:
172
147
  """
173
148
  global _tracer_provider, _initialized, _log_handler
174
149
 
175
- # Remove log handler
176
150
  if _log_handler:
177
151
  try:
178
152
  plato_logger = logging.getLogger("plato")
@@ -183,19 +157,13 @@ def shutdown_tracing(timeout_millis: int = 30000) -> None:
183
157
 
184
158
  if _tracer_provider:
185
159
  try:
186
- # Force flush all pending spans before shutdown
187
- print(f"[OTel] Flushing spans (timeout={timeout_millis}ms)...")
188
160
  flush_success = _tracer_provider.force_flush(timeout_millis=timeout_millis)
189
- if flush_success:
190
- print("[OTel] Span flush completed successfully")
191
- else:
192
- print("[OTel] Span flush timed out or failed")
193
-
161
+ if not flush_success:
162
+ logger.warning("Span flush timed out")
194
163
  _tracer_provider.shutdown()
195
- print("[OTel] Tracing shutdown complete")
164
+ logger.debug("Tracing shutdown complete")
196
165
  except Exception as e:
197
- print(f"[OTel] Error shutting down tracer: {e}")
198
- _module_logger.warning(f"Error shutting down tracer: {e}")
166
+ logger.warning(f"Error shutting down tracer: {e}")
199
167
 
200
168
  _tracer_provider = None
201
169
  _initialized = False
@@ -235,21 +203,15 @@ def instrument(service_name: str = "plato-agent") -> Tracer:
235
203
  Returns:
236
204
  OpenTelemetry Tracer
237
205
  """
238
- import os
239
-
240
206
  otel_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
241
207
  session_id = os.environ.get("SESSION_ID", "local")
242
208
  parent_trace_id = os.environ.get("OTEL_TRACE_ID")
243
209
  parent_span_id = os.environ.get("OTEL_PARENT_SPAN_ID")
244
210
 
245
- print(f"[OTel] instrument() called: service={service_name}, endpoint={otel_endpoint}, session={session_id}")
246
-
247
211
  if not otel_endpoint:
248
- # Return default tracer (no-op if no provider configured)
249
- print("[OTel] No OTEL_EXPORTER_OTLP_ENDPOINT set, returning no-op tracer")
212
+ logger.debug("No OTEL_EXPORTER_OTLP_ENDPOINT set, using no-op tracer")
250
213
  return trace.get_tracer(service_name)
251
214
 
252
- # Initialize tracing with parent context if provided
253
215
  init_tracing(
254
216
  service_name=service_name,
255
217
  session_id=session_id,
plato/agents/schema.py ADDED
@@ -0,0 +1,96 @@
1
+ """JSON schema generation for Plato agent configurations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from plato.agents.markers import Secret
8
+
9
+ if TYPE_CHECKING:
10
+ from plato.agents.config import AgentConfig
11
+
12
+
13
+ def get_field_secrets(config_cls: type[AgentConfig]) -> dict[str, Secret]:
14
+ """Get Secret annotations for each field.
15
+
16
+ Args:
17
+ config_cls: The AgentConfig subclass to inspect
18
+
19
+ Returns:
20
+ Dict mapping field name to Secret annotation
21
+ """
22
+ result: dict[str, Secret] = {}
23
+
24
+ for field_name, field_info in config_cls.model_fields.items():
25
+ for meta in field_info.metadata:
26
+ if isinstance(meta, Secret):
27
+ result[field_name] = meta
28
+ break
29
+
30
+ return result
31
+
32
+
33
+ def get_agent_config_schema(config_cls: type[AgentConfig]) -> dict[str, Any]:
34
+ """Get JSON schema for an agent config with secrets separated.
35
+
36
+ Args:
37
+ config_cls: The AgentConfig subclass to generate schema for
38
+
39
+ Returns:
40
+ JSON schema dict with properties, required, and secrets fields
41
+ """
42
+ full_schema = config_cls.model_json_schema()
43
+ full_schema.pop("title", None)
44
+
45
+ secrets_map = get_field_secrets(config_cls)
46
+ properties = full_schema.get("properties", {})
47
+
48
+ config_properties: dict[str, Any] = {}
49
+ secrets: list[dict[str, Any]] = []
50
+
51
+ # Skip internal fields
52
+ internal_fields = {"logs_dir"}
53
+
54
+ for field_name, prop_schema in properties.items():
55
+ if field_name in internal_fields:
56
+ continue
57
+
58
+ if field_name in secrets_map:
59
+ secret = secrets_map[field_name]
60
+ secrets.append(
61
+ {
62
+ "name": field_name,
63
+ "description": secret.description,
64
+ "required": secret.required,
65
+ }
66
+ )
67
+ else:
68
+ config_properties[field_name] = prop_schema
69
+
70
+ required = [r for r in full_schema.get("required", []) if r not in internal_fields and r not in secrets_map]
71
+
72
+ return {
73
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
74
+ "type": "object",
75
+ "properties": config_properties,
76
+ "required": required,
77
+ "secrets": secrets,
78
+ }
79
+
80
+
81
+ def get_agent_schema(agent_cls: type) -> dict[str, Any]:
82
+ """Get full schema for an agent including config and build schemas.
83
+
84
+ Args:
85
+ agent_cls: The BaseAgent subclass to generate schema for
86
+
87
+ Returns:
88
+ Dict with config and build schema sections
89
+ """
90
+ from plato.agents.build import BuildConfig
91
+
92
+ config_class = agent_cls.get_config_class()
93
+ return {
94
+ "config": get_agent_config_schema(config_class),
95
+ "build": BuildConfig.get_json_schema(),
96
+ }
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
- # filename: tmpi9d4hw2u.json
3
- # timestamp: 2026-01-29T22:45:54+00:00
2
+ # filename: tmpwnvb6ug0.json
3
+ # timestamp: 2026-02-03T22:34:53+00:00
4
4
 
5
5
  from __future__ import annotations
6
6
 
@@ -75,7 +75,15 @@ class AgentVersionInfo(BaseModel):
75
75
  extra="allow",
76
76
  )
77
77
  version: Annotated[str, Field(title="Version")]
78
- created_at: Annotated[AwareDatetime, Field(title="Created At")]
78
+ created_at: Annotated[str | None, Field(title="Created At")] = None
79
+
80
+
81
+ class AgentVersionsResponse(BaseModel):
82
+ model_config = ConfigDict(
83
+ extra="allow",
84
+ )
85
+ name: Annotated[str, Field(title="Name")]
86
+ versions: Annotated[list[AgentVersionInfo], Field(title="Versions")]
79
87
 
80
88
 
81
89
  class AuthStatusResponse(BaseModel):
@@ -525,10 +533,9 @@ class WorldInfo(BaseModel):
525
533
  extra="allow",
526
534
  )
527
535
  name: Annotated[str, Field(title="Name")]
528
- package: Annotated[str, Field(title="Package")]
529
- description: Annotated[str | None, Field(title="Description")] = None
530
- versions: Annotated[list[str] | None, Field(title="Versions")] = []
531
- latest_version: Annotated[str | None, Field(title="Latest Version")] = None
536
+ package_name: Annotated[str, Field(title="Package Name")]
537
+ version: Annotated[str | None, Field(title="Version")] = None
538
+ config_schema: Annotated[dict[str, Any] | None, Field(title="Config Schema")] = None
532
539
 
533
540
 
534
541
  class WorldResponse(BaseModel):
@@ -576,12 +583,15 @@ class ChronosApiRegistryAgentSchemaResponse(BaseModel):
576
583
  template_variables: Annotated[dict[str, Any] | None, Field(title="Template Variables")] = None
577
584
 
578
585
 
579
- class ChronosApiRegistryAgentVersionInfo(BaseModel):
586
+ class ChronosApiRegistryWorldInfo(BaseModel):
580
587
  model_config = ConfigDict(
581
588
  extra="allow",
582
589
  )
583
- version: Annotated[str, Field(title="Version")]
584
- created_at: Annotated[str | None, Field(title="Created At")] = None
590
+ name: Annotated[str, Field(title="Name")]
591
+ package: Annotated[str, Field(title="Package")]
592
+ description: Annotated[str | None, Field(title="Description")] = None
593
+ versions: Annotated[list[str] | None, Field(title="Versions")] = []
594
+ latest_version: Annotated[str | None, Field(title="Latest Version")] = None
585
595
 
586
596
 
587
597
  class ChronosModelsAgentAgentListResponse(BaseModel):
@@ -591,30 +601,20 @@ class ChronosModelsAgentAgentListResponse(BaseModel):
591
601
  agents: Annotated[list[AgentResponse], Field(title="Agents")]
592
602
 
593
603
 
594
- class ChronosModelsAgentAgentVersionsResponse(BaseModel):
595
- model_config = ConfigDict(
596
- extra="allow",
597
- )
598
- name: Annotated[str, Field(title="Name")]
599
- versions: Annotated[list[AgentVersionInfo], Field(title="Versions")]
600
-
601
-
602
- class ChronosModelsSessionWorldInfo(BaseModel):
604
+ class ChronosModelsAgentAgentVersionInfo(BaseModel):
603
605
  model_config = ConfigDict(
604
606
  extra="allow",
605
607
  )
606
- name: Annotated[str, Field(title="Name")]
607
- package_name: Annotated[str, Field(title="Package Name")]
608
- version: Annotated[str | None, Field(title="Version")] = None
609
- config_schema: Annotated[dict[str, Any] | None, Field(title="Config Schema")] = None
608
+ version: Annotated[str, Field(title="Version")]
609
+ created_at: Annotated[AwareDatetime, Field(title="Created At")]
610
610
 
611
611
 
612
- class AgentVersionsResponse(BaseModel):
612
+ class ChronosModelsAgentAgentVersionsResponse(BaseModel):
613
613
  model_config = ConfigDict(
614
614
  extra="allow",
615
615
  )
616
616
  name: Annotated[str, Field(title="Name")]
617
- versions: Annotated[list[ChronosApiRegistryAgentVersionInfo], Field(title="Versions")]
617
+ versions: Annotated[list[ChronosModelsAgentAgentVersionInfo], Field(title="Versions")]
618
618
 
619
619
 
620
620
  class CreatorsListResponse(BaseModel):
@@ -681,7 +681,7 @@ class SessionResponse(BaseModel):
681
681
  started_at: Annotated[AwareDatetime | None, Field(title="Started At")] = None
682
682
  ended_at: Annotated[AwareDatetime | None, Field(title="Ended At")] = None
683
683
  created_at: Annotated[AwareDatetime, Field(title="Created At")]
684
- world: ChronosModelsSessionWorldInfo | None = None
684
+ world: WorldInfo | None = None
685
685
  world_config: Annotated[dict[str, Any] | None, Field(title="World Config")] = {}
686
686
  trajectory: Annotated[dict[str, Any] | None, Field(title="Trajectory")] = None
687
687
  logs_url: Annotated[str | None, Field(title="Logs Url")] = None
@@ -703,7 +703,7 @@ class WorldCatalogResponse(BaseModel):
703
703
  model_config = ConfigDict(
704
704
  extra="allow",
705
705
  )
706
- worlds: Annotated[list[WorldInfo], Field(title="Worlds")]
706
+ worlds: Annotated[list[ChronosApiRegistryWorldInfo], Field(title="Worlds")]
707
707
 
708
708
 
709
709
  class WorldListResponse(BaseModel):