envloop-cli 1.27.0__py3-none-any.whl → 1.28.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.
envloop/_optional.py CHANGED
@@ -16,11 +16,11 @@ from typing import Any, override
16
16
  FEATURES = {'data': 'envloop_data', 'experiment': 'envloop_experiment', 'rsi-loop': 'envloop_rsi_loop'}
17
17
  FEATURE_COMMANDS = {
18
18
  'data': {
19
- 'dataset': 'Discover datasets, revisions and tasks',
19
+ 'dataset': 'Discover Datasets, revisions and tasks',
20
20
  'data': 'Manage versioned Data resources and verified file downloads',
21
21
  'runner': 'Discover available Runner revisions',
22
- 'checkpoint': 'List and download saved training checkpoints',
23
- 'profile': 'Manage platform runtime profiles',
22
+ 'checkpoint': 'List and download saved training Checkpoints',
23
+ 'profile': 'Manage platform runtime Profiles',
24
24
  },
25
25
  'experiment': {
26
26
  'job': 'Submit and manage Agent, Sandbox, Train and Inference Jobs',
envloop/auth_cli.py CHANGED
@@ -8,6 +8,8 @@ from typing import Any, cast
8
8
  from .config import ConfigError, ConfigStore, normalize_url, validate_key
9
9
 
10
10
  DEFAULT_BASE_URL = "https://api.envloop.ai"
11
+ CONFIG_FIELDS = ["base-url", "api-key", "workspace-id", "project-id"]
12
+ FIELD_HELP = "One of " + ", ".join(CONFIG_FIELDS)
11
13
 
12
14
 
13
15
  def add_auth_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
@@ -23,29 +25,29 @@ def add_auth_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParse
23
25
  actions = configure.add_subparsers(dest="config_action")
24
26
  init = actions.add_parser("init", help="Configure interactively with masked API key input")
25
27
  init.add_argument("--no-check", action="store_true", default=argparse.SUPPRESS, help="Save without API verification (offline)")
26
- project = actions.add_parser("project", help="Select and verify a project with its own API key")
27
- project.add_argument("project_id", nargs="?", help="Project ID; omit for an interactive list with names and IDs")
28
- project.add_argument("--stdin", action="store_true", help="Read the selected project's API key from stdin")
28
+ project = actions.add_parser("project", help="Select and verify a Project with its own API key")
29
+ project.add_argument("project_id", metavar="PROJECT_ID", nargs="?", help="Project ID; omit for an interactive list with names and IDs")
30
+ project.add_argument("--stdin", action="store_true", help="Read the selected Project's API key from stdin")
29
31
  setting = actions.add_parser("set", help="Save one field (API key is masked or read from stdin)")
30
- setting.add_argument("field", choices=["base-url", "api-key", "workspace-id", "project-id"])
31
- setting.add_argument("value", nargs="?", help=f"Base URL (default: {DEFAULT_BASE_URL}) or workspace ID; API keys use masked input or --stdin")
32
+ setting.add_argument("field", metavar="FIELD", choices=CONFIG_FIELDS, help=FIELD_HELP)
33
+ setting.add_argument("value", metavar="VALUE", nargs="?", help=f"Base URL (default: {DEFAULT_BASE_URL}) or Workspace ID; API keys use masked input or --stdin")
32
34
  setting.add_argument("--stdin", action="store_true", help="Read a single API key from stdin")
33
35
  setting.add_argument("--no-check", action="store_true", default=argparse.SUPPRESS, help="Save API key without verification (offline)")
34
36
  getting = actions.add_parser("get", help="Read saved configuration; never export an API key")
35
- getting.add_argument("field", choices=["base-url", "api-key", "workspace-id", "project-id"])
37
+ getting.add_argument("field", metavar="FIELD", choices=CONFIG_FIELDS, help=FIELD_HELP)
36
38
  actions.add_parser("path", help="Print configuration and credential file paths")
37
39
  show = actions.add_parser("show", help="Show saved and effective settings without revealing keys")
38
40
  show.add_argument("--json", action="store_true")
39
41
  remove = actions.add_parser("unset", help="Delete a saved field; does not revoke the server key")
40
- remove.add_argument("field", choices=["base-url", "api-key", "workspace-id", "project-id"])
42
+ remove.add_argument("field", metavar="FIELD", choices=CONFIG_FIELDS, help=FIELD_HELP)
41
43
  status = commands.add_parser("status", help="Inspect local credentials; --check verifies read access")
42
44
  status.add_argument("--check", action="store_true", help="Make a read-only request to the effective Base URL")
43
45
  status.add_argument("--json", action="store_true")
44
46
  commands.add_parser("logout", help="Remove the saved key for the stored default Base URL (local only)")
45
- workspace = commands.add_parser("workspace", help="Select and verify the active workspace")
46
- workspace.add_argument("workspace_id", nargs="?", help="Workspace ID; omit for an interactive list")
47
- workspace.add_argument("--project-id", help="Project within the workspace; its own API key is required")
48
- workspace.add_argument("--stdin", action="store_true", help="Read the selected workspace's API key from stdin")
47
+ workspace = commands.add_parser("workspace", help="Select and verify the active Workspace")
48
+ workspace.add_argument("workspace_id", metavar="WORKSPACE_ID", nargs="?", help="Workspace ID; omit for an interactive list")
49
+ workspace.add_argument("--project-id", help="Project within the Workspace; its own API key is required")
50
+ workspace.add_argument("--stdin", action="store_true", help="Read the selected Workspace's API key from stdin")
49
51
 
50
52
 
51
53
  def _key_from_input(stdin: bool = False) -> str:
envloop/lifecycle_cli.py CHANGED
@@ -6,7 +6,7 @@ from typing import Any
6
6
 
7
7
 
8
8
  def wait_options(parser: argparse.ArgumentParser):
9
- parser.add_argument('--wait', action='store_true', help='Wait for the persisted cleanup operation')
9
+ parser.add_argument('--wait', action='store_true', help='Wait for the persisted cleanup Operation')
10
10
  parser.add_argument('--timeout-seconds', type=float, default=600)
11
11
 
12
12
 
@@ -14,10 +14,10 @@ def add_management_commands(settings: "argparse._SubParsersAction[argparse.Argum
14
14
  operations = settings.add_parser('operation', help='Inspect or retry persisted cleanup').add_subparsers(dest='operation_action', required=True)
15
15
  for action in ('get', 'retry', 'wait'):
16
16
  command = operations.add_parser(action)
17
- command.add_argument('identifier')
17
+ command.add_argument('identifier', metavar='OPERATION_ID')
18
18
  if action == 'wait':
19
19
  command.add_argument('--timeout-seconds', type=float, default=600)
20
- cleanup = settings.add_parser('cleanup', help='Clean an explicit set of project resource IDs')
20
+ cleanup = settings.add_parser('cleanup', help='Clean an explicit set of Project resource IDs')
21
21
  cleanup.add_argument('--file', type=Path, required=True, help='JSON array of resource kind and ID objects')
22
22
  cleanup.add_argument('--request-id', required=True)
23
23
  wait_options(cleanup)
@@ -27,7 +27,7 @@ def add_management_commands(settings: "argparse._SubParsersAction[argparse.Argum
27
27
  create.add_argument('--request-id', required=True)
28
28
  for action in ('get', 'cleanup', 'retain'):
29
29
  command = scopes.add_parser(action)
30
- command.add_argument('identifier')
30
+ command.add_argument('identifier', metavar='RESOURCE_SCOPE_ID')
31
31
  if action == 'cleanup':
32
32
  wait_options(command)
33
33
  if action == 'retain':
envloop/resource_cli.py CHANGED
@@ -17,10 +17,10 @@ def listing(commands: "argparse._SubParsersAction[argparse.ArgumentParser]", *,
17
17
 
18
18
 
19
19
  def add_resource_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
20
- dashboard = groups.add_parser('dashboard', help='Inspect the current project overview').add_subparsers(dest='action', required=True)
21
- dashboard.add_parser('get', help='Show project details and recent experiment pages')
20
+ dashboard = groups.add_parser('dashboard', help='Inspect the current Project overview').add_subparsers(dest='action', required=True)
21
+ dashboard.add_parser('get', help='Show Project details and recent experiment pages')
22
22
 
23
- billing = groups.add_parser('billing', help='Inspect project billing and recorded usage').add_subparsers(dest='resource', required=True)
23
+ billing = groups.add_parser('billing', help='Inspect Project billing and recorded usage').add_subparsers(dest='resource', required=True)
24
24
  for resource in ('account', 'usage', 'ledger', 'price'):
25
25
  commands = billing.add_parser(resource, help=f'Inspect billing {resource}').add_subparsers(dest='action', required=True)
26
26
  command = commands.add_parser('get', help='Show the billing account') if resource == 'account' else listing(commands, paged=False)
@@ -31,27 +31,27 @@ def add_resource_commands(groups: "argparse._SubParsersAction[argparse.ArgumentP
31
31
  if resource == 'ledger':
32
32
  command.add_argument('--limit', type=int, default=25)
33
33
 
34
- limits = groups.add_parser('limit', help='Inspect project resource limits').add_subparsers(dest='action', required=True)
34
+ limits = groups.add_parser('limit', help='Inspect Project resource limits').add_subparsers(dest='action', required=True)
35
35
  limits.add_parser('get', help='Read effective execution limits, compose Runner state and the registry credential '
36
36
  'count (read-only)')
37
- members = groups.add_parser('member', help='Manage project members subject to server authorization').add_subparsers(dest='action', required=True)
37
+ members = groups.add_parser('member', help='Manage Project members subject to server authorization').add_subparsers(dest='action', required=True)
38
38
  listing(members, paged=False)
39
- candidates = members.add_parser('candidate', help='Find eligible project members').add_subparsers(dest='candidate_action', required=True)
40
- listing(candidates, paged=False, help='List eligible project members matching a query').add_argument('--query', required=True)
39
+ candidates = members.add_parser('candidate', help='Find eligible Project members').add_subparsers(dest='candidate_action', required=True)
40
+ listing(candidates, paged=False, help='List eligible Project members matching a query').add_argument('--query', required=True)
41
41
  for action in ('add', 'update', 'remove'):
42
- command = members.add_parser(action, help=f'{action.capitalize()} a project membership (human-session authorization required)')
43
- command.add_argument('user_id')
42
+ command = members.add_parser(action, help=f'{action.capitalize()} a Project membership (human-session authorization required)')
43
+ command.add_argument('user_id', metavar='USER_ID')
44
44
  if action != 'remove':
45
45
  command.add_argument('--role', choices=['viewer', 'developer', 'admin'], default='developer')
46
46
  command.add_argument('--expected-version', required=True, type=int, help='Current membership version; use 0 for a new grant')
47
47
 
48
- settings = groups.add_parser('setting', help='Inspect and update the current project settings').add_subparsers(dest='action', required=True)
49
- settings.add_parser('get', help='Show current project settings')
50
- update = settings.add_parser('update', help='Change project fields (human-session authorization required)')
48
+ settings = groups.add_parser('setting', help='Inspect and update the current Project settings').add_subparsers(dest='action', required=True)
49
+ settings.add_parser('get', help='Show current Project settings')
50
+ update = settings.add_parser('update', help='Change Project fields (human-session authorization required)')
51
51
  for field in ('name', 'slug', 'region'):
52
52
  update.add_argument('--' + field)
53
53
  update.add_argument('--expected-version', required=True, type=int)
54
- archive = settings.add_parser('archive', help='Archive the project (human-session authorization required)')
54
+ archive = settings.add_parser('archive', help='Archive the Project (human-session authorization required)')
55
55
  archive.add_argument('--expected-version', required=True, type=int)
56
56
  from .lifecycle_cli import add_management_commands
57
57
  add_management_commands(settings)
@@ -89,40 +89,40 @@ def _revision(value: str) -> str:
89
89
 
90
90
  def add_self_service_commands(settings: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
91
91
  """Project Admin settings that also work with an API key whose ceiling includes them."""
92
- registry = settings.add_parser('registry', help='Manage private registry credentials for this project\'s images'
92
+ registry = settings.add_parser('registry', help='Manage private registry credentials for this Project\'s images'
93
93
  ).add_subparsers(dest='registry_action', required=True)
94
94
  listing(registry, paged=False, help='List registry credentials (metadata only; secrets are write-only)')
95
- add = registry.add_parser('add', help='Register a write-only registry credential (project Admin)')
95
+ add = registry.add_parser('add', help='Register a write-only registry credential (Project Admin)')
96
96
  add.add_argument('--registry', required=True, help='Registry host, for example ghcr.io')
97
97
  add.add_argument('--repository-prefix', required=True, help='Repository prefix the credential covers, for example my-org')
98
98
  add.add_argument('--username', required=True)
99
99
  add.add_argument('--password-stdin', action='store_true',
100
100
  help='Read the token or password from stdin; without it you are prompted (no echo)')
101
101
  add.add_argument('--password', '--token', nargs='?', action=_RejectSecretArgument, help=argparse.SUPPRESS)
102
- remove = registry.add_parser('remove', help='Remove a credential; later image pulls no longer receive it (project Admin)')
103
- remove.add_argument('credential_id')
104
- compose = settings.add_parser('compose', help='Compose Runner (multi-container Task) access for this project'
102
+ remove = registry.add_parser('remove', help='Remove a credential; later image pulls no longer receive it (Project Admin)')
103
+ remove.add_argument('credential_id', metavar='CREDENTIAL_ID')
104
+ compose = settings.add_parser('compose', help='Compose Runner (multi-container Task) access for this Project'
105
105
  ).add_subparsers(dest='compose_action', required=True)
106
106
  compose.add_parser('get', help='Show the effective state, the reason and the platform bounds')
107
- compose.add_parser('enable', help='Request compose Runners; the platform policy may need operator approval (project Admin)')
108
- compose.add_parser('disable', help='Withdraw this project\'s compose Runner request (project Admin)')
109
- concurrency = settings.add_parser('concurrency', help='Concurrent Trial limit of this project'
107
+ compose.add_parser('enable', help='Request compose Runners; the platform policy may need operator approval (Project Admin)')
108
+ compose.add_parser('disable', help='Withdraw this Project\'s compose Runner request (Project Admin)')
109
+ concurrency = settings.add_parser('concurrency', help='Concurrent Trial limit of this Project'
110
110
  ).add_subparsers(dest='concurrency_action', required=True)
111
- command = concurrency.add_parser('set', help='Lower the project limit below the operator ceiling, or none to clear it '
112
- '(project Admin)')
111
+ command = concurrency.add_parser('set', help='Lower the Project limit below the operator ceiling, or none to clear it '
112
+ '(Project Admin)')
113
113
  command.add_argument('value', type=_concurrency, metavar='VALUE', help='1..10000 or none')
114
- model = settings.add_parser('model-access', help='Metered model access for published Runners of this project'
114
+ model = settings.add_parser('model-access', help='Metered model access for published Runners of this Project'
115
115
  ).add_subparsers(dest='model_access_action', required=True)
116
116
  listing(model, paged=False, help='List grants and whether the platform can deliver them now')
117
- grant = model.add_parser('grant', help='Let one published Runner receive the project\'s metered model key on Fly '
118
- 'Trials (project Admin)')
117
+ grant = model.add_parser('grant', help='Let one published Runner receive the Project\'s metered model key on Fly '
118
+ 'Trials (Project Admin)')
119
119
  grant.add_argument('runner', type=_runner_name, metavar='NAMESPACE/NAME', help='Published Runner name')
120
120
  grant.add_argument('--protocol', dest='protocols', action='append', choices=['anthropic', 'openai'], required=True,
121
121
  help='Model protocol to grant; repeat for both')
122
122
  grant.add_argument('--revision', type=_revision, help='Limit the grant to one full commit Revision')
123
123
  revoke = model.add_parser('revoke', help='Revoke a grant; Trials that have not started no longer get the key '
124
- '(project Admin)')
125
- revoke.add_argument('grant_id')
124
+ '(Project Admin)')
125
+ revoke.add_argument('grant_id', metavar='GRANT_ID')
126
126
 
127
127
 
128
128
  def _read_secret(args: argparse.Namespace) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: envloop-cli
3
- Version: 1.27.0
3
+ Version: 1.28.0
4
4
  Summary: EnvLoop CLI and Python SDK for jobs, training, checkpoints and inference
5
5
  Project-URL: Homepage, https://envloop.ai
6
6
  Project-URL: Documentation, https://docs.envloop.ai
@@ -226,8 +226,8 @@ Choose a published Runner readable in your project. Save this request as `gpu-ru
226
226
  {
227
227
  "request_id": "gpu-run-001",
228
228
  "trials": [
229
- {"name": "first", "spec": {"runner": "envloop/bash@FULL_COMMIT", "resources": {"gpu_type": "H100", "gpus": 4}, "timeout_sec": 600}},
230
- {"name": "second", "spec": {"runner": "envloop/bash@FULL_COMMIT", "resources": {"gpu_type": "H100", "gpus": 4}, "timeout_sec": 600}}
229
+ {"name": "first", "spec": {"runner": "envloop/bash@FULL_COMMIT", "resources": {"gpu_type": "H100", "gpus": 4}, "timeout_seconds": 600}},
230
+ {"name": "second", "spec": {"runner": "envloop/bash@FULL_COMMIT", "resources": {"gpu_type": "H100", "gpus": 4}, "timeout_seconds": 600}}
231
231
  ]
232
232
  }
233
233
  ```
@@ -272,7 +272,7 @@ with Client.remote() as client:
272
272
  ```
273
273
 
274
274
  Use the exact model returned by the Job. The SDK forwards the payload to the
275
- Job's `/Inference/decision` API; the approved plugin validates its business fields.
275
+ Job's `/inference/decision` API; the approved plugin validates its business fields.
276
276
  Availability requires matching platform support and approved content in your project.
277
277
 
278
278
  ## ANS data and verified downloads
@@ -1,6 +1,6 @@
1
1
  envloop/__init__.py,sha256=tX06Gh2EmufRr0J_3YKtjDeNrJ30auR3y3BJ3N3FHJY,421
2
- envloop/_optional.py,sha256=t6zg1JY1LR2QDDHl27h7dPVdxCZH_wgqASeavSh7oi0,8963
3
- envloop/auth_cli.py,sha256=tWsXa7HsuSOrlE6lz1WxVD-v4HNjWTpiyJ_QvpMRQes,15283
2
+ envloop/_optional.py,sha256=r9vtIbeDSneOQWZZRDdKVOCzkWBpjLbkFkKbGOolqqg,8963
3
+ envloop/auth_cli.py,sha256=Ve7G9AHNsO-zvWNrUgcKiVCTgXKvHYRHjnnhoeTstmE,15448
4
4
  envloop/auth_login.py,sha256=PBdgIO_GJ6nMVisWdbGBW1NL2gMV73t1Fao4q3aWvLs,6973
5
5
  envloop/auth_workspace.py,sha256=g92Dok4eSp6P-Du5qkwzZG4JDeSrdYTXJbn5WCzCFMo,14927
6
6
  envloop/cli.py,sha256=Vafivql1JBrlIrfJOD-XAw6AevQKIasmvDHmcazIzqo,15010
@@ -10,54 +10,54 @@ envloop/client.py,sha256=TdTUhHwtK0FL6xzIz4moaNdIPlC-TkisPvU4zNPnaTA,3270
10
10
  envloop/config.py,sha256=1Wr2lNdNNZzsQDPNGsk-TsnEUvDqOtocy9TgUpmOZO0,22494
11
11
  envloop/downloads.py,sha256=ktL-hz9HWPGNuQX_N5RmLyt16qbooArljW9z80XYmTo,3253
12
12
  envloop/http.py,sha256=2mu5yIgQDESC0y2LvdE53d7fPfSzT0oXVjlY25qwGkY,16479
13
- envloop/lifecycle_cli.py,sha256=fs6elyFmyxfvjf28kempcSnkgwzWdCZ6gwlxeQ2d5mc,3126
13
+ envloop/lifecycle_cli.py,sha256=eKK0mP1yXBmmuEpzafNPRoSQjyluGIxcSYGPfr07Rr0,3179
14
14
  envloop/navigation_resources.py,sha256=3PeZiYtXHXPMwuCLRdw1mUnZDTDJUYthUhsiJkHtI_g,10378
15
15
  envloop/profile_secrets.py,sha256=5QpHRV_Gy1MxObR62SX4nNblqk7gWqQZNtzy6wZNV0s,6325
16
- envloop/resource_cli.py,sha256=18X_bmkClnWCb46GAjLSepSrhjdIIQEXIB_cQSr8cq4,13307
16
+ envloop/resource_cli.py,sha256=JUMpptd3VvCas0D3cdS5p8_iHVCkiaiIqqGLeFdCnws,13371
17
17
  envloop/resources.py,sha256=V5nDuM8QgE52V9slSj2ywmhZqwgMKFosm4o5naFaEJo,3025
18
18
  envloop/terminal.py,sha256=kuvcOrFUxdgyNO9nEXabZzVJuX9QS0MhWZXhr3mrUWo,2617
19
19
  envloop/utils/__init__.py,sha256=huB3qPQuKNr3JxfH3Ys09YWFtHiDamCHFsBa4epANgQ,25
20
20
  envloop/utils/logging.py,sha256=rXN0qyxhfGEq4vWXxjwfaczPPZf_7atnOkkBTgNNRPc,1017
21
21
  envloop_data/__init__.py,sha256=nKDGmzFNAQv2mq16YijiI3Q-gDL_smNyZD-AQaqoz7Y,68
22
- envloop_data/cli.py,sha256=43l2spNRvm5n7Izh3-qEj8Vkt7j3ayDYEwYq8aBin8U,8783
23
- envloop_data/cli_output.py,sha256=71bQIR2vPGfWp_JdVL27LxJ48AO5wH8BlOezjjOpnkU,4305
22
+ envloop_data/cli.py,sha256=5J_pS5jf65gyaRpE2EV4jDfDUZ1oEsK0iHAmhJlk-oU,9023
23
+ envloop_data/cli_output.py,sha256=oPMBa_CQoMF9a6xnqzxlEUvxNs6g_xIDAKbZC2IhkdY,4271
24
24
  envloop_data/client.py,sha256=mw2RlyGOWD1EK0qzq0BiBAGcetxBk7LO23tpCZq9SoQ,12515
25
25
  envloop_data/data.py,sha256=OxixzVuNwY21GW_KiCWx9qu_HK3QKWyHsF9Jyd8xjNE,27541
26
- envloop_data/data_cli.py,sha256=asLgURq2Ih_3dy8i4fEKcouxMqdmr0TMx80hyMbFYVY,13284
26
+ envloop_data/data_cli.py,sha256=BoJyoCwHyaobxbqtBs8TS0CmsBMexlbEvofMfxjzC9g,13437
27
27
  envloop_data/data_publications.py,sha256=CigperPxG5-V8pDJqHc26SerbElrZEgpAqwifEGPxtA,6865
28
28
  envloop_data/data_upload.py,sha256=lQdxGbxnO2Zlxe9QWe1-rTyxAyNjc9aSD0BqC4mbiKU,14270
29
- envloop_data/http.py,sha256=czj_DaDhTCiVSrgNr121F-MfALkcKfAC7R3iHc42Gzc,6737
30
- envloop_data/navigation_resources.py,sha256=DDtW2Kx6ASdlPNijpJ7ZG_6Q8QuOP6q2950dWf0S3ng,12287
29
+ envloop_data/http.py,sha256=adbMFCUsS1YYanzdCoqM4HIgIOxjgXoHKOkxSwUuvW4,6726
30
+ envloop_data/navigation_resources.py,sha256=TB505mMv1yoG4V-QAohKHXAYCu7njVe3p-luOvpN1DM,12283
31
31
  envloop_data/profile_updates.py,sha256=FB3Huf0AIlqg6CUC0TcrEeWCF5GH1Z6iJN5Wkq8KcYg,3126
32
32
  envloop_data/recipes.py,sha256=JH0bvM0LCH_iN4p4ONfTSF53eSHAVLUQNFLueOx07UA,1301
33
- envloop_data/resource_cli.py,sha256=j7L7DITfE-QLGvpJwmZqK5VsibiO_PYdz0PPRbh8yRA,6974
33
+ envloop_data/resource_cli.py,sha256=0539WCdc4lGUd3ZlgKyCxF4Nk6jIX5ck3338nCDodcc,7129
34
34
  envloop_data/runner_packages.py,sha256=Xi50hZbdoew9DJ8iYQevMo2jv3qgR3iT50wtV04rFfQ,3424
35
35
  envloop_experiment/__init__.py,sha256=nKDGmzFNAQv2mq16YijiI3Q-gDL_smNyZD-AQaqoz7Y,68
36
- envloop_experiment/cli.py,sha256=z06mI_BL7oepfwJ6UasyRqk2mtp1C9vevLurE9yplI8,28492
36
+ envloop_experiment/cli.py,sha256=0RfMAGanAL_vd2J2kwSVieNeF0FCZfwWYYujFZGJrg0,28655
37
37
  envloop_experiment/cli_output.py,sha256=-FojsqOMIIQuOnfhk2AaqMtwUz5skoWvLeNASm8An6Y,2689
38
- envloop_experiment/client.py,sha256=SQ3iq_J5SaXPAPlpWtXQqQkyJw8k86GyWHWM3RJHhLI,25245
39
- envloop_experiment/decision.py,sha256=apsds-9tXmn-xN8IU3IU-Ye9E2P32jpN49DcVgc_JnM,917
40
- envloop_experiment/http.py,sha256=gJSnPRJrlIl7VkG_fmfjEwmS7aDnCSW9ST00UVzsWqo,6101
38
+ envloop_experiment/client.py,sha256=VKKqI9IDazxaN7-coP48DvXcz6SqjZ8ScM4O6plBNWI,25122
39
+ envloop_experiment/decision.py,sha256=HJTj-jSJJ5kcxQQzFXwrA9IICWXA6zSNBFL9EbbrxqI,918
40
+ envloop_experiment/http.py,sha256=A_PHWVr7PV4eiP30lXmOITiljXf6GEIYGkJlr1KFz-Q,6110
41
41
  envloop_experiment/inference.py,sha256=74MZDs0lqqqNwgdvj58rs-D3emDj4iHv1W-copc5PYA,2134
42
- envloop_experiment/job_cost.py,sha256=-h4tYQo3wm6W4ZRRa-_HeGsaBzRoDqRi5mo6_iNrfD4,1910
42
+ envloop_experiment/job_cost.py,sha256=KUaFTn0mg1yF8EvqQxlm131G_4YpsnCe1dM_QT4tHjg,1920
43
43
  envloop_experiment/job_logs.py,sha256=RLipbj89e5hQdBeXtszsxSYYw7By1hscwSTGFQzQ3Qw,1340
44
- envloop_experiment/job_spec_cli.py,sha256=pV-5f3rvFPFEaJsSjJaifaPiKNnTR_o9TDnWxccxUTg,7973
44
+ envloop_experiment/job_spec_cli.py,sha256=0IaQv_nR93WVYYx3Lba-HbqMN-WCLrzseChEKcXdkxA,7981
45
45
  envloop_experiment/navigation_resources.py,sha256=xoI4VFmd0dzviKvzndwKAoWdfbaL_f50cnXRxPkfoo4,1832
46
- envloop_experiment/resource_cli.py,sha256=viL7cfV4mm0r-1xYDP079MGnPR86yHuKErz6IKezQus,2039
47
- envloop_experiment/resources.py,sha256=XTOOmfocEvYsP0zsWmKdZoZuH5NG54klw2CL6PL4TBQ,7229
46
+ envloop_experiment/resource_cli.py,sha256=MCFWHl4iCNe1UiGyHcvMM67LOHQ7MZl3joFtftQwqLk,2083
47
+ envloop_experiment/resources.py,sha256=4guXzssv7XhYkn8ESH-79DIMWUyKDIg70uOa-KoNvp0,7236
48
48
  envloop_experiment/runtime_params.py,sha256=ITF6hDuhfdChnWo3e8NW27WxEoNoCuOTww-Hz60zqQU,3608
49
49
  envloop_experiment/sandbox.py,sha256=sTpAWbcs2jAPfb7gILK4MTJSmcpSeZG3anFkLA5TgdQ,1274
50
- envloop_experiment/train.py,sha256=Fp-06dJutmgTlr9ptrMEvcUx01-S5ucv0xXJdCsq0hg,5120
50
+ envloop_experiment/train.py,sha256=fb6Sqdyo15ynq-vKeUbnyRTfkG5anVu3Ewt152g0fK8,5110
51
51
  envloop_experiment/train_log_cli.py,sha256=quOgnUqjE647YhsO44C_zp778ndarmJ2Wr1ANCL45Ck,6368
52
52
  envloop_experiment/train_progress.py,sha256=Cln8NPWfmSH9Mnlml-78dOXqwZTqWVeJ8ArDWFAQv-A,2536
53
- envloop_experiment/train_runner_cli.py,sha256=Vs3jMDdhEAQc8-srwEPNAQBSz4zpIWtszrXWrn2W7Gg,3150
53
+ envloop_experiment/train_runner_cli.py,sha256=OiHv7Dp2VPoQVmFR0vgJoAd5Ni0uQvtCyNf5Lk4AFfc,3154
54
54
  envloop_experiment/training/__init__.py,sha256=IEQWbCHe-zZ2e7JQxOrFOdYlSQxMfOceCZUf_KeszBk,221
55
55
  envloop_rsi_loop/__init__.py,sha256=nKDGmzFNAQv2mq16YijiI3Q-gDL_smNyZD-AQaqoz7Y,68
56
- envloop_rsi_loop/agent_runs.py,sha256=cSWs01VniBbV7ZZMqTTcR5c3A2hUab4KX0XY3Ck9qqw,11151
57
- envloop_rsi_loop/cli.py,sha256=-Y9rCXRYIG586LHhD3cfuOXk04AMwe_bdD0Ep9nf7RM,3047
58
- envloop_rsi_loop/cli_output.py,sha256=D9gN4J4lXe0pAJ0D_oHCNgMAC5FuKd_pRxF7JcZw3to,3219
56
+ envloop_rsi_loop/agent_runs.py,sha256=GrXSfnQ8MchA9i8UI3LWJiTninpI0PeTm0hgz2TJ0No,10729
57
+ envloop_rsi_loop/cli.py,sha256=Kqearg20Mvfyj62r2mibVdWEMo1NHjthgvS-Tl7SC-A,3047
58
+ envloop_rsi_loop/cli_output.py,sha256=GxM4UiL4NzzSyjRrikai6ROW-1WPjFtwrIfpnE8GZ4U,3173
59
59
  envloop_rsi_loop/rsi_cli.py,sha256=e9XHeIPwMz1dZ6NhK3UIETCdy7LbIWgFosF1FnKVUQk,2580
60
- envloop_cli-1.27.0.dist-info/METADATA,sha256=uPCJwbFShC3IrKKfqhliiAN10Q8373-6gwue2MCIRuU,15648
61
- envloop_cli-1.27.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
62
- envloop_cli-1.27.0.dist-info/entry_points.txt,sha256=8aXOpgtVk4GndhChA4XNecShkb1w0xGHBHrKla78ofI,67
63
- envloop_cli-1.27.0.dist-info/RECORD,,
60
+ envloop_cli-1.28.0.dist-info/METADATA,sha256=ouQWXHdlfU-i6Hgn-D9jnh5nmwkEl7jw5HlDaPt4nVY,15656
61
+ envloop_cli-1.28.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
62
+ envloop_cli-1.28.0.dist-info/entry_points.txt,sha256=8aXOpgtVk4GndhChA4XNecShkb1w0xGHBHrKla78ofI,67
63
+ envloop_cli-1.28.0.dist-info/RECORD,,
envloop_data/cli.py CHANGED
@@ -79,10 +79,10 @@ def secret_option(command: argparse.ArgumentParser) -> None:
79
79
 
80
80
 
81
81
  def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
82
- profiles = groups.add_parser("profile", help="Manage platform runtime profiles").add_subparsers(dest="action", required=True)
82
+ profiles = groups.add_parser("profile", help="Manage platform runtime Profiles").add_subparsers(dest="action", required=True)
83
83
  put = profiles.add_parser("create", help="Create an immutable Profile version from TOML or JSON")
84
- put.add_argument("name")
85
- put.add_argument("version")
84
+ put.add_argument("name", metavar="NAME")
85
+ put.add_argument("version", metavar="VERSION")
86
86
  put.add_argument("--file", type=Path, required=True, help="Profile TOML or JSON file")
87
87
  put.add_argument("--runner", metavar="RUNNER", help="Bind a Runner revision to this version")
88
88
  secret_input = put.add_mutually_exclusive_group()
@@ -90,8 +90,8 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]")
90
90
  secret_input.add_argument("--model-api-key-stdin", action="store_true", help="Read the model API key from one line of standard input")
91
91
  secret_option(put)
92
92
  update = profiles.add_parser("update", help="Edit an existing Profile by publishing a new immutable version")
93
- update.add_argument("name")
94
- update.add_argument("version", help="New version; existing versions cannot be overwritten")
93
+ update.add_argument("name", metavar="NAME")
94
+ update.add_argument("version", metavar="VERSION", help="New version; existing versions cannot be overwritten")
95
95
  update.add_argument("--from-version", help="Source version; omit to use default, then newest unarchived creation")
96
96
  update.add_argument("--file", type=Path, required=True, help="TOML or JSON patch: merge params keys, replace supplied metadata; preserve omitted fields")
97
97
  update.add_argument("--runner", metavar="RUNNER", help="Bind a Runner revision; v1 requires a full commit; omit to preserve the source binding")
@@ -100,13 +100,13 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]")
100
100
  update_secret.add_argument("--model-api-key-stdin", action="store_true", help="Read an encrypted model connection key from one line of standard input")
101
101
  secret_option(update)
102
102
  fork = profiles.add_parser('fork', help='Fork an existing Profile into a new immutable identity')
103
- fork.add_argument('source_name')
104
- fork.add_argument('source_version')
105
- fork.add_argument('name')
106
- fork.add_argument('version')
103
+ fork.add_argument('source_name', metavar='SOURCE_NAME')
104
+ fork.add_argument('source_version', metavar='SOURCE_VERSION')
105
+ fork.add_argument('name', metavar='NAME')
106
+ fork.add_argument('version', metavar='VERSION')
107
107
  secret_option(fork)
108
108
  get = profiles.add_parser("get")
109
- get.add_argument("name")
109
+ get.add_argument("name", metavar="NAME")
110
110
  get.add_argument("--version")
111
111
  listing = profiles.add_parser("list")
112
112
  listing.add_argument("--name")
@@ -115,13 +115,13 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]")
115
115
  listing.add_argument("--cursor")
116
116
  listing.add_argument("--json", action="store_true", help="Output the complete response as JSON (default: table)")
117
117
  default = profiles.add_parser("set-default")
118
- default.add_argument("name")
119
- default.add_argument("version")
118
+ default.add_argument("name", metavar="NAME")
119
+ default.add_argument("version", metavar="VERSION")
120
120
  default.add_argument("--expected-version", help="Omit only when no default exists")
121
121
  for command in ("archive", "export"):
122
122
  item = profiles.add_parser(command)
123
- item.add_argument("name")
124
- item.add_argument("version")
123
+ item.add_argument("name", metavar="NAME")
124
+ item.add_argument("version", metavar="VERSION")
125
125
  from envloop_data.resource_cli import add_resource_commands
126
126
  add_resource_commands(groups)
127
127
 
@@ -38,9 +38,9 @@ def list_layout(items: list[dict[str, Any]], args: argparse.Namespace) -> tuple[
38
38
  render = dataset_row
39
39
  empty = 'No datasets found.'
40
40
  elif args.group == 'checkpoint':
41
- headers = ('CHECKPOINT ID', 'TYPE', 'MODEL', 'TRAIN JOB ID', 'URI')
41
+ headers = ('CHECKPOINT ID', 'TYPE', 'MODEL', 'JOB ID', 'URI')
42
42
  render = lambda item: (item.get('checkpoint_id'), item.get('checkpoint_type'), item.get('base_model'),
43
- item.get('job_id') or item.get('train_job_id'), item.get('uri'))
43
+ item.get('job_id'), item.get('uri'))
44
44
  empty = 'No checkpoints found.'
45
45
  elif args.group == "profile" and args.name is None:
46
46
  headers = ("NAME", "VERSIONS", "DEFAULT", "UPDATED AT")
envloop_data/data_cli.py CHANGED
@@ -18,14 +18,14 @@ from envloop_data.data_publications import (
18
18
 
19
19
 
20
20
  def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]", listing: Callable[[Any], argparse.ArgumentParser]):
21
- data = groups.add_parser('data', help='Manage project ANS resources and verified downloads').add_subparsers(dest='action', required=True)
21
+ data = groups.add_parser('data', help='Manage Project ANS resources and verified downloads').add_subparsers(dest='action', required=True)
22
22
  command = listing(data)
23
23
  command.add_argument('--type', required=True, choices=[*KINDS, 'artifact'])
24
24
  for option in ('ref', 'job-id', 'trial-id'):
25
25
  command.add_argument('--' + option)
26
26
  for action in ('get', 'files'):
27
27
  command = data.add_parser(action, help='Read a resource and its files' if action == 'get' else 'List one page of files')
28
- command.add_argument('data_id')
28
+ command.add_argument('data_id', metavar='DATA_ID')
29
29
  command.add_argument('--limit', type=int, default=25)
30
30
  command.add_argument('--cursor')
31
31
  command.add_argument('--json', action='store_true')
@@ -35,15 +35,15 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]",
35
35
  command = data.add_parser(action, help='Register native content or HF metadata' if action == 'create' else 'Register a new immutable revision')
36
36
  command.add_argument('--type', required=action == 'create', choices=KINDS)
37
37
  if action == 'update':
38
- command.add_argument('data_id')
38
+ command.add_argument('data_id', metavar='DATA_ID')
39
39
  publication_arguments(command, source=True)
40
40
  command.add_argument('--idempotency-key', help='Reuse for a resumable multipart retry of the same input')
41
- command = data.add_parser('delete', help='Retire a project registration; retain immutable content')
42
- command.add_argument('data_id')
41
+ command = data.add_parser('delete', help='Retire a Project registration; retain immutable content')
42
+ command.add_argument('data_id', metavar='DATA_ID')
43
43
  command.add_argument('--dry-run', action='store_true', help='Check authorization and preview without revoking')
44
44
  command.add_argument('--json', action='store_true')
45
45
  command = data.add_parser('download', help='Download and verify an ANS directory or one resource file')
46
- command.add_argument('data_id')
46
+ command.add_argument('data_id', metavar='DATA_ID')
47
47
  command.add_argument('--file', dest='file_id', help='Select one file by its resource file ID')
48
48
  command.add_argument('--split', help='Select the complete Dataset split directory')
49
49
  command.add_argument('--output', required=True, type=Path)
@@ -57,7 +57,7 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]",
57
57
  command.add_argument('--parent-id', help='Existing publication when creating a new revision')
58
58
  for action in ('get', 'part', 'complete', 'abort'):
59
59
  command = uploads.add_parser(action, help=f'{action.capitalize()} a multipart upload')
60
- command.add_argument('upload_id')
60
+ command.add_argument('upload_id', metavar='UPLOAD_ID')
61
61
  command.add_argument('--json', action='store_true')
62
62
  if action == 'part':
63
63
  command.add_argument('--number', type=int, required=True)
@@ -65,7 +65,7 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]",
65
65
 
66
66
 
67
67
  command = uploads.add_parser('harbor', help='Upload a private Harbor task directory or tar bundle; optionally register it for Jobs')
68
- command.add_argument('source', type=Path)
68
+ command.add_argument('source', metavar='SOURCE', type=Path)
69
69
  command.add_argument('--name', required=True, help='Dataset name as NAMESPACE/NAME')
70
70
  command.add_argument('--version', required=True, help='Exact version from dataset.toml')
71
71
  command.add_argument('--register', action='store_true', help='Register the uploaded snapshot with the selected Runner')
@@ -74,12 +74,12 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]",
74
74
  command.add_argument('--idempotency-key', help='Stable upload key; identical input resumes by default')
75
75
  command.add_argument('--json', action='store_true')
76
76
  command = data.add_parser('register', help='Register an uploaded Harbor Dataset snapshot for Jobs')
77
- command.add_argument('data_id')
77
+ command.add_argument('data_id', metavar='DATA_ID')
78
78
  command.add_argument('--runner', required=True, help='Existing Runner NAME@REVISION')
79
79
  command.add_argument('--wait', action='store_true', help='Wait until registration finishes (default: return after it starts)')
80
80
  command.add_argument('--json', action='store_true')
81
81
  command = data.add_parser('registration', help='Read and advance Harbor Dataset registration')
82
- command.add_argument('data_id')
82
+ command.add_argument('data_id', metavar='DATA_ID')
83
83
  command.add_argument('--json', action='store_true')
84
84
 
85
85
 
envloop_data/http.py CHANGED
@@ -58,9 +58,9 @@ class Endpoints:
58
58
  return {"kind": "dataset", **{key: matches[0][key] for key in ("name", "version", "manifest_sha256")}}
59
59
 
60
60
 
61
- def open_checkpoint_archive(self, train_job_id: str) -> Download:
61
+ def open_checkpoint_archive(self, job_id: str) -> Download:
62
62
  """Read a local mock checkpoint through its authenticated public API route."""
63
- path = f"/v1/job/{quote(train_job_id, safe='')}/checkpoint/download"
63
+ path = f"/v1/jobs/{quote(job_id, safe='')}/checkpoint/download"
64
64
  try:
65
65
  return Download(self.opener.open(Request(self.base + path, headers=self.headers), timeout=self.timeout))
66
66
  except HTTPError as error:
@@ -61,10 +61,10 @@ class Datasets:
61
61
  return self.transport.request('GET', query(path, limit=limit, cursor=cursor))
62
62
 
63
63
 
64
- def _checkpoint_matches(path: str, item: dict[str, Any]) -> bool:
65
- if path in {item.get('checkpoint_id'), item.get('uri'), item.get('model_uri'), item.get('ans_uri')}:
64
+ def _checkpoint_matches(checkpoint: str, item: dict[str, Any]) -> bool:
65
+ if checkpoint in {item.get('checkpoint_id'), item.get('uri'), item.get('model_uri')}:
66
66
  return True
67
- parsed = urlsplit(path)
67
+ parsed = urlsplit(checkpoint)
68
68
  if parsed.scheme not in {'envloop', 'tinker'} or parsed.query or parsed.fragment:
69
69
  return False
70
70
  name = parsed.path.lstrip('/')
@@ -128,12 +128,12 @@ class Checkpoints:
128
128
  def list(self, **filters: Any) -> Any:
129
129
  if self.training is not None:
130
130
  return self.training.checkpoints(**filters)
131
- return self.transport.request('GET', '/v1/job/checkpoints?' + urlencode({key: value for key, value in filters.items() if value is not None}))
131
+ return self.transport.request('GET', '/v1/checkpoints?' + urlencode({key: value for key, value in filters.items() if value is not None}))
132
132
 
133
133
  def delete(self, checkpoint_id: str) -> Any:
134
- return self.transport.request('DELETE', f"/v1/job/checkpoints/{quote(checkpoint_id, safe='')}")
134
+ return self.transport.request('DELETE', f"/v1/checkpoints/{quote(checkpoint_id, safe='')}")
135
135
 
136
- def download(self, path: str, directory: str | Path, *, job_id: str | None = None, run_id: str | None = None) -> dict[str, Any]:
136
+ def download(self, checkpoint: str, directory: str | Path, *, job_id: str | None = None, run_id: str | None = None) -> dict[str, Any]:
137
137
  from pathlib import Path
138
138
 
139
139
  directory = Path(directory)
@@ -141,7 +141,7 @@ class Checkpoints:
141
141
  while True:
142
142
  page = self.list(job_id=job_id, run_id=run_id, cursor=cursor, limit=100)
143
143
  for item in page['items']:
144
- if _checkpoint_matches(path, item):
144
+ if _checkpoint_matches(checkpoint, item):
145
145
  matches.append(item)
146
146
  cursor = page.get('next_cursor')
147
147
  if cursor is None:
@@ -172,7 +172,7 @@ class Checkpoints:
172
172
 
173
173
  def refresh(self) -> None:
174
174
  # Keep the original authorized identity and file hashes across refreshes.
175
- signed = transport.request('GET', query('/v1/job/checkpoints/download', path=path,
175
+ signed = transport.request('GET', query('/v1/checkpoints/download', uri=checkpoint,
176
176
  job_id=item['job_id'], run_id=run_id))
177
177
  urls = signed.get('urls', {})
178
178
  if (set(names) != set(urls)
@@ -210,7 +210,7 @@ class Checkpoints:
210
210
  import tempfile
211
211
  import zipfile
212
212
 
213
- job_id = item.get('train_job_id')
213
+ job_id = item.get('job_id')
214
214
  if not isinstance(job_id, str) or not job_id:
215
215
  raise RemoteError('CHECKPOINT_FILES_INVALID')
216
216
  # Bound the streamed archive by its declared payload plus ZIP metadata.
@@ -6,21 +6,21 @@ from envloop.resource_cli import listing
6
6
 
7
7
 
8
8
  def add_resource_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]"):
9
- datasets = groups.add_parser('dataset', help='Discover datasets, revisions and tasks').add_subparsers(dest='action', required=True)
9
+ datasets = groups.add_parser('dataset', help='Discover Datasets, revisions and tasks').add_subparsers(dest='action', required=True)
10
10
  command = listing(datasets)
11
- command.add_argument('--type', choices=['environment', 'trajectory', 'record'], help='Filter by dataset content type')
11
+ command.add_argument('--type', choices=['environment', 'trajectory', 'record'], help='Filter by Dataset content type')
12
12
  command.add_argument('--query', help='Search Dataset names')
13
- datasets.add_parser('get', help='Show a dataset and its pinned Runner reference').add_argument('name')
13
+ datasets.add_parser('get', help='Show a Dataset and its pinned Runner reference').add_argument('name', metavar='NAME')
14
14
  for resource in ('revision', 'task'):
15
- commands = datasets.add_parser(resource, help=f'Inspect dataset {resource}').add_subparsers(dest='operation', required=True)
15
+ commands = datasets.add_parser(resource, help=f'Inspect Dataset {resource}').add_subparsers(dest='operation', required=True)
16
16
  command = listing(commands)
17
- command.add_argument('name')
17
+ command.add_argument('name', metavar='NAME')
18
18
  if resource == 'task':
19
- command.add_argument('revision')
19
+ command.add_argument('revision', metavar='REVISION')
20
20
  else:
21
- command = commands.add_parser('get', help='Show an immutable dataset revision')
22
- command.add_argument('name')
23
- command.add_argument('revision')
21
+ command = commands.add_parser('get', help='Show an immutable Dataset revision')
22
+ command.add_argument('name', metavar='NAME')
23
+ command.add_argument('revision', metavar='REVISION')
24
24
 
25
25
  from envloop_data.data_cli import add_commands
26
26
  add_commands(groups, listing)
@@ -34,8 +34,8 @@ def add_resource_commands(groups: "argparse._SubParsersAction[argparse.ArgumentP
34
34
  'SHA-256, creates the Runner on first use and registers the revision; identical retries are idempotent and '
35
35
  'changed content for an existing revision is refused. This is el data create --type runner with the native '
36
36
  'manifest built for you; use that command to publish an existing manifest. The key needs envloop:data:write '
37
- 'and runners.write, and publish permission on a Runner owned by another project.')
38
- register.add_argument('directory', type=Path, help='Runner package directory; every file in it is published')
37
+ 'and runners.write, and publish permission on a Runner owned by another Project.')
38
+ register.add_argument('directory', metavar='DIRECTORY', type=Path, help='Runner package directory; every file in it is published')
39
39
  register.add_argument('--revision', required=True, help='Full 40- or 64-character source commit, for example $(git rev-parse HEAD)')
40
40
  register.add_argument('--name', help='NAMESPACE/NAME; must equal [runner] name in runner.toml (default: that name)')
41
41
  register.add_argument('--executable', action='append', dest='executables', metavar='PATH',
@@ -44,18 +44,18 @@ def add_resource_commands(groups: "argparse._SubParsersAction[argparse.ArgumentP
44
44
  register.add_argument('--source-ref-kind', choices=['branch', 'tag', 'commit'], default='branch', help='Kind of --source-ref (default: branch)')
45
45
  register.add_argument('--dry-run', action='store_true', help='Validate the package and authorization without publishing')
46
46
 
47
- checkpoints = groups.add_parser('checkpoint', help='List and download saved training checkpoints').add_subparsers(dest='action', required=True)
47
+ checkpoints = groups.add_parser('checkpoint', help='List and download saved training Checkpoints').add_subparsers(dest='action', required=True)
48
48
  command = listing(checkpoints)
49
- command.add_argument('--job-id', help='Only checkpoints of this Train Job')
50
- command.add_argument('--run-id', help='Only checkpoints of this training run label')
49
+ command.add_argument('--job-id', help='Only Checkpoints of this Train Job')
50
+ command.add_argument('--run-id', help='Only Checkpoints of this training run label')
51
51
  download = checkpoints.add_parser('download', help='Download files and verify their sizes and SHA-256 hashes')
52
- download.add_argument('checkpoint', metavar='CHECKPOINT', help='checkpoint ID or uri from `checkpoint list`')
53
- download.add_argument('directory', type=Path)
52
+ download.add_argument('checkpoint', metavar='CHECKPOINT', help='Checkpoint ID or uri from `checkpoint list`')
53
+ download.add_argument('directory', metavar='DIRECTORY', type=Path)
54
54
  download.add_argument('--job-id')
55
55
  download.add_argument('--run-id')
56
56
  from envloop.lifecycle_cli import wait_options
57
- delete = checkpoints.add_parser('delete', help='Remove an unreferenced checkpoint and stored files')
58
- delete.add_argument('identifier')
57
+ delete = checkpoints.add_parser('delete', help='Remove an unreferenced Checkpoint and stored files')
58
+ delete.add_argument('identifier', metavar='CHECKPOINT_ID')
59
59
  wait_options(delete)
60
60
 
61
61
 
envloop_experiment/cli.py CHANGED
@@ -95,8 +95,8 @@ def submission_options(args: argparse.Namespace):
95
95
  options["profile"] = {"seed": args.seed}
96
96
  else:
97
97
  options["seed"] = args.seed
98
- execution = {key: value for key, value in {"timeout_sec": args.timeout_seconds,
99
- "delete_delay_sec": args.delete_delay_seconds}.items() if value is not None}
98
+ execution = {key: value for key, value in {"timeout_seconds": args.timeout_seconds,
99
+ "delete_delay_seconds": args.delete_delay_seconds}.items() if value is not None}
100
100
  if execution:
101
101
  options["execution"] = execution
102
102
  return options
@@ -119,14 +119,14 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]"):
119
119
  help="Train Runner: per-Trial size in Mi/Gi/Ti (for example 64Gi)")
120
120
  job_submit.add_argument("--spec", type=Path, metavar="REQUEST.json", help="Job specification JSON; explicit submission options override file values (except --trials)")
121
121
  job_submit.add_argument("--source", type=Path, help="two-file directory for a single-task Job")
122
- job_submit.add_argument("--task-id", help="select one task within the dataset")
122
+ job_submit.add_argument("--task-id", help="select one task within the Dataset")
123
123
  job_submit.add_argument(
124
124
  "dataset", nargs="?", type=dataset_reference, metavar="NAME[@VERSION]",
125
- help="dataset name; optional author version (legacy positional form)",
125
+ help="Dataset name; optional author version (legacy positional form)",
126
126
  )
127
127
  job_submit.add_argument("--dataset", dest="dataset_option", type=dataset_reference, metavar="NAME[@VERSION]",
128
- help="dataset name; default selection uses the platform's pinned snapshot")
129
- job_submit.add_argument("--config", help="Agent: dataset subset; Train recipe: named preset; omitted preserves spec or server default")
128
+ help="Dataset name; default selection uses the platform's pinned snapshot")
129
+ job_submit.add_argument("--config", help="Agent: Dataset subset; Train recipe: named preset; omitted preserves spec or server default")
130
130
  job_submit.add_argument("--split", help="one complete split name; omitted selects all splits")
131
131
  job_submit.add_argument("--revision", help="Git branch/tag/commit; must match the platform's pinned snapshot")
132
132
  runner_arguments(job_submit)
@@ -141,15 +141,15 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]"):
141
141
  job_submit.add_argument("--timeout-seconds", type=wait_seconds,
142
142
  help="Agent/Sandbox or Train Runner execution timeout (whole seconds); Train recipe --wait timeout (default: 600)")
143
143
  job_submit.add_argument("--max-cost-usd", type=max_cost_usd, metavar="USD",
144
- help="Agent/Sandbox: Job spend cap in USD (model calls plus sandbox time); the Job stops "
144
+ help="Agent/Sandbox: Job spend cap in USD (model calls plus Sandbox time); the Job stops "
145
145
  "with error BUDGET_EXCEEDED once it is reached")
146
146
  job_submit.add_argument("--delete-delay-seconds", type=nonnegative_seconds, metavar="SECONDS",
147
- help="keep each sandbox (E2B or Fly) after completion for SECONDS (default: 0); cancellation/timeout delete immediately")
147
+ help="keep each Sandbox (E2B or Fly) after completion for SECONDS (default: 0); cancellation/timeout delete immediately")
148
148
  job_submit.add_argument("--wait", action="store_true")
149
149
  job_submit.add_argument("--json", action="store_true", help="Output the complete response as JSON (the default for submit; accepted for consistency with list commands)")
150
150
  for action in ("get", "stop", "result", "pause", "resume", "wake", "event"):
151
151
  command = jobs.add_parser(action)
152
- command.add_argument("job_id")
152
+ command.add_argument("job_id", metavar="JOB_ID")
153
153
  if action == 'event':
154
154
  command.add_argument('--json', action='store_true')
155
155
  command.set_defaults(_list_view=True)
@@ -174,7 +174,7 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]"):
174
174
  regrade.add_argument("--timeout-seconds", type=wait_seconds, help="--wait timeout in whole seconds (default: 600)")
175
175
  regrade.add_argument("--json", action="store_true", help="Output the complete response as JSON (the default)")
176
176
  decision = jobs.add_parser("decision", help="Invoke the frozen decision plugin of an Inference Job")
177
- decision.add_argument("job_id")
177
+ decision.add_argument("job_id", metavar="JOB_ID")
178
178
  decision.add_argument("--model", required=True)
179
179
  decision.add_argument("--spec", type=Path, required=True, metavar="PAYLOAD.json")
180
180
  from envloop_experiment.resource_cli import add_job_resources
@@ -187,7 +187,7 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]"):
187
187
  listing.add_argument("--json", action="store_true", help="Output the complete response as JSON (default: table)")
188
188
  listing.add_argument("--limit", type=int, default=25)
189
189
  train_log = jobs.add_parser("log", help="Read or follow Agent, Sandbox or Train Job logs")
190
- train_log.add_argument("job_id")
190
+ train_log.add_argument("job_id", metavar="JOB_ID")
191
191
  train_log.add_argument("--source", choices=("all", "system", "runtime"), default="all")
192
192
  train_log.add_argument("--process", metavar="NAME",
193
193
  help="Only one recorded process, e.g. trainer (a Train Job's step metrics)")
@@ -208,20 +208,20 @@ def add_commands(groups: "argparse._SubParsersAction[argparse.ArgumentParser]"):
208
208
  listing.add_argument("--cursor")
209
209
  listing.add_argument("--limit", type=int, default=25)
210
210
  snapshot = tasks.add_parser("snapshot")
211
- snapshot.add_argument("trial_id")
211
+ snapshot.add_argument("trial_id", metavar="TRIAL_ID")
212
212
  for action in ("get", "stop", "result"):
213
- tasks.add_parser(action).add_argument("trial_id")
213
+ tasks.add_parser(action).add_argument("trial_id", metavar="TRIAL_ID")
214
214
  listing.add_argument("--json", action="store_true", help="Output the complete response as JSON")
215
215
  artifacts = tasks.add_parser("artifact", help="List or download a Trial's artifacts").add_subparsers(dest="artifact_action", required=True)
216
216
  artifact_list = artifacts.add_parser("list", help="List one page of artifacts")
217
217
  artifact_list.set_defaults(_list_view=True)
218
- artifact_list.add_argument("trial_id")
218
+ artifact_list.add_argument("trial_id", metavar="TRIAL_ID")
219
219
  artifact_list.add_argument("--cursor")
220
220
  artifact_list.add_argument("--limit", type=int, default=25)
221
221
  artifact_list.add_argument("--json", action="store_true")
222
222
  download = artifacts.add_parser("download", help="Download all uploaded artifacts and verify their sizes and hashes")
223
- download.add_argument("trial_id")
224
- download.add_argument("directory", type=Path)
223
+ download.add_argument("trial_id", metavar="TRIAL_ID")
224
+ download.add_argument("directory", metavar="DIRECTORY", type=Path)
225
225
 
226
226
  def validate(args: argparse.Namespace, cli: argparse.ArgumentParser):
227
227
  if args.group == "job" and args.action == "decision":
@@ -250,14 +250,14 @@ class Jobs:
250
250
  runner: str | None = None,
251
251
  profile_id: str | None = None, job_id: str | None = None, tag: str | None = None,
252
252
  queue_id: str = "default",
253
- priority: int = 0, command: str | None = None, source_train_job_id: str | None = None,
254
- source_train_trial_id: str | None = None,
253
+ priority: int = 0, command: str | None = None, source_job_id: str | None = None,
254
+ source_trial_id: str | None = None,
255
255
  type: str = 'agent', image: str | None = None, params: Mapping[str, Any] | None = None,
256
256
  max_cost_usd: float | None = None, **options: Any):
257
257
  """Submit an Agent Job or a Sandbox Job using a registered Runner.
258
258
 
259
- A source Trial requires its Train Job ID. The API verifies source type,
260
- ownership and project scope, and resolves any legacy Train Job alias.
259
+ ``source_trial_id`` requires its ``source_job_id``. The API verifies the
260
+ source type, ownership and project scope.
261
261
  ``max_cost_usd`` caps the Job's model plus sandbox spend; the Job stops with
262
262
  ``error=BUDGET_EXCEEDED`` when it is reached and its result reports ``cost``.
263
263
  """
@@ -265,12 +265,12 @@ class Jobs:
265
265
  raise ValueError('Jobs.submit type must be agent or sandbox; use training_jobs.submit for train')
266
266
  if image is not None and type != 'sandbox':
267
267
  raise ValueError('image requires a Sandbox Job')
268
- for field, value in (("source_train_job_id", source_train_job_id),
269
- ("source_train_trial_id", source_train_trial_id)):
268
+ for field, value in (("source_job_id", source_job_id),
269
+ ("source_trial_id", source_trial_id)):
270
270
  if value is not None and (not isinstance(value, str) or not value.strip()):
271
271
  raise ValueError(f"{field} must be a non-empty string")
272
- if source_train_trial_id is not None and source_train_job_id is None:
273
- raise ValueError("source_train_trial_id requires source_train_job_id")
272
+ if source_trial_id is not None and source_job_id is None:
273
+ raise ValueError("source_trial_id requires source_job_id")
274
274
  if request_id is not None and (not isinstance(request_id, str) or not request_id.strip() or len(request_id) > 128):
275
275
  raise ValueError("request_id must be a non-empty string of at most 128 characters")
276
276
  if params is not None:
@@ -286,7 +286,7 @@ class Jobs:
286
286
  )
287
287
 
288
288
  if any(value is not None for value in (dataset, source, task_ref, task_id, namespace,
289
- config, split, revision, source_train_job_id, source_train_trial_id)):
289
+ config, split, revision, source_job_id, source_trial_id)):
290
290
  raise ValueError('Sandbox Jobs accept a Runner without Dataset, Task or training-source options')
291
291
  if any(key in options for key in ('seed', 'profile_name', 'profile_version')):
292
292
  raise ValueError('Sandbox profiles must use profile_id with a registered Runner')
@@ -333,8 +333,8 @@ class Jobs:
333
333
  else:
334
334
  raise ValueError("dataset must be a fixed reference, name or name@version")
335
335
  for key, value in dict(runner=runner, profile_id=profile_id,
336
- job_id=job_id, tag=tag, source_train_job_id=source_train_job_id,
337
- source_train_trial_id=source_train_trial_id).items():
336
+ job_id=job_id, tag=tag, source_job_id=source_job_id,
337
+ source_trial_id=source_trial_id).items():
338
338
  if value is not None:
339
339
  options[key] = value
340
340
  # Omit new defaults on legacy submissions to retain older API compatibility.
@@ -16,5 +16,5 @@ class DecisionClient:
16
16
 
17
17
  def evaluate(self, job_id: str, *, model: str, payload: dict[str, Any]):
18
18
  self.validate(job_id, model, payload)
19
- return self.transport.request('POST', f'/v1/job/{quote(job_id, safe="")}/Inference/decision',
19
+ return self.transport.request('POST', f'/v1/jobs/{quote(job_id, safe="")}/inference/decision',
20
20
  {'model': model, **payload})
@@ -29,35 +29,35 @@ class Endpoints:
29
29
  from envloop_experiment.runtime_params import validate_params
30
30
  body = {**body, 'params': validate_params(body['params'])}
31
31
  configure_transport(self)
32
- return self.request("POST", "/v1/job", body)
32
+ return self.request("POST", "/v1/jobs", body)
33
33
 
34
34
 
35
35
  def get_job(self, job_id: str):
36
- return self.request("GET", f"/v1/job/{quote(job_id, safe='')}")
36
+ return self.request("GET", f"/v1/jobs/{quote(job_id, safe='')}")
37
37
 
38
38
 
39
39
  def get_job_result(self, job_id: str):
40
- return self.request("GET", f"/v1/job/{quote(job_id, safe='')}/result")
40
+ return self.request("GET", f"/v1/jobs/{quote(job_id, safe='')}/result")
41
41
 
42
42
 
43
43
  def get_job_logs(self, job_id: str, **kwargs: Any):
44
- return self.request("GET", f"/v1/job/{quote(job_id, safe='')}/logs?" + urlencode(kwargs))
44
+ return self.request("GET", f"/v1/jobs/{quote(job_id, safe='')}/logs?" + urlencode(kwargs))
45
45
 
46
46
 
47
47
  def list_jobs(self, **kwargs: Any):
48
- return self.request("GET", "/v1/job?" + urlencode({key: value for key, value in kwargs.items() if value is not None}))
48
+ return self.request("GET", "/v1/jobs?" + urlencode({key: value for key, value in kwargs.items() if value is not None}))
49
49
 
50
50
 
51
51
  def job_action(self, job_id: str, action: str):
52
- return self.request('POST', f"/v1/job/{quote(job_id, safe='')}/{action}", {})
52
+ return self.request('POST', f"/v1/jobs/{quote(job_id, safe='')}/{action}", {})
53
53
 
54
54
 
55
55
  def job_events(self, job_id: str):
56
- return self.request('GET', f"/v1/job/{quote(job_id, safe='')}/events")
56
+ return self.request('GET', f"/v1/jobs/{quote(job_id, safe='')}/events")
57
57
 
58
58
 
59
59
  def stop_job(self, job_id: str):
60
- return self.request("POST", f"/v1/job/{quote(job_id, safe='')}/stop")
60
+ return self.request("POST", f"/v1/jobs/{quote(job_id, safe='')}/stop")
61
61
 
62
62
 
63
63
  def list_trials(self, **kwargs: Any):
@@ -129,7 +129,7 @@ def configure_transport(transport: Any):
129
129
 
130
130
  def request_policy(self: Any, method: str, path: str, body: Any, headers: Any):
131
131
  from envloop import http as base_http
132
- if method != 'POST' or path != '/v1/job':
132
+ if method != 'POST' or path != '/v1/jobs':
133
133
  return self._send(method, path, body, headers, self.timeout)
134
134
  try:
135
135
  return self._send(method, path, body, headers, max(self.timeout, getattr(base_http, "SUBMIT_TIMEOUT_SEC", 120)))
@@ -25,8 +25,8 @@ def cost_line(job: Any) -> str | None:
25
25
  return None
26
26
  line = f"Cost (estimate): {_usd(cost.get('total_usd'))} (model {_usd(cost.get('model_usd'))}, " \
27
27
  f"sandbox {_usd(cost.get('sandbox_usd'))})"
28
- if cost.get("cap_usd") is not None:
29
- line += f" of a {_usd(cost.get('cap_usd'))} budget"
28
+ if cost.get("max_cost_usd") is not None:
29
+ line += f" of a {_usd(cost.get('max_cost_usd'))} budget"
30
30
  if cost.get("unmetered") is True:
31
31
  line += "; some usage has no price, so the budget cannot be enforced"
32
32
  elif cost.get("budget_exceeded") is True:
@@ -133,9 +133,9 @@ def spec_request(args: argparse.Namespace):
133
133
  if args.timeout_seconds is not None:
134
134
  if not args.timeout_seconds.is_integer():
135
135
  raise ValueError('--timeout-seconds must be whole seconds')
136
- execution['timeout_sec'] = int(args.timeout_seconds)
136
+ execution['timeout_seconds'] = int(args.timeout_seconds)
137
137
  if args.delete_delay_seconds is not None:
138
- execution['delete_delay_sec'] = args.delete_delay_seconds
138
+ execution['delete_delay_seconds'] = args.delete_delay_seconds
139
139
  if execution:
140
140
  request['execution'] = {**request.get('execution', {}), **execution}
141
141
  if args.max_cost_usd is not None:
@@ -10,15 +10,15 @@ from envloop.resource_cli import listing
10
10
  def add_job_resources(jobs: "argparse._SubParsersAction[argparse.ArgumentParser]"):
11
11
  profiles = jobs.add_parser('serving-profile', help='Discover deployable serving profiles').add_subparsers(dest='resource_action', required=True)
12
12
  listing(profiles, paged=False)
13
- adapters = jobs.add_parser('adapter', help='Manage reusable inference adapters').add_subparsers(dest='resource_action', required=True)
13
+ adapters = jobs.add_parser('adapter', help='Manage reusable inference Adapters').add_subparsers(dest='resource_action', required=True)
14
14
  listing(adapters, paged=False)
15
- adapters.add_parser('get', help='Show an adapter').add_argument('identifier')
16
- register = adapters.add_parser('register', help='Register an adapter using a JSON request')
15
+ adapters.add_parser('get', help='Show an Adapter').add_argument('identifier', metavar='ADAPTER_ID')
16
+ register = adapters.add_parser('register', help='Register an Adapter using a JSON request')
17
17
  register.add_argument('--file', type=Path, required=True, metavar='REQUEST.json',
18
18
  help='{"serving_profile": "NAME", "source": {"type": "train_checkpoint", '
19
19
  '"uri": "envloop://..."}}; request_id is generated when omitted')
20
- delete = adapters.add_parser('delete', help='Delete an unreferenced adapter')
21
- delete.add_argument('identifier')
20
+ delete = adapters.add_parser('delete', help='Delete an unreferenced Adapter')
21
+ delete.add_argument('identifier', metavar='ADAPTER_ID')
22
22
  from envloop.lifecycle_cli import wait_options
23
23
  wait_options(delete)
24
24
 
@@ -46,7 +46,7 @@ class Endpoints:
46
46
 
47
47
  def create(self, *, name: str, serving_profile: str, adapters: Sequence[dict[str, str]], request_id: str | None = None, **options: Any):
48
48
  body = {"request_id": request_id or str(uuid4()), "name": name, "serving_profile": serving_profile,
49
- "adapters": adapters, "scaling": {"min_replicas": 0, "max_replicas": 1, "scale_to_zero_after_s": 300},
49
+ "adapters": adapters, "scaling": {"min_replicas": 0, "max_replicas": 1, "scale_to_zero_after_seconds": 300},
50
50
  **options}
51
51
  if self.temporary_scope_id:
52
52
  body["temporary_scope_id"] = self.temporary_scope_id
@@ -86,7 +86,7 @@ class Endpoints:
86
86
  """Return the API completion; transient cold-start errors remain observable."""
87
87
  if job_id is None:
88
88
  raise ValueError('job_id is required; use the Job ID returned by endpoint creation')
89
- return self.transport.request("POST", f"/v1/job/{_id(job_id)}/Inference/chat/completions", {
89
+ return self.transport.request("POST", f"/v1/jobs/{_id(job_id)}/inference/chat/completions", {
90
90
  "model": model, "messages": [{"role": "user", "content": prompt}], **options,
91
91
  })
92
92
 
@@ -18,11 +18,11 @@ class TrainJobs:
18
18
  def models(self) -> dict[str, Any]:
19
19
  """Discover configured managed recipes; admission and GPU readiness remain separate."""
20
20
  try:
21
- return self.transport.request('GET', '/v1/job/training-models')
21
+ return self.transport.request('GET', '/v1/training-models')
22
22
  except RemoteError as error:
23
23
  if error.status_code == 404:
24
24
  raise RuntimeError(
25
- "This platform does not serve /v1/job/training-models; upgrade the platform to a "
25
+ "This platform does not serve /v1/training-models; upgrade the platform to a "
26
26
  "version that matches this client (managed training discovery is unavailable).") from error
27
27
  raise
28
28
 
@@ -43,13 +43,13 @@ class TrainJobs:
43
43
  if not isinstance(request, dict) or request.get("type", "train") != "train":
44
44
  raise ValueError("Train Job request must be a JSON object")
45
45
  # Like every other submission, an omitted idempotency key is generated so an unanswered submit can be retried.
46
- return self._view(self.transport.request("POST", "/v1/job", {"request_id": str(uuid4())} | request | {"type": "train"}))
46
+ return self._view(self.transport.request("POST", "/v1/jobs", {"request_id": str(uuid4())} | request | {"type": "train"}))
47
47
 
48
48
  def status(self, job_id: str) -> dict[str, Any]:
49
- return self._view(self.transport.request("GET", f"/v1/job/{quote(job_id, safe='')}") )
49
+ return self._view(self.transport.request("GET", f"/v1/jobs/{quote(job_id, safe='')}") )
50
50
 
51
51
  def result(self, job_id: str) -> dict[str, Any]:
52
- return self._view(self.transport.request('GET', f'/v1/job/{quote(job_id, safe="")}/result'))
52
+ return self._view(self.transport.request('GET', f'/v1/jobs/{quote(job_id, safe="")}/result'))
53
53
 
54
54
  def logs(self, job_id: str, *, source: str = "all", cursor: str | None = None, tail: int | None = None, limit: int = 200, process: str | None = None) -> dict[str, Any]:
55
55
  """Read a log page; cursors retain the server's source and capture metadata.
@@ -60,16 +60,16 @@ class TrainJobs:
60
60
  from envloop_experiment.job_logs import log_query
61
61
 
62
62
  query = urlencode(log_query(source=source, cursor=cursor, tail=tail, limit=limit, process=process))
63
- return self.transport.request("GET", f"/v1/job/{quote(job_id, safe='')}/logs?{query}")
63
+ return self.transport.request("GET", f"/v1/jobs/{quote(job_id, safe='')}/logs?{query}")
64
64
 
65
65
  def stop(self, job_id: str) -> dict[str, Any]:
66
- return self._view(self.transport.request('POST', f'/v1/job/{quote(job_id, safe="")}/stop'))
66
+ return self._view(self.transport.request('POST', f'/v1/jobs/{quote(job_id, safe="")}/stop'))
67
67
 
68
68
  def list(self, *, status: str | None = None, cursor: str | None = None, limit: int = 25) -> dict[str, Any]:
69
69
  query = urlencode({key: value for key, value in {
70
70
  "type": "train", "status": status, "cursor": cursor, "limit": limit,
71
71
  }.items() if value is not None})
72
- page = self.transport.request("GET", "/v1/job?" + query)
72
+ page = self.transport.request("GET", "/v1/jobs?" + query)
73
73
  return {**page, "items": [self._view(item) for item in page["items"]]}
74
74
 
75
75
  def checkpoints(self, *, job_id: str | None = None, run_id: str | None = None, cursor: str | None = None, limit: int = 25) -> dict[str, Any]:
@@ -77,10 +77,10 @@ class TrainJobs:
77
77
  "job_id": job_id, "run_id": run_id,
78
78
  "cursor": cursor, "limit": limit,
79
79
  }.items() if value is not None})
80
- return self.transport.request("GET", "/v1/job/checkpoints?" + query)
80
+ return self.transport.request("GET", "/v1/checkpoints?" + query)
81
81
 
82
82
  def delete_checkpoint(self, checkpoint_id: str) -> dict[str, Any]:
83
- return self.transport.request("DELETE", f"/v1/job/checkpoints/{quote(checkpoint_id, safe='')}")
83
+ return self.transport.request("DELETE", f"/v1/checkpoints/{quote(checkpoint_id, safe='')}")
84
84
 
85
85
  def wait(self, job_id: str, *, timeout: float = 600) -> dict[str, Any]:
86
86
  if not math.isfinite(timeout) or timeout < 0:
@@ -61,6 +61,6 @@ def runner_request(args: argparse.Namespace):
61
61
  if args.timeout_seconds is not None:
62
62
  if args.timeout_seconds <= 0 or not args.timeout_seconds.is_integer():
63
63
  raise ValueError('Train Runner --timeout-seconds must be positive whole seconds')
64
- spec['timeout_sec'] = int(args.timeout_seconds)
64
+ spec['timeout_seconds'] = int(args.timeout_seconds)
65
65
  return {'type': 'train', 'request_id': args.request_id if args.request_id is not None else str(uuid4()),
66
66
  'trials': [{'name': f'trial-{index + 1}', 'spec': dict(spec)} for index in range(count)]}
@@ -2,7 +2,7 @@
2
2
 
3
3
  The platform stores no run object. Agents tag every Train Job they create with
4
4
  ``tags`` (the RSI loop: ``rsi_run``, ``rsi_round``, ``rsi_checkpoint``...);
5
- this module pages through ``GET /v1/job`` and derives runs from those tags,
5
+ this module pages through ``GET /v1/jobs`` and derives runs from those tags,
6
6
  using the same rules as the Console's agent runs page.
7
7
  """
8
8
 
@@ -76,10 +76,10 @@ def _round(job: dict[str, Any]) -> int | None:
76
76
 
77
77
  def _order(job: dict[str, Any]) -> tuple[str, str]:
78
78
  # Creation order, as the platform pages jobs (newest first, reversed here).
79
- return (str(job.get("created_at") or ""), str(job.get("job_id") or job.get("train_job_id") or ""))
79
+ return (str(job.get("created_at") or ""), str(job.get("job_id") or ""))
80
80
 
81
81
 
82
- def _duration_sec(job: dict[str, Any]) -> int | None:
82
+ def _duration_seconds(job: dict[str, Any]) -> int | None:
83
83
  if job.get("status") not in TERMINAL_STATUSES:
84
84
  return None
85
85
  try:
@@ -91,19 +91,19 @@ def _duration_sec(job: dict[str, Any]) -> int | None:
91
91
 
92
92
 
93
93
  def _checkpoint(job: dict[str, Any]) -> dict[str, Any] | None:
94
- """The checkpoint a job produced: its agent's version name and platform path."""
94
+ """The checkpoint a job produced: its agent's version name and platform uri."""
95
95
  stored = job.get("checkpoint")
96
- path = stored.get("path") if isinstance(stored, dict) else None
96
+ uri = stored.get("uri") if isinstance(stored, dict) else None
97
97
  name = _field(job, "checkpoint")
98
- if not isinstance(path, str) or not path:
98
+ if not isinstance(uri, str) or not uri:
99
99
  return None
100
- return {"name": name, "path": path}
100
+ return {"name": name, "uri": uri}
101
101
 
102
102
 
103
103
  def _job_view(job: dict[str, Any]) -> dict[str, Any]:
104
104
  metadata = _metadata(job)
105
105
  return {
106
- "train_job_id": job.get("job_id") or job.get("train_job_id"),
106
+ "job_id": job.get("job_id"),
107
107
  "status": job.get("status"),
108
108
  "round": _round(job),
109
109
  "purpose": _field(job, "purpose"),
@@ -111,7 +111,7 @@ def _job_view(job: dict[str, Any]) -> dict[str, Any]:
111
111
  "transitions": list(job.get("transitions") or []),
112
112
  "created_at": job.get("created_at"),
113
113
  "updated_at": job.get("updated_at"),
114
- "duration_sec": _duration_sec(job),
114
+ "duration_seconds": _duration_seconds(job),
115
115
  "base_model": job.get("base_model"),
116
116
  "lora_rank": job.get("lora_rank"),
117
117
  "model_id": job.get("model_id"),
@@ -131,8 +131,7 @@ def _summary(run: str, jobs: list[dict[str, Any]]) -> dict[str, Any]:
131
131
  latest_saved = next((job for job in reversed(jobs) if _checkpoint(job) is not None), None)
132
132
  latest_checkpoint = _checkpoint(latest_saved) if latest_saved is not None else None
133
133
  if latest_checkpoint is not None:
134
- latest_checkpoint["train_job_id"] = (cast(dict[str, Any], latest_saved).get("job_id")
135
- or cast(dict[str, Any], latest_saved).get("train_job_id"))
134
+ latest_checkpoint["job_id"] = cast(dict[str, Any], latest_saved).get("job_id")
136
135
  rounds = [number for number in map(_round, jobs) if number is not None]
137
136
  activity = [stamp for job in jobs if isinstance(stamp := job.get("updated_at") or job.get("created_at"), str)]
138
137
  return {
@@ -155,7 +154,7 @@ def _detail(run: str, jobs: list[dict[str, Any]]) -> dict[str, Any]:
155
154
  rounds.setdefault(view["round"], []).append(view)
156
155
  ordered = sorted(rounds, key=lambda number: (number is None, number or 0))
157
156
  checkpoints = [
158
- {**view["checkpoint"], "round": view["round"], "train_job_id": view["train_job_id"],
157
+ {**view["checkpoint"], "round": view["round"], "job_id": view["job_id"],
159
158
  "created_at": view["created_at"]}
160
159
  for view in views if view["checkpoint"] is not None
161
160
  ]
@@ -166,10 +165,6 @@ def _detail(run: str, jobs: list[dict[str, Any]]) -> dict[str, Any]:
166
165
  }
167
166
 
168
167
 
169
-
170
- # SDK limit names and the RSI API's `limits` keys.
171
- RSI_LIMIT_FIELDS = {"max_model_cost_usd": "model_usd_cap", "max_cost_usd": "usd_cap", "max_model_requests": "model_request_cap"}
172
-
173
168
  class AgentRuns:
174
169
  """Read-only agent runs derived from the visible Train Jobs listing."""
175
170
 
@@ -183,7 +178,7 @@ class AgentRuns:
183
178
  if observed_at is None:
184
179
  observed_at = page.get("observed_at")
185
180
  for job in page.get("items") or []:
186
- job_id = job.get("job_id") or job.get("train_job_id") if isinstance(job, dict) else None
181
+ job_id = job.get("job_id") if isinstance(job, dict) else None
187
182
  if not isinstance(job, dict) or job_id in ids:
188
183
  continue
189
184
  if job_id is not None:
@@ -227,10 +222,10 @@ class AgentRuns:
227
222
  max_model_requests: int | None = None, domain: str | None = None, rounds: int | None = None,
228
223
  dry_run: bool = False, student_model: str | None = None) -> dict[str, Any]:
229
224
  """Start a new RSI run; the platform validates every field. ``dry_run`` uses no GPU."""
230
- limits: dict[str, Any] = {RSI_LIMIT_FIELDS["max_model_cost_usd"]: max_model_cost_usd}
225
+ limits: dict[str, Any] = {"max_model_cost_usd": max_model_cost_usd}
231
226
  for key, value in (("max_cost_usd", max_cost_usd), ("max_model_requests", max_model_requests)):
232
227
  if value is not None:
233
- limits[RSI_LIMIT_FIELDS[key]] = value
228
+ limits[key] = value
234
229
  body: dict[str, Any] = {"run": run, "brief": brief, "limits": limits}
235
230
  for key, value in (("domain", domain), ("rounds", rounds), ("student_model", student_model)):
236
231
  if value is not None:
envloop_rsi_loop/cli.py CHANGED
@@ -5,7 +5,7 @@ def add_commands(jobs: "argparse._SubParsersAction[argparse.ArgumentParser]"):
5
5
  runs = jobs.add_parser("run", help="Inspect runs grouped from their Train Jobs").add_subparsers(dest="run_action", required=True)
6
6
  runs_list = runs.add_parser("list", help="List agent runs, most recently updated first")
7
7
  runs_list.add_argument("--json", action="store_true", help="Output the complete response as JSON (default: table)")
8
- runs_get = runs.add_parser("get", help="Show a run's rounds, Train Jobs and checkpoints")
8
+ runs_get = runs.add_parser("get", help="Show a run's rounds, Train Jobs and Checkpoints")
9
9
  runs_get.add_argument("run", metavar="RUN", help="run tag: tags.rsi_run or agent_run")
10
10
  runs_get.add_argument("--json", action="store_true", help="Output the complete run as JSON (default: tables)")
11
11
  runs_start = runs.add_parser("start", help="Start a new RSI loop run (paid; needs training.run)")
@@ -18,7 +18,7 @@ def trainings(run: dict[str, Any]) -> str:
18
18
  def checkpoint_name(checkpoint: dict[str, Any] | None) -> Any:
19
19
  if not checkpoint:
20
20
  return None
21
- return checkpoint.get("name") or checkpoint.get("path")
21
+ return checkpoint.get("name") or checkpoint.get("uri")
22
22
 
23
23
 
24
24
  def run_row(item: dict[str, Any]) -> tuple[Any, ...]:
@@ -42,8 +42,8 @@ def run_output(run: dict[str, Any]) -> str:
42
42
  """A run's summary, one row per Train Job in round order, then its checkpoints."""
43
43
  latest = run.get("latest_checkpoint")
44
44
  checkpoint = checkpoint_name(latest)
45
- if latest and latest.get("name") and latest.get("path"):
46
- checkpoint = f"{display(latest['name'])} {display(latest['path'])}"
45
+ if latest and latest.get("name") and latest.get("uri"):
46
+ checkpoint = f"{display(latest['name'])} {display(latest['uri'])}"
47
47
  fields = (
48
48
  ("Run", run.get("run")), ("Agent", run.get("agent")), ("Status", run_status(run.get("status"))),
49
49
  ("Latest round", run.get("latest_round")), ("Trainings", trainings(run) + " succeeded"),
@@ -53,16 +53,16 @@ def run_output(run: dict[str, Any]) -> str:
53
53
  summary = "\n".join(f"{label + ':':<19}" + (status_text(display(value)) if label == "Status" else display(value))
54
54
  for label, value in fields)
55
55
  jobs = table(
56
- ("ROUND", "TRAIN JOB ID", "STATUS", "PURPOSE", "METHOD", "RANK", "DURATION", "CHECKPOINT"),
57
- [(round_.get("round"), job.get("job_id") or job.get("train_job_id"), job.get("status"), job.get("purpose"),
58
- job.get("method"), job.get("lora_rank"), duration(job.get("duration_sec")),
56
+ ("ROUND", "JOB ID", "STATUS", "PURPOSE", "METHOD", "RANK", "DURATION", "CHECKPOINT"),
57
+ [(round_.get("round"), job.get("job_id"), job.get("status"), job.get("purpose"),
58
+ job.get("method"), job.get("lora_rank"), duration(job.get("duration_seconds")),
59
59
  checkpoint_name(job.get("checkpoint")))
60
60
  for round_ in run.get("rounds") or [] for job in round_.get("jobs") or []],
61
61
  )
62
62
  checkpoints = run.get("checkpoints") or []
63
63
  produced = table(
64
- ("CHECKPOINT", "ROUND", "TRAIN JOB ID", "PATH"),
65
- [(item.get("name"), item.get("round"), item.get("train_job_id"), item.get("path")) for item in checkpoints],
64
+ ("CHECKPOINT", "ROUND", "JOB ID", "URI"),
65
+ [(item.get("name"), item.get("round"), item.get("job_id"), item.get("uri")) for item in checkpoints],
66
66
  ) if checkpoints else "No checkpoints yet."
67
67
  return "\n\n".join((summary, jobs, produced))
68
68