agent-apprenticeship 0.1.6 → 0.1.7

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.
Files changed (33) hide show
  1. package/bin/agent-apprenticeship.js +85 -37
  2. package/package.json +1 -1
  3. package/pyproject.toml +1 -1
  4. package/src/agent_apprenticeship_trace/__init__.py +1 -1
  5. package/src/agent_apprenticeship_trace/actual_outputs_normalizer.py +104 -28
  6. package/src/agent_apprenticeship_trace/apprentice_adapters.py +3 -1
  7. package/src/agent_apprenticeship_trace/artifact_resolver.py +6 -0
  8. package/src/agent_apprenticeship_trace/bundle_exporter.py +132 -2
  9. package/src/agent_apprenticeship_trace/cli.py +1171 -262
  10. package/src/agent_apprenticeship_trace/codex_runner.py +89 -6
  11. package/src/agent_apprenticeship_trace/config.py +251 -32
  12. package/src/agent_apprenticeship_trace/ecosystem_r2.py +515 -0
  13. package/src/agent_apprenticeship_trace/env.py +5 -5
  14. package/src/agent_apprenticeship_trace/evaluator.py +127 -11
  15. package/src/agent_apprenticeship_trace/grader.py +12 -11
  16. package/src/agent_apprenticeship_trace/learning.py +270 -1
  17. package/src/agent_apprenticeship_trace/llm_output_normalizer.py +8 -7
  18. package/src/agent_apprenticeship_trace/loop.py +103 -4
  19. package/src/agent_apprenticeship_trace/loop_review.py +686 -0
  20. package/src/agent_apprenticeship_trace/mentor_checkpoints.py +49 -21
  21. package/src/agent_apprenticeship_trace/openai_structured.py +84 -14
  22. package/src/agent_apprenticeship_trace/package_exporter.py +1 -1
  23. package/src/agent_apprenticeship_trace/progress.py +2 -1
  24. package/src/agent_apprenticeship_trace/public_run.py +369 -57
  25. package/src/agent_apprenticeship_trace/public_sanitizer.py +37 -1
  26. package/src/agent_apprenticeship_trace/rubric_generation.py +11 -10
  27. package/src/agent_apprenticeship_trace/schemas.py +378 -3
  28. package/src/agent_apprenticeship_trace/task_intake.py +43 -9
  29. package/src/agent_apprenticeship_trace/tdo.py +2304 -0
  30. package/src/agent_apprenticeship_trace/trace_normalizer.py +351 -17
  31. package/src/agent_apprenticeship_trace/trace_prompt.py +38 -11
  32. package/src/agent_apprenticeship_trace/training_signals.py +1 -1
  33. package/src/agent_apprenticeship_trace/verifier.py +12 -6
@@ -16,23 +16,93 @@ from .io import read_json, write_json
16
16
  from .recipes import MODEL_PROVIDER_RECIPES, WORKER_AGENT_RECIPES
17
17
 
18
18
 
19
+ ApprenticeshipMode = Literal["autonomous", "expert_led", "org_custom"]
19
20
  MentorMode = Literal["model_assisted", "expert_led", "hybrid"]
20
21
  SensitiveInfoMasking = Literal["standard", "no_masking"]
21
- EcosystemAutoShare = Literal["manual", "ask", "automatic"]
22
+ EcosystemAutoShare = Literal["manual", "automatic", "disabled"]
23
+ TrainingContributionMode = Literal["public_ecosystem", "private_internal_only"]
24
+ EcosystemStorageBackend = Literal["forsy-r2", "github"]
22
25
  EvaluationMode = Literal["model-assisted", "expert-led", "hybrid"]
23
26
  DataSharingLevel = Literal["standard", "full-context"]
24
27
 
28
+ APPRENTICESHIP_MODES: tuple[str, ...] = ("autonomous", "expert_led", "org_custom")
25
29
  MENTOR_MODES: tuple[str, ...] = ("model_assisted", "expert_led", "hybrid")
26
30
  SENSITIVE_INFO_MASKING_LEVELS: tuple[str, ...] = ("standard", "no_masking")
