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
@@ -3,11 +3,12 @@ import json, re
3
3
  from pathlib import Path
4
4
  from typing import Any
5
5
  from pydantic import BaseModel, Field
6
- from .schemas import AgentTrace, ActualOutputs
6
+ from .schemas import AgentTrace, AgentTraceStep, ActualOutputs
7
7
  from .io import write_json
8
+ from .artifact_resolver import normalize_artifact_ref
8
9
 
9
- CANONICAL_TOP = {'schema_version','trace_id','collection_id','prior_trace_id','trace_mode','task','task_id','task_family_id','attempt_id','attempt_kind','attempt_status','agent_tools','started_at','ended_at','system_prompt','system_prompt_hash','skills','memory','agent_config','learning','termination_reason','steps','actual_outputs','artifacts','metadata_json'}
10
- CANONICAL_STEP = {'step','turn','actor','action','operation','tool','execution_mode','parallel_group','observation','input','input_source','output','state_change','reasoning','caused_by','causal_type','causal_note','alternatives_considered','success','step_outcome','error_type','error_message','message_role','feedback_type','feedback_content','started_at','ended_at','retry_of','artifact_refs','metadata_json'}
10
+ CANONICAL_TOP = {'schema_version','trace_id','collection_id','run_id','package_id','bundle_id','prior_trace_id','trace_mode','task','task_id','task_family_id','attempt_id','attempt_kind','attempt_status','agent_tools','started_at','ended_at','system_prompt','system_prompt_hash','skills','memory','agent_config','environment_domain','actual_outputs_ref','input_artifact_refs','artifact_refs','deliverable_refs','output_summary','final_output_summary','learning','termination_reason','steps','actual_outputs','artifacts','metadata_json'}
11
+ CANONICAL_STEP = set(AgentTraceStep.model_fields)
11
12
  VALID_OUTCOMES={'progress','neutral','blocked','failed','corrected','completed'}
12
13
  VALID_ACTIONS={'user_message','agent_step','output','error'}
13
14
  VALID_OPS={'plan','analyze','search','read','write','edit','execute','verify','download','install','ask_user','answer','select','grade','evaluate','revise','other'}
