agent-hitch 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/adapters/providers/deepseek.js +3 -1
- package/dist/src/adapters/providers/deepseek.js.map +1 -1
- package/dist/src/adapters/providers/shared.js +3 -2
- package/dist/src/adapters/providers/shared.js.map +1 -1
- package/dist/src/backends/harbor/backend.js +100 -36
- package/dist/src/backends/harbor/backend.js.map +1 -1
- package/dist/src/backends/harbor/dataset-config.js +36 -0
- package/dist/src/backends/harbor/dataset-config.js.map +1 -0
- package/dist/src/cli/commands/eval.js +39 -3
- package/dist/src/cli/commands/eval.js.map +1 -1
- package/dist/src/cli/output.js +4 -1
- package/dist/src/cli/output.js.map +1 -1
- package/dist/src/evals/harbor-bridge-error.js +44 -0
- package/dist/src/evals/harbor-bridge-error.js.map +1 -0
- package/dist/src/evals/index.js +4 -2
- package/dist/src/evals/index.js.map +1 -1
- package/dist/src/evals/progress.js +198 -0
- package/dist/src/evals/progress.js.map +1 -0
- package/dist/src/evals/request.js +37 -1
- package/dist/src/evals/request.js.map +1 -1
- package/dist/src/evals/rerun-slots.js +95 -0
- package/dist/src/evals/rerun-slots.js.map +1 -0
- package/dist/src/evals/rerun.js +417 -0
- package/dist/src/evals/rerun.js.map +1 -0
- package/dist/src/evals/service.js +193 -44
- package/dist/src/evals/service.js.map +1 -1
- package/dist/src/evals/trial-import.js +130 -60
- package/dist/src/evals/trial-import.js.map +1 -1
- package/docs/schemas/eval-progress.schema.json +61 -0
- package/integrations/harbor/hitch_harbor_agent.py +270 -30
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ import shutil
|
|
|
13
13
|
import stat as stat_module
|
|
14
14
|
import tempfile
|
|
15
15
|
import uuid
|
|
16
|
-
from datetime import datetime
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
17
|
from pathlib import Path, PurePosixPath
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
@@ -28,6 +28,20 @@ LOCAL_GIT_TRANSPORT_MAX_BYTES = 512 * 1024 * 1024
|
|
|
28
28
|
LOCAL_GIT_REMOTE_ROOT = "/opt/hitch-local-source"
|
|
29
29
|
HARNESS_ARTIFACT_REMOTE_ROOT = "/opt/hitch-harness-artifact"
|
|
30
30
|
HITCH_CONTAINER_STATE_ROOT = "/tmp/hitch-state"
|
|
31
|
+
HITCH_BRIDGE_ERROR_LOG = "/logs/agent/hitch-bridge-error.json"
|
|
32
|
+
HITCH_DIAGNOSTIC_MAX_BYTES = 8 * 1024
|
|
33
|
+
HITCH_BRIDGE_ERROR_MAX_BYTES = 64 * 1024
|
|
34
|
+
HITCH_RESULT_MISSING_EXIT = 44
|
|
35
|
+
HITCH_RESULT_NOT_FILE_EXIT = 45
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class HitchBridgeError(RuntimeError):
|
|
39
|
+
"""Stable Harbor-facing infrastructure failure with structured evidence."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, code: str, message: str, evidence: dict[str, Any]) -> None:
|
|
42
|
+
super().__init__(f"{code}: {message}")
|
|
43
|
+
self.code = code
|
|
44
|
+
self.evidence = evidence
|
|
31
45
|
|
|
32
46
|
|
|
33
47
|
class HitchHarborAgent(BaseAgent):
|
|
@@ -51,6 +65,7 @@ class HitchHarborAgent(BaseAgent):
|
|
|
51
65
|
benchmark_id: str | None = None,
|
|
52
66
|
benchmark_revision: str | None = None,
|
|
53
67
|
verifier_identity: str | None = None,
|
|
68
|
+
logical_attempt: int | None = None,
|
|
54
69
|
**kwargs: Any,
|
|
55
70
|
) -> None:
|
|
56
71
|
super().__init__(logs_dir=logs_dir, **kwargs)
|
|
@@ -69,6 +84,9 @@ class HitchHarborAgent(BaseAgent):
|
|
|
69
84
|
self.benchmark_id = benchmark_id
|
|
70
85
|
self.benchmark_revision = benchmark_revision
|
|
71
86
|
self.verifier_identity = verifier_identity
|
|
87
|
+
if logical_attempt is not None and (isinstance(logical_attempt, bool) or not isinstance(logical_attempt, int) or logical_attempt < 1):
|
|
88
|
+
raise ValueError("logical_attempt must be a positive integer")
|
|
89
|
+
self.logical_attempt = logical_attempt
|
|
72
90
|
self._hitch_version: str | None = None
|
|
73
91
|
self._entrypoint: str | None = None
|
|
74
92
|
self._artifact_manifest: dict[str, Any] | None = None
|
|
@@ -816,7 +834,8 @@ git -C {LOCAL_GIT_REMOTE_ROOT}/repo.git update-ref refs/heads/hitch-local {commi
|
|
|
816
834
|
context: AgentContext,
|
|
817
835
|
) -> None:
|
|
818
836
|
self.logs_dir.mkdir(parents=True, exist_ok=True)
|
|
819
|
-
|
|
837
|
+
assigned_run_id = "run_" + uuid.uuid4().hex
|
|
838
|
+
run_id = assigned_run_id
|
|
820
839
|
trial_id, task_id, attempt = self._trial_identity()
|
|
821
840
|
context_payload: dict[str, Any] = {"kind": "ad_hoc"}
|
|
822
841
|
parent_payload: dict[str, Any] | None = None
|
|
@@ -920,31 +939,82 @@ git -C {LOCAL_GIT_REMOTE_ROOT}/repo.git update-ref refs/heads/hitch-local {commi
|
|
|
920
939
|
)
|
|
921
940
|
execution = await environment.exec(command, cwd=self.workdir)
|
|
922
941
|
events = self._events(execution.stdout or "")
|
|
923
|
-
observed_run_id = next((
|
|
942
|
+
observed_run_id = next((
|
|
943
|
+
value
|
|
944
|
+
for event in events
|
|
945
|
+
for value in [event.get("run_id")]
|
|
946
|
+
if isinstance(value, str) and re.fullmatch(r"run_[a-f0-9]{32}", value)
|
|
947
|
+
), None)
|
|
924
948
|
if observed_run_id:
|
|
925
949
|
run_id = str(observed_run_id)
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
950
|
+
result_path = f"/tmp/hitch-state/runs/{run_id}/result.json"
|
|
951
|
+
quoted_result_path = shlex.quote(result_path)
|
|
952
|
+
result_read = await environment.exec(
|
|
953
|
+
f"""
|
|
954
|
+
if [ ! -e {quoted_result_path} ]; then exit {HITCH_RESULT_MISSING_EXIT}; fi
|
|
955
|
+
if [ -L {quoted_result_path} ] || [ ! -f {quoted_result_path} ]; then exit {HITCH_RESULT_NOT_FILE_EXIT}; fi
|
|
956
|
+
cat -- {quoted_result_path}
|
|
957
|
+
""".strip()
|
|
958
|
+
)
|
|
959
|
+
result_copy = await environment.exec(
|
|
960
|
+
f"if [ -f {quoted_result_path} ] && [ ! -L {quoted_result_path} ]; then "
|
|
961
|
+
f"cp -- {quoted_result_path} /logs/agent/hitch-result.json; fi"
|
|
962
|
+
)
|
|
963
|
+
bundle_stage = f"/logs/agent/.hitch-run-bundle.{uuid.uuid4().hex}"
|
|
964
|
+
bundle_marker = json.dumps({
|
|
965
|
+
"schema_version": "1",
|
|
966
|
+
"run_id": run_id,
|
|
967
|
+
"eval_id": self.eval_id,
|
|
968
|
+
"trial_id": trial_id,
|
|
969
|
+
"completed_at": datetime.now(timezone.utc).isoformat(),
|
|
970
|
+
}, separators=(",", ":"))
|
|
971
|
+
bundle_export = await environment.exec(
|
|
972
|
+
f"""
|
|
936
973
|
set -eu
|
|
937
974
|
source_dir={shlex.quote(f'/tmp/hitch-state/runs/{run_id}')}
|
|
938
975
|
target_dir=/logs/agent/hitch-run-bundle
|
|
939
|
-
|
|
940
|
-
|
|
976
|
+
stage_dir={shlex.quote(bundle_stage)}
|
|
977
|
+
rm -rf "$stage_dir"
|
|
978
|
+
mkdir -p "$stage_dir"
|
|
941
979
|
for name in request.json resolution.json manifest.json result.json events.jsonl stdout.log stderr.log trajectory.ref.json trajectory; do
|
|
942
|
-
if [ -e "$source_dir/$name" ]; then cp -a "$source_dir/$name" "$
|
|
980
|
+
if [ -e "$source_dir/$name" ]; then cp -a "$source_dir/$name" "$stage_dir/$name"; fi
|
|
943
981
|
done
|
|
982
|
+
printf '%s\n' {shlex.quote(bundle_marker)} > "$stage_dir/bundle.complete.json"
|
|
983
|
+
rm -rf "$target_dir"
|
|
984
|
+
mv "$stage_dir" "$target_dir"
|
|
944
985
|
""".strip()
|
|
986
|
+
)
|
|
987
|
+
hitch_result, result_error_code, result_error_message = self._parse_hitch_result(result_read, run_id)
|
|
988
|
+
primary_code: str | None = None
|
|
989
|
+
primary_message: str | None = None
|
|
990
|
+
if execution.return_code != 0:
|
|
991
|
+
primary_code = "hitch_process_failed"
|
|
992
|
+
diagnostic = (execution.stderr or "").strip()
|
|
993
|
+
if hitch_result and isinstance(hitch_result.get("error"), dict):
|
|
994
|
+
result_message = hitch_result["error"].get("message")
|
|
995
|
+
if isinstance(result_message, str) and result_message.strip():
|
|
996
|
+
diagnostic = result_message.strip()
|
|
997
|
+
primary_message = (
|
|
998
|
+
f"Hitch agent run failed with code {execution.return_code} "
|
|
999
|
+
f"(run_id={run_id}, trial_id={trial_id}): {self._bounded_tail(diagnostic or 'no diagnostic output')}"
|
|
945
1000
|
)
|
|
946
|
-
|
|
947
|
-
|
|
1001
|
+
elif result_error_code is not None:
|
|
1002
|
+
primary_code = result_error_code
|
|
1003
|
+
primary_message = result_error_message
|
|
1004
|
+
elif hitch_result is not None and hitch_result.get("revision_identity") != self.revision_identity:
|
|
1005
|
+
primary_code = "hitch_revision_identity_mismatch"
|
|
1006
|
+
primary_message = (
|
|
1007
|
+
"Hitch resolved a different harness revision inside the trial container "
|
|
1008
|
+
f"(run_id={run_id}, trial_id={trial_id}): expected {self.revision_identity}, "
|
|
1009
|
+
f"got {hitch_result.get('revision_identity')}"
|
|
1010
|
+
)
|
|
1011
|
+
elif bundle_export.return_code != 0:
|
|
1012
|
+
primary_code = "hitch_run_bundle_export_failed"
|
|
1013
|
+
primary_message = f"Hitch run bundle export failed (run_id={run_id}, trial_id={trial_id})"
|
|
1014
|
+
elif result_copy.return_code != 0:
|
|
1015
|
+
primary_code = "hitch_result_artifact_copy_failed"
|
|
1016
|
+
primary_message = f"Hitch result artifact copy failed (run_id={run_id}, trial_id={trial_id})"
|
|
1017
|
+
|
|
948
1018
|
context.metadata = {
|
|
949
1019
|
"candidate_id": self.candidate_id,
|
|
950
1020
|
"harness_ref": self.harness_ref,
|
|
@@ -977,26 +1047,196 @@ done
|
|
|
977
1047
|
"node_version": self._artifact_manifest.get("toolchain", {}).get("node"),
|
|
978
1048
|
"status": self._artifact_transport_status or "container_prepare",
|
|
979
1049
|
}
|
|
980
|
-
if
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1050
|
+
if primary_code is not None:
|
|
1051
|
+
context.metadata["hitch_bridge_error_code"] = primary_code
|
|
1052
|
+
context.metadata["hitch_bridge_error_artifact"] = "hitch-bridge-error.json"
|
|
1053
|
+
evidence = self._bridge_error_evidence(
|
|
1054
|
+
code=primary_code,
|
|
1055
|
+
message=primary_message or primary_code,
|
|
1056
|
+
trial_id=trial_id,
|
|
1057
|
+
task_id=task_id,
|
|
1058
|
+
attempt=attempt,
|
|
1059
|
+
assigned_run_id=assigned_run_id,
|
|
1060
|
+
observed_run_id=str(observed_run_id) if observed_run_id else None,
|
|
1061
|
+
result_path=result_path,
|
|
1062
|
+
execution=execution,
|
|
1063
|
+
result_read=result_read,
|
|
1064
|
+
result_diagnostic=result_error_code,
|
|
1065
|
+
last_event=events[-1] if events else None,
|
|
1066
|
+
bundle_export=bundle_export,
|
|
1067
|
+
result_copy=result_copy,
|
|
986
1068
|
)
|
|
987
|
-
|
|
988
|
-
raise
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1069
|
+
await self._write_bridge_error(environment, evidence)
|
|
1070
|
+
raise HitchBridgeError(primary_code, primary_message or primary_code, evidence)
|
|
1071
|
+
|
|
1072
|
+
@staticmethod
|
|
1073
|
+
def _parse_hitch_result(
|
|
1074
|
+
result: ExecResult,
|
|
1075
|
+
expected_run_id: str,
|
|
1076
|
+
) -> tuple[dict[str, Any] | None, str | None, str | None]:
|
|
1077
|
+
if result.return_code != 0:
|
|
1078
|
+
if result.return_code == HITCH_RESULT_MISSING_EXIT:
|
|
1079
|
+
return None, "hitch_result_missing", f"Hitch result file is missing (run_id={expected_run_id})"
|
|
1080
|
+
if result.return_code == HITCH_RESULT_NOT_FILE_EXIT:
|
|
1081
|
+
return None, "hitch_result_not_file", f"Hitch result path is not a regular file (run_id={expected_run_id})"
|
|
1082
|
+
return None, "hitch_result_read_failed", (
|
|
1083
|
+
f"Hitch result file could not be read with code {result.return_code} (run_id={expected_run_id})"
|
|
1084
|
+
)
|
|
1085
|
+
payload = (result.stdout or "").strip()
|
|
1086
|
+
if not payload:
|
|
1087
|
+
return None, "hitch_result_empty", f"Hitch result file is empty (run_id={expected_run_id})"
|
|
1088
|
+
try:
|
|
1089
|
+
value = json.loads(payload)
|
|
1090
|
+
except json.JSONDecodeError:
|
|
1091
|
+
return None, "hitch_result_invalid_json", f"Hitch result is not valid JSON (run_id={expected_run_id})"
|
|
1092
|
+
if not isinstance(value, dict):
|
|
1093
|
+
return None, "hitch_result_schema_invalid", f"Hitch result must be a JSON object (run_id={expected_run_id})"
|
|
1094
|
+
error = HitchHarborAgent._result_schema_error(value)
|
|
1095
|
+
if error is not None:
|
|
1096
|
+
return None, "hitch_result_schema_invalid", f"Hitch result schema is invalid: {error} (run_id={expected_run_id})"
|
|
1097
|
+
if value["run_id"] != expected_run_id:
|
|
1098
|
+
return None, "hitch_result_run_id_mismatch", (
|
|
1099
|
+
f"Hitch result run id mismatch: expected {expected_run_id}, got {value['run_id']}"
|
|
993
1100
|
)
|
|
1101
|
+
return value, None, None
|
|
1102
|
+
|
|
1103
|
+
@staticmethod
|
|
1104
|
+
def _result_schema_error(value: dict[str, Any]) -> str | None:
|
|
1105
|
+
if value.get("schema_version") != "1":
|
|
1106
|
+
return "schema_version must be '1'"
|
|
1107
|
+
run_id = value.get("run_id")
|
|
1108
|
+
if not isinstance(run_id, str) or re.fullmatch(r"run_[a-f0-9]{32}", run_id) is None:
|
|
1109
|
+
return "run_id is invalid"
|
|
1110
|
+
if value.get("status") not in {"succeeded", "failed", "timed_out", "cancelled"}:
|
|
1111
|
+
return "status is invalid"
|
|
1112
|
+
exit_code = value.get("exit_code")
|
|
1113
|
+
if isinstance(exit_code, bool) or not isinstance(exit_code, int) or exit_code < 0:
|
|
1114
|
+
return "exit_code must be a non-negative integer"
|
|
1115
|
+
completed_at = value.get("completed_at")
|
|
1116
|
+
if not isinstance(completed_at, str) or not completed_at.strip():
|
|
1117
|
+
return "completed_at must be a non-empty string"
|
|
1118
|
+
if "error" in value:
|
|
1119
|
+
error = value["error"]
|
|
1120
|
+
if not isinstance(error, dict):
|
|
1121
|
+
return "error must be an object"
|
|
1122
|
+
if not isinstance(error.get("code"), str) or not isinstance(error.get("message"), str):
|
|
1123
|
+
return "error.code and error.message must be strings"
|
|
1124
|
+
return None
|
|
1125
|
+
|
|
1126
|
+
def _bridge_error_evidence(
|
|
1127
|
+
self,
|
|
1128
|
+
*,
|
|
1129
|
+
code: str,
|
|
1130
|
+
message: str,
|
|
1131
|
+
trial_id: str,
|
|
1132
|
+
task_id: str,
|
|
1133
|
+
attempt: int,
|
|
1134
|
+
assigned_run_id: str,
|
|
1135
|
+
observed_run_id: str | None,
|
|
1136
|
+
result_path: str,
|
|
1137
|
+
execution: ExecResult,
|
|
1138
|
+
result_read: ExecResult,
|
|
1139
|
+
result_diagnostic: str | None,
|
|
1140
|
+
last_event: dict[str, Any] | None,
|
|
1141
|
+
bundle_export: ExecResult,
|
|
1142
|
+
result_copy: ExecResult,
|
|
1143
|
+
) -> dict[str, Any]:
|
|
1144
|
+
signal_value = getattr(execution, "signal", None)
|
|
1145
|
+
evidence: dict[str, Any] = {
|
|
1146
|
+
"schema_version": "1",
|
|
1147
|
+
"code": code,
|
|
1148
|
+
"message": self._bounded_tail(message, 2048),
|
|
1149
|
+
"recorded_at": datetime.now(timezone.utc).isoformat(),
|
|
1150
|
+
"eval_id": self.eval_id,
|
|
1151
|
+
"trial_id": trial_id,
|
|
1152
|
+
"task_id": task_id,
|
|
1153
|
+
"attempt": attempt,
|
|
1154
|
+
"assigned_run_id": assigned_run_id,
|
|
1155
|
+
"observed_run_id": observed_run_id,
|
|
1156
|
+
"result_path": result_path,
|
|
1157
|
+
"process": {
|
|
1158
|
+
"return_code": execution.return_code,
|
|
1159
|
+
"signal": signal_value if isinstance(signal_value, str) else None,
|
|
1160
|
+
"stdout_tail": self._bounded_tail(execution.stdout or ""),
|
|
1161
|
+
"stderr_tail": self._bounded_tail(execution.stderr or ""),
|
|
1162
|
+
},
|
|
1163
|
+
"result_read": {
|
|
1164
|
+
"return_code": result_read.return_code,
|
|
1165
|
+
"stdout_tail": self._bounded_tail(result_read.stdout or ""),
|
|
1166
|
+
"stderr_tail": self._bounded_tail(result_read.stderr or ""),
|
|
1167
|
+
},
|
|
1168
|
+
"last_event": self._bounded_event(last_event),
|
|
1169
|
+
"result_diagnostic": result_diagnostic,
|
|
1170
|
+
}
|
|
1171
|
+
if bundle_export.return_code != 0:
|
|
1172
|
+
evidence["bundle_export"] = {
|
|
1173
|
+
"return_code": bundle_export.return_code,
|
|
1174
|
+
"stdout_tail": self._bounded_tail(bundle_export.stdout or ""),
|
|
1175
|
+
"stderr_tail": self._bounded_tail(bundle_export.stderr or ""),
|
|
1176
|
+
}
|
|
1177
|
+
if result_copy.return_code != 0:
|
|
1178
|
+
evidence["result_copy"] = {
|
|
1179
|
+
"return_code": result_copy.return_code,
|
|
1180
|
+
"stdout_tail": self._bounded_tail(result_copy.stdout or ""),
|
|
1181
|
+
"stderr_tail": self._bounded_tail(result_copy.stderr or ""),
|
|
1182
|
+
}
|
|
1183
|
+
if len(json.dumps(evidence, ensure_ascii=False, separators=(",", ":")).encode("utf-8")) > HITCH_BRIDGE_ERROR_MAX_BYTES:
|
|
1184
|
+
evidence["message"] = self._bounded_tail(str(evidence["message"]), 1024)
|
|
1185
|
+
evidence["last_event"] = self._bounded_event_summary(last_event)
|
|
1186
|
+
for section_name in ["process", "result_read", "bundle_export", "result_copy"]:
|
|
1187
|
+
section = evidence.get(section_name)
|
|
1188
|
+
if not isinstance(section, dict):
|
|
1189
|
+
continue
|
|
1190
|
+
for field in ["stdout_tail", "stderr_tail"]:
|
|
1191
|
+
section[field] = self._bounded_tail(str(section.get(field, "")), 2048)
|
|
1192
|
+
evidence["diagnostics_truncated"] = True
|
|
1193
|
+
return evidence
|
|
1194
|
+
|
|
1195
|
+
@staticmethod
|
|
1196
|
+
def _bounded_tail(value: str, max_bytes: int = HITCH_DIAGNOSTIC_MAX_BYTES) -> str:
|
|
1197
|
+
encoded = value.encode("utf-8", errors="replace")
|
|
1198
|
+
if len(encoded) <= max_bytes:
|
|
1199
|
+
return value
|
|
1200
|
+
marker = f"[truncated {len(encoded) - max_bytes} bytes]\n".encode("utf-8")
|
|
1201
|
+
available = max(0, max_bytes - len(marker))
|
|
1202
|
+
suffix = encoded[-available:] if available else b""
|
|
1203
|
+
suffix = suffix.decode("utf-8", errors="ignore").encode("utf-8")
|
|
1204
|
+
return (marker + suffix).decode("utf-8")
|
|
1205
|
+
|
|
1206
|
+
@staticmethod
|
|
1207
|
+
def _bounded_event(event: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
1208
|
+
if event is None:
|
|
1209
|
+
return None
|
|
1210
|
+
encoded = json.dumps(event, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
|
1211
|
+
if len(encoded) <= HITCH_DIAGNOSTIC_MAX_BYTES:
|
|
1212
|
+
return event
|
|
1213
|
+
return HitchHarborAgent._bounded_event_summary(event)
|
|
1214
|
+
|
|
1215
|
+
@staticmethod
|
|
1216
|
+
def _bounded_event_summary(event: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
1217
|
+
if event is None:
|
|
1218
|
+
return None
|
|
1219
|
+
return {
|
|
1220
|
+
"type": event.get("type"),
|
|
1221
|
+
"run_id": event.get("run_id"),
|
|
1222
|
+
"truncated": True,
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
@staticmethod
|
|
1226
|
+
async def _write_bridge_error(environment: BaseEnvironment, evidence: dict[str, Any]) -> None:
|
|
1227
|
+
payload = json.dumps(evidence, ensure_ascii=False, separators=(",", ":"), sort_keys=True)
|
|
1228
|
+
await environment.exec(
|
|
1229
|
+
f"umask 077; printf '%s\\n' {shlex.quote(payload)} > {HITCH_BRIDGE_ERROR_LOG}"
|
|
1230
|
+
)
|
|
994
1231
|
|
|
995
1232
|
def _trial_identity(self) -> tuple[str, str, int]:
|
|
996
1233
|
"""Read Harbor's stable trial/task identity from the persisted trial state."""
|
|
997
1234
|
trial_dir = self.logs_dir.parent if self.logs_dir.name == "agent" else self.logs_dir
|
|
998
1235
|
trial_id = trial_dir.name or "trial__1"
|
|
999
1236
|
task_id = self._locked_task_id(trial_dir)
|
|
1237
|
+
if self.logical_attempt is not None:
|
|
1238
|
+
fallback_task_id = trial_id.rsplit("__", 1)[0] if "__" in trial_id else trial_id
|
|
1239
|
+
return trial_id, task_id or fallback_task_id, self.logical_attempt
|
|
1000
1240
|
match = re.fullmatch(r"(.+)__(\d+)", trial_id)
|
|
1001
1241
|
if match:
|
|
1002
1242
|
return trial_id, task_id or match.group(1), max(1, int(match.group(2)))
|