27
- ECOSYSTEM_AUTO_SHARE_MODES: tuple[str, ...] = ("manual", "ask", "automatic")
31
+ ECOSYSTEM_AUTO_SHARE_MODES: tuple[str, ...] = ("manual", "automatic", "disabled")
32
+ TRAINING_CONTRIBUTION_MODES: tuple[str, ...] = ("public_ecosystem", "private_internal_only")
28
33
  EVALUATION_MODES: tuple[str, ...] = ("model-assisted", "expert-led", "hybrid")
29
34
  DATA_SHARING_LEVELS: tuple[str, ...] = ("standard", "full-context")
30
35
  DEFAULT_APP_HOME = Path("~/.agent-apprenticeship").expanduser()
31
36
  DEFAULT_PUBLIC_ECOSYSTEM_REPO = "Forsy-AI/agent-apprenticeship"
32
37
  DEFAULT_PUBLIC_ECOSYSTEM_URL = f"https://github.com/{DEFAULT_PUBLIC_ECOSYSTEM_REPO}"
38
+ DEFAULT_ECOSYSTEM_STORAGE_BACKEND = "forsy-r2"
39
+ DEFAULT_R2_BUCKET = "apprenticeship-dev"
40
+ DEFAULT_R2_INDEX_PREFIX = "indexes"
41
+ DEFAULT_R2_CONTRIBUTION_PREFIX = "contributions/packages"
42
+ DEFAULT_R2_SEED_PREFIX = "contributions/seed_dataset"
43
+ DEFAULT_R2_API_BASE = "https://agent-apprenticeship-ingest.sai-1c1.workers.dev"
33
44
  _SETTINGS_OVERRIDE: ContextVar[Any] = ContextVar("agent_apprenticeship_settings_override", default=None)
34
45
 
35
46
 
47
+ def normalize_apprenticeship_mode(value: str | None, default: str = "autonomous") -> str:
48
+ if value is None or str(value).strip() == "":
49
+ return default
50
+ normalized = str(value).strip().lower().replace("-", "_").replace(" ", "_")
51
+ aliases = {
52
+ "autonomous": "autonomous",
53
+ "autonomous_apprenticeship": "autonomous",
54
+ "model": "autonomous",
55
+ "model_assisted": "autonomous",
56
+ "model_assisted_automated": "autonomous",
57
+ "modelassisted": "autonomous",
58
+ "automated": "autonomous",
59
+ "expert": "expert_led",
60
+ "expert_led": "expert_led",
61
+ "expert_led_apprenticeship": "expert_led",
62
+ "expert_led_manual": "expert_led",
63
+ "manual": "expert_led",
64
+ "human": "expert_led",
65
+ "hybrid": "expert_led",
66
+ "org": "org_custom",
67
+ "organization": "org_custom",
68
+ "organization_custom": "org_custom",
69
+ "org_custom": "org_custom",
70
+ "custom_org": "org_custom",
71
+ "custom": "org_custom",
72
+ }
73
+ normalized = aliases.get(normalized, normalized)
74
+ if normalized not in APPRENTICESHIP_MODES:
75
+ raise ValueError(f"Unsupported Apprenticeship Mode: {value}")
76
+ return normalized
77
+
78
+
79
+ def apprenticeship_mode_display(value: str | None) -> str:
80
+ normalized = normalize_apprenticeship_mode(value)
81
+ return {
82
+ "autonomous": "Autonomous Apprenticeship",
83
+ "expert_led": "Expert-Led Apprenticeship",
84
+ "org_custom": "Organization Custom",
85
+ }[normalized]
86
+
87
+
88
+ def apprenticeship_mode_to_mentor_mode(value: str | None) -> str:
89
+ normalized = normalize_apprenticeship_mode(value)
90
+ return {
91
+ "autonomous": "model_assisted",
92
+ "expert_led": "expert_led",
93
+ "org_custom": "expert_led",
94
+ }[normalized]
95
+
96
+
97
+ def mentor_mode_to_apprenticeship_mode(value: str | None) -> str:
98
+ normalized = normalize_mentor_mode(value)
99
+ return {
100
+ "model_assisted": "autonomous",
101
+ "expert_led": "expert_led",
102
+ "hybrid": "expert_led",
103
+ }[normalized]
104
+
105
+
36
106
  def normalize_mentor_mode(value: str | None, default: str = "model_assisted") -> str:
37
107
  if value is None or str(value).strip() == "":
38
108
  return default
@@ -50,7 +120,7 @@ def normalize_mentor_mode(value: str | None, default: str = "model_assisted") ->
50
120
  }
51
121
  normalized = aliases.get(normalized, normalized)
52
122
  if normalized not in MENTOR_MODES:
53
- raise ValueError(f"Unsupported Mentor Mode: {value}")
123
+ raise ValueError(f"Unsupported apprenticeship mode: {value}")
54
124
  return normalized
55
125
 
56
126
 
@@ -68,12 +138,7 @@ def evaluation_mode_to_mentor_mode(value: str | None) -> str:
68
138
 
69
139
 
70
140
  def mentor_mode_display(value: str | None) -> str:
71
- normalized = normalize_mentor_mode(value)
72
- return {
73
- "model_assisted": "model-assisted",
74
- "expert_led": "expert-led",
75
- "hybrid": "hybrid",
76
- }[normalized]
141
+ return apprenticeship_mode_display(mentor_mode_to_apprenticeship_mode(value))
77
142
 
78
143
 
79
144
  def normalize_sensitive_info_masking(value: str | None, default: str = "standard") -> str:
@@ -110,31 +175,100 @@ def normalize_ecosystem_auto_share(value: str | None, default: str = "manual") -
110
175
  normalized = str(value).strip().lower().replace("-", "_").replace(" ", "_")
111
176
  aliases = {
112
177
  "manual": "manual",
113
- "off": "manual",
114
- "none": "manual",
115
- "ask": "ask",
116
- "prompt": "ask",
117
- "ask_before_sharing": "ask",
178
+ "ask": "manual",
179
+ "prompt": "manual",
180
+ "ask_before_sharing": "manual",
118
181
  "share_automatically": "automatic",
119
182
  "automatic": "automatic",
120
183
  "auto": "automatic",
121
184
  "on": "automatic",
185
+ "disabled": "disabled",
186
+ "disable": "disabled",
187
+ "off": "disabled",
188
+ "none": "disabled",
122
189
  }
123
190
  normalized = aliases.get(normalized, normalized)
124
191
  if normalized not in ECOSYSTEM_AUTO_SHARE_MODES:
125
- raise ValueError(f"Unsupported Ecosystem Auto-Share setting: {value}")
192
+ raise ValueError(f"Unsupported ecosystem sharing setting: {value}")
126
193
  return normalized
127
194
 
128
195
 
129
196
  def ecosystem_auto_share_display(value: str | None) -> str:
130
197
  normalized = normalize_ecosystem_auto_share(value)
131
198
  return {
132
- "manual": "Manual",
133
- "ask": "Ask before sharing",
134
- "automatic": "Share automatically",
199
+ "manual": "manual",
200
+ "automatic": "automatic",
201
+ "disabled": "disabled",
135
202
  }[normalized]
136
203
 
137
204
 
