agent-apprenticeship 0.1.0 → 0.1.2
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.
- package/README.md +6 -15
- package/bin/agent-apprenticeship.js +92 -13
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/agent_apprenticeship_trace/__init__.py +1 -1
- package/src/agent_apprenticeship_trace/apprentice_adapters.py +2 -1
- package/src/agent_apprenticeship_trace/bundle_exporter.py +63 -3
- package/src/agent_apprenticeship_trace/cli.py +392 -70
- package/src/agent_apprenticeship_trace/codex_runner.py +46 -3
- package/src/agent_apprenticeship_trace/config.py +16 -0
- package/src/agent_apprenticeship_trace/openai_structured.py +6 -0
- package/src/agent_apprenticeship_trace/public_run.py +118 -57
- package/src/agent_apprenticeship_trace/task_intake.py +45 -2
|
@@ -2,11 +2,16 @@ from __future__ import annotations
|
|
|
2
2
|
import base64
|
|
3
3
|
import hashlib
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
6
|
+
import select
|
|
5
7
|
import shlex
|
|
6
8
|
import sys
|
|
7
9
|
import subprocess
|
|
8
10
|
import shutil, json
|
|
9
11
|
import zipfile
|
|
12
|
+
import urllib.error
|
|
13
|
+
import urllib.parse
|
|
14
|
+
import urllib.request
|
|
10
15
|
from contextlib import contextmanager
|
|
11
16
|
from datetime import datetime, timezone
|
|
12
17
|
from pathlib import Path
|
|
@@ -32,6 +37,7 @@ from .config import (
|
|
|
32
37
|
configured_model_provider_ready,
|
|
33
38
|
apprentice_agent_readiness_status,
|
|
34
39
|
mentor_model_provider_readiness,
|
|
40
|
+
mentor_mode_display,
|
|
35
41
|
normalize_mentor_mode,
|
|
36
42
|
normalize_ecosystem_auto_share,
|
|
37
43
|
normalize_sensitive_info_masking,
|
|
@@ -61,9 +67,9 @@ from .learning import (
|
|
|
61
67
|
update_pack_status,
|
|
62
68
|
write_before_after_result,
|
|
63
69
|
)
|
|
64
|
-
from .public_run import apprentice_agent_readiness, continue_session, finish_session, run_prompt_task, run_root_for
|
|
65
|
-
from .progress import format_progress_event, format_run_status, read_run_status, watch_progress
|
|
66
|
-
from .recipes import MODEL_PROVIDER_RECIPES,
|
|
70
|
+
from .public_run import RunInterrupted, apprentice_agent_readiness, continue_session, finish_session, run_prompt_task, run_root_for
|
|
71
|
+
from .progress import append_progress_event, format_progress_event, format_run_status, read_run_status, watch_progress
|
|
72
|
+
from .recipes import MODEL_PROVIDER_RECIPES, WORKER_AGENT_RECIPES
|
|
67
73
|
app=typer.Typer(add_completion=False)
|
|
68
74
|
configure_app=typer.Typer(add_completion=False, help='Configure Apprentice Agents and Mentor Model Providers.')
|
|
69
75
|
ecosystem_app=typer.Typer(add_completion=False, help='Explore and contribute Agent Apprenticeship ecosystem bundles.')
|
|
@@ -76,6 +82,11 @@ app.add_typer(learn_app, name='learn')
|
|
|
76
82
|
|
|
77
83
|
SLACK_LINK='https://join.slack.com/t/fsycommunity/shared_invite/zt-37417grrb-jFD6BQIYgC5wEMrW2bHssw'
|
|
78
84
|
|
|
85
|
+
FIRST_RUN_BANNER = """╭────────────────────────────────────────────╮
|
|
86
|
+
│ Agent Apprenticeship │
|
|
87
|
+
│ Real-world work -> reusable agent signals │
|
|
88
|
+
╰────────────────────────────────────────────╯"""
|
|
89
|
+
|
|
79
90
|
MODEL_KEY_CANDIDATES = {
|
|
80
91
|
"openai": ["OPENAI_API_KEY"],
|
|
81
92
|
"anthropic": ["ANTHROPIC_API_KEY"],
|
|
@@ -102,6 +113,11 @@ def _print_public_ecosystem_contribution_help(bundle: str | Path) -> None:
|
|
|
102
113
|
typer.echo('Public ecosystem:')
|
|
103
114
|
typer.echo(_ecosystem_repo_url())
|
|
104
115
|
|
|
116
|
+
|
|
117
|
+
def _print_first_run_banner() -> None:
|
|
118
|
+
typer.echo(FIRST_RUN_BANNER)
|
|
119
|
+
typer.echo("")
|
|
120
|
+
|
|
105
121
|
def _print_task_result(
|
|
106
122
|
status: dict,
|
|
107
123
|
*,
|
|
@@ -202,6 +218,24 @@ def _env_next_step(env_var: str | None) -> str:
|
|
|
202
218
|
return f"Next: export {env}=... or add {env}=... to ~/.agent-apprenticeship/.env.local"
|
|
203
219
|
|
|
204
220
|
|
|
221
|
+
def _stdin_allows_prompt() -> bool:
|
|
222
|
+
if sys.stdin.isatty():
|
|
223
|
+
return True
|
|
224
|
+
try:
|
|
225
|
+
ready, _, _ = select.select([sys.stdin], [], [], 0)
|
|
226
|
+
if not ready:
|
|
227
|
+
return False
|
|
228
|
+
except Exception:
|
|
229
|
+
pass
|
|
230
|
+
try:
|
|
231
|
+
pos = sys.stdin.tell()
|
|
232
|
+
char = sys.stdin.read(1)
|
|
233
|
+
sys.stdin.seek(pos)
|
|
234
|
+
return bool(char)
|
|
235
|
+
except Exception:
|
|
236
|
+
return False
|
|
237
|
+
|
|
238
|
+
|
|
205
239
|
def _detect_apprentice_agents() -> list[dict]:
|
|
206
240
|
detected = []
|
|
207
241
|
for agent_id, commands in AGENT_COMMAND_CANDIDATES.items():
|
|
@@ -289,7 +323,7 @@ def _configure_detected_model_provider(row: dict) -> None:
|
|
|
289
323
|
)
|
|
290
324
|
|
|
291
325
|
|
|
292
|
-
def _first_run_setup(*, interactive: bool) -> None:
|
|
326
|
+
def _first_run_setup(*, interactive: bool, defaults: bool=False) -> None:
|
|
293
327
|
detected_agents = _detect_apprentice_agents()
|
|
294
328
|
typer.echo("")
|
|
295
329
|
if detected_agents:
|
|
@@ -298,7 +332,10 @@ def _first_run_setup(*, interactive: bool) -> None:
|
|
|
298
332
|
typer.echo(f"{idx}. {row['display_name']} - command found ({row['command']})")
|
|
299
333
|
custom_index = len(detected_agents) + 1
|
|
300
334
|
typer.echo(f"{custom_index}. Custom - use a custom command template")
|
|
301
|
-
if
|
|
335
|
+
if defaults:
|
|
336
|
+
_configure_detected_apprentice_agent(detected_agents[0])
|
|
337
|
+
typer.echo(f"Configured Apprentice Agent: {detected_agents[0]['display_name']}")
|
|
338
|
+
elif interactive:
|
|
302
339
|
choice = _choose_detected_index(custom_index, default=1)
|
|
303
340
|
if choice and choice <= len(detected_agents):
|
|
304
341
|
_configure_detected_apprentice_agent(detected_agents[choice - 1])
|
|
@@ -318,7 +355,10 @@ def _first_run_setup(*, interactive: bool) -> None:
|
|
|
318
355
|
typer.echo("Detected Mentor Model Provider keys:")
|
|
319
356
|
for idx, row in enumerate(detected_providers, 1):
|
|
320
357
|
typer.echo(f"{idx}. {row['display_name']} - {row['api_key_env_var']} visible")
|
|
321
|
-
if
|
|
358
|
+
if defaults:
|
|
359
|
+
_configure_detected_model_provider(detected_providers[0])
|
|
360
|
+
typer.echo(f"Configured Mentor Model Provider: {detected_providers[0]['display_name']}")
|
|
361
|
+
elif interactive:
|
|
322
362
|
choice = _choose_detected_index(len(detected_providers), default=1)
|
|
323
363
|
if choice:
|
|
324
364
|
_configure_detected_model_provider(detected_providers[choice - 1])
|
|
@@ -342,7 +382,7 @@ def _progress_callback(quiet: bool=False, verbose: bool=False, json_progress: bo
|
|
|
342
382
|
typer.echo('')
|
|
343
383
|
typer.echo(f'Run: {status.get("run_id")}')
|
|
344
384
|
typer.echo(f'Apprentice Agent: {status.get("apprentice_agent") or status.get("worker_agent")}')
|
|
345
|
-
typer.echo(f'Mentor Mode: {status.get("mentor_mode")}')
|
|
385
|
+
typer.echo(f'Mentor Mode: {mentor_mode_display(status.get("mentor_mode"))}')
|
|
346
386
|
typer.echo(f'Maximum Improvement Loops: {status.get("maximum_improvement_loops")}')
|
|
347
387
|
typer.echo(f'Task workspace: {status.get("task_workspace_path")}')
|
|
348
388
|
typer.echo(f'Artifacts: {status.get("artifacts_path")}')
|
|
@@ -568,6 +608,13 @@ def _ensure_evaluation_ready(settings, interactive: bool=True):
|
|
|
568
608
|
return settings
|
|
569
609
|
if not interactive:
|
|
570
610
|
raise typer.BadParameter('model-assisted/hybrid Mentor Mode requires a ready Mentor Model Provider; use configure model or --mentor-mode expert-led')
|
|
611
|
+
if not _stdin_allows_prompt():
|
|
612
|
+
readiness = mentor_model_provider_readiness(settings)
|
|
613
|
+
reason = readiness.get("reason") or "Mentor Model Provider is not ready."
|
|
614
|
+
raise typer.BadParameter(
|
|
615
|
+
f"model-assisted/hybrid Mentor Mode requires a ready Mentor Model Provider. {reason} "
|
|
616
|
+
"Run `apprentice doctor --live`, `apprentice configure model`, or use `--mentor-mode expert-led`."
|
|
617
|
+
)
|
|
571
618
|
typer.echo('Model-assisted Mentor Mode needs a ready Mentor Model Provider.')
|
|
572
619
|
readiness=mentor_model_provider_readiness(settings)
|
|
573
620
|
if readiness.get("provider"):
|
|
@@ -578,7 +625,14 @@ def _ensure_evaluation_ready(settings, interactive: bool=True):
|
|
|
578
625
|
typer.echo('2. Run this session in expert-led mode')
|
|
579
626
|
typer.echo('3. Cancel')
|
|
580
627
|
typer.echo('')
|
|
581
|
-
|
|
628
|
+
try:
|
|
629
|
+
choice=typer.prompt('Default', default='expert-led').strip().lower()
|
|
630
|
+
except typer.Abort:
|
|
631
|
+
reason = readiness.get("reason") or "Mentor Model Provider is not ready."
|
|
632
|
+
raise typer.BadParameter(
|
|
633
|
+
f"model-assisted/hybrid Mentor Mode requires a ready Mentor Model Provider. {reason} "
|
|
634
|
+
"Run `apprentice doctor --live`, `apprentice configure model`, or use `--mentor-mode expert-led`."
|
|
635
|
+
) from None
|
|
582
636
|
if choice in {'1','setup','set up','configure','configure mentor model provider','set up model provider now'}:
|
|
583
637
|
configured=_configure_model_impl(None, None, None, test_connection=False)
|
|
584
638
|
return configured
|
|
@@ -596,6 +650,14 @@ def _ensure_apprentice_agent_ready(settings, runner: str | None=None, interactiv
|
|
|
596
650
|
reason = status.get("reason")
|
|
597
651
|
if not interactive:
|
|
598
652
|
raise typer.BadParameter(reason or 'Apprentice Agent is not ready.')
|
|
653
|
+
if not _stdin_allows_prompt():
|
|
654
|
+
if status.get("command_found"):
|
|
655
|
+
typer.echo("Apprentice Agent readiness is untested; command was found, continuing without an interactive prompt.")
|
|
656
|
+
return
|
|
657
|
+
raise typer.BadParameter(
|
|
658
|
+
f"Apprentice Agent is not ready. {reason or 'Command was not found.'} "
|
|
659
|
+
"Run `apprentice configure agent` or install/authenticate a selected Apprentice Agent CLI."
|
|
660
|
+
)
|
|
599
661
|
if status.get("status") == "untested":
|
|
600
662
|
typer.echo('Apprentice Agent Status: Untested')
|
|
601
663
|
if reason:
|
|
@@ -607,7 +669,16 @@ def _ensure_apprentice_agent_ready(settings, runner: str | None=None, interactiv
|
|
|
607
669
|
typer.echo('3. Configure Apprentice Agent')
|
|
608
670
|
typer.echo('4. Cancel')
|
|
609
671
|
typer.echo('')
|
|
610
|
-
|
|
672
|
+
try:
|
|
673
|
+
choice=typer.prompt('Default', default='run anyway').strip().lower()
|
|
674
|
+
except typer.Abort:
|
|
675
|
+
if status.get("command_found"):
|
|
676
|
+
typer.echo("Apprentice Agent readiness is untested; command was found, continuing without an interactive prompt.")
|
|
677
|
+
return
|
|
678
|
+
raise typer.BadParameter(
|
|
679
|
+
f"Apprentice Agent is not ready. {reason or 'Command was not found.'} "
|
|
680
|
+
"Run `apprentice configure agent` or install/authenticate a selected Apprentice Agent CLI."
|
|
681
|
+
) from None
|
|
611
682
|
if choice in {'1','run check','check'}:
|
|
612
683
|
if status.get("command_found"):
|
|
613
684
|
update_settings(apprentice_agent_readiness_status="ready", apprentice_agent_readiness_reason="Command availability check passed.")
|
|
@@ -629,7 +700,16 @@ def _ensure_apprentice_agent_ready(settings, runner: str | None=None, interactiv
|
|
|
629
700
|
typer.echo('2. Continue anyway')
|
|
630
701
|
typer.echo('3. Cancel')
|
|
631
702
|
typer.echo('')
|
|
632
|
-
|
|
703
|
+
try:
|
|
704
|
+
choice=typer.prompt('Default', default='cancel').strip().lower()
|
|
705
|
+
except typer.Abort:
|
|
706
|
+
if status.get("command_found"):
|
|
707
|
+
typer.echo("Apprentice Agent is not ready; command was found, continuing without an interactive prompt.")
|
|
708
|
+
return
|
|
709
|
+
raise typer.BadParameter(
|
|
710
|
+
f"Apprentice Agent is not ready. {reason or 'Command was not found.'} "
|
|
711
|
+
"Run `apprentice configure agent` or install/authenticate a selected Apprentice Agent CLI."
|
|
712
|
+
) from None
|
|
633
713
|
if choice in {'1','configure','configure apprentice agent','configure agent'}:
|
|
634
714
|
typer.echo('Run: agent-apprenticeship configure agent')
|
|
635
715
|
raise typer.Exit(1)
|
|
@@ -641,10 +721,14 @@ def _ensure_apprentice_agent_ready(settings, runner: str | None=None, interactiv
|
|
|
641
721
|
def init(
|
|
642
722
|
overwrite: bool=typer.Option(False, help='Replace existing settings.json with defaults.'),
|
|
643
723
|
setup: bool=typer.Option(False, '--setup', help='Detect installed Apprentice Agents and Mentor Model Provider keys.'),
|
|
724
|
+
defaults: bool=typer.Option(False, '--defaults', '--non-interactive', help='Initialize with deterministic defaults and never prompt.'),
|
|
644
725
|
):
|
|
726
|
+
_print_first_run_banner()
|
|
645
727
|
path=init_settings(overwrite=overwrite)
|
|
646
728
|
typer.echo(f'initialized Agent Apprenticeship settings at {path}')
|
|
647
|
-
if
|
|
729
|
+
if defaults:
|
|
730
|
+
_first_run_setup(interactive=False, defaults=True)
|
|
731
|
+
elif setup or sys.stdin.isatty():
|
|
648
732
|
_first_run_setup(interactive=bool(setup or sys.stdin.isatty()))
|
|
649
733
|
|
|
650
734
|
@app.command('settings')
|
|
@@ -688,7 +772,7 @@ def doctor(
|
|
|
688
772
|
typer.echo(f'Apprentice Agent command: {agent.get("command") or "Not configured"}')
|
|
689
773
|
typer.echo(f'Apprentice Agent command found: {_yes_no(bool(agent.get("command_found")))}')
|
|
690
774
|
typer.echo(_status_line('Apprentice Agent readiness', agent.get('status'), agent.get('reason')))
|
|
691
|
-
typer.echo(f'Mentor Mode: {settings.mentor_mode}')
|
|
775
|
+
typer.echo(f'Mentor Mode: {mentor_mode_display(settings.mentor_mode)}')
|
|
692
776
|
typer.echo(f'Mentor Model Provider: {provider.get("provider_display")}')
|
|
693
777
|
typer.echo(f'Mentor model: {provider.get("model") or "Not configured"}')
|
|
694
778
|
typer.echo(f'API key env var: {provider.get("api_key_env_var") or "Not configured"}')
|
|
@@ -917,11 +1001,6 @@ def _print_integration_groups(report: dict) -> None:
|
|
|
917
1001
|
typer.echo(f"- {item}")
|
|
918
1002
|
else:
|
|
919
1003
|
typer.echo("- none")
|
|
920
|
-
if REMOVED_V0_MODEL_PROVIDER_IDS:
|
|
921
|
-
typer.echo("")
|
|
922
|
-
typer.echo("Removed From V0")
|
|
923
|
-
for provider_id in REMOVED_V0_MODEL_PROVIDER_IDS:
|
|
924
|
-
typer.echo(f"- {provider_id}")
|
|
925
1004
|
|
|
926
1005
|
|
|
927
1006
|
@app.command('integrations')
|
|
@@ -990,8 +1069,6 @@ def _print_certification_groups(report: dict) -> None:
|
|
|
990
1069
|
typer.echo(f" - {item}")
|
|
991
1070
|
else:
|
|
992
1071
|
typer.echo(" - none")
|
|
993
|
-
if REMOVED_V0_MODEL_PROVIDER_IDS:
|
|
994
|
-
typer.echo(f"- Removed from v0: {', '.join(REMOVED_V0_MODEL_PROVIDER_IDS)}")
|
|
995
1072
|
|
|
996
1073
|
|
|
997
1074
|
@app.command('certify-integrations')
|
|
@@ -1196,7 +1273,15 @@ def start(
|
|
|
1196
1273
|
hybrid_auto_approve=hybrid_auto_approve,
|
|
1197
1274
|
mentor_interactive_checkpoints=interactive_checkpoints,
|
|
1198
1275
|
):
|
|
1199
|
-
|
|
1276
|
+
try:
|
|
1277
|
+
run_root,bundle=run_prompt_task(instruction, assets=assets, settings=settings, runner=runner, progress_callback=_progress_callback(quiet, verbose, json_progress))
|
|
1278
|
+
except RunInterrupted as exc:
|
|
1279
|
+
if not quiet and not json_progress:
|
|
1280
|
+
typer.echo("")
|
|
1281
|
+
typer.echo("Run interrupted.")
|
|
1282
|
+
typer.echo("Run directory:")
|
|
1283
|
+
typer.echo(str(exc.run_root))
|
|
1284
|
+
raise typer.Exit(130)
|
|
1200
1285
|
status = read_run_status(run_root)
|
|
1201
1286
|
if quiet:
|
|
1202
1287
|
_print_task_result(status, include_contribution_help=False)
|
|
@@ -1254,15 +1339,23 @@ def run_prompt(
|
|
|
1254
1339
|
hybrid_auto_approve=hybrid_auto_approve,
|
|
1255
1340
|
mentor_interactive_checkpoints=interactive_checkpoints,
|
|
1256
1341
|
):
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1342
|
+
try:
|
|
1343
|
+
run_root,bundle=run_prompt_task(
|
|
1344
|
+
effective_instruction,
|
|
1345
|
+
assets=asset,
|
|
1346
|
+
run_id=run_id,
|
|
1347
|
+
settings=settings,
|
|
1348
|
+
runner=runner,
|
|
1349
|
+
progress_callback=_progress_callback(quiet, verbose, json_progress),
|
|
1350
|
+
experience_pack_refs=experience_refs,
|
|
1351
|
+
)
|
|
1352
|
+
except RunInterrupted as exc:
|
|
1353
|
+
if not quiet and not json_progress:
|
|
1354
|
+
typer.echo("")
|
|
1355
|
+
typer.echo("Run interrupted.")
|
|
1356
|
+
typer.echo("Run directory:")
|
|
1357
|
+
typer.echo(str(exc.run_root))
|
|
1358
|
+
raise typer.Exit(130)
|
|
1266
1359
|
status = read_run_status(run_root)
|
|
1267
1360
|
if quiet:
|
|
1268
1361
|
_print_task_result(status, include_contribution_help=False)
|
|
@@ -1315,7 +1408,28 @@ def continue_run(
|
|
|
1315
1408
|
hybrid_auto_approve=hybrid_auto_approve,
|
|
1316
1409
|
mentor_interactive_checkpoints=interactive_checkpoints,
|
|
1317
1410
|
):
|
|
1318
|
-
|
|
1411
|
+
try:
|
|
1412
|
+
run_root,bundle=continue_session(run_id, followup_instruction, assets=asset, run_loop=run_loop, settings=settings, runner=runner, progress_callback=_progress_callback(quiet, verbose, json_progress))
|
|
1413
|
+
except KeyboardInterrupt:
|
|
1414
|
+
previous_status = read_run_status(run_root)
|
|
1415
|
+
append_progress_event(
|
|
1416
|
+
run_root,
|
|
1417
|
+
"followup_interrupted",
|
|
1418
|
+
run_id=run_root.name,
|
|
1419
|
+
message="Follow-up interrupted by user.",
|
|
1420
|
+
phase="interrupted",
|
|
1421
|
+
run_status="partial",
|
|
1422
|
+
task_status="partial",
|
|
1423
|
+
contribution_bundle_path=previous_status.get("contribution_bundle_path"),
|
|
1424
|
+
operational_error="Follow-up interrupted by user.",
|
|
1425
|
+
)
|
|
1426
|
+
if not quiet and not json_progress:
|
|
1427
|
+
typer.echo("")
|
|
1428
|
+
typer.echo("Follow-up interrupted.")
|
|
1429
|
+
if previous_status.get("contribution_bundle_path"):
|
|
1430
|
+
typer.echo("Previous Contribution Bundle:")
|
|
1431
|
+
typer.echo(str(previous_status.get("contribution_bundle_path")))
|
|
1432
|
+
raise typer.Exit(130)
|
|
1319
1433
|
status = read_run_status(run_root)
|
|
1320
1434
|
if quiet:
|
|
1321
1435
|
_print_task_result(status, followup=True, record_only=not run_loop, include_contribution_help=False)
|
|
@@ -1540,6 +1654,150 @@ def _index_from_gh(repo: str) -> list[dict]:
|
|
|
1540
1654
|
)
|
|
1541
1655
|
|
|
1542
1656
|
|
|
1657
|
+
def _read_public_url_text(url: str, *, timeout: int = 20) -> str:
|
|
1658
|
+
req = urllib.request.Request(
|
|
1659
|
+
url,
|
|
1660
|
+
headers={
|
|
1661
|
+
"Accept": "application/vnd.github+json",
|
|
1662
|
+
"User-Agent": "agent-apprenticeship-cli",
|
|
1663
|
+
},
|
|
1664
|
+
)
|
|
1665
|
+
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
1666
|
+
return response.read().decode("utf-8")
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
def _github_raw_url(repo: str, path: str, ref: str = "main") -> str:
|
|
1670
|
+
quoted = urllib.parse.quote(path.strip("/"), safe="/")
|
|
1671
|
+
return f"https://raw.githubusercontent.com/{repo}/{ref}/{quoted}"
|
|
1672
|
+
|
|
1673
|
+
|
|
1674
|
+
def _github_contents_url(repo: str, path: str, ref: str = "main") -> str:
|
|
1675
|
+
quoted = urllib.parse.quote(path.strip("/"), safe="/")
|
|
1676
|
+
suffix = f"/{quoted}" if quoted else ""
|
|
1677
|
+
return f"https://api.github.com/repos/{repo}/contents{suffix}?ref={urllib.parse.quote(ref)}"
|
|
1678
|
+
|
|
1679
|
+
|
|
1680
|
+
def _public_index_cache_path(repo: str) -> Path:
|
|
1681
|
+
slug = re.sub(r"[^A-Za-z0-9_.-]+", "_", repo)
|
|
1682
|
+
return get_settings().app_home / "ecosystem" / "public_index_cache" / f"{slug}.json"
|
|
1683
|
+
|
|
1684
|
+
|
|
1685
|
+
def _load_public_index_cache(repo: str) -> list[dict] | None:
|
|
1686
|
+
path = _public_index_cache_path(repo)
|
|
1687
|
+
if not path.exists():
|
|
1688
|
+
return None
|
|
1689
|
+
try:
|
|
1690
|
+
data = read_json(path)
|
|
1691
|
+
except Exception:
|
|
1692
|
+
return None
|
|
1693
|
+
rows = data.get("rows") if isinstance(data, dict) else None
|
|
1694
|
+
if not isinstance(rows, list):
|
|
1695
|
+
return None
|
|
1696
|
+
return [row for row in rows if isinstance(row, dict)]
|
|
1697
|
+
|
|
1698
|
+
|
|
1699
|
+
def _write_public_index_cache(repo: str, rows: list[dict]) -> None:
|
|
1700
|
+
path = _public_index_cache_path(repo)
|
|
1701
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1702
|
+
write_json(
|
|
1703
|
+
path,
|
|
1704
|
+
{
|
|
1705
|
+
"kind": "public_ecosystem_index_cache",
|
|
1706
|
+
"repo": repo,
|
|
1707
|
+
"repo_url": _ecosystem_repo_url(repo),
|
|
1708
|
+
"cached_at": datetime.now(timezone.utc).isoformat(),
|
|
1709
|
+
"rows": rows,
|
|
1710
|
+
},
|
|
1711
|
+
)
|
|
1712
|
+
|
|
1713
|
+
|
|
1714
|
+
def _rows_from_index_text(text: str, *, suffix: str) -> list[dict]:
|
|
1715
|
+
if suffix == ".jsonl":
|
|
1716
|
+
return [json.loads(line) for line in text.splitlines() if line.strip()]
|
|
1717
|
+
data = json.loads(text or "{}")
|
|
1718
|
+
if isinstance(data, list):
|
|
1719
|
+
return [row for row in data if isinstance(row, dict)]
|
|
1720
|
+
if isinstance(data, dict):
|
|
1721
|
+
rows = (
|
|
1722
|
+
data.get("bundles")
|
|
1723
|
+
or data.get("items")
|
|
1724
|
+
or data.get("entries")
|
|
1725
|
+
or data.get("contributions")
|
|
1726
|
+
or []
|
|
1727
|
+
)
|
|
1728
|
+
return [row for row in rows if isinstance(row, dict)]
|
|
1729
|
+
return []
|
|
1730
|
+
|
|
1731
|
+
|
|
1732
|
+
def _index_from_public_repo_url(repo: str) -> list[dict]:
|
|
1733
|
+
rows: list[dict] = []
|
|
1734
|
+
errors: list[str] = []
|
|
1735
|
+
for path in [
|
|
1736
|
+
"seed_dataset/ecosystem_registry.jsonl",
|
|
1737
|
+
"seed_dataset/ecosystem_registry.json",
|
|
1738
|
+
"ecosystem/contributions/index.json",
|
|
1739
|
+
"ecosystem/contributions/index.jsonl",
|
|
1740
|
+
]:
|
|
1741
|
+
try:
|
|
1742
|
+
text = _read_public_url_text(_github_raw_url(repo, path), timeout=20)
|
|
1743
|
+
except (urllib.error.URLError, TimeoutError, OSError) as exc:
|
|
1744
|
+
errors.append(f"{path}: {exc}")
|
|
1745
|
+
continue
|
|
1746
|
+
for row in _rows_from_index_text(text, suffix=Path(path).suffix):
|
|
1747
|
+
out = dict(row)
|
|
1748
|
+
out.setdefault("public_repo_slug", repo)
|
|
1749
|
+
out.setdefault("public_repo_url", _ecosystem_repo_url(repo))
|
|
1750
|
+
if path.startswith("seed_dataset/"):
|
|
1751
|
+
out.setdefault("kind", "seed_task")
|
|
1752
|
+
out.setdefault("experience_source_type", "seed_task")
|
|
1753
|
+
else:
|
|
1754
|
+
out.setdefault("kind", "contribution_bundle")
|
|
1755
|
+
out.setdefault("experience_source_type", "contribution_bundle")
|
|
1756
|
+
rows.append(out)
|
|
1757
|
+
if not rows:
|
|
1758
|
+
detail = "; ".join(errors[:3])
|
|
1759
|
+
raise FileNotFoundError(
|
|
1760
|
+
f"Could not read public ecosystem index from https://github.com/{repo}. {detail}".strip()
|
|
1761
|
+
)
|
|
1762
|
+
deduped: list[dict] = []
|
|
1763
|
+
seen: set[str] = set()
|
|
1764
|
+
for row in rows:
|
|
1765
|
+
key = str(row.get("bundle_id") or row.get("seed_task_id") or row.get("task_id") or "")
|
|
1766
|
+
if key and key in seen:
|
|
1767
|
+
continue
|
|
1768
|
+
if key:
|
|
1769
|
+
seen.add(key)
|
|
1770
|
+
deduped.append(row)
|
|
1771
|
+
_write_public_index_cache(repo, deduped)
|
|
1772
|
+
return deduped
|
|
1773
|
+
|
|
1774
|
+
|
|
1775
|
+
def _download_public_repo_path(repo: str, rel_path: str, dest: Path) -> None:
|
|
1776
|
+
data = json.loads(_read_public_url_text(_github_contents_url(repo, rel_path), timeout=20))
|
|
1777
|
+
if isinstance(data, list):
|
|
1778
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
1779
|
+
for item in data:
|
|
1780
|
+
item_type = item.get("type")
|
|
1781
|
+
item_path = item.get("path")
|
|
1782
|
+
item_name = item.get("name")
|
|
1783
|
+
if not item_path or not item_name:
|
|
1784
|
+
continue
|
|
1785
|
+
if item_type == "dir":
|
|
1786
|
+
_download_public_repo_path(repo, item_path, dest / item_name)
|
|
1787
|
+
elif item_type == "file":
|
|
1788
|
+
download_url = item.get("download_url") or _github_raw_url(repo, item_path)
|
|
1789
|
+
target = dest / item_name
|
|
1790
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
1791
|
+
target.write_text(_read_public_url_text(download_url, timeout=20))
|
|
1792
|
+
return
|
|
1793
|
+
if isinstance(data, dict) and data.get("type") == "file":
|
|
1794
|
+
download_url = data.get("download_url") or _github_raw_url(repo, data.get("path") or rel_path)
|
|
1795
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
1796
|
+
dest.write_text(_read_public_url_text(download_url, timeout=20))
|
|
1797
|
+
return
|
|
1798
|
+
raise FileNotFoundError(f"Could not download public ecosystem path: {rel_path}")
|
|
1799
|
+
|
|
1800
|
+
|
|
1543
1801
|
def _load_json_rows(path: Path) -> list[dict]:
|
|
1544
1802
|
if not path.exists():
|
|
1545
1803
|
return []
|
|
@@ -1648,15 +1906,21 @@ def _load_registry(registry: Path | None=None) -> list[dict]:
|
|
|
1648
1906
|
return _index_from_public_repo_path(repo_path)
|
|
1649
1907
|
repo = _configured_ecosystem_repo()
|
|
1650
1908
|
if repo:
|
|
1909
|
+
cached = _load_public_index_cache(repo)
|
|
1910
|
+
if cached:
|
|
1911
|
+
return cached
|
|
1651
1912
|
try:
|
|
1652
|
-
return
|
|
1913
|
+
return _index_from_public_repo_url(repo)
|
|
1653
1914
|
except FileNotFoundError as exc:
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1915
|
+
try:
|
|
1916
|
+
return _index_from_gh(repo)
|
|
1917
|
+
except FileNotFoundError as gh_exc:
|
|
1918
|
+
seed_rows = search_learning_sources(None)
|
|
1919
|
+
if repo == DEFAULT_PUBLIC_ECOSYSTEM_REPO and seed_rows:
|
|
1920
|
+
return seed_rows
|
|
1921
|
+
raise FileNotFoundError(
|
|
1922
|
+
f"Public ecosystem repo is configured as {repo}, but the index could not be read. {exc} {gh_exc}"
|
|
1923
|
+
)
|
|
1660
1924
|
path=_default_registry_path()
|
|
1661
1925
|
if not path.exists():
|
|
1662
1926
|
seed_rows = search_learning_sources(None)
|
|
@@ -1769,7 +2033,7 @@ def _format_registry_row(row: dict) -> str:
|
|
|
1769
2033
|
str(row.get("task_status") or row.get("run_status") or "status_unknown"),
|
|
1770
2034
|
]
|
|
1771
2035
|
if row.get("mentor_mode"):
|
|
1772
|
-
details.append(f"mentor_mode={row.get('mentor_mode')}")
|
|
2036
|
+
details.append(f"mentor_mode={mentor_mode_display(row.get('mentor_mode'))}")
|
|
1773
2037
|
traces = row.get("traced_steps")
|
|
1774
2038
|
if traces is not None:
|
|
1775
2039
|
details.append(f"traces: {_format_count(traces)}")
|
|
@@ -1884,7 +2148,7 @@ def _submission_summary(metadata: dict, bundle: Path, package_zip: Path | None =
|
|
|
1884
2148
|
f"Task Status: {metadata.get('task_status')}",
|
|
1885
2149
|
f"Run Status: {metadata.get('run_status')}",
|
|
1886
2150
|
f"Apprentice Agent: {metadata.get('apprentice_agent') or 'unknown'}",
|
|
1887
|
-
f"Mentor Mode: {metadata.get('mentor_mode')}",
|
|
2151
|
+
f"Mentor Mode: {mentor_mode_display(metadata.get('mentor_mode'))}",
|
|
1888
2152
|
f"Mentor Model Provider: {metadata.get('mentor_model_provider') or 'none'}",
|
|
1889
2153
|
f"Attempts: {metadata.get('attempts')}",
|
|
1890
2154
|
f"Traced Steps: {metadata.get('traced_steps')}",
|
|
@@ -2045,7 +2309,7 @@ def _print_auto_share_summary(metadata: dict) -> None:
|
|
|
2045
2309
|
typer.echo(f"title: {metadata.get('title')}")
|
|
2046
2310
|
typer.echo(f"task_status: {metadata.get('task_status')}")
|
|
2047
2311
|
typer.echo(f"Apprentice Agent: {metadata.get('apprentice_agent') or 'unknown'}")
|
|
2048
|
-
typer.echo(f"Mentor Mode: {metadata.get('mentor_mode')}")
|
|
2312
|
+
typer.echo(f"Mentor Mode: {mentor_mode_display(metadata.get('mentor_mode'))}")
|
|
2049
2313
|
typer.echo(f"Mentor Model Provider: {metadata.get('mentor_model_provider') or 'none'}")
|
|
2050
2314
|
typer.echo(f"artifact_count: {metadata.get('artifact_count')}")
|
|
2051
2315
|
|
|
@@ -2605,6 +2869,7 @@ def ecosystem_pull(
|
|
|
2605
2869
|
row = {**row, "experience_source_type": row.get("experience_source_type") or "seed_task", "pulled_item_type": "seed_task"}
|
|
2606
2870
|
try:
|
|
2607
2871
|
write_json(dest / "ecosystem_item.json", row)
|
|
2872
|
+
public_repo = row.get("public_repo_slug")
|
|
2608
2873
|
|
|
2609
2874
|
def resolve_seed_path(raw: str | None) -> Path | None:
|
|
2610
2875
|
if not raw:
|
|
@@ -2613,15 +2878,17 @@ def ecosystem_pull(
|
|
|
2613
2878
|
return path if path.is_absolute() else Path.cwd() / path
|
|
2614
2879
|
|
|
2615
2880
|
def copy_seed_path(raw: str | None, target_rel: str) -> None:
|
|
2616
|
-
|
|
2617
|
-
if not source_path or not source_path.exists():
|
|
2881
|
+
if not raw:
|
|
2618
2882
|
return
|
|
2883
|
+
source_path = resolve_seed_path(raw)
|
|
2619
2884
|
target = dest / target_rel
|
|
2620
2885
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
2621
|
-
if source_path.is_dir():
|
|
2886
|
+
if source_path and source_path.exists() and source_path.is_dir():
|
|
2622
2887
|
shutil.copytree(source_path, target, dirs_exist_ok=True)
|
|
2623
|
-
|
|
2888
|
+
elif source_path and source_path.exists():
|
|
2624
2889
|
shutil.copy2(source_path, target)
|
|
2890
|
+
elif public_repo:
|
|
2891
|
+
_download_public_repo_path(str(public_repo), str(raw), target)
|
|
2625
2892
|
|
|
2626
2893
|
copy_seed_path(row.get("task_path") or row.get("task_packet_path"), "task")
|
|
2627
2894
|
copy_seed_path(row.get("rubric_path"), "rubric/rubric.json")
|
|
@@ -2632,8 +2899,8 @@ def ecosystem_pull(
|
|
|
2632
2899
|
copy_seed_path(row.get("learning_signals_path"), "learning_signals")
|
|
2633
2900
|
for idx, trace_path in enumerate(row.get("trace_paths") or [], 1):
|
|
2634
2901
|
trace_source = resolve_seed_path(str(trace_path))
|
|
2635
|
-
|
|
2636
|
-
|
|
2902
|
+
target_name = Path(trace_path).parent.name or f"trace_{idx}"
|
|
2903
|
+
if (trace_source and trace_source.exists()) or public_repo:
|
|
2637
2904
|
copy_seed_path(str(trace_path), f"traces/{target_name}/{Path(trace_path).name}")
|
|
2638
2905
|
except OSError as exc:
|
|
2639
2906
|
typer.echo('Could not pull ecosystem seed task.')
|
|
@@ -2668,7 +2935,13 @@ def _summarize_bundle_manifest(manifest: dict) -> str:
|
|
|
2668
2935
|
'agent_apprentice_role','expected_economic_value',
|
|
2669
2936
|
'expected_economic_value_for_agent_apprentice','mentor_mode','task_status','run_status'
|
|
2670
2937
|
]
|
|
2671
|
-
|
|
2938
|
+
lines = []
|
|
2939
|
+
for key in keys:
|
|
2940
|
+
if key not in manifest:
|
|
2941
|
+
continue
|
|
2942
|
+
value = mentor_mode_display(manifest.get(key)) if key == "mentor_mode" else manifest.get(key)
|
|
2943
|
+
lines.append(f"{key}: {value}")
|
|
2944
|
+
return "\n".join(lines)
|
|
2672
2945
|
|
|
2673
2946
|
|
|
2674
2947
|
def _summarize_seed_registry_row(row: dict) -> str:
|
|
@@ -2718,7 +2991,8 @@ def _summarize_contribution_registry_row(row: dict) -> str:
|
|
|
2718
2991
|
"expected_economic_value_for_agent_apprentice",
|
|
2719
2992
|
]:
|
|
2720
2993
|
if row.get(key) is not None:
|
|
2721
|
-
|
|
2994
|
+
value = mentor_mode_display(row.get(key)) if key == "mentor_mode" else row.get(key)
|
|
2995
|
+
lines.append(f"{key}: {value}")
|
|
2722
2996
|
if row.get("domains"):
|
|
2723
2997
|
lines.append(f"domains: {row.get('domains')}")
|
|
2724
2998
|
if row.get("subdomains"):
|
|
@@ -2775,6 +3049,60 @@ def bundle_inspect(path: Path=typer.Argument(..., help='Contribution Bundle fold
|
|
|
2775
3049
|
typer.echo(f"Next: apprentice ecosystem contribute {path}")
|
|
2776
3050
|
|
|
2777
3051
|
|
|
3052
|
+
@bundle_app.command('check')
|
|
3053
|
+
def bundle_check(path: Path=typer.Argument(..., help='Contribution Bundle folder.')):
|
|
3054
|
+
issues: list[str] = []
|
|
3055
|
+
bundle = Path(path).expanduser()
|
|
3056
|
+
manifest_path = bundle / "contribution_manifest.json"
|
|
3057
|
+
if not bundle.exists():
|
|
3058
|
+
issues.append(f"Bundle path does not exist: {bundle}")
|
|
3059
|
+
elif not manifest_path.exists():
|
|
3060
|
+
issues.append(f"Contribution Bundle manifest not found: {manifest_path}")
|
|
3061
|
+
manifest_data = {}
|
|
3062
|
+
if manifest_path.exists():
|
|
3063
|
+
try:
|
|
3064
|
+
manifest_data = read_json(manifest_path)
|
|
3065
|
+
except Exception as exc:
|
|
3066
|
+
issues.append(f"contribution_manifest.json could not be parsed: {exc}")
|
|
3067
|
+
artifact_index_path = bundle / "outputs" / "artifacts_index.json"
|
|
3068
|
+
artifacts: list[dict] = []
|
|
3069
|
+
if artifact_index_path.exists():
|
|
3070
|
+
try:
|
|
3071
|
+
raw_artifacts = read_json(artifact_index_path)
|
|
3072
|
+
if isinstance(raw_artifacts, list):
|
|
3073
|
+
artifacts = raw_artifacts
|
|
3074
|
+
else:
|
|
3075
|
+
issues.append("outputs/artifacts_index.json must contain a JSON list.")
|
|
3076
|
+
except Exception as exc:
|
|
3077
|
+
issues.append(f"outputs/artifacts_index.json could not be parsed: {exc}")
|
|
3078
|
+
for row in artifacts:
|
|
3079
|
+
ref = row.get("package_relative_path") or row.get("artifact_ref")
|
|
3080
|
+
if ref and not (bundle / str(ref)).exists():
|
|
3081
|
+
issues.append(f"Referenced artifact is missing: {ref}")
|
|
3082
|
+
for optional in ["session_events.jsonl", "progress_events.jsonl"]:
|
|
3083
|
+
file_path = bundle / optional
|
|
3084
|
+
if file_path.exists():
|
|
3085
|
+
try:
|
|
3086
|
+
read_jsonl(file_path)
|
|
3087
|
+
except Exception as exc:
|
|
3088
|
+
issues.append(f"{optional} could not be parsed: {exc}")
|
|
3089
|
+
secret_hits = _bundle_secret_hits(bundle) if bundle.exists() else []
|
|
3090
|
+
if secret_hits:
|
|
3091
|
+
issues.append("Obvious secret-like values were found: " + ", ".join(secret_hits[:10]))
|
|
3092
|
+
if issues:
|
|
3093
|
+
typer.echo("Bundle check: needs attention")
|
|
3094
|
+
typer.echo(f"Bundle: {bundle}")
|
|
3095
|
+
for issue in issues:
|
|
3096
|
+
typer.echo(f"Reason: {redact_secrets(issue)}")
|
|
3097
|
+
typer.echo("Next action: fix the packaging issue, then rerun `apprentice bundle check`.")
|
|
3098
|
+
raise typer.Exit(1)
|
|
3099
|
+
status = manifest_data.get("task_status") or manifest_data.get("run_status") or "unknown"
|
|
3100
|
+
typer.echo("Bundle check: passed")
|
|
3101
|
+
typer.echo(f"Bundle: {bundle}")
|
|
3102
|
+
typer.echo(f"Task status: {status}")
|
|
3103
|
+
typer.echo("This bundle is ready to add to the public ecosystem.")
|
|
3104
|
+
|
|
3105
|
+
|
|
2778
3106
|
@bundle_app.command('contribute')
|
|
2779
3107
|
def bundle_contribute(path: Path=typer.Argument(..., help='Contribution Bundle folder.')):
|
|
2780
3108
|
manifest=path/'contribution_manifest.json'
|
|
@@ -2782,11 +3110,10 @@ def bundle_contribute(path: Path=typer.Argument(..., help='Contribution Bundle f
|
|
|
2782
3110
|
typer.echo(f'Contribution Bundle manifest not found: {manifest}')
|
|
2783
3111
|
raise typer.Exit(1)
|
|
2784
3112
|
try:
|
|
2785
|
-
|
|
3113
|
+
submission_dir, package_zip, metadata = _create_ecosystem_submission(path)
|
|
2786
3114
|
except Exception as exc:
|
|
2787
3115
|
typer.echo(f"Could not prepare public ecosystem contribution: {redact_secrets(str(exc))}")
|
|
2788
3116
|
raise typer.Exit(1)
|
|
2789
|
-
metadata = result["metadata"]
|
|
2790
3117
|
typer.echo('Contribution Bundle ready.')
|
|
2791
3118
|
typer.echo('')
|
|
2792
3119
|
typer.echo(f"Bundle ID: {metadata.get('bundle_id')}")
|
|
@@ -2794,14 +3121,9 @@ def bundle_contribute(path: Path=typer.Argument(..., help='Contribution Bundle f
|
|
|
2794
3121
|
typer.echo('Bundle:')
|
|
2795
3122
|
typer.echo(str(path))
|
|
2796
3123
|
typer.echo('')
|
|
2797
|
-
if result["contribution_created"]:
|
|
2798
|
-
typer.echo(f"Public contribution URL: {result['contribution_url']}")
|
|
2799
|
-
typer.echo("No bundle files were uploaded automatically. Attach or submit the generated package according to the repo instructions.")
|
|
2800
|
-
return
|
|
2801
3124
|
typer.echo('No upload was performed by this command.')
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
typer.echo(f"Reason: {reason}")
|
|
3125
|
+
typer.echo(f"Submission package: {package_zip}")
|
|
3126
|
+
typer.echo(f"Submission metadata: {submission_dir / 'ecosystem_submission.json'}")
|
|
2805
3127
|
typer.echo('')
|
|
2806
3128
|
typer.echo('Contribute to public ecosystem:')
|
|
2807
3129
|
typer.echo(f'apprentice ecosystem contribute {path}')
|
|
@@ -2812,27 +3134,27 @@ def bundle_contribute(path: Path=typer.Argument(..., help='Contribution Bundle f
|
|
|
2812
3134
|
typer.echo('View files:')
|
|
2813
3135
|
typer.echo(f'open {path}')
|
|
2814
3136
|
|
|
2815
|
-
@app.command('init-env')
|
|
3137
|
+
@app.command('init-env', hidden=True)
|
|
2816
3138
|
def init_env():
|
|
2817
3139
|
dst=Path('.env.local')
|
|
2818
3140
|
if not dst.exists(): shutil.copyfile('.env.example', dst); typer.echo('created .env.local from .env.example')
|
|
2819
3141
|
else: typer.echo('.env.local already exists')
|
|
2820
3142
|
|
|
2821
|
-
@app.command('run-task')
|
|
3143
|
+
@app.command('run-task', hidden=True)
|
|
2822
3144
|
def run_task(input: Path=typer.Option(Path('data/seed_tasks/hard_finance_reconciliation.jsonl')), output_root: Path=Path('outputs'), runner: str='deterministic'):
|
|
2823
3145
|
raw=RawTaskRecord.model_validate(read_jsonl(input)[0])
|
|
2824
3146
|
run_root=output_root/'runs'/'single'
|
|
2825
3147
|
pkg=run_one(raw, run_root, runner=runner); typer.echo(str(pkg))
|
|
2826
3148
|
|
|
2827
|
-
@app.command('run-batch')
|
|
3149
|
+
@app.command('run-batch', hidden=True)
|
|
2828
3150
|
def run_batch(input: Path=typer.Option(...), limit: int|None=None, resume: bool=False, max_parallel: int=1, retry_limit: int=0, task_timeout_seconds: int=900, runner: str='deterministic', release_id: str|None=None, output_root: Path=Path('outputs'), max_iterations: int|None=None):
|
|
2829
3151
|
typer.echo(str(run_batch_impl(input, output_root, limit, resume, max_parallel, retry_limit, task_timeout_seconds, runner, release_id, max_iterations=max_iterations)))
|
|
2830
3152
|
|
|
2831
|
-
@app.command('run-many')
|
|
3153
|
+
@app.command('run-many', hidden=True)
|
|
2832
3154
|
def run_many(input: Path=typer.Option(...), limit: int|None=None, resume: bool=False, max_parallel: int=1, retry_limit: int=0, task_timeout_seconds: int=900, runner: str='deterministic', release_id: str|None=None, output_root: Path=Path('outputs'), max_iterations: int|None=None):
|
|
2833
3155
|
typer.echo(str(run_batch_impl(input, output_root, limit, resume, max_parallel, retry_limit, task_timeout_seconds, runner, release_id, max_iterations=max_iterations)))
|
|
2834
3156
|
|
|
2835
|
-
@app.command('create-bundle')
|
|
3157
|
+
@app.command('create-bundle', hidden=True)
|
|
2836
3158
|
def create_bundle(
|
|
2837
3159
|
run_root: Path=typer.Option(...),
|
|
2838
3160
|
bundle_root: Path|None=typer.Option(None),
|
|
@@ -2841,15 +3163,15 @@ def create_bundle(
|
|
|
2841
3163
|
):
|
|
2842
3164
|
typer.echo(str(create_contribution_bundle(run_root, bundle_root, include_debug=include_debug, release_style=release_style)))
|
|
2843
3165
|
|
|
2844
|
-
@app.command('create-release')
|
|
3166
|
+
@app.command('create-release', hidden=True)
|
|
2845
3167
|
def create_release(run_root: Path=typer.Option(Path('outputs/runs/single')), release_root: Path=typer.Option(Path('outputs/releases/manual'))):
|
|
2846
3168
|
typer.echo(str(create_release_impl(run_root, release_root)))
|
|
2847
3169
|
|
|
2848
|
-
@app.command('validate-release')
|
|
3170
|
+
@app.command('validate-release', hidden=True)
|
|
2849
3171
|
def validate_release(release_root: Path=typer.Option(...)):
|
|
2850
3172
|
c=validate_release_impl(release_root); typer.echo(format_counters(c)); raise typer.Exit(0 if c['release_valid'] else 1)
|
|
2851
3173
|
|
|
2852
|
-
@app.command('validate-public-release')
|
|
3174
|
+
@app.command('validate-public-release', hidden=True)
|
|
2853
3175
|
def validate_public_release(release_root: Path=typer.Option(...)):
|
|
2854
3176
|
public=release_root/'public' if (release_root/'public').exists() else release_root
|
|
2855
3177
|
c=validate_release_impl(release_root if (release_root/'public').exists() else release_root.parent) if public.name == 'public' else validate_release_impl(release_root)
|
|
@@ -2857,7 +3179,7 @@ def validate_public_release(release_root: Path=typer.Option(...)):
|
|
|
2857
3179
|
raise typer.Exit(0 if c.get('public_release_valid') else 1)
|
|
2858
3180
|
|
|
2859
3181
|
|
|
2860
|
-
@app.command('repair-roles')
|
|
3182
|
+
@app.command('repair-roles', hidden=True)
|
|
2861
3183
|
def repair_roles(run_root: Path=typer.Option(...), task_id: str=typer.Option(...), roles: str=typer.Option('evaluator_agent,grader_agent,verifier_agent'), attempts: str=typer.Option('baseline,revised'), rebuild_release: Path|None=typer.Option(None)):
|
|
2862
3184
|
"""Re-run model evaluation roles from existing task package artifacts/traces."""
|
|
2863
3185
|
from .schemas import RubricSpec, ActualOutputs, AgentTrace, GraderResult, VerifierResult
|
|
@@ -2916,7 +3238,7 @@ def repair_roles(run_root: Path=typer.Option(...), task_id: str=typer.Option(...
|
|
|
2916
3238
|
typer.echo(f'rebuilt release={rebuild_release}')
|
|
2917
3239
|
|
|
2918
3240
|
|
|
2919
|
-
@app.command('summarize-releases')
|
|
3241
|
+
@app.command('summarize-releases', hidden=True)
|
|
2920
3242
|
def summarize_releases(release_root: Path=typer.Option(Path('outputs/releases')), pattern: str=typer.Option('tasks-*')):
|
|
2921
3243
|
"""Summarize release readiness across a release directory."""
|
|
2922
3244
|
from .validation import validate_release
|
|
@@ -2952,17 +3274,17 @@ def summarize_releases(release_root: Path=typer.Option(Path('outputs/releases'))
|
|
|
2952
3274
|
typer.echo('task_ids_publishable_with_warnings=' + ','.join(warnings))
|
|
2953
3275
|
typer.echo('task_ids_clean_publishable=' + ','.join(clean))
|
|
2954
3276
|
|
|
2955
|
-
@app.command('inspect-trace')
|
|
3277
|
+
@app.command('inspect-trace', hidden=True)
|
|
2956
3278
|
def inspect_trace(trace_path: Path):
|
|
2957
3279
|
t=AgentTrace.model_validate_json(trace_path.read_text()); typer.echo(f'trace_id={t.trace_id}\ntask_id={t.task_id}\nsteps={len(t.steps)}')
|
|
2958
3280
|
|
|
2959
|
-
@app.command('codex-smoke')
|
|
3281
|
+
@app.command('codex-smoke', hidden=True)
|
|
2960
3282
|
def codex_smoke():
|
|
2961
3283
|
if not shutil.which('codex'):
|
|
2962
3284
|
typer.echo('codex_available=false'); raise typer.Exit(1)
|
|
2963
3285
|
typer.echo('codex_available=true')
|
|
2964
3286
|
|
|
2965
|
-
@app.command('llm-smoke')
|
|
3287
|
+
@app.command('llm-smoke', hidden=True)
|
|
2966
3288
|
def llm_smoke(
|
|
2967
3289
|
output_dir: Path=Path('outputs/llm_smoke'),
|
|
2968
3290
|
provider: str | None=typer.Option(None, '--provider', help='Mentor Model Provider id to test. Defaults to configured provider.'),
|