@@ -20,6 +21,14 @@ VALID_ATTEMPT_KINDS={'baseline','revised','apprentice_without_lessons','apprenti
20
21
  VALID_TRACE_MODES={'live','retraced','hybrid'}
21
22
  VALID_TERMINATION_REASONS={'task_complete','verifier_passed','verifier_failed','max_iterations_reached','agent_blocked','timeout','error_unrecoverable','partial_then_stopped','provider_usage_limit','other'}
22
23
  VALID_ATTEMPT_STATUSES={'completed','failed','blocked','fallback','partial'}
24
+ ENVIRONMENT_DOMAINS={'terminal','browser','computer_use','web','search','codebase','software','os','android','mcp','api','database','spreadsheet','file','multimodal','device','robotics','lab','industrial','physical','mixed','unknown'}
25
+ INTERACTION_SURFACES={'api','mcp_tool','mcp_resource','mcp_prompt','browser','computer_use','cli','file','codebase','database','spreadsheet','search','cloud_console','saas_app','async_job','queue','webhook','scheduler','messaging','email','collaboration','document','pdf','table','multimodal','device','industrial','physical','mixed','other_tool','other_surface','unknown'}
26
+ BASE_STEP_FIELDS={'step','turn','actor','action','operation','tool','environment_domain','execution_mode','parallel_group','observation','input','input_source','output','state_change','reasoning','caused_by','causal_type','causal_note','alternatives_considered','success','step_outcome','error_type','error_message','message_role','feedback_type','feedback_content','started_at','ended_at','retry_of','artifact_refs','metadata_json'}
27
+ SURFACE_STEP_FIELDS=CANONICAL_STEP-BASE_STEP_FIELDS
28
+ LIST_SURFACE_FIELDS={name for name in SURFACE_STEP_FIELDS if name.endswith('_refs') or name in {'modalities','connected_surfaces','depends_on_agent_outputs','formula_refs','pivot_refs','chart_refs','data_validation_refs','file_paths','selected_result_refs','citation_refs','attachment_refs','decision_refs','page_refs','section_refs','table_extract_refs','figure_refs','annotation_refs','safety_limit_refs','compliance_refs','cross_system_dependencies','handoff_refs','state_sync_refs','consistency_checks','evidence_refs','verification_refs','redaction_notes','missing_surface_fields'}}
29
+ INT_SURFACE_FIELDS={'http_status','retry_count','row_count','affected_row_count','column_count','result_count','timeout_seconds'}
30
+ FLOAT_SURFACE_FIELDS={'latency_ms'}
31
+ BOOL_SURFACE_FIELDS={'operator_override','human_supervisor_present','emergency_stop','followup_required'}
23
32
 
24
33
  class TraceNormalizationContext(BaseModel):
25
34
  task_id: str
@@ -74,6 +83,218 @@ def _as_list(v: Any) -> list[Any]:
74
83
  if isinstance(v, list): return v
75
84
  return [v]
76
85
 
86
+ def _normalize_ref(ref: Any) -> str:
87
+ value = normalize_artifact_ref(ref).replace("\\", "/").strip().lstrip("/")
88
+ while value.startswith("./"):
89
+ value = value[2:]
90
+ return value
91
+
92
+ def _normalize_ref_list(value: Any) -> list[str]:
93
+ refs: list[str] = []
94
+ for item in _as_list(value):
95
+ ref = _normalize_ref(item)
96
+ if ref and ref not in refs:
97
+ refs.append(ref)
98
+ return refs
99
+
100
+ def _actual_ref(attempt_kind: str) -> str:
101
+ return f"attempts/{attempt_kind}/actual_outputs.json"
102
+
103
+ def _input_source(value: Any, meta: dict[str, Any]) -> dict[str, Any] | None:
104
+ if isinstance(value, dict):
105
+ return value
106
+ if isinstance(value, str) and value.strip():
107
+ return {"source": value.strip()}
108
+ if value is not None:
109
+ meta["original_input_source"] = value
110
+ return None
111
+
112
+ def _infer_environment_domain(tool: Any, operation: Any = None, input_value: Any = None, output_value: Any = None) -> str:
113
+ text = " ".join(str(x or "") for x in [tool, operation, input_value, output_value]).lower()
114
+ if any(x in text for x in ["mcp__", "mcp ", "model context protocol"]):
115
+ return "mcp"
116
+ if any(x in text for x in ["robot", "robotics", "actuator", "telemetry", "sensor", "calibration", "emergency stop"]):
117
+ return "robotics" if "robot" in text else "physical"
118
+ if any(x in text for x in ["industrial", "manufacturing", "plc", "scada", "field device", "lab instrument"]):
119
+ return "industrial"
120
+ if any(x in text for x in ["browser", "playwright", "screenshot", "dom", "page.goto"]):
121
+ return "browser"
122
+ if any(x in text for x in ["computer_use", "desktop app", "ui automation", "accessibility tree"]):
123
+ return "computer_use"
124
+ if any(x in text for x in ["http://", "https://", "fetch", "curl ", "web "]):
125
+ return "web"
126
+ if any(x in text for x in ["search", "web_search"]):
127
+ return "search"
128
+ if any(x in text for x in ["sqlite", "postgres", "mysql", "sql ", "database", "db "]):
129
+ return "database"
130
+ if any(x in text for x in ["xlsx", "xls", "spreadsheet", "workbook", "csv"]):
131
+ return "spreadsheet"
132
+ if any(x in text for x in ["pdf", "document", "ocr", "transcript", "image", "video", "audio", "screenshot"]):
133
+ return "multimodal"
134
+ if any(x in text for x in ["android", "adb "]):
135
+ return "android"
136
+ if any(x in text for x in ["api", "json endpoint", "graphql", "rest "]):
137
+ return "api"
138
+ if any(x in text for x in ["apply_patch", "pytest", "compileall", "git ", "npm ", "node ", "python", "rg ", "grep ", "codebase"]):
139
+ return "codebase"
140
+ if any(x in text for x in ["bash", "zsh", "shell", "terminal", "subprocess", "command"]):
141
+ return "terminal"
142
+ if any(x in text for x in ["file_read", "file_write", "read_file", "write_file", "input/", "artifacts/"]):
143
+ return "file"
144
+ if any(x in text for x in ["os.", "filesystem", "mkdir", "cp ", "mv ", "rm "]):
145
+ return "os"
146
+ return "unknown"
147
+
148
+
149
+ def _infer_interaction_surface(raw: dict[str, Any], tool: Any, operation: Any, input_value: Any, output_value: Any) -> str:
150
+ explicit = raw.get("interaction_surface")
151
+ if explicit in INTERACTION_SURFACES:
152
+ return explicit
153
+ text = " ".join(str(x or "") for x in [tool, operation, input_value, output_value, raw.get("surface_type"), raw.get("app_name")]).lower()
154
+ if any(x in text for x in ["mcp__", "mcp tool", "mcp_tool"]):
155
+ return "mcp_tool"
156
+ if any(x in text for x in ["mcp resource", "mcp_resource"]):
157
+ return "mcp_resource"
158
+ if any(x in text for x in ["mcp prompt", "mcp_prompt"]):
159
+ return "mcp_prompt"
160
+ if any(x in text for x in ["curl ", "http ", "https://", "graphql", "rest api", "sdk", "api "]):
161
+ return "api"
162
+ if any(x in text for x in ["sql", "postgres", "mysql", "sqlite", "database", "db "]):
163
+ return "database"
164
+ if any(x in text for x in ["xlsx", "workbook", "spreadsheet", "sheet_name"]):
165
+ return "spreadsheet"
166
+ if any(x in text for x in ["csv", "table", "dataframe"]):
167
+ return "table"
168
+ if any(x in text for x in ["browser", "playwright", "dom", "page.", "url"]):
169
+ return "browser"
170
+ if any(x in text for x in ["desktop app", "computer_use", "accessibility tree", "screenshot"]):
171
+ return "computer_use"
172
+ if any(x in text for x in ["web_search", "search", "query"]):
173
+ return "search"
174
+ if any(x in text for x in ["cloud console", "aws console", "gcp console", "azure portal"]):
175
+ return "cloud_console"
176
+ if any(x in text for x in ["saas", "crm", "ticket", "tenant"]):
177
+ return "saas_app"
178
+ if any(x in text for x in ["webhook", "callback"]):
179
+ return "webhook"
180
+ if any(x in text for x in ["queue", "message_id", "dead letter"]):
181
+ return "queue"
182
+ if any(x in text for x in ["cron", "scheduler", "schedule"]):
183
+ return "scheduler"
184
+ if any(x in text for x in ["async job", "job_id", "polling"]):
185
+ return "async_job"
186
+ if any(x in text for x in ["email", "smtp", "inbox"]):
187
+ return "email"
188
+ if any(x in text for x in ["slack", "teams", "discord", "message", "thread"]):
189
+ return "messaging"
190
+ if any(x in text for x in ["comment", "collaboration", "shared doc"]):
191
+ return "collaboration"
192
+ if "pdf" in text:
193
+ return "pdf"
194
+ if any(x in text for x in ["document", "docx", "ocr", "report"]):
195
+ return "document"
196
+ if any(x in text for x in ["image", "video", "audio", "multimodal"]):
197
+ return "multimodal"
198
+ if any(x in text for x in ["robot", "industrial", "sensor", "actuator", "telemetry", "device"]):
199
+ return "industrial" if "industrial" in text else "device"
200
+ if any(x in text for x in ["apply_patch", "git ", "pytest", "npm ", "python", "node ", "rg ", "bash", "shell", "command"]):
201
+ return "cli"
202
+ if any(x in text for x in ["file_read", "file_write", "read_file", "write_file", "artifact", "input/"]):
203
+ return "file"
204
+ if any(x in text for x in ["codebase", "test", "build", "lint", "compile"]):
205
+ return "codebase"
206
+ return "unknown"
207
+
208
+
209
+ def _surface_required_fields(surface: str) -> list[str]:
210
+ return {
211
+ "api": ["api_endpoint", "api_method", "http_status"],
212
+ "mcp_tool": ["mcp_server", "mcp_tool_name", "mcp_result_ref"],
213
+ "mcp_resource": ["mcp_server", "mcp_resource_uri", "mcp_result_ref"],
214
+ "mcp_prompt": ["mcp_server", "mcp_prompt_name", "mcp_result_ref"],
215
+ "database": ["database_type", "query_ref", "table_name"],
216
+ "spreadsheet": ["workbook_ref", "sheet_name", "cell_range"],
217
+ "table": ["table_ref", "row_count", "column_count"],
218
+ "cli": ["command", "exit_code", "stdout_ref"],
219
+ "file": ["file_path", "file_operation"],
220
+ "codebase": ["command", "test_result_ref", "diff_ref"],
221
+ "browser": ["visible_state_ref", "screenshot_ref", "element_selector", "action_type"],
222
+ "computer_use": ["visible_state_ref", "screenshot_ref", "action_type"],
223
+ "async_job": ["job_id", "job_status_after", "async_result_ref"],
224
+ "queue": ["queue_name", "message_id", "delivery_status"],
225
+ "webhook": ["webhook_id", "callback_status", "async_result_ref"],
226
+ "scheduler": ["schedule_id", "trigger_type", "job_status_after"],
227
+ "device": ["device_type", "telemetry_ref", "safety_state"],
228
+ "industrial": ["physical_environment_type", "telemetry_ref", "safety_state"],
229
+ "physical": ["physical_environment_type", "physical_state_after_ref", "safety_state"],
230
+ }.get(surface, [])
231
+
232
+
233
+ def _normalize_surface_value(field: str, value: Any, meta: dict[str, Any]) -> Any:
234
+ if field in LIST_SURFACE_FIELDS:
235
+ if field.endswith("_refs") or field in {"file_paths", "selected_result_refs", "citation_refs", "attachment_refs", "decision_refs", "page_refs", "section_refs", "table_extract_refs", "figure_refs", "annotation_refs", "safety_limit_refs", "compliance_refs", "handoff_refs", "state_sync_refs", "evidence_refs", "verification_refs"}:
236
+ return _normalize_ref_list(value)
237
+ return [str(v) for v in _as_list(value) if v not in (None, "")]
238
+ if field.endswith("_ref"):
239
+ return _normalize_ref(value)
240
+ if field in INT_SURFACE_FIELDS:
241
+ try:
242
+ return int(value)
243
+ except (TypeError, ValueError):
244
+ meta.setdefault("surface_field_parse_warnings", []).append(f"{field}_not_integer")
245
+ return None
246
+ if field in FLOAT_SURFACE_FIELDS:
247
+ try:
248
+ return float(value)
249
+ except (TypeError, ValueError):
250
+ meta.setdefault("surface_field_parse_warnings", []).append(f"{field}_not_float")
251
+ return None
252
+ if field in BOOL_SURFACE_FIELDS:
253
+ if isinstance(value, bool):
254
+ return value
255
+ if isinstance(value, str) and value.lower() in {"true", "false", "yes", "no", "1", "0"}:
256
+ return value.lower() in {"true", "yes", "1"}
257
+ return None
258
+ return _string_or_none(value, meta, field)
259
+
260
+
261
+ def _surface_completeness(step: dict[str, Any], meta: dict[str, Any]) -> tuple[str, list[str]]:
262
+ surface = step.get("interaction_surface")
263
+ if not surface or surface in {"unknown", "other_tool", "other_surface"}:
264
+ return "not_applicable", []
265
+ required = _surface_required_fields(surface)
266
+ missing = [name for name in required if step.get(name) in (None, "", [], {})]
267
+ if surface == "cli":
268
+ if "command" in missing and step.get("input"):
269
+ missing.remove("command")
270
+ if "stdout_ref" in missing and step.get("output"):
271
+ meta.setdefault("surface_evidence_substitutions", []).append("cli_stdout_ref_satisfied_by_output")
272
+ missing.remove("stdout_ref")
273
+ if "exit_code" in missing and step.get("success") is not None:
274
+ meta.setdefault("surface_evidence_substitutions", []).append("cli_exit_code_satisfied_by_success_signal")
275
+ missing.remove("exit_code")
276
+ if surface == "file":
277
+ if "file_path" in missing and (step.get("artifact_refs") or step.get("input")):
278
+ missing.remove("file_path")
279
+ if "file_operation" in missing and step.get("operation"):
280
+ missing.remove("file_operation")
281
+ if surface == "codebase":
282
+ if "command" in missing and step.get("input"):
283
+ missing.remove("command")
284
+ if "test_result_ref" in missing and step.get("output"):
285
+ meta.setdefault("surface_evidence_substitutions", []).append("codebase_test_result_ref_satisfied_by_output")
286
+ missing.remove("test_result_ref")
287
+ if "diff_ref" in missing and (step.get("artifact_refs") or step.get("state_change")):
288
+ meta.setdefault("surface_evidence_substitutions", []).append("codebase_diff_ref_satisfied_by_artifact_or_state_change")
289
+ missing.remove("diff_ref")
290
+ if not required:
291
+ return "partial" if step.get("output") or step.get("state_change") or step.get("artifact_refs") else "unavailable", []
292
+ if not missing:
293
+ return "complete", []
294
+ if step.get("surface_capture_notes") or meta.get("surface_capture_unavailable_reason"):
295
+ return "unavailable", missing
296
+ return "partial", missing
297
+
77
298
  def _operation(raw_action: str | None, raw_op: str | None, meta: dict[str,Any], report: TraceNormalizationReport | None = None) -> str:
78
299
  val=str(raw_op or raw_action or '').strip()
79
300
  mapped=OP_MAP.get(val, val if val in VALID_OPS else 'other')
@@ -221,7 +442,7 @@ def _operation_from_phrase(value: str | None) -> str | None:
221
442
  return "execute"
222
443
 
223
444
  read_markers = (
224
- "read", "inspect", "inventory", "list", "listing", "file listing",
445
+ "read", "inspect", "inventory", "list", ("list" + "ing"), ("file " + "list" + "ing"),
225
446
  "file inspection", "read context", "inspect task", "inspect inputs",
226
447
  "inspect workspace", "load", "loaded", "sed", "cat", "head", "tail",
227
448
  "less", "ls", "find", "rg", "grep", "git status", "unzip",
@@ -242,7 +463,7 @@ def _operation_from_command(command: str | None, meta: dict[str, Any], report: T
242
463
  verify_markers=['json.tool','pytest','compileall','schema','validate','validation','validator',' check','check ','checksum','sha256sum','shasum','md5sum','openpyxl','xlsx validation','csv validation','json validation','markdown validation','test ']
243
464
  write_markers=['apply_patch','file edit','edit file','write artifact','write_artifact','create file','file creation','write','create','edit','patch','builder script','write final trace','persist','save']
244
465
  execute_markers=['execute','run','rerun','re run','executed','script execution','builder','python','python3','node','npm','bash','sh','calculation script','environment setup','symlink','ln -s']
245
- read_markers=['read','inspect','inventory','list','listing','file listing','file inspection','read context','inspect task','inspect inputs','inspect workspace','load','loaded','sed','cat','head','tail','less','ls','find','rg','grep','git status','unzip']
466
+ read_markers=['read','inspect','inventory','list',('list' + 'ing'),('file ' + 'list' + 'ing'),'file inspection','read context','inspect task','inspect inputs','inspect workspace','load','loaded','sed','cat','head','tail','less','ls','find','rg','grep','git status','unzip']
246
467
  if any(m in low for m in verify_markers) or any(f in {'pytest','py.test'} for f in firsts):
247
468
  return 'verify'
248
469
  if 'git status' in low:
@@ -251,7 +472,7 @@ def _operation_from_command(command: str | None, meta: dict[str, Any], report: T
251
472
  return 'write'
252
473
  if any(f in {'sed','cat','head','tail','less','ls','find','rg','grep','file','pwd'} for f in firsts):
253
474
  return 'read'
254
- if any('unzip -l' in p or 'zipinfo' in p for p in parts) or ' inventory' in low or 'listing' in low:
475
+ if any('unzip -l' in p or 'zipinfo' in p for p in parts) or ' inventory' in low or ('list' + 'ing') in low:
255
476
  return 'read'
256
477
  if any(f in {'mkdir','cp','mv','touch','tee'} for f in firsts):
257
478
  return 'write'
@@ -312,9 +533,22 @@ def normalize_trace_for_export(trace: dict[str, Any], report: dict[str, Any] | N
312
533
  op=_operation_from_command(str(command) if command is not None else None, meta)
313
534
  if op and (not ns.get('operation') or ns.get('operation') == 'other'):
314
535
  ns['operation']=op
536
+ if not ns.get("environment_domain"):
537
+ ns["environment_domain"] = _infer_environment_domain(ns.get("tool"), ns.get("operation"), ns.get("input"), ns.get("output"))
538
+ if ns.get("artifact_refs"):
539
+ ns["artifact_refs"] = _normalize_ref_list(ns.get("artifact_refs"))
315
540
  ns['metadata_json']=meta
316
541
  steps.append(ns)
317
542
  out['steps']=steps
543
+ actual = out.get("actual_outputs") if isinstance(out.get("actual_outputs"), dict) else {}
544
+ attempt_kind = str(out.get("attempt_kind") or actual.get("attempt_kind") or "baseline")
545
+ if actual:
546
+ out.setdefault("actual_outputs_ref", _actual_ref(attempt_kind))
547
+ out.setdefault("output_summary", actual.get("output_summary"))
548
+ out.setdefault("final_output_summary", actual.get("output_summary"))
549
+ out.setdefault("artifact_refs", _normalize_ref_list(actual.get("artifact_refs") or actual.get("files_created")))
550
+ out.setdefault("deliverable_refs", _normalize_ref_list(actual.get("deliverable_refs")))
551
+ out.setdefault("input_artifact_refs", _normalize_ref_list(actual.get("input_artifact_refs")))
318
552
  out['attempt_status']=infer_attempt_status(out, report)
319
553
  return out
320
554
 
@@ -453,6 +687,7 @@ def _normalize_step(raw_step: Any, ordinal: int, used_steps: set[int], report: T
453
687
  if raw.get('artifact_paths') is not None:
454
688
  meta['original_artifact_paths']=raw.get('artifact_paths')
455
689
  artifact_refs.extend(str(x) for x in _as_list(raw.get('artifact_paths')))
690
+ artifact_refs = _normalize_ref_list(artifact_refs)
456
691
  if raw.get('exit_code') is not None: meta['exit_code']=raw.get('exit_code')
457
692
  if raw.get('decision_summary') is not None: meta['decision_summary']=raw.get('decision_summary')
458
693
 
@@ -481,20 +716,81 @@ def _normalize_step(raw_step: Any, ordinal: int, used_steps: set[int], report: T
481
716
 
482
717
  actor=raw.get('actor') or ('user' if action == 'user_message' else 'agent:worker')
483
718
  if action == 'user_message': actor='user'
719
+ raw_execution_mode = raw.get('execution_mode') if raw.get('execution_mode') in {'serial','parallel'} else None
720
+ execution_mode = None if action == 'user_message' else (raw_execution_mode or 'serial')
721
+ parallel_group = _string_or_none(raw.get('parallel_group'), meta, 'parallel_group') if execution_mode == 'parallel' else None
722
+ tool = None if action=='user_message' else _tool(command if command is not None else input_val, raw, op)
723
+ environment_domain = None if action == 'user_message' else (
724
+ raw.get('environment_domain') if raw.get('environment_domain') in ENVIRONMENT_DOMAINS else _infer_environment_domain(tool, op, input_val, output_val)
725
+ )
726
+ interaction_surface = None if action == "user_message" else _infer_interaction_surface(raw, tool, op, input_val, output_val)
727
+ feedback_type = raw.get('feedback_type') if raw.get('feedback_type') in {'correction','approval','clarification','new_instruction','other'} else None
728
+ feedback_content = _string_or_none(raw.get('feedback_content'), meta, 'feedback_content')
484
729
  step={
485
730
  'step':step_num,'turn':_safe_int(raw.get('turn'), 1, 'turn', meta, report),'actor':actor,'action':action,
486
- 'operation': None if action=='user_message' else op,'tool': None if action=='user_message' else _tool(command if command is not None else input_val, raw, op),
487
- 'execution_mode':raw.get('execution_mode') if raw.get('execution_mode') in {'serial','parallel'} else None,'parallel_group':_string_or_none(raw.get('parallel_group'), meta, 'parallel_group'),'observation':None if action=='user_message' else observation,
488
- 'input':input_val,'input_source':raw.get('input_source') if isinstance(raw.get('input_source'), dict) else None,'output':None if action=='user_message' else output_val,
731
+ 'operation': None if action=='user_message' else op,'tool': tool,
732
+ 'environment_domain': environment_domain,
733
+ 'execution_mode':execution_mode,'parallel_group':parallel_group,'observation':None if action=='user_message' else observation,
734
+ 'input':input_val,'input_source':_input_source(raw.get('input_source'), meta),'output':None if action=='user_message' else output_val,
489
735
  'state_change':state_change,'reasoning':None if action=='user_message' else reasoning,
490
736
  'caused_by':caused_by,'causal_type':causal_type,'causal_note':causal_note,'alternatives_considered':_string_or_none(raw.get('alternatives_considered'), meta, 'alternatives_considered'),
491
737
  'success':None if action=='user_message' else raw.get('success') if isinstance(raw.get('success'), bool) else None,'step_outcome':None if action=='user_message' else _normalize_outcome(raw.get('step_outcome'), meta),
492
738
  'error_type':_string_or_none(raw.get('error_type'), meta, 'error_type'),'error_message':_string_or_none(raw.get('error_message'), meta, 'error_message'),'message_role':raw.get('message_role') if action=='user_message' else None,
493
- 'feedback_type':raw.get('feedback_type') if action=='user_message' else None,'feedback_content':_string_or_none(raw.get('feedback_content'), meta, 'feedback_content') if action=='user_message' else None,
739
+ 'feedback_type':feedback_type,'feedback_content':feedback_content,
494
740
  'started_at':_string_or_none(raw.get('started_at'), meta, 'started_at'),'ended_at':_string_or_none(raw.get('ended_at'), meta, 'ended_at'),'retry_of':raw.get('retry_of') if isinstance(raw.get('retry_of'), int) else None,'artifact_refs':artifact_refs,'metadata_json':meta,
495
741
  }
742
+ if action != "user_message":
743
+ step["interaction_surface"] = interaction_surface
744
+ if interaction_surface and interaction_surface != "unknown" and environment_domain == "unknown":
745
+ if interaction_surface in {"cli"}:
746
+ step["environment_domain"] = "terminal"
747
+ elif interaction_surface in {"mcp_tool", "mcp_resource", "mcp_prompt"}:
748
+ step["environment_domain"] = "mcp"
749
+ elif interaction_surface in {"cloud_console", "saas_app"}:
750
+ step["environment_domain"] = "software"
751
+ elif interaction_surface in {"document", "pdf", "table"}:
752
+ step["environment_domain"] = "multimodal" if interaction_surface in {"document", "pdf"} else "spreadsheet"
753
+ elif interaction_surface in {"device", "industrial", "physical"}:
754
+ step["environment_domain"] = "industrial" if interaction_surface == "industrial" else interaction_surface
755
+ elif interaction_surface in ENVIRONMENT_DOMAINS:
756
+ step["environment_domain"] = interaction_surface
757
+ for field in SURFACE_STEP_FIELDS:
758
+ if field in {"interaction_surface", "surface_capture_status", "missing_surface_fields", "surface_capture_notes"}:
759
+ continue
760
+ if field in raw:
761
+ value = _normalize_surface_value(field, raw.get(field), meta)
762
+ if value not in (None, "", [], {}):
763
+ step[field] = value
764
+ if command is not None and not step.get("command"):
765
+ step["command"] = str(command)
766
+ elif interaction_surface == "cli" and input_val and not step.get("command"):
767
+ step["command"] = input_val
768
+ if interaction_surface == "file":
769
+ if not step.get("file_path") and artifact_refs:
770
+ step["file_path"] = artifact_refs[0]
771
+ if not step.get("file_operation") and op:
772
+ step["file_operation"] = op
773
+ if raw.get("exit_code") is not None and step.get("exit_code") is None:
774
+ try:
775
+ step["exit_code"] = int(raw.get("exit_code"))
776
+ except (TypeError, ValueError):
777
+ pass
778
+ if raw.get("stdout_ref") is None and raw.get("stdout_summary") is not None:
779
+ meta.setdefault("surface_stdout_summary", raw.get("stdout_summary"))
780
+ if step.get("environment_domain") == "mixed" and not step.get("connected_surfaces") and interaction_surface:
781
+ step["connected_surfaces"] = [interaction_surface]
782
+ status, missing = _surface_completeness(step, meta)
783
+ if step.get("surface_capture_status") is None:
784
+ step["surface_capture_status"] = status
785
+ if status in {"partial", "unavailable"} and missing and not step.get("surface_capture_notes"):
786
+ step["surface_capture_notes"] = f"{interaction_surface} surface evidence is incomplete; missing fields were recorded without fabrication."
787
+ if missing and not step.get("missing_surface_fields"):
788
+ step["missing_surface_fields"] = missing
789
+ meta.setdefault("trace_quality_warnings", []).append(f"{interaction_surface}_missing_surface_fields:{','.join(missing)}")
790
+ if step.get("success") is True and not any(step.get(name) for name in ["output", "state_change", "artifact_refs", "evidence_refs", "raw_response_ref", "response_payload_ref", "screenshot_ref", "visible_state_ref", "telemetry_ref"]):
791
+ meta.setdefault("trace_quality_warnings", []).append("successful_step_missing_output_state_or_evidence")
496
792
  if action == 'user_message':
497
- step.update({'operation':None,'tool':None,'execution_mode':None,'observation':None,'reasoning':None,'success':None,'step_outcome':None,'output':None})
793
+ step.update({'operation':None,'tool':None,'environment_domain':None,'execution_mode':None,'observation':None,'reasoning':None,'success':None,'step_outcome':None,'output':None})
498
794
  return step
499
795
 
500
796
  def _model_dump(obj: Any) -> dict[str, Any]:
@@ -588,18 +884,50 @@ def normalize_agent_trace(raw: dict[str,Any], context: TraceNormalizationContext
588
884
  for i,s in enumerate(steps, 1):
589
885
  s['metadata_json'].setdefault('original_step_number', s['step']); s['step']=i
590
886
  report.warnings.append('steps renumbered to canonical 1..N order')
591
- success_like=bool(raw.get('actual_outputs') or any(s.get('success') for s in steps))
887
+ actual_outputs = _valid_actual_outputs(raw.get('actual_outputs'), context, metadata)
888
+ success_like=bool(actual_outputs or raw.get('actual_outputs') or any(s.get('success') for s in steps))
592
889
  metadata.update({'raw_trace_preserved':True,'discarded_step_count':0,'trace_normalization_status':'normalized' if not report.warnings else 'partial'})
890
+ attempt_kind = _canonical_attempt_kind(raw, context, metadata)
891
+ raw_agent_config = raw.get('agent_config') if isinstance(raw.get('agent_config'), dict) else None
892
+ actual_artifact_refs = _normalize_ref_list((actual_outputs or {}).get('artifact_refs') or (actual_outputs or {}).get('files_created'))
893
+ actual_deliverable_refs = _normalize_ref_list((actual_outputs or {}).get('deliverable_refs'))
894
+ input_artifact_refs = _normalize_ref_list(raw.get('input_artifact_refs') or (actual_outputs or {}).get('input_artifact_refs'))
895
+ environment_domains = {
896
+ s.get("environment_domain")
897
+ for s in steps
898
+ if s.get("environment_domain") and s.get("environment_domain") != "unknown"
899
+ }
900
+ top_environment_domain = raw.get("environment_domain") if raw.get("environment_domain") in ENVIRONMENT_DOMAINS else (
901
+ environment_domains.pop() if len(environment_domains) == 1 else ("mixed" if environment_domains else "unknown")
902
+ )
903
+ missing_runtime_fields = []
904
+ for field in ("run_id", "package_id", "bundle_id", "started_at", "ended_at", "agent_config"):
905
+ if raw.get(field) in (None, "", {}):
906
+ missing_runtime_fields.append(field)
907
+ if missing_runtime_fields:
908
+ metadata.setdefault("missing_runtime_fields", missing_runtime_fields)
593
909
  trace={
594
910
  'schema_version': str(raw.get('schema_version') or 'aa-trace-v0.1'),
595
911
  'trace_id': str(raw.get('trace_id') or f'{context.trace_id_prefix}_{context.attempt_id}_normalized'),
596
- 'collection_id': _string_or_none(raw.get('collection_id'), metadata, 'collection_id'), 'prior_trace_id': _string_or_none(raw.get('prior_trace_id'), metadata, 'prior_trace_id'), 'trace_mode': _canonical_trace_mode(raw, metadata),
912
+ 'collection_id': _string_or_none(raw.get('collection_id'), metadata, 'collection_id'),
913
+ 'run_id': _string_or_none(raw.get('run_id'), metadata, 'run_id'),
914
+ 'package_id': _string_or_none(raw.get('package_id'), metadata, 'package_id'),
915
+ 'bundle_id': _string_or_none(raw.get('bundle_id'), metadata, 'bundle_id'),
916
+ 'prior_trace_id': _string_or_none(raw.get('prior_trace_id'), metadata, 'prior_trace_id'), 'trace_mode': _canonical_trace_mode(raw, metadata),
597
917
  'task': _string_or_none(raw.get('task'), metadata, 'task') or context.task, 'task_id': _string_or_none(raw.get('task_id'), metadata, 'task_id') or context.task_id, 'task_family_id': _string_or_none(raw.get('task_family_id'), metadata, 'task_family_id'),
598
- 'attempt_id': _string_or_none(raw.get('attempt_id'), metadata, 'attempt_id') or context.attempt_id, 'attempt_kind': _canonical_attempt_kind(raw, context, metadata),
918
+ 'attempt_id': _string_or_none(raw.get('attempt_id'), metadata, 'attempt_id') or context.attempt_id, 'attempt_kind': attempt_kind,
599
919
  'agent_tools': _canonical_agent_tools(raw, metadata),
600
- 'started_at': _string_or_none(raw.get('started_at'), metadata, 'started_at'), 'ended_at': _string_or_none(raw.get('ended_at'), metadata, 'ended_at'), 'system_prompt': _string_or_none(raw.get('system_prompt'), metadata, 'system_prompt'), 'system_prompt_hash': _string_or_none(raw.get('system_prompt_hash'), metadata, 'system_prompt_hash'), 'skills': raw.get('skills') if isinstance(raw.get('skills'), list) else None, 'memory': _string_or_none(raw.get('memory'), metadata, 'memory'), 'agent_config': raw.get('agent_config') if isinstance(raw.get('agent_config'), dict) else None, 'learning': _string_or_none(raw.get('learning'), metadata, 'learning'),
920
+ 'started_at': _string_or_none(raw.get('started_at'), metadata, 'started_at'), 'ended_at': _string_or_none(raw.get('ended_at'), metadata, 'ended_at'), 'system_prompt': _string_or_none(raw.get('system_prompt'), metadata, 'system_prompt'), 'system_prompt_hash': _string_or_none(raw.get('system_prompt_hash'), metadata, 'system_prompt_hash'), 'skills': raw.get('skills') if isinstance(raw.get('skills'), list) else None, 'memory': _string_or_none(raw.get('memory'), metadata, 'memory'), 'agent_config': raw_agent_config,
921
+ 'environment_domain': top_environment_domain,
922
+ 'actual_outputs_ref': _string_or_none(raw.get('actual_outputs_ref'), metadata, 'actual_outputs_ref') or _actual_ref(attempt_kind),
923
+ 'input_artifact_refs': input_artifact_refs,
924
+ 'artifact_refs': _normalize_ref_list(raw.get('artifact_refs')) or actual_artifact_refs,
925
+ 'deliverable_refs': _normalize_ref_list(raw.get('deliverable_refs')) or actual_deliverable_refs,
926
+ 'output_summary': _string_or_none(raw.get('output_summary'), metadata, 'output_summary') or (actual_outputs or {}).get('output_summary'),
927
+ 'final_output_summary': _string_or_none(raw.get('final_output_summary'), metadata, 'final_output_summary') or (actual_outputs or {}).get('output_summary'),
928
+ 'learning': _string_or_none(raw.get('learning'), metadata, 'learning'),
601
929
  'termination_reason': _canonical_termination_reason(raw, success_like, metadata),
602
- 'steps': steps, 'actual_outputs': _valid_actual_outputs(raw.get('actual_outputs'), context, metadata), 'artifacts': _canonical_artifacts(raw, metadata), 'metadata_json': metadata,
930
+ 'steps': steps, 'actual_outputs': actual_outputs, 'artifacts': _canonical_artifacts(raw, metadata), 'metadata_json': metadata,
603
931
  }
604
932
  trace['attempt_status']=infer_attempt_status(trace)
605
933
  report.raw_step_count=len(raw_steps); report.normalized_step_count=len(steps); report.discarded_step_count=0
@@ -628,7 +956,12 @@ def _validate_normalized_trace(trace: dict[str, Any]) -> tuple[bool, list[str]]:
628
956
  def repair_agent_trace_file(raw_trace_path: Path, context: TraceNormalizationContext) -> NormalizationResult:
629
957
  report=TraceNormalizationReport(task_id=context.task_id, attempt_id=context.attempt_id, attempt_kind=context.attempt_kind, raw_trace_ref=raw_trace_path.name)
630
958
  try:
631
- raw=json.loads(raw_trace_path.read_text())
959
+ text = raw_trace_path.read_text()
960
+ try:
961
+ raw=json.loads(text)
962
+ except json.JSONDecodeError as strict_error:
963
+ raw=json.loads(text, strict=False)
964
+ report.warnings.append(f"raw trace parsed with permissive JSON control-character handling: {strict_error}")
632
965
  if isinstance(raw, list):
633
966
  raw={'steps': raw, 'metadata_json': {'raw_trace_shape': 'list', 'raw_trace_list': raw}}
634
967
  elif isinstance(raw, dict):
@@ -649,6 +982,7 @@ def repair_agent_trace_file(raw_trace_path: Path, context: TraceNormalizationCon
649
982
  raw_step_count = len(raw_steps) if isinstance(raw_steps, list) else 0
650
983
  normalized_step_count = len(trace.get('steps') or [])
651
984
  comp=TraceNormalizationReport(task_id=context.task_id, attempt_id=context.attempt_id, attempt_kind=context.attempt_kind, raw_trace_ref=raw_trace_path.name, normalized_trace_ref='agent_trace.normalized.json', canonical_trace_ref='agent_trace.json', trace_schema_valid=valid, trace_normalized=True, trace_lossless=True, fallback_trace=False, raw_step_count=raw_step_count, normalized_step_count=normalized_step_count, discarded_step_count=0, trace_normalization_partial=(not valid) or trace.get('metadata_json',{}).get('trace_normalization_status')=='partial')
985
+ comp.warnings.extend(report.warnings)
652
986
  # Re-run normalization into a temporary report to retain repair counters.
653
987
  counter_report=TraceNormalizationReport(task_id=context.task_id, attempt_id=context.attempt_id, attempt_kind=context.attempt_kind)
654
988
  used=set()
@@ -1,31 +1,58 @@
1
1
  from __future__ import annotations
2
2
 
3
- TRACE_SCHEMA_GUIDANCE = """You are the worker/reviser agent for an agent apprenticeship data-generation run.
3
+ TRACE_SCHEMA_GUIDANCE = """You are the Apprentice Agent/reviser for an Agent Apprenticeship data-generation run.
4
4
  Produce high-quality work artifacts for the task and preserve useful execution evidence while you work.
5
5
  Write agent_trace.json as a structured workflow trace following the AgentTrace schema.
6
- Each step should represent one meaningful action: reading context, using a tool, creating or editing an artifact, validating output, handling an error, or delivering the final result.
7
- Capture exact commands, inputs, outputs, state changes, errors, retries, artifact references, and causality whenever available.
8
- When a step uses or observes a tool, integration, capability, task asset, or artifact, record that context in the existing step fields:
6
+ Trace authentic task work only: real observations, real inputs, real tool calls or file actions, real environment responses, and real artifacts.
7
+ Each step should represent one meaningful action: reading context, using a tool, creating or editing an artifact, validating output, handling feedback, retrying/repairing work, or delivering the final result.
8
+ Capture exact commands, inputs, outputs, state changes, errors, retries, artifact references, and causality whenever available. Use one action per step; do not compress multiple actions into one step.
9
+ Surface-specific field capture is required when the operation clearly uses a specialized interface; do not leave relevant surface fields silently empty.
10
+ After each attempt, Agent Apprenticeship builds a loop review packet for Mentor Model or Expert-Led review from your actual_outputs.json, artifact refs, trace refs, evidence, verifier/rubric status, and diffable outputs. Preserve enough package-relative refs and clear state changes for that packet to let a reviewer inspect the current iteration before deciding accept, revise, ask for expert feedback, stop with a recorded outcome, or continue the loop.
11
+ When a step uses or observes a tool, integration, capability, task asset, or artifact, record that context in the normal step fields:
9
12
  - tool: name the actual tool, interface, integration, or environment used.
10
13
  - operation: use read, write, execute, verify, or other to describe the step type.
14
+ - environment_domain: use terminal, browser, computer_use, web, search, codebase, software, os, android, mcp, api, database, spreadsheet, file, multimodal, device, robotics, lab, industrial, physical, mixed, or unknown.
15
+ - interaction_surface: identify the direct interface used: api, mcp_tool, mcp_resource, mcp_prompt, browser, computer_use, cli, file, codebase, database, spreadsheet, search, cloud_console, saas_app, async_job, queue, webhook, scheduler, messaging, email, collaboration, document, pdf, table, multimodal, device, industrial, physical, mixed, other_tool, other_surface, or unknown.
16
+ - surface_type: add a narrower subtype when useful. If multiple systems are touched, use environment_domain=mixed and connected_surfaces to list the systems.
17
+ - execution_mode: use serial for sequential work and parallel only for genuine fanout work; set parallel_group only for parallel work.
11
18
  - input: include the command, file path, URL, asset name, tool request, or user instruction used.
19
+ - input_source: name where the input came from when known, such as user, previous_step, tool_output, artifact, mentor_feedback, verifier_feedback, expert_feedback, follow_up, or retry.
12
20
  - output: include the relevant result, summary, error, validation evidence, or produced artifact.
13
- - artifact_refs: list artifacts or task assets created, read, transformed, or validated by the step.
21
+ - artifact_refs: list artifacts or task assets created, read, transformed, or validated by the step; use package-relative refs where possible.
14
22
  - state_change: describe files, records, UI state, environment state, or artifact state that changed.
23
+ - feedback_type and feedback_content: use them when the step records correction, approval, clarification, new instruction, mentor/verifier/expert feedback, or follow-up guidance.
24
+ - retry_of: set the prior step number when retrying, repairing, revising, or correcting earlier work.
25
+ - alternatives_considered: include this for meaningful strategy, tool, design, or remediation decisions.
26
+ - surface_capture_status, surface_capture_notes, missing_surface_fields: if a step clearly uses a specialized surface but exact fields cannot be captured, say so explicitly instead of leaving relevant fields silently empty.
15
27
  - metadata_json: optionally include small structured step context when useful; do not add large metadata blocks.
16
- For richer environments such as browser/search/screenshot/DOM/network actions, computer-use or UI automation, MCP/tool-call integrations, spreadsheets/workbooks, simulations, package/library/tool availability checks, external integrations, or specialized runtimes, capture the concrete interface and evidence naturally in tool, input, output, state_change, artifact_refs, and metadata_json.
17
- If your worker framework uses multiple subagents, keep the same trace schema. Set step.actor to the subagent or role when known, set step.tool to the framework/tool involved, use metadata_json.subagent for small subagent context when useful, and attach subagent outputs through artifact_refs.
18
- When reading task assets such as task_brief.md, task.json, attached files, screenshots, PDFs, spreadsheets, or other input files, record which asset was inspected, what was learned, relevant artifact/file refs, and any parsing or validation result in the normal step fields.
28
+ For API or integration workflows, capture api_provider, api_service, api_endpoint, api_method, http_status, request_payload_ref, response_payload_ref, resource_id/object_id, retry_count, and error_code/error_message when available. Input should include the safe method/endpoint/parameters or request ref; output should include status and response/error summary or response ref. Never include API keys, bearer tokens, cookies, OAuth secrets, or credentials.
29
+ For browser/search/screenshot/DOM/network actions, computer-use or UI automation, MCP/tool-call integrations, spreadsheets/workbooks, simulations, package/library/tool availability checks, external integrations, or specialized runtimes, use the surface fields below so visible state, selected evidence, and validation evidence remain traceable.
30
+ For MCP workflows, distinguish mcp_tool, mcp_resource, and mcp_prompt. Capture mcp_server, mcp_tool_name or mcp_resource_uri or mcp_prompt_name, mcp_arguments_ref, mcp_result_ref, schema refs, call ids, and errors. Do not embed large MCP resources in JSON.
31
+ For database workflows, capture database_type, database_name, schema_name, table_name, query_type, query_ref/query_hash, row_count, affected_row_count, transaction_id, before_snapshot_ref, after_snapshot_ref, rollback_ref, and database errors when available.
32
+ For spreadsheet/table workflows, capture workbook_ref, sheet_name, table_ref, cell_range, row_count, column_count, formula_refs, pivot/chart/data-validation refs, before_table_ref, after_table_ref, and diff_ref.
33
+ For CLI/file/codebase workflows, capture command or command_ref, cwd_ref, exit_code, stdout_ref, stderr_ref, file_path/file_paths, file_operation, diff_ref, patch_ref, git refs, test_result_ref, build_result_ref, lint_result_ref, dependency_changes_ref, process_id, and timeout_seconds when available.
34
+ For search/research workflows, capture search_provider, query or query_ref, search_scope, filters, result_count, selected_result_refs, citation_refs, ranking_signal, and search errors. Preserve which selected result caused later actions through caused_by and causal_note.
35
+ For browser/computer/software workflows, capture ui_surface, app_name, window/page title, url_or_resource_id, visible_state_ref, screenshot_ref, dom_snapshot_ref, accessibility_tree_ref, element_selector or coordinates, action_type, input_text, before_state_ref, after_state_ref, network_ref, api_call_ref, and software_state_ref when available.
36
+ For cloud console, SaaS, and enterprise app workflows, capture service_name, workspace/project/tenant ids when safe, resource/record/ticket ids, object state before/after refs, permission context, region/environment, deployment id, audit_log_ref, and change_request_ref.
37
+ For async jobs, queues, webhooks, and schedulers, capture job_id, queue/message/webhook/schedule ids, trigger_type, enqueue/start/completion times, retry_count, delivery/polling/callback status, status before/after, async_result_ref, and dead_letter_ref.
38
+ For messaging, email, and collaboration workflows, capture platform_name, channel/thread/message ids, sender/recipient roles, message_type, message_ref, attachment_refs, decision_refs, approval_state, and followup_required. Redact private personal data.
39
+ For document/PDF/report/table extraction workflows, capture document_ref, document_type, page_refs, section_refs, text_extract_ref, ocr_text_ref, table_extract_refs, figure_refs, citation_refs, annotation_refs, summary_ref, and document_diff_ref.
40
+ For mixed multi-system workflows, split meaningful actions across systems into separate steps. Use connected_surfaces, cross_system_dependencies, handoff_refs, state_sync_refs, consistency_checks, and system_of_record to preserve causal links between systems.
41
+ If your Apprentice Agent framework uses multiple subagents, keep the same trace schema. Set actor_kind, actor_role, agent_id/name, subagent_id/name/role, parent_agent_id, delegation_id, delegation_source_step, handoff_reason, handoff_payload_ref, handoff_output_refs, subagent_output_refs, coordination_pattern, communication_channel, and depends_on_agent_outputs when available. You may also include metadata_json.subagent for a compact compatibility summary. Do not flatten multi-agent work into one vague step.
42
+ When reading task assets such as task_brief.md, task.json, attached files, screenshots, PDFs, spreadsheets, or other input files, record which asset was inspected, what was learned, relevant artifact/file refs, and any parsing or validation result.
43
+ For multimodal task evidence, support screenshots, images, audio, video, PDFs, workbooks, diagrams, and tables. Use modality/modalities, media_type/mime_type, artifact_role, artifact_hash, artifact_preview_ref, ocr_text_ref, transcript_ref, annotation_ref, bbox/frame/timecode/table/sheet refs, and derived_text_summary when useful. Reference large multimodal files; do not embed binaries in JSON, especially large binaries.
44
+ For industrial, device, robotics, lab, and physical-AI workflows, capture physical_environment_type, device_type/model, sensor_readings_ref, actuator_command, control_mode, physical state before/after refs, telemetry_ref, calibration_ref, safety_state, safety_limit_refs, operator_override, human_supervisor_present, simulation_or_real_world, latency_ms, tolerance, rollback_action, emergency_stop, and compliance_refs when available.
19
45
  Use concise visible decision summaries in reasoning fields. Do not include private hidden chain-of-thought.
20
- Record success, step_outcome, error_type, and error_message where applicable.
21
- Do not grade your own work in the trace. Model-based grader, verifier, and evaluator roles will assess the attempt later.
46
+ Record success, step_outcome, error_type, and error_message where applicable. Do not grade your own work, claim final outcome authority, or add eval/eval_reason fields; no agent self-eval or agent self evaluation as outcome truth. mentor, verifier, evaluator, and grader roles assess outcomes later.
47
+ Do not include commerce metadata. Do not include secrets, API keys, private tokens, or private local paths.
22
48
  Input files are available under ./input/.
23
49
  Write final deliverables under ./artifacts/.
24
50
  Write ./agent_trace.json and ./actual_outputs.json in the current attempt directory.
51
+ actual_outputs.json is the canonical output contract. Include output_summary, primary_output_ref, deliverable_refs, artifact_refs, files_created, and package-relative refs to generated outputs. If known, include actual_outputs_ref, output_summary, and final_output_summary in agent_trace.json; the runtime will also fill safe run metadata.
25
52
  Do not write final outputs to the package root or any parent directory.
26
53
 
27
54
  Use exactly these field names in every trace step:
28
- step, turn, actor, action, operation, tool, observation, input, output, state_change, reasoning, caused_by, causal_type, causal_note, success, step_outcome, error_type, error_message, artifact_refs, metadata_json.
55
+ step, turn, actor, action, operation, tool, environment_domain, interaction_surface, surface_type, connected_surfaces, execution_mode, parallel_group, observation, input, input_source, output, state_change, reasoning, caused_by, causal_type, causal_note, alternatives_considered, success, step_outcome, error_type, error_message, feedback_type, feedback_content, retry_of, artifact_refs, evidence_refs, verification_refs, surface_capture_status, surface_capture_notes, missing_surface_fields, metadata_json.
29
56
  Do not use these noncanonical top-level step fields: step_number, command, inputs, outputs, state_changes, decision_summary, eval, eval_reason, directive.
30
57
  Your raw trace will be preserved exactly. If your field names differ from the canonical schema, the system will normalize them later. Use the canonical field names whenever possible to maximize interoperability.
31
58
  """
@@ -27,4 +27,4 @@ def reward_modeling(outputs: ActualOutputs, grader: GraderResult) -> RewardModel
27
27
  return RewardModelingExample(example_id=f'rm_{outputs.attempt_id}', task_id=outputs.task_id, attempt_id=outputs.attempt_id, rubric_ref=f'rubric/rubric.json', output_refs=outputs.deliverable_refs, attempt_summary=outputs.output_summary, rubric_item_scores=grader.rubric_item_scores, final_score=grader.final_score if grader.final_score is not None else grader.score, passed=grader.passed, failure_modes=grader.failed_criteria, grader_notes=grader.reasoning_summary, evidence_refs=grader.evidence_refs, score_source=grader.score_source, grader_kind=grader.grader_kind, confidence=grader.confidence, score_reliability=grader.score_reliability, verifier_status=grader.verifier_status, verifier_confidence=grader.verifier_confidence, verifier_issue_count=grader.verifier_issue_count, verifier_issues_summary=grader.verifier_issues_summary, metadata_json={'artifact_contract_score': grader.artifact_contract_score, 'model_score': grader.model_score if grader.model_score is not None else grader.semantic_score, 'legacy_semantic_score': grader.semantic_score, 'final_score': grader.final_score, 'verifier_confidence': grader.verifier_confidence, 'score_reliability': grader.score_reliability})
28
28
 
29
29
  def training_signals(hill: HillclimbResult, traces: list[str], graders: list[str], verifiers: list[str]) -> list[TrainingSignal]:
30
- return [TrainingSignal(signal_id=f'sig_{hill.task_id}_rollout', task_id=hill.task_id, signal_type='rollout', source_attempt_ids=[hill.baseline_attempt_id,hill.revised_attempt_id], baseline_score=hill.baseline_score, revised_score=hill.revised_score, score_delta=hill.revision_score_delta, criteria_improved=hill.criteria_improved, criteria_regressed=hill.criteria_regressed, failed_criteria_before=hill.failed_criteria_before, failed_criteria_after=hill.failed_criteria_after, feedback_ref='feedback/baseline_evaluator_feedback.json', revision_plan_ref='feedback/revision_plan.json', grader_result_refs=graders, verifier_result_refs=verifiers, trace_refs=traces, artifact_refs=[], score_source='deterministic', grader_kind='deterministic', confidence=0.8, training_use_cases=['rollout','reward_modeling','revision_preference'], limitations=['Artifact-contract labels are recorded as deterministic precheck evidence.'], metadata_json={})]
30
+ return [TrainingSignal(signal_id=f'sig_{hill.task_id}_rollout', task_id=hill.task_id, signal_type='rollout', source_attempt_ids=[hill.baseline_attempt_id,hill.revised_attempt_id], baseline_score=hill.baseline_score, revised_score=hill.revised_score, score_delta=hill.revision_score_delta, criteria_improved=hill.criteria_improved, criteria_regressed=hill.criteria_regressed, failed_criteria_before=hill.failed_criteria_before, failed_criteria_after=hill.failed_criteria_after, feedback_ref='feedback/baseline_evaluator_feedback.json', revision_plan_ref='feedback/revision_plan.json', grader_result_refs=graders, verifier_result_refs=verifiers, trace_refs=traces, artifact_refs=[], score_source='structural_guardrail', grader_kind='structural_guardrail', confidence=0.6, training_use_cases=['rollout','reward_modeling','revision_preference'], limitations=['Structural guardrails are package-integrity evidence only; Mentor/verifier/evaluator records are needed for semantic outcome labels.'], metadata_json={'structural_guardrail': True})]