205
+ def normalize_training_contribution_mode(value: str | None, default: str = "public_ecosystem") -> str:
206
+ if value is None or str(value).strip() == "":
207
+ return default
208
+ normalized = str(value).strip().lower().replace("-", "_").replace(" ", "_")
209
+ aliases = {
210
+ "public": "public_ecosystem",
211
+ "public_ecosystem": "public_ecosystem",
212
+ "public_ecosystem_default": "public_ecosystem",
213
+ "ecosystem": "public_ecosystem",
214
+ "share": "public_ecosystem",
215
+ "shared": "public_ecosystem",
216
+ "automatic": "public_ecosystem",
217
+ "auto": "public_ecosystem",
218
+ "on": "public_ecosystem",
219
+ "private": "private_internal_only",
220
+ "private_internal": "private_internal_only",
221
+ "private_internal_only": "private_internal_only",
222
+ "internal": "private_internal_only",
223
+ "local": "private_internal_only",
224
+ "manual": "private_internal_only",
225
+ "ask": "private_internal_only",
226
+ "disabled": "private_internal_only",
227
+ "disable": "private_internal_only",
228
+ "off": "private_internal_only",
229
+ "none": "private_internal_only",
230
+ }
231
+ normalized = aliases.get(normalized, normalized)
232
+ if normalized not in TRAINING_CONTRIBUTION_MODES:
233
+ raise ValueError(f"Unsupported Agent Training Contribution Mode: {value}")
234
+ return normalized
235
+
236
+
237
+ def training_contribution_mode_display(value: str | None) -> str:
238
+ normalized = normalize_training_contribution_mode(value)
239
+ return {
240
+ "public_ecosystem": "Public Ecosystem",
241
+ "private_internal_only": "Private Internal Only",
242
+ }[normalized]
243
+
244
+
245
+ def training_contribution_to_auto_share(value: str | None) -> str:
246
+ return "automatic" if normalize_training_contribution_mode(value) == "public_ecosystem" else "disabled"
247
+
248
+
249
+ def auto_share_to_training_contribution(value: str | None) -> str:
250
+ return "private_internal_only" if normalize_ecosystem_auto_share(value) == "disabled" else "public_ecosystem"
251
+
252
+
253
+ def normalize_ecosystem_storage(value: str | None, default: str = DEFAULT_ECOSYSTEM_STORAGE_BACKEND) -> str:
254
+ if value is None or str(value).strip() == "":
255
+ return default
256
+ normalized = str(value).strip().lower().replace("_", "-")
257
+ aliases = {
258
+ "r2": "forsy-r2",
259
+ "cloudflare-r2": "forsy-r2",
260
+ "cloudflare_r2": "forsy-r2",
261
+ "forsy-r2": "forsy-r2",
262
+ "github": "github",
263
+ "github-issue": "github",
264
+ "legacy-github": "github",
265
+ }
266
+ normalized = aliases.get(normalized, normalized)
267
+ if normalized not in {"forsy-r2", "github"}:
268
+ raise ValueError(f"Unsupported ecosystem storage backend: {value}")
269
+ return normalized
270
+
271
+
138
272
  class Settings(BaseModel):
139
273
  # Public product settings.
140
274
  app_home: Path = DEFAULT_APP_HOME
@@ -145,6 +279,7 @@ class Settings(BaseModel):
145
279
  model_provider: str | None = None
146
280
  model_provider_api_key_env: str | None = None
147
281
  model_provider_model: str | None = None
282
+ apprenticeship_mode: ApprenticeshipMode = "autonomous"
148
283
  mentor_mode: MentorMode = "model_assisted"
149
284
  sensitive_info_masking: SensitiveInfoMasking = "standard"
150
285
  evaluation_mode: EvaluationMode = "model-assisted"
@@ -159,7 +294,14 @@ class Settings(BaseModel):
159
294
  mentor_model_provider_readiness_reason: str | None = None
160
295
  ecosystem_repo: str | None = DEFAULT_PUBLIC_ECOSYSTEM_REPO
161
296
  ecosystem_repo_path: Path | None = None
162
- ecosystem_auto_share: EcosystemAutoShare = "manual"
297
+ training_contribution_mode: TrainingContributionMode = "public_ecosystem"
298
+ ecosystem_auto_share: EcosystemAutoShare = "automatic"
299
+ ecosystem_storage_backend: EcosystemStorageBackend = DEFAULT_ECOSYSTEM_STORAGE_BACKEND
300
+ r2_api_url: str | None = DEFAULT_R2_API_BASE
301
+ r2_bucket: str = DEFAULT_R2_BUCKET
302
+ r2_index_prefix: str = DEFAULT_R2_INDEX_PREFIX
303
+ r2_contribution_prefix: str = DEFAULT_R2_CONTRIBUTION_PREFIX
304
+ r2_seed_prefix: str = DEFAULT_R2_SEED_PREFIX
163
305
 
