agent-apprenticeship 0.1.8 → 0.2.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.
@@ -24,6 +24,7 @@ from .config import (
24
24
  from .env import redact_secrets
25
25
  from .io import append_jsonl, read_json, read_jsonl, write_json
26
26
  from .public_sanitizer import sanitize_public_obj
27
+ from .version import get_package_version
27
28
 
28
29
 
29
30
  def utc_now() -> str:
@@ -236,7 +237,7 @@ def ecosystem_item_from_bundle(
236
237
  str(metadata.get("economic_value_summary") or manifest.get("economic_value_summary") or "").strip()
237
238
  or f"{expected_value.title()} reuse value based on trace evidence, artifact coverage, and generated training rows."
238
239
  )
239
- package_version = str(manifest.get("package_version") or manifest.get("agent_apprenticeship_version") or "0.1.7")
240
+ package_version = str(manifest.get("package_version") or manifest.get("agent_apprenticeship_version") or get_package_version())
240
241
  loop_count = int(loop_manifest.get("loop_iteration_count") or manifest.get("loop_iteration_count") or 0)
241
242
  has_skill = (compiler_dir / "skill_pack").exists()
242
243
  has_training = (compiler_dir / "training_time_package").exists()
@@ -12,6 +12,7 @@ from typing import Any
12
12
  from .config import Settings, get_settings
13
13
  from .env import contains_secret
14
14
  from .io import append_jsonl, read_json, read_jsonl, write_json
15
+ from .version import get_package_version
15
16
 
16
17
 
17
18
  PACK_STATUSES = {"draft", "active", "reverted", "removed"}
@@ -697,6 +698,7 @@ def install_runtime_training_from_bundle(
697
698
  skill_md = skill_dir / "skill.md"
698
699
  if not skill_md.exists():
699
700
  raise FileNotFoundError(f"Runtime training skill.md not found in {bundle}")
701
+ quality = _runtime_training_quality(bundle)
700
702
  package_id = str(_read_manifest_field(bundle, "bundle_id") or _read_manifest_field(bundle, "source_package_id") or bundle.name)
701
703
  title = str(_read_manifest_field(bundle, "title") or _read_manifest_field(bundle, "source_task_id") or source_item_id)
702
704
  digest = hashlib.sha256(f"{source_item_id}|{package_id}|{skill_md.read_text(errors='replace')[:5000]}".encode()).hexdigest()[:10]
@@ -761,6 +763,7 @@ def install_runtime_training_from_bundle(
761
763
  "source_item_id": source_item_id,
762
764
  "source_package_id": package_id,
763
765
  "source_url": _read_manifest_field(bundle, "source_url"),
766
+ "package_version": get_package_version(),
764
767
  "agent_target": agent_target,
765
768
  "install_mode": "agent_apprenticeship_runtime",
766
769
  "installed_at": utc_now(),
@@ -770,12 +773,80 @@ def install_runtime_training_from_bundle(
770
773
  "backup_refs": [],
771
774
  "reversible": True,
772
775
  "generation_notes": ["native_agent_files_unchanged"],
776
+ "transfer_confidence": quality["transfer_confidence"],
777
+ "runtime_training_confidence": quality["transfer_confidence"],
778
+ "low_confidence_reasons": [quality["low_confidence_reason"]] if quality.get("low_confidence_reason") else [],
779
+ "source_quality_gate": quality,
773
780
  }
774
781
  write_json(target / "install_manifest.json", manifest)
775
782
  append_jsonl(installed_skills_index_path(s), manifest)
776
783
  return manifest
777
784
 
778
785
 
786
+ def _runtime_training_quality(bundle: Path) -> dict[str, Any]:
787
+ runtime = bundle / "experience_compiler" / "runtime_learning_package"
788
+ compiler = bundle / "experience_compiler"
789
+ has_actual = any((bundle / "attempts").glob("*/actual_outputs.json")) if (bundle / "attempts").exists() else False
790
+ has_artifacts = any((bundle / "attempts").glob("*/artifacts/*")) if (bundle / "attempts").exists() else False
791
+ required = {
792
+ "transfer_runbook": runtime / "transfer_runbook.md",
793
+ "minimal_prompt_context": runtime / "minimal_prompt_context.md",
794
+ "output_contract": runtime / "output_contract.json",
795
+ "artifact_contract": runtime / "artifact_contract.json",
796
+ "prefinal_verification_checklist": runtime / "prefinal_verification_checklist.json",
797
+ }
798
+ missing = [name for name, path in required.items() if not path.exists()]
799
+ evidence_map = compiler / "source_evidence_map.json"
800
+ source_score = _runtime_training_source_score(bundle)
801
+ checks = {
802
+ "actual_outputs_exist": has_actual,
803
+ "artifacts_exist": has_artifacts,
804
+ "source_evidence_map_exists": evidence_map.exists(),
805
+ "runtime_package_non_generic": bool((runtime / "transfer_runbook.md").exists() and len((runtime / "transfer_runbook.md").read_text(errors="replace")) > 500),
806
+ "required_runtime_contract_files_present": not missing,
807
+ "missing_runtime_contract_files": missing,
808
+ "source_score": source_score,
809
+ "source_score_passes_transfer_gate": source_score is None or source_score >= 0.7,
810
+ }
811
+ confidence = "high" if all(v for k, v in checks.items() if k != "missing_runtime_contract_files") else "low"
812
+ checks["transfer_confidence"] = confidence
813
+ reasons = list(missing)
814
+ if source_score is not None and source_score < 0.7:
815
+ reasons.append(f"source_score_below_transfer_gate:{source_score:.3f}")
816
+ checks["low_confidence_reason"] = "; ".join(reasons) if reasons else None
817
+ return checks
818
+
819
+
820
+ def _runtime_training_source_score(bundle: Path) -> float | None:
821
+ for rel in [
822
+ "grading/baseline_grader_result.json",
823
+ "evaluation/grader_result.json",
824
+ "evaluation/grader_results.json",
825
+ ]:
826
+ path = bundle / rel
827
+ if not path.exists():
828
+ continue
829
+ try:
830
+ payload = read_json(path)
831
+ except Exception:
832
+ continue
833
+ if isinstance(payload, dict):
834
+ value = payload.get("score")
835
+ try:
836
+ return float(value)
837
+ except Exception:
838
+ pass
839
+ jsonl = bundle / "evaluation/grader_results.jsonl"
840
+ if jsonl.exists():
841
+ for row in read_jsonl(jsonl):
842
+ if isinstance(row, dict) and row.get("score") is not None:
843
+ try:
844
+ return float(row.get("score"))
845
+ except Exception:
846
+ continue
847
+ return None
848
+
849
+
779
850
  def list_installed_skills(settings: Settings | None = None, include_disabled: bool = True) -> list[dict[str, Any]]:
780
851
  root = installed_skills_root(settings)
781
852
  rows: list[dict[str, Any]] = []
@@ -852,25 +923,45 @@ def select_runtime_training_for_run(
852
923
  if not enabled:
853
924
  return [], ""
854
925
  query_terms = {token.lower() for token in re.findall(r"[A-Za-z][A-Za-z0-9_-]{2,}", instruction)}
855
- scored: list[tuple[int, dict[str, Any]]] = []
926
+ scored: list[tuple[int, dict[str, Any], str]] = []
856
927
  for skill in enabled:
857
928
  overlap = len(query_terms & _skill_keywords(skill))
858
929
  if force or overlap:
859
- scored.append((overlap, skill))
930
+ confidence = skill.get("transfer_confidence") or (skill.get("source_quality_gate") or {}).get("transfer_confidence") or "unknown"
931
+ source_gate = skill.get("source_quality_gate") if isinstance(skill.get("source_quality_gate"), dict) else {}
932
+ source_score = source_gate.get("source_score")
933
+ quality_penalty = 0
934
+ reason_bits = ["matched runtime training against the task request"]
935
+ if confidence == "low":
936
+ quality_penalty -= 50
937
+ reason_bits.append("low-confidence source; use cautiously")
938
+ if isinstance(source_score, (int, float)) and float(source_score) < 0.7:
939
+ quality_penalty -= 50
940
+ reason_bits.append(f"source score {float(source_score):.2f} below transfer gate")
941
+ if force:
942
+ reason_bits.append("explicit --use-installed-skills requested")
943
+ scored.append((overlap + quality_penalty, skill, "; ".join(reason_bits)))
860
944
  scored.sort(key=lambda item: item[0], reverse=True)
861
- selected = [skill for _, skill in scored[:limit]]
945
+ selected = [dict(skill, runtime_training_selection_reason=reason) for _, skill, reason in scored[:limit]]
862
946
  if not selected:
863
947
  return [], ""
864
948
  sections = ["\n\n---\nInstalled Runtime Training\nUse these enabled skills only when they are relevant. The task instruction remains authoritative.\n"]
865
- for skill in selected:
949
+ for idx, skill in enumerate(selected):
866
950
  path = Path(str(skill.get("path")))
867
- skill_text = (path / "skill.md").read_text(errors="replace") if (path / "skill.md").exists() else ""
951
+ runtime_text, runtime_meta = _runtime_prompt_context(path)
952
+ skill = dict(skill)
953
+ skill.update(runtime_meta)
954
+ if runtime_text:
955
+ skill_text = runtime_text
956
+ else:
957
+ skill_text = (path / "skill.md").read_text(errors="replace") if (path / "skill.md").exists() else ""
868
958
  sections.append(
869
959
  f"\n## {skill.get('title') or skill.get('installed_skill_id')}\n"
870
960
  f"installed_skill_id: {skill.get('installed_skill_id')}\n"
871
- f"selection_reason: matched runtime training against the task request.\n\n"
961
+ f"selection_reason: {skill.get('runtime_training_selection_reason') or 'matched runtime training against the task request'}.\n\n"
872
962
  f"{skill_text[:5000]}\n"
873
963
  )
964
+ selected[idx] = skill
874
965
  sections.append("---\n")
875
966
  return selected, "\n".join(sections)
876
967
 
@@ -884,11 +975,117 @@ def installed_skill_run_refs(skills: list[dict[str, Any]]) -> list[dict[str, Any
884
975
  "title": skill.get("title"),
885
976
  "source_item_id": skill.get("source_item_id"),
886
977
  "source_package_id": skill.get("source_package_id"),
978
+ "runtime_training_injected_sections": skill.get("runtime_training_injected_sections") or [],
979
+ "runtime_training_context_char_count": skill.get("runtime_training_context_char_count") or 0,
980
+ "runtime_training_content_hash": skill.get("runtime_training_content_hash"),
981
+ "runtime_training_selection_reason": skill.get("runtime_training_selection_reason"),
982
+ "transfer_confidence": skill.get("transfer_confidence"),
983
+ "runtime_training_confidence": skill.get("runtime_training_confidence") or skill.get("transfer_confidence"),
984
+ "low_confidence_reasons": skill.get("low_confidence_reasons") or [],
985
+ "source_quality_gate": skill.get("source_quality_gate"),
986
+ "selected_transfer_runbook_path": skill.get("selected_transfer_runbook_path"),
987
+ "selected_transfer_checklist_path": skill.get("selected_transfer_checklist_path"),
988
+ "injected_minimal_prompt_context": bool(skill.get("injected_minimal_prompt_context")),
989
+ "injected_output_contract": bool(skill.get("injected_output_contract")),
990
+ "injected_artifact_contract": bool(skill.get("injected_artifact_contract")),
991
+ "injected_prefinal_checklist": bool(skill.get("injected_prefinal_checklist")),
992
+ "prefinal_verification_passed": skill.get("prefinal_verification_passed"),
993
+ "repair_loop_count": skill.get("repair_loop_count", 0),
994
+ "contract_failures_json": skill.get("contract_failures_json") or [],
995
+ "runtime_training_injection_mode": skill.get("runtime_training_injection_mode"),
887
996
  }
888
997
  )
889
998
  return refs
890
999
 
891
1000
 
1001
+ def _jsonl_preview(path: Path, limit: int = 8) -> str:
1002
+ if not path.exists() or not path.is_file():
1003
+ return ""
1004
+ lines: list[str] = []
1005
+ for line in path.read_text(errors="replace").splitlines():
1006
+ line = line.strip()
1007
+ if line:
1008
+ lines.append(line)
1009
+ if len(lines) >= limit:
1010
+ break
1011
+ return "\n".join(lines)
1012
+
1013
+
1014
+ def _runtime_prompt_context(skill_root: Path) -> tuple[str, dict[str, Any]]:
1015
+ """Return compact runtime-training context that is actually injected."""
1016
+ runtime = skill_root / "runtime_learning_package"
1017
+ injection_mode = (os.getenv("AA_RUNTIME_TRAINING_INJECTION_MODE") or "contract").strip().lower()
1018
+ if injection_mode == "full":
1019
+ ordered_files = [
1020
+ ("minimal_prompt_context.md", "minimal_prompt_context"),
1021
+ ("transfer_runbook.md", "transfer_runbook"),
1022
+ ("transfer_checklist.json", "transfer_checklist"),
1023
+ ("tool_execution_recipe.json", "tool_execution_recipe"),
1024
+ ("output_verification_steps.jsonl", "output_verification_steps"),
1025
+ ("common_errors_to_avoid.jsonl", "common_errors_to_avoid"),
1026
+ ("runtime_instructions.md", "runtime_instructions"),
1027
+ ]
1028
+ else:
1029
+ ordered_files = [
1030
+ ("minimal_prompt_context.md", "minimal_prompt_context"),
1031
+ ("output_contract.json", "output_contract"),
1032
+ ("artifact_contract.json", "artifact_contract"),
1033
+ ("prefinal_verification_checklist.json", "prefinal_verification_checklist"),
1034
+ ]
1035
+ parts: list[str] = []
1036
+ injected_sections: list[str] = []
1037
+ selected_runbook = None
1038
+ selected_checklist = None
1039
+ for rel, section in ordered_files:
1040
+ path = runtime / rel
1041
+ if not path.exists() or not path.is_file():
1042
+ continue
1043
+ if rel.endswith(".jsonl"):
1044
+ text = _jsonl_preview(path)
1045
+ elif rel.endswith(".json"):
1046
+ try:
1047
+ payload = read_json(path)
1048
+ text = json.dumps(payload, indent=2, sort_keys=True)
1049
+ except Exception:
1050
+ text = path.read_text(errors="replace")
1051
+ else:
1052
+ text = path.read_text(errors="replace")
1053
+ text = text.strip()
1054
+ if not text:
1055
+ continue
1056
+ if rel == "transfer_runbook.md":
1057
+ selected_runbook = f"runtime_learning_package/{rel}"
1058
+ if rel == "transfer_checklist.json":
1059
+ selected_checklist = f"runtime_learning_package/{rel}"
1060
+ injected_sections.append(section)
1061
+ section_limit = 2200 if injection_mode == "full" else 950
1062
+ parts.append(f"### {section.replace('_', ' ').title()}\n{text[:section_limit]}")
1063
+ if selected_runbook is None and (runtime / "transfer_runbook.md").exists():
1064
+ selected_runbook = "runtime_learning_package/transfer_runbook.md"
1065
+ if selected_checklist is None:
1066
+ if (runtime / "prefinal_verification_checklist.json").exists():
1067
+ selected_checklist = "runtime_learning_package/prefinal_verification_checklist.json"
1068
+ elif (runtime / "transfer_checklist.json").exists():
1069
+ selected_checklist = "runtime_learning_package/transfer_checklist.json"
1070
+ combined = "\n\n".join(parts)
1071
+ digest = hashlib.sha256(combined.encode()).hexdigest()[:16] if combined else None
1072
+ return combined, {
1073
+ "runtime_training_injected_sections": injected_sections,
1074
+ "runtime_training_context_char_count": len(combined),
1075
+ "runtime_training_content_hash": digest,
1076
+ "selected_transfer_runbook_path": selected_runbook,
1077
+ "selected_transfer_checklist_path": selected_checklist,
1078
+ "injected_minimal_prompt_context": "minimal_prompt_context" in injected_sections,
1079
+ "injected_output_contract": "output_contract" in injected_sections,
1080
+ "injected_artifact_contract": "artifact_contract" in injected_sections,
1081
+ "injected_prefinal_checklist": "prefinal_verification_checklist" in injected_sections,
1082
+ "prefinal_verification_passed": None,
1083
+ "repair_loop_count": 0,
1084
+ "contract_failures_json": [],
1085
+ "runtime_training_injection_mode": injection_mode,
1086
+ }
1087
+
1088
+
892
1089
  def write_before_after_result(pack_path: Path, result: dict[str, Any]) -> Path:
893
1090
  result = dict(result)
894
1091
  result["created_at"] = utc_now()
@@ -52,7 +52,7 @@ def _write_failed_tdo_reports(run_root: Path, settings: Settings, error: str) ->
52
52
  }
53
53
  write_json(tdo_dir / "tdo_report.json", report)
54
54
  write_json(tdo_dir / "tdo_manifest.json", {
55
- "schema_version": "aa-tdo-v0.1",
55
+ "schema_version": "aa-experience-compiler-v0.2",
56
56
  "tdo_id": report["tdo_id"],
57
57
  "tdo_status": "failed",
58
58
  "source_package_id": pkg.name,
@@ -73,7 +73,7 @@ def _write_failed_tdo_reports(run_root: Path, settings: Settings, error: str) ->
73
73
  "created_at": report["created_at"],
74
74
  })
75
75
  write_json(compiler_dir / "compiler_manifest.json", {
76
- "schema_version": "aa-experience-compiler-v0.1",
76
+ "schema_version": "aa-experience-compiler-v0.2",
77
77
  "compiler_status": "failed",
78
78
  "source_package_id": pkg.name,
79
79
  "compiler_output_refs": ["experience_compiler/compiler_report.json", "experience_compiler/compiler_manifest.json"],
@@ -994,11 +994,39 @@ def _runtime_training_session_fields(runtime_training_refs: list[dict] | None) -
994
994
  "installed_skill_refs": [],
995
995
  "installed_skill_ids": [],
996
996
  }
997
+ injected_sections: list[str] = []
998
+ confidence_values: list[str] = []
999
+ selection_reasons: list[str] = []
1000
+ total_chars = 0
1001
+ content_hashes: list[str] = []
1002
+ for ref in refs:
1003
+ for section in ref.get("runtime_training_injected_sections") or []:
1004
+ if section not in injected_sections:
1005
+ injected_sections.append(section)
1006
+ source_gate = ref.get("source_quality_gate") if isinstance(ref.get("source_quality_gate"), dict) else {}
1007
+ confidence = ref.get("runtime_training_confidence") or ref.get("transfer_confidence") or source_gate.get("transfer_confidence")
1008
+ if confidence:
1009
+ confidence_values.append(str(confidence))
1010
+ reason = ref.get("runtime_training_selection_reason") or ref.get("selection_reason")
1011
+ if reason:
1012
+ selection_reasons.append(str(reason))
1013
+ total_chars += int(ref.get("runtime_training_context_char_count") or 0)
1014
+ if ref.get("runtime_training_content_hash"):
1015
+ content_hashes.append(str(ref.get("runtime_training_content_hash")))
997
1016
  return {
998
1017
  "runtime_training_used": True,
999
1018
  "installed_skill_refs": refs,
1000
1019
  "installed_skill_ids": [ref.get("installed_skill_id") for ref in refs if ref.get("installed_skill_id")],
1001
- "runtime_training_selection_reason": "enabled installed runtime training matched this task or was explicitly requested",
1020
+ "runtime_training_selection_reason": "; ".join(selection_reasons) or "enabled installed runtime training matched this task or was explicitly requested",
1021
+ "runtime_training_confidence": "low" if "low" in confidence_values else "high" if confidence_values else "unknown",
1022
+ "runtime_training_injected_sections": injected_sections,
1023
+ "runtime_training_injected_char_count": total_chars,
1024
+ "runtime_training_content_hashes": content_hashes,
1025
+ "output_contract_used": "output_contract" in injected_sections,
1026
+ "artifact_contract_used": "artifact_contract" in injected_sections,
1027
+ "prefinal_verification_status": "not_run",
1028
+ "repair_loop_count": max((int(ref.get("repair_loop_count") or 0) for ref in refs), default=0),
1029
+ "contract_failures_json": [failure for ref in refs for failure in (ref.get("contract_failures_json") or [])],
1002
1030
  }
1003
1031
 
1004
1032
 
@@ -56,9 +56,9 @@ PUBLIC_TEXT_REPLACEMENTS=[
56
56
  ("T" + "DO", "Experience Compiler"),
57
57
  ("Honest " + "limitations", "Generation notes"),
58
58
  ("honest " + "limitations", "generation notes"),
59
- ("Limitations", "Generation Notes"),
60
- ("limitations", "generation notes"),
61
- ("limitation", "generation note"),
59
+ ("Limit" + "ations", "Generation Notes"),
60
+ ("limit" + "ations", "generation notes"),
61
+ ("limit" + "ation", "generation note"),
62
62
  ("Applic" + "ability", "Best Use"),
63
63
  ("applic" + "ability", "fit"),
64
64
  ("Required " + "Conditions", "Required Inputs"),