agent-apprenticeship 0.1.0
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/LICENSE +21 -0
- package/README.md +217 -0
- package/bin/agent-apprenticeship.js +131 -0
- package/package.json +30 -0
- package/pyproject.toml +23 -0
- package/src/agent_apprenticeship_trace/__init__.py +2 -0
- package/src/agent_apprenticeship_trace/actual_outputs_normalizer.py +240 -0
- package/src/agent_apprenticeship_trace/apprentice_adapters.py +348 -0
- package/src/agent_apprenticeship_trace/artifact_capture.py +23 -0
- package/src/agent_apprenticeship_trace/artifact_previews.py +80 -0
- package/src/agent_apprenticeship_trace/artifact_resolver.py +142 -0
- package/src/agent_apprenticeship_trace/batch_runner.py +116 -0
- package/src/agent_apprenticeship_trace/bundle_exporter.py +254 -0
- package/src/agent_apprenticeship_trace/certification.py +580 -0
- package/src/agent_apprenticeship_trace/cli.py +2979 -0
- package/src/agent_apprenticeship_trace/codex_runner.py +428 -0
- package/src/agent_apprenticeship_trace/command_discovery.py +94 -0
- package/src/agent_apprenticeship_trace/config.py +609 -0
- package/src/agent_apprenticeship_trace/contract_diagnostics.py +69 -0
- package/src/agent_apprenticeship_trace/env.py +46 -0
- package/src/agent_apprenticeship_trace/evaluator.py +64 -0
- package/src/agent_apprenticeship_trace/grader.py +194 -0
- package/src/agent_apprenticeship_trace/integration_status.py +193 -0
- package/src/agent_apprenticeship_trace/io.py +20 -0
- package/src/agent_apprenticeship_trace/learning.py +627 -0
- package/src/agent_apprenticeship_trace/lesson_extractor.py +5 -0
- package/src/agent_apprenticeship_trace/llm_output_normalizer.py +467 -0
- package/src/agent_apprenticeship_trace/loop.py +111 -0
- package/src/agent_apprenticeship_trace/mentor_checkpoints.py +354 -0
- package/src/agent_apprenticeship_trace/openai_structured.py +783 -0
- package/src/agent_apprenticeship_trace/package_exporter.py +303 -0
- package/src/agent_apprenticeship_trace/progress.py +223 -0
- package/src/agent_apprenticeship_trace/public_run.py +1109 -0
- package/src/agent_apprenticeship_trace/public_sanitizer.py +139 -0
- package/src/agent_apprenticeship_trace/recipes.py +129 -0
- package/src/agent_apprenticeship_trace/release_exporter.py +259 -0
- package/src/agent_apprenticeship_trace/revision.py +21 -0
- package/src/agent_apprenticeship_trace/role_runners.py +7 -0
- package/src/agent_apprenticeship_trace/rubric_generation.py +75 -0
- package/src/agent_apprenticeship_trace/schemas.py +273 -0
- package/src/agent_apprenticeship_trace/session_events.py +99 -0
- package/src/agent_apprenticeship_trace/task_intake.py +112 -0
- package/src/agent_apprenticeship_trace/trace_normalizer.py +669 -0
- package/src/agent_apprenticeship_trace/trace_prompt.py +51 -0
- package/src/agent_apprenticeship_trace/training_signals.py +30 -0
- package/src/agent_apprenticeship_trace/validation.py +210 -0
- package/src/agent_apprenticeship_trace/verifier.py +55 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import time, traceback, json
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from .schemas import RawTaskRecord
|
|
6
|
+
from .io import read_jsonl, append_jsonl, write_json
|
|
7
|
+
from .loop import run_task
|
|
8
|
+
from .release_exporter import create_release
|
|
9
|
+
from .public_sanitizer import classify_provider_failure
|
|
10
|
+
from .validation import validate_release
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _task_id_for_row(row: dict) -> str:
|
|
14
|
+
raw_id = str(row.get('raw_task_id') or row.get('task_id') or 'task_unknown')
|
|
15
|
+
payload = row.get('raw_payload') if isinstance(row.get('raw_payload'), dict) else {}
|
|
16
|
+
return str(payload.get('task_id') or row.get('task_id') or raw_id.replace('raw_', 'task_'))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _utc_now() -> str:
|
|
20
|
+
return datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _progress(run_root: Path, task_index: int, task_count: int, task_id: str, stage: str, status: str = 'ok', message: str | None = None) -> None:
|
|
24
|
+
msg = message or stage.replace('_', ' ')
|
|
25
|
+
print(f'[{task_index}/{task_count}] {msg}', flush=True)
|
|
26
|
+
append_jsonl(run_root/'progress.jsonl', {
|
|
27
|
+
'timestamp': _utc_now(),
|
|
28
|
+
'task_index': task_index,
|
|
29
|
+
'task_count': task_count,
|
|
30
|
+
'task_id': task_id,
|
|
31
|
+
'stage': stage,
|
|
32
|
+
'status': status,
|
|
33
|
+
'message': msg,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _task_scale_ready(package_path: Path) -> bool | None:
|
|
38
|
+
manifest = package_path/'package_manifest.json'
|
|
39
|
+
if not manifest.exists():
|
|
40
|
+
return None
|
|
41
|
+
try:
|
|
42
|
+
data=json.loads(manifest.read_text())
|
|
43
|
+
if 'scale_ready_task' in data:
|
|
44
|
+
return bool(data['scale_ready_task'])
|
|
45
|
+
except Exception:
|
|
46
|
+
return None
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_batch(input_path: Path, output_root: Path, limit: int | None=None, resume=False, max_parallel=1, retry_limit=0, task_timeout_seconds=900, runner='deterministic', release_id: str | None=None, max_iterations: int | None=None) -> Path:
|
|
51
|
+
run_id=release_id or f'run_{int(time.time())}'
|
|
52
|
+
release_root=output_root/'releases'/run_id
|
|
53
|
+
if release_root.exists() and not resume:
|
|
54
|
+
raise RuntimeError('Release already exists. Use --resume or delete the existing output directory.')
|
|
55
|
+
|
|
56
|
+
rows=read_jsonl(input_path)[:limit]
|
|
57
|
+
task_ids=[_task_id_for_row(row) for row in rows]
|
|
58
|
+
duplicates=sorted({tid for tid in task_ids if task_ids.count(tid) > 1})
|
|
59
|
+
if duplicates:
|
|
60
|
+
raise RuntimeError('Duplicate task IDs found in input file: ' + ', '.join(duplicates))
|
|
61
|
+
|
|
62
|
+
run_root=output_root/'runs'/run_id; run_root.mkdir(parents=True, exist_ok=True); (run_root/'quarantine').mkdir(exist_ok=True)
|
|
63
|
+
checkpoint=run_root/'checkpoint.json'; done=set()
|
|
64
|
+
if resume and checkpoint.exists(): done=set(__import__('json').loads(checkpoint.read_text()).get('completed',[]))
|
|
65
|
+
|
|
66
|
+
completed=0; failed=0
|
|
67
|
+
total=len(rows)
|
|
68
|
+
for idx, row in enumerate(rows, start=1):
|
|
69
|
+
raw=RawTaskRecord.model_validate(row); tid=_task_id_for_row(row)
|
|
70
|
+
started=time.time()
|
|
71
|
+
_progress(run_root, idx, total, tid, 'starting', message=f'starting task_id={tid}')
|
|
72
|
+
if tid in done or raw.raw_task_id in done:
|
|
73
|
+
_progress(run_root, idx, total, tid, 'skipped', status='skipped', message='complete status=skipped')
|
|
74
|
+
append_jsonl(run_root/'batch_status.jsonl', {'task_id':tid,'status':'skipped','package_path':None,'error_type':None,'error_message':None,'duration_seconds':round(time.time()-started,3),'scale_ready_task':None})
|
|
75
|
+
continue
|
|
76
|
+
try:
|
|
77
|
+
_progress(run_root, idx, total, tid, 'baseline_started', message='baseline started')
|
|
78
|
+
pkg=run_task(raw, run_root, runner=runner, max_iterations=max_iterations)
|
|
79
|
+
_progress(run_root, idx, total, tid, 'baseline_complete', message='baseline complete')
|
|
80
|
+
_progress(run_root, idx, total, tid, 'evaluation_complete', message='evaluation complete')
|
|
81
|
+
_progress(run_root, idx, total, tid, 'revised_started', message='revised started')
|
|
82
|
+
_progress(run_root, idx, total, tid, 'revised_complete', message='revised complete')
|
|
83
|
+
done.add(pkg.name); done.add(tid); done.add(raw.raw_task_id)
|
|
84
|
+
error_type=None; error_message=None; final_status='completed'
|
|
85
|
+
try:
|
|
86
|
+
attempts=[json.loads((pkg/'attempts/baseline/actual_outputs.json').read_text()), json.loads((pkg/'attempts/revised/actual_outputs.json').read_text())]
|
|
87
|
+
if any((a.get('metadata_json') or {}).get('provider_failure_type')=='usage_limit' for a in attempts):
|
|
88
|
+
final_status='failed'; error_type='ProviderUsageLimit'; error_message='Provider usage limit encountered during attempt.'
|
|
89
|
+
elif any(a.get('status') in ['failed','timeout','error'] for a in attempts):
|
|
90
|
+
final_status='failed'; error_type='AttemptCompletedWithErrors'; error_message='One or more attempts completed with error status.'
|
|
91
|
+
except Exception as exc:
|
|
92
|
+
final_status='failed'; error_type=type(exc).__name__; error_message=str(exc)
|
|
93
|
+
if final_status == 'completed': completed += 1
|
|
94
|
+
else: failed += 1
|
|
95
|
+
_progress(run_root, idx, total, tid, 'package_exported', message='package exported')
|
|
96
|
+
_progress(run_root, idx, total, tid, 'complete', status=final_status, message=f'complete status={"ok" if final_status == "completed" else "failed"}')
|
|
97
|
+
append_jsonl(run_root/'batch_status.jsonl', {'task_id':pkg.name,'status':final_status,'package_path':str(pkg),'error_type':error_type,'error_message':error_message,'duration_seconds':round(time.time()-started,3),'scale_ready_task':_task_scale_ready(pkg)})
|
|
98
|
+
except Exception as e:
|
|
99
|
+
failed += 1
|
|
100
|
+
cls=classify_provider_failure(str(e))
|
|
101
|
+
etype=cls.get('error_type') or type(e).__name__
|
|
102
|
+
emsg=str(e)
|
|
103
|
+
_progress(run_root, idx, total, tid, 'complete', status='failed', message='complete status=failed')
|
|
104
|
+
append_jsonl(run_root/'batch_status.jsonl', {'task_id':tid,'status':'failed','package_path':None,'error_type':etype,'error_message':emsg,'duration_seconds':round(time.time()-started,3),'scale_ready_task':False})
|
|
105
|
+
(run_root/'quarantine'/f'{tid}.txt').write_text(traceback.format_exc())
|
|
106
|
+
write_json(checkpoint, {'completed':sorted(done)})
|
|
107
|
+
create_release(run_root, release_root)
|
|
108
|
+
counters=validate_release(release_root)
|
|
109
|
+
print('Run complete:', flush=True)
|
|
110
|
+
print(f'tasks_total={total}', flush=True)
|
|
111
|
+
print(f'tasks_completed={completed}', flush=True)
|
|
112
|
+
print(f'tasks_failed={failed}', flush=True)
|
|
113
|
+
print(f'release_path={release_root}', flush=True)
|
|
114
|
+
print(f'scale_ready={counters.get("scale_ready")}', flush=True)
|
|
115
|
+
print(f'scale_blockers={counters.get("scale_blockers")}', flush=True)
|
|
116
|
+
return run_root
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import tempfile
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .config import Settings, get_settings, normalize_mentor_mode
|
|
10
|
+
from .io import append_jsonl, read_json, read_jsonl, write_json
|
|
11
|
+
from .public_sanitizer import sanitize_public_obj
|
|
12
|
+
from .release_exporter import create_release
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _count_jsonl(path: Path) -> int:
|
|
16
|
+
return len(read_jsonl(path))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _trace_step_count(path: Path) -> int:
|
|
20
|
+
return sum(len(row.get("steps") or []) for row in read_jsonl(path))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _is_nonempty_jsonl(path: Path) -> bool:
|
|
24
|
+
return _count_jsonl(path) > 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _copy_jsonl_if_nonempty(src: Path, dst: Path) -> bool:
|
|
28
|
+
if not src.exists() or not _is_nonempty_jsonl(src):
|
|
29
|
+
return False
|
|
30
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
dst.write_text("")
|
|
32
|
+
for row in read_jsonl(src):
|
|
33
|
+
append_jsonl(dst, sanitize_public_obj(row) if isinstance(row, dict) else row)
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _sanitize_bundle_json(data: Any) -> Any:
|
|
38
|
+
if isinstance(data, dict):
|
|
39
|
+
return sanitize_public_obj(data)
|
|
40
|
+
if isinstance(data, list):
|
|
41
|
+
return [sanitize_public_obj(row) if isinstance(row, dict) else row for row in data]
|
|
42
|
+
return data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _copy_json_if_exists(src: Path, dst: Path) -> bool:
|
|
46
|
+
if not src.exists():
|
|
47
|
+
return False
|
|
48
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
write_json(dst, _sanitize_bundle_json(read_json(src)))
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _run_status(release_root: Path) -> str:
|
|
54
|
+
rows = read_jsonl(release_root / "packages_index.jsonl")
|
|
55
|
+
statuses = [row.get("task_status") for row in rows if row.get("task_status")]
|
|
56
|
+
if statuses and all(status == "completed" for status in statuses):
|
|
57
|
+
return "completed"
|
|
58
|
+
if statuses and all(status == "failed" for status in statuses):
|
|
59
|
+
return "failed"
|
|
60
|
+
return "partial" if statuses else "failed"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def contribution_counts(release_root: Path) -> dict[str, int]:
|
|
64
|
+
return {
|
|
65
|
+
"attempts": _count_jsonl(release_root / "actual_outputs.jsonl"),
|
|
66
|
+
"traced_steps": _trace_step_count(release_root / "agent_traces.jsonl"),
|
|
67
|
+
"process_supervision_rows": _count_jsonl(release_root / "process_supervision.jsonl"),
|
|
68
|
+
"reward_modeling_rows": _count_jsonl(release_root / "reward_modeling.jsonl"),
|
|
69
|
+
"revision_preference_pairs": _count_jsonl(release_root / "revision_preference_pairs.jsonl"),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _write_card(bundle_root: Path) -> None:
|
|
74
|
+
(bundle_root / "contribution_card.md").write_text(
|
|
75
|
+
"# Agent Apprenticeship Contribution Bundle\n\n"
|
|
76
|
+
"This local bundle packages an apprenticeship session for review and later contribution. "
|
|
77
|
+
"It includes session events, task instructions, traces, actual outputs, evaluation results, "
|
|
78
|
+
"and learning-data views when available.\n\n"
|
|
79
|
+
"No automatic upload is performed. Review this folder locally before sharing.\n\n"
|
|
80
|
+
"To contribute, submit the bundle to the Agent Apprenticeship ecosystem repo, "
|
|
81
|
+
"or get help in Slack: https://join.slack.com/t/fsycommunity/shared_invite/zt-37417grrb-jFD6BQIYgC5wEMrW2bHssw\n"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _session_mentor_mode(session: dict[str, Any], settings: Settings) -> str:
|
|
86
|
+
return normalize_mentor_mode(session.get("mentor_mode") or session.get("evaluation_mode") or settings.mentor_mode)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _clean_session_metadata(session: dict[str, Any], settings: Settings) -> dict[str, Any]:
|
|
90
|
+
allowed = {
|
|
91
|
+
"run_id",
|
|
92
|
+
"session_id",
|
|
93
|
+
"run_status",
|
|
94
|
+
"task_status",
|
|
95
|
+
"task_id",
|
|
96
|
+
"task_instruction",
|
|
97
|
+
"task_assets",
|
|
98
|
+
"latest_package",
|
|
99
|
+
"latest_attempt_id",
|
|
100
|
+
"max_improvement_loops",
|
|
101
|
+
"apprentice_agent",
|
|
102
|
+
"model_provider",
|
|
103
|
+
"status_reason",
|
|
104
|
+
"session_status",
|
|
105
|
+
}
|
|
106
|
+
clean = {k: v for k, v in session.items() if k in allowed and v is not None}
|
|
107
|
+
clean["mentor_mode"] = _session_mentor_mode(session, settings)
|
|
108
|
+
return clean
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _copy_session_files(run_root: Path, bundle_root: Path, settings: Settings) -> None:
|
|
112
|
+
if (run_root / "session_events.jsonl").exists():
|
|
113
|
+
shutil.copy2(run_root / "session_events.jsonl", bundle_root / "session_events.jsonl")
|
|
114
|
+
if (run_root / "session.json").exists():
|
|
115
|
+
write_json(bundle_root / "session_metadata.json", _clean_session_metadata(read_json(run_root / "session.json"), settings))
|
|
116
|
+
if (run_root / "task").exists():
|
|
117
|
+
shutil.copytree(run_root / "task", bundle_root / "task", dirs_exist_ok=True)
|
|
118
|
+
if (run_root / "mentor_checkpoints").exists():
|
|
119
|
+
shutil.copytree(run_root / "mentor_checkpoints", bundle_root / "mentor_checkpoints", dirs_exist_ok=True)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _copy_clean_bundle_files(release_root: Path, bundle_root: Path, include_debug: bool) -> None:
|
|
123
|
+
for dirname in ["task", "traces", "outputs", "evaluation", "learning_data"]:
|
|
124
|
+
(bundle_root / dirname).mkdir(parents=True, exist_ok=True)
|
|
125
|
+
|
|
126
|
+
_copy_jsonl_if_nonempty(release_root / "agent_traces.jsonl", bundle_root / "traces" / "agent_traces.jsonl")
|
|
127
|
+
_copy_jsonl_if_nonempty(release_root / "raw_agent_traces.jsonl", bundle_root / "traces" / "raw_agent_traces.jsonl")
|
|
128
|
+
|
|
129
|
+
_copy_jsonl_if_nonempty(release_root / "actual_outputs.jsonl", bundle_root / "outputs" / "actual_outputs.jsonl")
|
|
130
|
+
_copy_json_if_exists(release_root / "artifacts_index.json", bundle_root / "outputs" / "artifacts_index.json")
|
|
131
|
+
|
|
132
|
+
for name in [
|
|
133
|
+
"grader_results.jsonl",
|
|
134
|
+
"verifier_results.jsonl",
|
|
135
|
+
"evaluator_feedback.jsonl",
|
|
136
|
+
"revision_plans.jsonl",
|
|
137
|
+
"hillclimb_results.jsonl",
|
|
138
|
+
]:
|
|
139
|
+
_copy_jsonl_if_nonempty(release_root / name, bundle_root / "evaluation" / name)
|
|
140
|
+
|
|
141
|
+
for name in [
|
|
142
|
+
"process_supervision.jsonl",
|
|
143
|
+
"reward_modeling.jsonl",
|
|
144
|
+
"revision_preference_pairs.jsonl",
|
|
145
|
+
"training_signals.jsonl",
|
|
146
|
+
]:
|
|
147
|
+
_copy_jsonl_if_nonempty(release_root / name, bundle_root / "learning_data" / name)
|
|
148
|
+
|
|
149
|
+
if include_debug:
|
|
150
|
+
copied = False
|
|
151
|
+
for name in [
|
|
152
|
+
"trace_normalization_reports.jsonl",
|
|
153
|
+
"actual_outputs_normalization_reports.jsonl",
|
|
154
|
+
"role_results_index.jsonl",
|
|
155
|
+
]:
|
|
156
|
+
copied = _copy_jsonl_if_nonempty(release_root / name, bundle_root / "debug" / name) or copied
|
|
157
|
+
copied = _copy_json_if_exists(release_root / "quality_report.json", bundle_root / "debug" / "quality_report.json") or copied
|
|
158
|
+
if not copied and (bundle_root / "debug").exists():
|
|
159
|
+
shutil.rmtree(bundle_root / "debug")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _first_jsonl_row(path: Path) -> dict[str, Any]:
|
|
163
|
+
rows = read_jsonl(path) if path.exists() else []
|
|
164
|
+
for row in rows:
|
|
165
|
+
if isinstance(row, dict):
|
|
166
|
+
return row
|
|
167
|
+
return {}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _slug(text: str) -> str:
|
|
171
|
+
return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")[:80].strip("-") or "bundle"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _list_value(value: Any) -> list[str]:
|
|
175
|
+
if value is None:
|
|
176
|
+
return []
|
|
177
|
+
if isinstance(value, list):
|
|
178
|
+
return [str(v) for v in value if v]
|
|
179
|
+
return [str(value)]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _bundle_metadata_shape(bundle_root: Path, release_root: Path, settings: Settings) -> dict[str, Any]:
|
|
183
|
+
counts = contribution_counts(release_root)
|
|
184
|
+
session = read_json(bundle_root / "session_metadata.json") if (bundle_root / "session_metadata.json").exists() else {}
|
|
185
|
+
task = _first_jsonl_row(release_root / "tasks.jsonl")
|
|
186
|
+
run_status = session.get("run_status") or _run_status(release_root)
|
|
187
|
+
task_status = session.get("task_status") or run_status
|
|
188
|
+
instruction = str(session.get("task_instruction") or "")
|
|
189
|
+
title = (
|
|
190
|
+
task.get("normalized_title")
|
|
191
|
+
or task.get("raw_title")
|
|
192
|
+
or (instruction.strip().splitlines()[0][:90] if instruction.strip() else None)
|
|
193
|
+
or "Agent Apprenticeship session"
|
|
194
|
+
)
|
|
195
|
+
run_id = str(session.get("run_id") or bundle_root.parent.name or _slug(title))
|
|
196
|
+
role = task.get("agent_apprentice_role") or task.get("apprenticeship_role")
|
|
197
|
+
metadata = {
|
|
198
|
+
"bundle_id": f"aa-bundle-{_slug(run_id)}",
|
|
199
|
+
"title": title,
|
|
200
|
+
**counts,
|
|
201
|
+
"domains": _list_value(task.get("domain")),
|
|
202
|
+
"subdomains": _list_value(task.get("subdomain")),
|
|
203
|
+
"agent_apprentice_role": role,
|
|
204
|
+
"expected_economic_value": task.get("expected_economic_value"),
|
|
205
|
+
"expected_economic_value_for_agent_apprentice": task.get("expected_economic_value_for_agent_apprentice"),
|
|
206
|
+
"mentor_mode": _session_mentor_mode(session, settings),
|
|
207
|
+
"task_status": task_status,
|
|
208
|
+
"run_status": run_status,
|
|
209
|
+
}
|
|
210
|
+
return metadata
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _write_manifest(bundle_root: Path, release_root: Path, settings: Settings, include_internal_schema: bool = False) -> None:
|
|
214
|
+
manifest = _bundle_metadata_shape(bundle_root, release_root, settings)
|
|
215
|
+
release_manifest: dict[str, Any] = {}
|
|
216
|
+
if (release_root / "dataset_manifest.json").exists():
|
|
217
|
+
release_manifest = read_json(release_root / "dataset_manifest.json")
|
|
218
|
+
session = read_json(bundle_root / "session_metadata.json") if (bundle_root / "session_metadata.json").exists() else {}
|
|
219
|
+
if session.get("status_reason") and manifest.get("run_status") != "completed":
|
|
220
|
+
manifest["status_reason"] = session["status_reason"]
|
|
221
|
+
if include_internal_schema:
|
|
222
|
+
manifest["source_release_schema_version"] = release_manifest.get("schema_version")
|
|
223
|
+
write_json(bundle_root / "contribution_manifest.json", manifest)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def create_contribution_bundle(
|
|
227
|
+
run_root: Path,
|
|
228
|
+
bundle_root: Path | None = None,
|
|
229
|
+
settings: Settings | None = None,
|
|
230
|
+
include_debug: bool = False,
|
|
231
|
+
release_style: bool = False,
|
|
232
|
+
) -> Path:
|
|
233
|
+
settings = settings or get_settings()
|
|
234
|
+
bundle_root = bundle_root or (run_root / "contribution_bundle")
|
|
235
|
+
if bundle_root.exists():
|
|
236
|
+
shutil.rmtree(bundle_root)
|
|
237
|
+
bundle_root.mkdir(parents=True, exist_ok=True)
|
|
238
|
+
|
|
239
|
+
if release_style:
|
|
240
|
+
create_release(run_root, bundle_root)
|
|
241
|
+
_copy_session_files(run_root, bundle_root, settings)
|
|
242
|
+
_write_manifest(bundle_root, bundle_root, settings, include_internal_schema=True)
|
|
243
|
+
_write_card(bundle_root)
|
|
244
|
+
return bundle_root
|
|
245
|
+
|
|
246
|
+
with tempfile.TemporaryDirectory(prefix="aa-release-for-bundle-") as tmp:
|
|
247
|
+
release_root = Path(tmp) / "release"
|
|
248
|
+
create_release(run_root, release_root)
|
|
249
|
+
_copy_session_files(run_root, bundle_root, settings)
|
|
250
|
+
_copy_clean_bundle_files(release_root, bundle_root, include_debug=include_debug)
|
|
251
|
+
_write_manifest(bundle_root, release_root, settings, include_internal_schema=include_debug)
|
|
252
|
+
_write_card(bundle_root)
|
|
253
|
+
|
|
254
|
+
return bundle_root
|