164
306
  # Compatibility settings used by the existing internal pipeline.
165
307
  openai_api_key: str | None = None
@@ -264,13 +406,19 @@ def get_settings(root: Path | None = None) -> Settings:
264
406
  )
265
407
  legacy_iterations = _int_env("AA_MAX_ITERATIONS", int(stored.get("max_iterations") or 3))
266
408
  openai_model = os.getenv("AA_OPENAI_MODEL", stored.get("openai_model") or "gpt-5-mini")
267
- mentor_mode = normalize_mentor_mode(
409
+ new_mode_source = os.getenv("AA_APPRENTICESHIP_MODE") or stored.get("apprenticeship_mode")
410
+ legacy_mode_source = (
268
411
  os.getenv("AA_MENTOR_MODE")
269
412
  or stored.get("mentor_mode")
270
413
  or os.getenv("AA_EVALUATION_MODE")
271
414
  or stored.get("evaluation_mode")
272
- or "model_assisted"
273
415
  )
416
+ apprenticeship_mode = normalize_apprenticeship_mode(new_mode_source or legacy_mode_source or "autonomous")
417
+ mentor_mode = apprenticeship_mode_to_mentor_mode(apprenticeship_mode)
418
+ if not new_mode_source and legacy_mode_source:
419
+ legacy_mentor_mode = normalize_mentor_mode(legacy_mode_source)
420
+ if legacy_mentor_mode == "hybrid":
421
+ mentor_mode = "hybrid"
274
422
  sensitive_info_masking = normalize_sensitive_info_masking(
275
423
  os.getenv("AA_SENSITIVE_INFO_MASKING")
276
424
  or stored.get("sensitive_info_masking")
@@ -278,6 +426,18 @@ def get_settings(root: Path | None = None) -> Settings:
278
426
  or stored.get("data_sharing_level")
279
427
  or "standard"
280
428
  )
429
+ auto_share_source = os.getenv("AA_ECOSYSTEM_AUTO_SHARE") or stored.get("ecosystem_auto_share")
430
+ training_contribution_source = os.getenv("AA_TRAINING_CONTRIBUTION_MODE") or stored.get("training_contribution_mode")
431
+ if training_contribution_source:
432
+ training_contribution_mode = normalize_training_contribution_mode(training_contribution_source)
433
+ elif auto_share_source:
434
+ training_contribution_mode = auto_share_to_training_contribution(auto_share_source)
435
+ else:
436
+ training_contribution_mode = "public_ecosystem"
437
+ if auto_share_source:
438
+ ecosystem_auto_share = normalize_ecosystem_auto_share(auto_share_source)
439
+ else:
440
+ ecosystem_auto_share = training_contribution_to_auto_share(training_contribution_mode)
281
441
 
282
442
  return Settings(
283
443
  app_home=home,
@@ -288,6 +448,7 @@ def get_settings(root: Path | None = None) -> Settings:
288
448
  model_provider=provider,
289
449
  model_provider_api_key_env=provider_key_env,
290
450
  model_provider_model=provider_model,
451
+ apprenticeship_mode=apprenticeship_mode,
291
452
  mentor_mode=mentor_mode,
292
453
  sensitive_info_masking=sensitive_info_masking,
293
454
  evaluation_mode=mentor_mode_to_evaluation_mode(mentor_mode),
@@ -304,9 +465,24 @@ def get_settings(root: Path | None = None) -> Settings:
304
465
  ecosystem_repo_path=Path(os.getenv("AA_ECOSYSTEM_REPO_PATH") or stored.get("ecosystem_repo_path")).expanduser()
305
466
  if (os.getenv("AA_ECOSYSTEM_REPO_PATH") or stored.get("ecosystem_repo_path"))
306
467
  else None,
307
- ecosystem_auto_share=normalize_ecosystem_auto_share(
308
- os.getenv("AA_ECOSYSTEM_AUTO_SHARE") or stored.get("ecosystem_auto_share") or "manual"
468
+ training_contribution_mode=training_contribution_mode,
469
+ ecosystem_auto_share=ecosystem_auto_share,
470
+ ecosystem_storage_backend=normalize_ecosystem_storage(
471
+ os.getenv("AA_R2_STORAGE_BACKEND")
472
+ or os.getenv("AA_ECOSYSTEM_STORAGE_BACKEND")
473
+ or stored.get("ecosystem_storage_backend")
474
+ or DEFAULT_ECOSYSTEM_STORAGE_BACKEND
475
+ ),
476
+ r2_api_url=(
477
+ os.getenv("AA_R2_API_BASE")
478
+ or os.getenv("AA_R2_API_URL")
479
+ or stored.get("r2_api_url")
480
+ or DEFAULT_R2_API_BASE
309
481
  ),
482
+ r2_bucket=os.getenv("AA_R2_BUCKET") or stored.get("r2_bucket") or DEFAULT_R2_BUCKET,
483
+ r2_index_prefix=os.getenv("AA_R2_INDEX_PREFIX") or stored.get("r2_index_prefix") or DEFAULT_R2_INDEX_PREFIX,
484
+ r2_contribution_prefix=os.getenv("AA_R2_CONTRIBUTION_PREFIX") or stored.get("r2_contribution_prefix") or DEFAULT_R2_CONTRIBUTION_PREFIX,
485
+ r2_seed_prefix=os.getenv("AA_R2_SEED_PREFIX") or stored.get("r2_seed_prefix") or DEFAULT_R2_SEED_PREFIX,
310
486
  openai_api_key=os.getenv("OPENAI_API_KEY"),
311
487
  openai_model=openai_model,
312
488
  worker_runner=os.getenv("AA_WORKER_RUNNER", os.getenv("AA_TRACE_WORKER_RUNNER", stored.get("worker_runner") or worker_agent)),
@@ -355,18 +531,45 @@ def update_settings(**updates: Any) -> Settings:
355
531
  settings = get_settings()
356
532
  data = settings.model_dump()
357
533
  clean_updates = {k: v for k, v in updates.items() if v is not None}
534
+ legacy_requested_mode = None
535
+ if "apprenticeship_mode" not in clean_updates:
536
+ if "mentor_mode" in clean_updates:
537
+ legacy_requested_mode = clean_updates["mentor_mode"]
538
+ elif "evaluation_mode" in clean_updates:
539
+ legacy_requested_mode = evaluation_mode_to_mentor_mode(clean_updates["evaluation_mode"])
540
+ if "mentor_mode" in clean_updates and "apprenticeship_mode" not in clean_updates:
541
+ clean_updates["apprenticeship_mode"] = mentor_mode_to_apprenticeship_mode(clean_updates["mentor_mode"])
358
542
  if "evaluation_mode" in clean_updates and "mentor_mode" not in clean_updates:
359
- clean_updates["mentor_mode"] = evaluation_mode_to_mentor_mode(clean_updates["evaluation_mode"])
543
+ clean_updates["apprenticeship_mode"] = mentor_mode_to_apprenticeship_mode(
544
+ evaluation_mode_to_mentor_mode(clean_updates["evaluation_mode"])
545
+ )
546
+ if "apprenticeship_mode" in clean_updates:
547
+ clean_updates["apprenticeship_mode"] = normalize_apprenticeship_mode(clean_updates["apprenticeship_mode"])
548
+ clean_updates["mentor_mode"] = apprenticeship_mode_to_mentor_mode(clean_updates["apprenticeship_mode"])
549
+ clean_updates["evaluation_mode"] = mentor_mode_to_evaluation_mode(clean_updates["mentor_mode"])
360
550
  if "mentor_mode" in clean_updates:
361
551
  clean_updates["mentor_mode"] = normalize_mentor_mode(clean_updates["mentor_mode"])
552
+ clean_updates["apprenticeship_mode"] = mentor_mode_to_apprenticeship_mode(clean_updates["mentor_mode"])
362
553
  clean_updates["evaluation_mode"] = mentor_mode_to_evaluation_mode(clean_updates["mentor_mode"])
554
+ if legacy_requested_mode and normalize_mentor_mode(legacy_requested_mode) == "hybrid":
555
+ clean_updates["apprenticeship_mode"] = "expert_led"
556
+ clean_updates["mentor_mode"] = "hybrid"
557
+ clean_updates["evaluation_mode"] = "hybrid"
363
558
  if "data_sharing_level" in clean_updates and "sensitive_info_masking" not in clean_updates:
364
559
  clean_updates["sensitive_info_masking"] = data_sharing_to_sensitive_info_masking(clean_updates["data_sharing_level"])
365
560
  if "sensitive_info_masking" in clean_updates:
366
561
  clean_updates["sensitive_info_masking"] = normalize_sensitive_info_masking(clean_updates["sensitive_info_masking"])
367
562
  clean_updates["data_sharing_level"] = sensitive_info_masking_to_data_sharing(clean_updates["sensitive_info_masking"])
563
+ if "ecosystem_auto_share" in clean_updates and "training_contribution_mode" not in clean_updates:
564
+ clean_updates["training_contribution_mode"] = auto_share_to_training_contribution(clean_updates["ecosystem_auto_share"])
565
+ if "training_contribution_mode" in clean_updates:
566
+ clean_updates["training_contribution_mode"] = normalize_training_contribution_mode(clean_updates["training_contribution_mode"])
567
+ if "ecosystem_auto_share" not in clean_updates:
568
+ clean_updates["ecosystem_auto_share"] = training_contribution_to_auto_share(clean_updates["training_contribution_mode"])
368
569
  if "ecosystem_auto_share" in clean_updates:
369
570
  clean_updates["ecosystem_auto_share"] = normalize_ecosystem_auto_share(clean_updates["ecosystem_auto_share"])
571
+ if "ecosystem_storage_backend" in clean_updates:
572
+ clean_updates["ecosystem_storage_backend"] = normalize_ecosystem_storage(clean_updates["ecosystem_storage_backend"])
370
573
  if "worker_agent" in clean_updates and "worker_runner" not in clean_updates:
371
574
  clean_updates["worker_runner"] = clean_updates["worker_agent"]
372
575
  clean_updates["reviser_runner"] = clean_updates["worker_agent"]
@@ -377,15 +580,19 @@ def update_settings(**updates: Any) -> Settings:
377
580
  if updated.model_provider is not None and updated.model_provider not in MODEL_PROVIDER_RECIPES:
378
581
  raise ValueError(f"Unsupported Mentor Model Provider: {updated.model_provider}")
379
582
  if updated.evaluation_mode not in EVALUATION_MODES:
380
- raise ValueError(f"Unsupported Mentor Mode: {updated.mentor_mode}")
583
+ raise ValueError(f"Unsupported Apprenticeship Mode: {updated.apprenticeship_mode}")
381
584
  if updated.data_sharing_level not in DATA_SHARING_LEVELS:
382
585
  raise ValueError(f"Unsupported Sensitive Info Masking setting: {updated.sensitive_info_masking}")
383
586
  if updated.mentor_mode not in MENTOR_MODES:
384
587
  raise ValueError(f"Unsupported mentor mode: {updated.mentor_mode}")
588
+ if updated.apprenticeship_mode not in APPRENTICESHIP_MODES:
589
+ raise ValueError(f"Unsupported Apprenticeship Mode: {updated.apprenticeship_mode}")
385
590
  if updated.sensitive_info_masking not in SENSITIVE_INFO_MASKING_LEVELS:
386
591
  raise ValueError(f"Unsupported sensitive info masking setting: {updated.sensitive_info_masking}")
387
592
  if updated.ecosystem_auto_share not in ECOSYSTEM_AUTO_SHARE_MODES:
388
- raise ValueError(f"Unsupported Ecosystem Auto-Share setting: {updated.ecosystem_auto_share}")
593
+ raise ValueError(f"Unsupported ecosystem sharing setting: {updated.ecosystem_auto_share}")
594
+ if updated.training_contribution_mode not in TRAINING_CONTRIBUTION_MODES:
595
+ raise ValueError(f"Unsupported Agent Training Contribution Mode: {updated.training_contribution_mode}")
389
596
  if updated.worker_agent == "custom":
390
597
  template = updated.custom_worker_command_template or ""
391
598
  missing = [token for token in ("{workspace}", "{prompt_file}") if token not in template]
@@ -575,12 +782,18 @@ def public_settings(settings: Settings | None = None) -> dict[str, Any]:
575
782
  "mentor_model_provider_status_reason": provider_status.get("reason"),
576
783
  "mentor_model_provider_api_key_env": provider_status.get("api_key_env_var"),
577
784
  "mentor_model_provider_api_key_visible": provider_status.get("api_key_visible"),
578
- "mentor_mode": s.mentor_mode,
785
+ "apprenticeship_mode": s.apprenticeship_mode,
579
786
  "sensitive_info_masking": s.sensitive_info_masking,
580
787
  "max_improvement_loops": s.max_improvement_loops,
581
788
  "ecosystem_repo": s.ecosystem_repo,
582
789
  "ecosystem_repo_path": str(s.ecosystem_repo_path) if s.ecosystem_repo_path else None,
583
- "ecosystem_auto_share": s.ecosystem_auto_share,
790
+ "training_contribution_mode": s.training_contribution_mode,
791
+ "ecosystem_storage_backend": s.ecosystem_storage_backend,
792
+ "r2_api_url": s.r2_api_url,
793
+ "r2_bucket": s.r2_bucket,
794
+ "r2_index_prefix": s.r2_index_prefix,
795
+ "r2_contribution_prefix": s.r2_contribution_prefix,
796
+ "r2_seed_prefix": s.r2_seed_prefix,
584
797
  "app_home": str(s.app_home),
585
798
  }
586
799
 
@@ -596,19 +809,25 @@ def public_settings_text(settings: Settings | None = None) -> str:
596
809
  _readiness_line("Apprentice Agent Status", data["apprentice_agent_status"], data.get("apprentice_agent_status_reason")),
597
810
  f"Mentor Model Provider: {provider}",
598
811
  _readiness_line("Mentor Model Provider Status", data["mentor_model_provider_status"], data.get("mentor_model_provider_status_reason")),
599
- f"Mentor Mode: {mentor_mode_display(data['mentor_mode'])}",
812
+ f"Apprenticeship Mode: {apprenticeship_mode_display(data['apprenticeship_mode'])}",
600
813
  f"Sensitive Info Masking: {data['sensitive_info_masking']}",
601
814
  f"Maximum Improvement Loops: {data['max_improvement_loops']}",
602
815
  f"Public Ecosystem Repo: {data['ecosystem_repo'] or DEFAULT_PUBLIC_ECOSYSTEM_REPO}",
603
816
  *([f"Public Ecosystem Repo Path: {data['ecosystem_repo_path']}"] if data.get("ecosystem_repo_path") else []),
604
- f"Ecosystem Auto-Share: {ecosystem_auto_share_display(data['ecosystem_auto_share'])}",
817
+ f"Storage backend: {data['ecosystem_storage_backend']}",
818
+ f"R2 ingestion API: {data['r2_api_url'] or 'not configured'}",
819
+ f"R2 bucket: {data['r2_bucket']}",
820
+ f"R2 index prefix: {data['r2_index_prefix']}",
821
+ f"R2 contribution prefix: {data['r2_contribution_prefix']}",
822
+ f"R2 seed prefix: {data['r2_seed_prefix']}",
823
+ f"Agent Training Contribution Mode: {training_contribution_mode_display(data['training_contribution_mode'])}",
605
824
  f"App Home: {data['app_home']}",
606
825
  ]
607
- if s.mentor_mode in {"model_assisted", "hybrid"} and not configured_model_provider_ready(s):
826
+ if s.apprenticeship_mode == "autonomous" and not configured_model_provider_ready(s):
608
827
  lines.extend(
609
828
  [
610
829
  "",
611
- "Model-assisted Mentor Mode needs a ready Mentor Model Provider.",
830
+ "Autonomous Apprenticeship needs a ready Mentor Model Provider.",
612
831
  "Run: apprentice configure model",
613
832
  ]
614
833
  )