已合并
feat(rsi): add evidence-driven recursive harness evolution framework #2497
feat(rsi): add evidence-driven recursive harness evolution framework #2497
已合并
zhitinggao创建于 10 天前
48 个文件变更+30646-598
Mopenjiuwen/rsi/config/config.py+59-0文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/data_loader/loader.py+28-0文件内容审核中,请稍后刷新重试
@@ -0,0 +1,1183 @@
1+# coding: utf-8
2+# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
3+"""Deterministic, decision-centered compression for analyzer evidence."""
4+ 
5+from __future__ import annotations
6+ 
7+import hashlib
8+import json
9+import re
10+from collections import Counter, defaultdict
11+from collections.abc import Mapping, Sequence
12+from pathlib import Path
13+from typing import Any
14+ 
15+_MIN_SELECTED_CALLS_PER_TRIAL = 8
16+_MAX_SELECTED_CALLS_PER_TRIAL = 20
17+_MAX_TOOL_SEQUENCE = 80
18+_MUTATING_TOOL_PATTERN = re.compile(
19+ r"(?:^|_)(?:assign|cancel|create|delete|disable|enable|finish|modify|order|patch|save|schedule|send|submit|"
20+ r"update|write)(?:_|$)",
21+ re.IGNORECASE,
22+)
23+_ARTIFACT_PATTERN = re.compile(
24+ r"(?:^|[\s`'\"])([^\s`'\"]+\.(?:csv|docx|html|json|md|pdf|pptx|txt|xlsx))(?:$|[\s`'\"])", re.IGNORECASE
25+)
26+_FAILED_RESPONSE_PATTERN = re.compile(
27+ r"(?:success\s*=\s*false|\"success\"\s*:\s*false|exit\s+code\s*:\s*[1-9]\d*|traceback\s*\(|access\s+denied)",
28+ re.IGNORECASE,
29+)
30+_BASH_MUTATION_PATTERN = re.compile(
31+ r"(?:^|[;&|\n]\s*)(?:cp|mv|rm|mkdir|touch|install)\b|"
32+ r"(?:\.save\s*\(|save_workbook\s*\(|write_text\s*\(|write_bytes\s*\(|"
33+ r"shutil\.(?:copy|copy2|copytree|move)|libreoffice\b.*--convert-to|(?:^|\s)>\s*[^&])",
34+ re.IGNORECASE,
35+)
36+_BASH_CONTENT_PATTERN = re.compile(
37+ r"(?:pdftotext|python\w*\b.*(?:docx|openpyxl|pypdf|python-pptx)|"
38+ r"(?:cat|sed|head|tail|unzip)\s)",
39+ re.IGNORECASE | re.DOTALL,
40+)
41+_CRITICAL_EVIDENCE_TERM_PATTERN = re.compile(r"[A-Za-z][A-Za-z0-9_.-]{2,}|\d+(?:\.\d+)?|[\u4e00-\u9fff]{2,8}")
42+_CRITICAL_EVIDENCE_STOPWORDS = {
43+ "and",
44+ "agent",
45+ "answer",
46+ "assessment",
47+ "because",
48+ "between",
49+ "but",
50+ "calculating",
51+ "conclusion",
52+ "correctly",
53+ "criterion",
54+ "did",
55+ "directly",
56+ "evidence",
57+ "explicitly",
58+ "failed",
59+ "failure",
60+ "first",
61+ "identified",
62+ "met",
63+ "must",
64+ "not",
65+ "requirement",
66+ "required",
67+ "response",
68+ "second",
69+ "specific",
70+ "state",
71+ "stated",
72+ "states",
73+ "task",
74+ "text",
75+ "than",
76+ "the",
77+ "that",
78+ "this",
79+ "timeline",
80+ "timelines",
81+ "with",
82+}
83+_COMPACTION_MARKER = (
84+ "[ANALYZER_EVIDENCE_COMPACTION: omitted {omitted} source chars; this marker was not observed by the task agent]"
85+)
86+ 
87+ 
88+def public_task_contract_snapshot(task: Mapping[str, Any]) -> dict[str, Any]:
89+ """Keep the public task and tool contract while excluding scorer internals."""
90+ public = task.get("public_task_contract")
91+ public = public if isinstance(public, Mapping) else {}
92+ metadata = public.get("metadata", task.get("metadata"))
93+ metadata = metadata if isinstance(metadata, Mapping) else {}
94+ tool_schemas = public.get("tool_schemas")
95+ tool_schemas = tool_schemas if isinstance(tool_schemas, Sequence) and not isinstance(tool_schemas, str) else []
96+ task_metadata: dict[str, Any] = {}
97+ for key, value in metadata.items():
98+ if key in {"canary", "category", "difficulty", "language"} and _is_scalar(value):
99+ task_metadata[str(key)] = value
100+ normalized_tool_schemas: list[dict[str, Any]] = []
101+ for schema in tool_schemas:
102+ if not isinstance(schema, Mapping):
103+ continue
104+ normalized = _normalize_tool_schema(schema)
105+ if normalized:
106+ normalized_tool_schemas.append(normalized)
107+ return {
108+ "schema_version": 1,
109+ "provenance": "official_suite.public_task_contract",
110+ "task_id": str(task.get("id") or public.get("task_id") or ""),
111+ "domain": str(public.get("domain") or task.get("domain") or ""),
112+ "prompt": str(public.get("prompt") or task.get("prompt") or ""),
113+ "task_metadata": task_metadata,
114+ "tool_schemas": normalized_tool_schemas,
115+ }
116+ 
117+ 
118+def load_public_task_contract(
119+ *,
120+ case_id: str,
121+ result_path: str,
122+ evaluation_metadata: Mapping[str, Any],
123+ task_input: str,
124+) -> dict[str, Any]:
125+ """Load a materialized public contract, with a safe fallback for older runs."""
126+ direct = evaluation_metadata.get("analysis_task_contract")
127+ if isinstance(direct, Mapping):
128+ return dict(direct)
129+ 
130+ result = Path(result_path)
131+ evaluation_dir = result.parent.parent.parent if len(result.parents) >= 3 else Path()
132+ suite_path = evaluation_dir / "official" / "suite.json"
133+ suite = _read_mapping(suite_path)
134+ for split_name in ("validation", "evaluation", "tasks"):
135+ tasks = suite.get(split_name)
136+ if not isinstance(tasks, list):
137+ continue
138+ for task in tasks:
139+ if isinstance(task, Mapping) and str(task.get("id") or "") == case_id:
140+ return public_task_contract_snapshot(task)
141+ 
142+ return {
143+ "schema_version": 1,
144+ "provenance": "case.input",
145+ "task_id": case_id,
146+ "prompt": task_input,
147+ "tool_schemas": [],
148+ }
149+ 
150+ 
151+def build_causal_evidence_digest( # pylint: disable=huawei-too-many-arguments
152+ *,
153+ case_id: str,
154+ task_input: str,
155+ response: str,
156+ evaluation_passed: bool,
157+ evaluation_score: float,
158+ evaluation_reason: str,
159+ evaluation_metadata: Mapping[str, Any],
160+ trace_data: Mapping[str, Any],
161+ task_contract: Mapping[str, Any],
162+) -> dict[str, Any]:
163+ """Build a compact evidence packet that preserves trial differences and exact actions."""
164+ traces = trace_data.get("traces")
165+ traces = traces if isinstance(traces, list) else []
166+ trial_scores = _aligned_list(evaluation_metadata.get("trial_scores"), len(traces))
167+ trial_passed = _aligned_list(evaluation_metadata.get("trial_passed"), len(traces))
168+ trial_exit_reasons = _aligned_list(evaluation_metadata.get("trial_exit_reasons"), len(traces))
169+ trial_details = _aligned_list(evaluation_metadata.get("trial_details"), len(traces))
170+ has_trial_outcomes = bool(traces) and bool(trial_scores) and bool(trial_passed)
171+ critical_evidence_terms = _failed_requirement_terms(evaluation_metadata)
172+ 
173+ trial_records: list[dict[str, Any]] = []
174+ all_calls: list[dict[str, Any]] = []
175+ raw_message_count = 0
176+ for index, raw_trace in enumerate(traces):
177+ if not isinstance(raw_trace, Mapping):
178+ continue
179+ score = trial_scores[index] if has_trial_outcomes else (evaluation_score if len(traces) == 1 else None)
180+ passed = trial_passed[index] if has_trial_outcomes else (evaluation_passed if len(traces) == 1 else None)
181+ exit_reason = trial_exit_reasons[index] if trial_exit_reasons else ""
182+ trial = _build_trial_record(
183+ raw_trace,
184+ index=index,
185+ score=score,
186+ passed=passed,
187+ exit_reason=exit_reason,
188+ critical_evidence_terms=critical_evidence_terms,
189+ )
190+ trial["trial_evaluation"] = _compact_trial_evaluation(trial_details[index] if trial_details else None)
191+ raw_message_count += int(trial.pop("_raw_message_count"))
192+ all_calls.extend(trial.pop("_all_calls"))
193+ trial_records.append(trial)
194+ 
195+ _deduplicate_selected_payloads(trial_records)
196+ tool_contracts = _tool_contract_observations(task_contract, all_calls)
197+ compact_task_contract = {
198+ key: _compact_value(value) for key, value in task_contract.items() if key not in {"prompt", "tool_schemas"}
199+ }
200+ compact_task_contract["prompt_ref"] = "authoritative_task_contract.input_excerpt"
201+ compact_task_contract["tool_schema_ref"] = "tool_contract_observations"
202+ digest = {
203+ "schema_version": 1,
204+ "compression_policy": {
205+ "method": "deterministic_decision_centered",
206+ "no_llm_summarization": True,
207+ "trial_boundaries_preserved": has_trial_outcomes,
208+ "exact_numbers_and_structured_tool_arguments_preserved": True,
209+ "action_selection": (
210+ "dynamic decision-linked budget; prioritize observed failures, state mutations, "
211+ "content-bearing reads, tool boundaries, and the terminal window"
212+ ),
213+ "response_only_field_policy": (
214+ "A response-only field is not a missing request field unless the public tool schema declares it."
215+ ),
216+ "lossless_evidence_policy": (
217+ "Compacted response excerpts are display views, not task-agent observations. "
218+ "ANALYZER_EVIDENCE_COMPACTION markers are generated after execution. "
219+ "Exact failed-requirement-linked spans are retained separately with raw evidence pointers."
220+ ),
221+ },
222+ "task_contract": compact_task_contract,
223+ "outcome": {
224+ "case_id": case_id,
225+ "passed": evaluation_passed,
226+ "score": evaluation_score,
227+ "reason": _compact_text(evaluation_reason, 1_000),
228+ "trial_count": len(trial_records),
229+ "judge_dimensions": _judge_dimensions(evaluation_metadata),
230+ "judge_evidence": _compact_judge_evidence(evaluation_metadata),
231+ },
232+ "tool_contract_observations": tool_contracts,
233+ "critical_evidence_terms": critical_evidence_terms,
234+ "trials": trial_records,
235+ "cross_trial_contrast": _cross_trial_contrast(trial_records),
236+ "fallback_final_response": (
237+ {"available": False, "reason": "per_trial_final_outputs_available"}
238+ if trial_records
239+ else {"available": True, **_output_summary(response)}
240+ ),
241+ "compression_stats": {
242+ "raw_trace_count": len(traces),
243+ "raw_message_count": raw_message_count,
244+ "raw_tool_call_count": len(all_calls),
245+ "selected_tool_call_count": sum(len(trial["selected_actions"]) for trial in trial_records),
246+ },
247+ }
248+ return digest
249+ 
250+ 
251+def _compact_trial_evaluation(value: Any) -> dict[str, Any]:
252+ """Preserve per-trial grader evidence without treating absence as zero."""
253+ if not isinstance(value, Mapping):
254+ return {
255+ "schema_version": 1,
256+ "availability": {
257+ "score_file": "not_instrumented",
258+ "score_reason": "not_instrumented",
259+ "judge_detail": "not_instrumented",
260+ "dimension_scores": "not_instrumented",
261+ },
262+ "score_reason": "",
263+ "judge_detail": None,
264+ "dimension_scores": {},
265+ "source": {},
266+ }
267+ 
268+ raw_availability = value.get("availability")
269+ raw_availability = raw_availability if isinstance(raw_availability, Mapping) else {}
270+ availability = {
271+ str(key): str(item) for key, item in raw_availability.items() if isinstance(key, str) and isinstance(item, str)
272+ }
273+ raw_dimensions = value.get("dimension_scores")
274+ raw_dimensions = raw_dimensions if isinstance(raw_dimensions, Mapping) else {}
275+ dimensions: dict[str, dict[str, Any]] = {}
276+ for name, raw_dimension in raw_dimensions.items():
277+ if not isinstance(raw_dimension, Mapping):
278+ continue
279+ state = str(raw_dimension.get("availability") or "not_available")
280+ raw_score = raw_dimension.get("value")
281+ dimensions[str(name)] = {
282+ "availability": state,
283+ "value": raw_score if _is_number(raw_score) else None,
284+ "source": str(raw_dimension.get("source")) if raw_dimension.get("source") else None,
285+ }
286+ raw_judge_detail = value.get("judge_detail")
287+ judge_detail = _compact_value(raw_judge_detail) if isinstance(raw_judge_detail, Mapping) else None
288+ source = value.get("source")
289+ source = _compact_value(source) if isinstance(source, Mapping) else {}
290+ return {
291+ "schema_version": 1,
292+ "availability": availability,
293+ "score": value.get("score") if _is_number(value.get("score")) else None,
294+ "passed": value.get("passed") if isinstance(value.get("passed"), bool) else None,
295+ "score_reason": _compact_text(value.get("score_reason"), 1_000),
296+ "judge_detail": judge_detail,
297+ "dimension_scores": dimensions,
298+ "source": source,
299+ }
300+ 
301+ 
302+def _compact_judge_evidence(metadata: Mapping[str, Any]) -> dict[str, Any]:
303+ """Keep aggregate criterion feedback that is not present in trial score files."""
304+ normalized = metadata.get("judge_evidence")
305+ if isinstance(normalized, Mapping):
306+ criteria = normalized.get("criteria")
307+ return {
308+ "schema_version": 1,
309+ "availability": str(normalized.get("availability") or "not_available"),
310+ "grading_run_status": str(normalized.get("grading_run_status") or ""),
311+ "criteria": _compact_value(criteria) if isinstance(criteria, list) else [],
312+ }
313+ 
314+ detail = metadata.get("judge_detail")
315+ if not isinstance(detail, Mapping):
316+ return {
317+ "schema_version": 1,
318+ "availability": "not_instrumented",
319+ "grading_run_status": "",
320+ "criteria": [],
321+ }
322+ raw_criteria = detail.get("criteria")
323+ if not isinstance(raw_criteria, list):
324+ return {
325+ "schema_version": 1,
326+ "availability": "not_available",
327+ "grading_run_status": str(detail.get("grading_run_status") or ""),
328+ "criteria": [],
329+ }
330+ criteria = [_compact_value(item) for item in raw_criteria if isinstance(item, Mapping)]
331+ return {
332+ "schema_version": 1,
333+ "availability": "available" if criteria else "invalid",
334+ "grading_run_status": str(detail.get("grading_run_status") or ""),
335+ "criteria": criteria,
336+ }
337+ 
338+ 
339+def compact_candidate_feedback(feedback: Mapping[str, Any] | None) -> dict[str, Any]:
340+ """Normalize paired intervention evidence without losing outcome signals.
341+ 
342+ Candidate evaluation and historical journal records use slightly different
343+ field names. Normalize both into one analyzer-facing schema so a diagnosis
344+ always sees the pre-evaluation prediction, activation evidence, strict
345+ acceptance score, continuous diagnostic score, and per-requirement delta.
346+ """
347+ if not isinstance(feedback, Mapping):
348+ return {}
349+ records = feedback.get("experiments")
350+ if not isinstance(records, list):
351+ return {}
352+ experiments: list[dict[str, Any]] = []
353+ for record in records[-3:]:
354+ if not isinstance(record, Mapping):
355+ continue
356+ diagnoses = record.get("candidate_failure_diagnoses")
357+ diagnoses = diagnoses if isinstance(diagnoses, list) else []
358+ raw_observed = record.get("observed_outcome")
359+ raw_observed = raw_observed if isinstance(raw_observed, Mapping) else {}
360+ raw_prediction = record.get("prediction")
361+ raw_prediction = raw_prediction if isinstance(raw_prediction, Mapping) else {}
362+ strict_score = _compact_score_comparison(
363+ raw_observed.get("strict_score"),
364+ source=_first_present(raw_observed, record, keys=("source_target_score", "source_strict_score")),
365+ candidate=_first_present(
366+ raw_observed,
367+ record,
368+ keys=("candidate_target_score", "candidate_strict_score"),
369+ ),
370+ delta=_first_present(raw_observed, record, keys=("target_score_delta", "strict_score_delta")),
371+ )
372+ continuous_score = _compact_score_comparison(
373+ raw_observed.get("continuous_score"),
374+ source=_first_present(raw_observed, record, keys=("source_native_score", "source_continuous_score")),
375+ candidate=_first_present(
376+ raw_observed,
377+ record,
378+ keys=("candidate_native_score", "candidate_continuous_score"),
379+ ),
380+ delta=_first_present(raw_observed, record, keys=("native_score_delta", "continuous_score_delta")),
381+ )
382+ continuous_score.update(
383+ {
384+ "source_signal": str(
385+ _first_present(raw_observed, record, keys=("source_native_signal", "source_continuous_signal"))
386+ or ""
387+ ),
388+ "candidate_signal": str(
389+ _first_present(
390+ raw_observed,
391+ record,
392+ keys=("candidate_native_signal", "candidate_continuous_signal"),
393+ )
394+ or ""
395+ ),
396+ "role": str(_first_present(raw_observed, record, keys=("native_signal_role",)) or ""),
397+ }
398+ )
399+ requirement_delta = _first_mapping(
400+ raw_observed.get("requirement_delta"),
401+ raw_observed.get("verifier_delta"),
402+ record.get("requirement_delta"),
403+ record.get("verifier_delta"),
404+ )
405+ dimension_deltas = _first_mapping(
406+ raw_observed.get("dimension_deltas"),
407+ raw_observed.get("native_dimension_deltas"),
408+ record.get("dimension_deltas"),
409+ record.get("native_dimension_deltas"),
410+ )
411+ contracts = raw_prediction.get("causal_intervention_contracts")
412+ if not isinstance(contracts, list):
413+ contracts = record.get("causal_intervention_contracts")
414+ contracts = contracts if isinstance(contracts, list) else []
415+ activation = record.get("activation")
416+ activation = activation if isinstance(activation, Mapping) else {}
417+ observed_outcome = {
418+ "status": str(_first_present(raw_observed, record, keys=("status", "outcome")) or ""),
419+ "reason": str(_first_present(raw_observed, record, keys=("reason",)) or ""),
420+ "strict_score": strict_score,
421+ "continuous_score": continuous_score,
422+ "requirement_delta": _compact_value(requirement_delta),
423+ "dimension_deltas": _compact_value(dimension_deltas),
424+ "selected_for_promotion": _first_present(
425+ raw_observed,
426+ record,
427+ keys=("selected_for_promotion",),
428+ ),
429+ # Preserve v1 aliases while analyzer consumers migrate to the
430+ # explicit strict/continuous comparison objects above.
431+ "source_target_score": strict_score["source"],
432+ "candidate_target_score": strict_score["candidate"],
433+ "target_score_delta": strict_score["delta"],
434+ "source_native_score": continuous_score["source"],
435+ "candidate_native_score": continuous_score["candidate"],
436+ "native_score_delta": continuous_score["delta"],
437+ "verifier_delta": _compact_value(requirement_delta),
438+ }
439+ experiments.append(
440+ {
441+ "schema_version": 2,
442+ "experiment_id": str(record.get("experiment_id") or ""),
443+ "surface": str(record.get("surface") or ""),
444+ "predicted_rank": record.get("predicted_rank"),
445+ "predicted_score": record.get("predicted_score"),
446+ "prediction": {
447+ "predicted_rank": _first_present(raw_prediction, record, keys=("predicted_rank",)),
448+ "predicted_score": _first_present(raw_prediction, record, keys=("predicted_score",)),
449+ "candidate_patch_excerpt": _compact_text(
450+ _first_present(raw_prediction, record, keys=("candidate_patch_excerpt",)),
451+ 2_000,
452+ ),
453+ "causal_intervention_contracts": _compact_value(contracts),
454+ },
455+ "observed_outcome": observed_outcome,
456+ "activation": _compact_value(activation),
457+ "causal_intervention_contracts": _compact_value(contracts),
458+ "verifier_delta": _compact_value(requirement_delta),
459+ "candidate_failure_diagnoses": _compact_candidate_failure_diagnoses(diagnoses),
460+ }
461+ )
462+ return {"case_id": str(feedback.get("case_id") or ""), "experiments": experiments}
463+ 
464+ 
465+def _first_present(*values: Mapping[str, Any], keys: Sequence[str]) -> Any:
466+ """Return the first explicitly present alias, including false and zero."""
467+ for value in values:
468+ for key in keys:
469+ if key in value:
470+ return value.get(key)
471+ return None
472+ 
473+ 
474+def _compact_candidate_failure_diagnoses(diagnoses: Sequence[Any]) -> list[dict[str, Any]]:
475+ keys = (
476+ "summary",
477+ "root_cause",
478+ "target_ref",
479+ "recommendation",
480+ "decision_contract",
481+ "hypothesis_assessment",
482+ "prior_experiment_assessment",
483+ )
484+ compacted: list[dict[str, Any]] = []
485+ for diagnosis in diagnoses[:2]:
486+ if not isinstance(diagnosis, Mapping):
487+ continue
488+ item: dict[str, Any] = {}
489+ for key in keys:
490+ if key in diagnosis:
491+ item[key] = _compact_value(diagnosis.get(key))
492+ compacted.append(item)
493+ return compacted
494+ 
495+ 
496+def _first_mapping(*values: Any) -> Mapping[str, Any]:
497+ for value in values:
498+ if isinstance(value, Mapping):
499+ return value
500+ return {}
501+ 
502+ 
503+def _compact_score_comparison(
504+ nested: Any,
505+ *,
506+ source: Any,
507+ candidate: Any,
508+ delta: Any,
509+) -> dict[str, Any]:
510+ nested = nested if isinstance(nested, Mapping) else {}
511+ source_value = nested.get("source") if "source" in nested else source
512+ candidate_value = nested.get("candidate") if "candidate" in nested else candidate
513+ delta_value = nested.get("delta") if "delta" in nested else delta
514+ if not _is_number(delta_value) and _is_number(source_value) and _is_number(candidate_value):
515+ delta_value = float(candidate_value) - float(source_value)
516+ return {
517+ "source": source_value if _is_number(source_value) else None,
518+ "candidate": candidate_value if _is_number(candidate_value) else None,
519+ "delta": delta_value if _is_number(delta_value) else None,
520+ }
521+ 
522+ 
523+def _build_trial_record(
524+ trace: Mapping[str, Any],
525+ *,
526+ index: int,
527+ score: Any,
528+ passed: Any,
529+ exit_reason: Any,
530+ critical_evidence_terms: Sequence[str] = (),
531+) -> dict[str, Any]:
532+ trace_id = str(trace.get("trace_id") or f"trace_{index + 1}")
533+ role = str(trace.get("member_role") or trace.get("role") or "")
534+ messages = trace.get("messages")
535+ messages = messages if isinstance(messages, list) else []
536+ calls: list[dict[str, Any]] = []
537+ assistant_outputs: list[dict[str, Any]] = []
538+ for message in messages:
539+ if not isinstance(message, Mapping):
540+ continue
541+ message_index = message.get("message_index", "")
542+ step_pointer = str(message.get("step_pointer") or "")
543+ content = str(message.get("content") or "").strip()
544+ if str(message.get("role") or "") == "assistant" and content:
545+ assistant_outputs.append(
546+ {
547+ "message_index": message_index,
548+ "step_pointer": step_pointer,
549+ "content": content,
550+ }
551+ )
552+ raw_calls = message.get("tool_calls")
553+ if not isinstance(raw_calls, list):
554+ continue
555+ for call_index, raw_call in enumerate(raw_calls):
556+ if not isinstance(raw_call, Mapping):
557+ continue
558+ raw_input = raw_call.get("input", "")
559+ raw_output = raw_call.get("output", "")
560+ raw_output_text = str(raw_output or "")
561+ raw_error = str(raw_call.get("error") or "")
562+ evidence_id = f"{trace_id}:message_{message_index}:call_{call_index}"
563+ calls.append(
564+ {
565+ "evidence_id": evidence_id,
566+ "trace_id": trace_id,
567+ "role": role,
568+ "message_index": message_index,
569+ "step_pointer": str(raw_call.get("step_pointer") or step_pointer),
570+ "tool": str(raw_call.get("name") or ""),
571+ "request": _parse_and_compact(raw_input),
572+ "response": _parse_and_compact(raw_output),
573+ "response_evidence": _response_evidence_view(
574+ raw_output_text,
575+ evidence_id=evidence_id,
576+ critical_terms=critical_evidence_terms,
577+ ),
578+ "error": _compact_text(raw_error, 1_000),
579+ "decision_context": _compact_text(content, 600),
580+ "_raw_input": str(raw_input or ""),
581+ "_raw_output": raw_output_text,
582+ }
583+ )
584+ selected = _select_calls(calls)
585+ public_selected = [_public_call(call) for call in selected]
586+ final_output = assistant_outputs[-1] if assistant_outputs else {}
587+ delivered_output, delivered_reference = _finish_delivery(calls)
588+ if delivered_output:
589+ final_output = {"content": delivered_output, **delivered_reference}
590+ delivery_channel = "finish_tool_request"
591+ else:
592+ delivery_channel = "assistant_message"
593+ sequence = [str(call.get("tool") or "") for call in calls]
594+ return {
595+ "trial_id": trace_id,
596+ "role": role,
597+ "passed": passed if isinstance(passed, bool) else None,
598+ "score": score if _is_number(score) else None,
599+ "exit_reason": str(exit_reason or ""),
600+ "tool_call_count": len(calls),
601+ "tool_sequence": sequence[:_MAX_TOOL_SEQUENCE],
602+ "tool_sequence_truncated": len(sequence) > _MAX_TOOL_SEQUENCE,
603+ "selected_actions": public_selected,
604+ "selection_coverage": {
605+ "policy": "decision_linked_dynamic_budget",
606+ "selected_count": len(selected),
607+ "omitted_count": max(0, len(calls) - len(selected)),
608+ "failed_call_count": sum(1 for call in calls if _call_failed(call)),
609+ "selected_failed_call_count": sum(1 for call in selected if _call_failed(call)),
610+ "state_mutation_call_count": sum(1 for call in calls if _call_mutates_state(call)),
611+ "selected_state_mutation_call_count": sum(1 for call in selected if _call_mutates_state(call)),
612+ "content_evidence_call_count": sum(1 for call in calls if _call_has_content_evidence(call)),
613+ "selected_content_evidence_call_count": sum(1 for call in selected if _call_has_content_evidence(call)),
614+ },
615+ "final_output": {
616+ **_output_summary(str(final_output.get("content") or "")),
617+ "delivery_channel": delivery_channel,
618+ "evidence_ref": {
619+ "trace_id": trace_id,
620+ "role": role,
621+ "message_index": final_output.get("message_index", ""),
622+ "step_pointer": final_output.get("step_pointer", ""),
623+ },
624+ },
625+ "_raw_message_count": len(messages),
626+ "_all_calls": calls,
627+ }
628+ 
629+ 
630+def _finish_delivery(calls: list[dict[str, Any]]) -> tuple[str, dict[str, Any]]:
631+ for call in reversed(calls):
632+ if str(call.get("tool") or "").lower() != "finish":
633+ continue
634+ raw_input = str(call.get("_raw_input") or "")
635+ try:
636+ payload = json.loads(raw_input)
637+ except json.JSONDecodeError:
638+ continue
639+ answer = payload.get("answer") if isinstance(payload, Mapping) else None
640+ if isinstance(answer, str) and answer.strip():
641+ return answer, {
642+ "message_index": call.get("message_index", ""),
643+ "step_pointer": call.get("step_pointer", ""),
644+ }
645+ return "", {}
646+ 
647+ 
648+def _select_calls(calls: list[dict[str, Any]]) -> list[dict[str, Any]]:
649+ budget = min(
650+ _MAX_SELECTED_CALLS_PER_TRIAL,
651+ max(_MIN_SELECTED_CALLS_PER_TRIAL, (len(calls) * 2 + 2) // 3),
652+ )
653+ if len(calls) <= budget:
654+ for call in calls:
655+ call["selection_reasons"] = ["complete_trace"]
656+ return calls
657+ 
658+ first_by_tool: dict[str, int] = {}
659+ last_by_tool: dict[str, int] = {}
660+ for index, call in enumerate(calls):
661+ tool = str(call.get("tool") or "")
662+ first_by_tool.setdefault(tool, index)
663+ last_by_tool[tool] = index
664+ 
665+ first_indexes = set(first_by_tool.values())
666+ last_indexes = set(last_by_tool.values())
667+ 
668+ def _reasons(index: int) -> list[str]:
669+ call = calls[index]
670+ reasons: list[str] = []
671+ if _call_failed(call):
672+ reasons.append("observed_failure")
673+ if _call_mutates_state(call):
674+ reasons.append("state_mutation")
675+ if _call_has_content_evidence(call):
676+ reasons.append("content_evidence")
677+ if index in first_indexes:
678+ reasons.append("first_use_of_tool")
679+ if index in last_indexes:
680+ reasons.append("last_use_of_tool")
681+ if index >= len(calls) - 3:
682+ reasons.append("terminal_window")
683+ return reasons
684+ 
685+ ranked = sorted(
686+ range(len(calls)),
687+ key=lambda index: (
688+ not _call_failed(calls[index]),
689+ not _call_mutates_state(calls[index]),
690+ not _call_has_content_evidence(calls[index]),
691+ index not in first_indexes and index not in last_indexes,
692+ index < len(calls) - 3,
693+ -len(str(calls[index].get("_raw_output") or "")),
694+ index,
695+ ),
696+ )
697+ selected_indexes = set(ranked[:budget])
698+ for index in selected_indexes:
699+ calls[index]["selection_reasons"] = _reasons(index) or ["dynamic_budget"]
700+ return [calls[index] for index in sorted(selected_indexes)]
701+ 
702+ 
703+def _public_call(call: Mapping[str, Any]) -> dict[str, Any]:
704+ public = {
705+ key: value
706+ for key, value in call.items()
707+ if not key.startswith("_") and (value not in ("", {}, []) or key in {"evidence_id", "tool", "request"})
708+ }
709+ if not _call_failed(call) and not _call_mutates_state(call):
710+ public.pop("decision_context", None)
711+ return public
712+ 
713+ 
714+def _deduplicate_selected_payloads(trials: list[dict[str, Any]]) -> None:
715+ seen: dict[str, str] = {}
716+ for trial in trials:
717+ for call in trial.get("selected_actions", []):
718+ payload = {
719+ "tool": call.get("tool"),
720+ "request": call.get("request"),
721+ "response": call.get("response"),
722+ "error": call.get("error"),
723+ }
724+ fingerprint = hashlib.sha256(
725+ json.dumps(payload, ensure_ascii=False, sort_keys=True, default=str).encode("utf-8")
726+ ).hexdigest()
727+ original = seen.get(fingerprint)
728+ if original:
729+ call["duplicate_of"] = original
730+ call.pop("request", None)
731+ call.pop("response", None)
732+ call.pop("decision_context", None)
733+ else:
734+ seen[fingerprint] = str(call.get("evidence_id") or "")
735+ 
736+ 
737+def _tool_contract_observations(
738+ task_contract: Mapping[str, Any],
739+ calls: list[dict[str, Any]],
740+) -> list[dict[str, Any]]:
741+ schemas: dict[str, dict[str, Any]] = {}
742+ raw_schemas = task_contract.get("tool_schemas")
743+ if isinstance(raw_schemas, list):
744+ for schema in raw_schemas:
745+ if isinstance(schema, Mapping):
746+ name = str(schema.get("name") or "")
747+ if name:
748+ schemas[name] = dict(schema)
749+ 
750+ observed_request_fields: dict[str, set[str]] = defaultdict(set)
751+ observed_response_fields: dict[str, set[str]] = defaultdict(set)
752+ observed_response_leaf_fields: dict[str, set[str]] = defaultdict(set)
753+ call_counts: Counter[str] = Counter()
754+ for call in calls:
755+ tool = str(call.get("tool") or "")
756+ if not tool:
757+ continue
758+ call_counts[tool] += 1
759+ request = call.get("request")
760+ response = call.get("response")
761+ if isinstance(request, Mapping):
762+ observed_request_fields[tool].update(map(str, request))
763+ if isinstance(response, Mapping):
764+ observed_response_fields[tool].update(map(str, response))
765+ observed_response_leaf_fields[tool].update(_leaf_field_names(response))
766+ 
767+ observations: list[dict[str, Any]] = []
768+ for tool in sorted(set(call_counts) | set(schemas)):
769+ schema = schemas.get(tool, {})
770+ allowed = set(map(str, schema.get("allowed_request_fields", [])))
771+ required = set(map(str, schema.get("required_request_fields", [])))
772+ request_fields = observed_request_fields[tool]
773+ response_fields = observed_response_fields[tool]
774+ response_leaf_fields = observed_response_leaf_fields[tool]
775+ observation = {
776+ "tool": tool,
777+ "call_count": call_counts[tool],
778+ "public_schema_available": bool(schema),
779+ "description": schema.get("description", ""),
780+ "allowed_request_fields": sorted(allowed),
781+ "required_request_fields": sorted(required),
782+ "request_field_contracts": schema.get("request_field_contracts", {}),
783+ "observed_request_fields": sorted(request_fields),
784+ "observed_response_fields": sorted(response_fields),
785+ "observed_response_leaf_fields": sorted(response_leaf_fields),
786+ "response_only_fields": sorted(response_fields - request_fields),
787+ }
788+ if schema:
789+ observation["response_fields_not_in_public_request_schema"] = sorted(response_fields - allowed)
790+ observation["response_leaf_fields_not_in_public_request_schema"] = sorted(response_leaf_fields - allowed)
791+ observation["observed_request_fields_outside_public_schema"] = sorted(request_fields - allowed)
792+ observations.append(observation)
793+ return observations
794+ 
795+ 
796+def _cross_trial_contrast(trials: list[dict[str, Any]]) -> dict[str, Any]:
797+ if not trials:
798+ return {"available": False, "reason": "no_normalized_traces"}
799+ sequences = [list(map(str, trial.get("tool_sequence", []))) for trial in trials]
800+ tool_sets = [set(sequence) for sequence in sequences]
801+ passed_ids = [trial["trial_id"] for trial in trials if trial.get("passed") is True]
802+ failed_ids = [trial["trial_id"] for trial in trials if trial.get("passed") is False]
803+ success_tools = set().union(
804+ *(tool_sets[index] for index, trial in enumerate(trials) if trial.get("passed") is True)
805+ )
806+ failure_tools = set().union(
807+ *(tool_sets[index] for index, trial in enumerate(trials) if trial.get("passed") is False)
808+ )
809+ stable_tools = set.intersection(*tool_sets) if tool_sets else set()
810+ return {
811+ "available": len(trials) > 1,
812+ "successful_trials": passed_ids,
813+ "failed_trials": failed_ids,
814+ "stable_tools": sorted(stable_tools),
815+ "success_only_tools": sorted(success_tools - failure_tools) if passed_ids and failed_ids else [],
816+ "failure_only_tools": sorted(failure_tools - success_tools) if passed_ids and failed_ids else [],
817+ "first_tool_sequence_divergence": _first_sequence_divergence(trials, sequences),
818+ "terminal_action_variants": _terminal_action_variants(trials),
819+ "final_output_comparison": [
820+ {
821+ "trial_id": trial["trial_id"],
822+ "passed": trial.get("passed"),
823+ "score": trial.get("score"),
824+ "character_count": trial.get("final_output", {}).get("character_count", 0),
825+ "line_count": trial.get("final_output", {}).get("line_count", 0),
826+ "delivery_channel": trial.get("final_output", {}).get("delivery_channel", ""),
827+ "artifact_mentions": trial.get("final_output", {}).get("artifact_mentions", []),
828+ "evidence_ref": trial.get("final_output", {}).get("evidence_ref", {}),
829+ }
830+ for trial in trials
831+ ],
832+ }
833+ 
834+ 
835+def _first_sequence_divergence(trials: list[dict[str, Any]], sequences: list[list[str]]) -> dict[str, Any]:
836+ if len(sequences) < 2:
837+ return {}
838+ max_length = max(map(len, sequences), default=0)
839+ for index in range(max_length):
840+ values = [sequence[index] if index < len(sequence) else "<end>" for sequence in sequences]
841+ if len(set(values)) > 1:
842+ return {
843+ "tool_index": index,
844+ "by_trial": {str(trial["trial_id"]): value for trial, value in zip(trials, values, strict=True)},
845+ }
846+ return {}
847+ 
848+ 
849+def _terminal_action_variants(trials: list[dict[str, Any]]) -> list[dict[str, Any]]:
850+ by_tool: dict[str, list[dict[str, Any]]] = defaultdict(list)
851+ for trial in trials:
852+ actions = trial.get("selected_actions", [])
853+ for action in actions:
854+ tool = str(action.get("tool") or "")
855+ if _is_mutating_tool(tool):
856+ by_tool[tool].append(
857+ {
858+ "trial_id": trial["trial_id"],
859+ "passed": trial.get("passed"),
860+ "score": trial.get("score"),
861+ "request": action.get("request"),
862+ "evidence_id": action.get("evidence_id"),
863+ }
864+ )
865+ return [{"tool": tool, "variants": variants} for tool, variants in sorted(by_tool.items())]
866+ 
867+ 
868+def _normalize_tool_schema(schema: Mapping[str, Any]) -> dict[str, Any]:
869+ function = schema.get("function")
870+ function = function if isinstance(function, Mapping) else schema
871+ name = str(function.get("name") or "")
872+ if not name:
873+ return {}
874+ parameters = function.get("parameters")
875+ parameters = parameters if isinstance(parameters, Mapping) else {}
876+ properties = parameters.get("properties")
877+ properties = properties if isinstance(properties, Mapping) else {}
878+ return {
879+ "name": name,
880+ "description": _compact_text(function.get("description"), 500),
881+ "allowed_request_fields": sorted(map(str, properties)),
882+ "required_request_fields": sorted(map(str, parameters.get("required", []))),
883+ "request_field_contracts": {
884+ str(field): {
885+ key: _compact_value(spec.get(key))
886+ for key in ("type", "description", "enum", "items")
887+ if isinstance(spec, Mapping) and key in spec
888+ }
889+ for field, spec in properties.items()
890+ },
891+ }
892+ 
893+ 
894+def _judge_dimensions(metadata: Mapping[str, Any]) -> dict[str, Any]:
895+ detail = metadata.get("judge_detail")
896+ if not isinstance(detail, Mapping):
897+ return {}
898+ return {str(key): value for key, value in detail.items() if _is_scalar(value)}
899+ 
900+ 
901+def _leaf_field_names(value: Mapping[str, Any], *, depth: int = 0) -> set[str]:
902+ leaves: set[str] = set()
903+ for key, item in value.items():
904+ name = str(key)
905+ if isinstance(item, Mapping) and depth < 3:
906+ leaves.update(_leaf_field_names(item, depth=depth + 1))
907+ else:
908+ leaves.add(name)
909+ return leaves
910+ 
911+ 
912+def _output_summary(value: str) -> dict[str, Any]:
913+ text = str(value or "")
914+ artifacts = []
915+ for match in _ARTIFACT_PATTERN.finditer(text):
916+ candidate = match.group(1)
917+ if candidate not in artifacts:
918+ artifacts.append(candidate)
919+ return {
920+ "character_count": len(text),
921+ "line_count": len(text.splitlines()),
922+ "artifact_mentions": artifacts[:12],
923+ "excerpt": _head_tail(text, 1_600),
924+ }
925+ 
926+ 
927+def _failed_requirement_terms(metadata: Mapping[str, Any]) -> list[str]:
928+ """Extract bounded anchors from failed evaluator criteria.
929+ 
930+ These terms are used only to retain exact public trajectory spans. They do
931+ not become conclusions and do not change the evaluator outcome.
932+ """
933+ normalized = metadata.get("judge_evidence")
934+ detail = normalized if isinstance(normalized, Mapping) else metadata.get("judge_detail")
935+ detail = detail if isinstance(detail, Mapping) else {}
936+ criteria = detail.get("criteria")
937+ criteria = criteria if isinstance(criteria, list) else []
938+ values: list[str] = []
939+ for criterion in criteria:
940+ if not isinstance(criterion, Mapping):
941+ continue
942+ score = criterion.get("score")
943+ if _is_number(score) and float(score) > 0:
944+ continue
945+ values.extend(
946+ str(criterion.get(key) or "") for key in ("criterion_id", "verifier_id", "rationale") if criterion.get(key)
947+ )
948+ 
949+ terms: list[str] = []
950+ seen: set[str] = set()
951+ for value in values:
952+ for match in _CRITICAL_EVIDENCE_TERM_PATTERN.findall(value):
953+ normalized_term = match.casefold()
954+ if normalized_term in _CRITICAL_EVIDENCE_STOPWORDS or normalized_term in seen:
955+ continue
956+ seen.add(normalized_term)
957+ terms.append(match)
958+ if len(terms) >= 48:
959+ return terms
960+ return terms
961+ 
962+ 
963+def extract_critical_evidence_spans(
964+ value: Any,
965+ terms: Sequence[str],
966+ *,
967+ max_spans: int = 3,
968+ max_total_chars: int = 6_000,
969+) -> list[dict[str, Any]]:
970+ """Return exact line windows tied to failed-requirement terms.
971+ 
972+ Unlike the display excerpt, these windows are selected from the raw tool
973+ response before compaction. The returned text never uses an unlabelled
974+ omission marker.
975+ """
976+ text, projection = _readable_evidence_text(str(value or ""))
977+ normalized_terms = list(dict.fromkeys(str(term).casefold() for term in terms if str(term).strip()))
978+ if not text or not normalized_terms:
979+ return []
980+ if max_spans <= 0 or max_total_chars <= 0:
981+ return []
982+ 
983+ lines = text.splitlines() or [text]
984+ ranked: list[tuple[int, int, list[str]]] = []
985+ for index, line in enumerate(lines):
986+ lowered = line.casefold()
987+ matched = [term for term in normalized_terms if term in lowered]
988+ if matched:
989+ ranked.append((len(set(matched)), index, matched))
990+ if not ranked:
991+ return []
992+ 
993+ selected_lines = sorted(ranked, key=lambda item: (-item[0], item[1]))[:max_spans]
994+ windows: list[tuple[int, int, set[str]]] = []
995+ for _, index, matched in sorted(selected_lines, key=lambda item: item[1]):
996+ start = max(0, index - 3)
997+ end = min(len(lines), index + 4)
998+ if windows and start <= windows[-1][1]:
999+ previous_start, previous_end, previous_terms = windows[-1]
1000+ windows[-1] = (previous_start, max(previous_end, end), previous_terms | set(matched))
1001+ else:
1002+ windows.append((start, end, set(matched)))
1003+ 
1004+ spans: list[dict[str, Any]] = []
1005+ remaining = max_total_chars
1006+ for start, end, matched in windows:
1007+ if remaining <= 0:
1008+ break
1009+ raw_span = "\n".join(lines[start:end])
1010+ span_text, complete = _bounded_critical_span(raw_span, matched, remaining)
1011+ if not span_text:
1012+ continue
1013+ spans.append(
1014+ {
1015+ "source": "raw_tool_response",
1016+ "projection": projection,
1017+ "line_start": start + 1,
1018+ "line_end": end,
1019+ "matched_terms": sorted(matched),
1020+ "text": span_text,
1021+ "window_complete": complete,
1022+ }
1023+ )
1024+ remaining -= len(span_text)
1025+ return spans
1026+ 
1027+ 
1028+def _readable_evidence_text(text: str) -> tuple[str, str]:
1029+ """Project serialized tool-result newlines without semantic summarization."""
1030+ if "\n" not in text and "\\n" in text:
1031+ return (
1032+ text.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\\t", "\t"),
1033+ "escaped_newlines_normalized",
1034+ )
1035+ return text, "verbatim"
1036+ 
1037+ 
1038+def _bounded_critical_span(text: str, matched_terms: set[str], limit: int) -> tuple[str, bool]:
1039+ if len(text) <= limit:
1040+ return text, True
1041+ lowered = text.casefold()
1042+ positions = [lowered.find(term) for term in matched_terms if lowered.find(term) >= 0]
1043+ center = min(positions) if positions else len(text) // 2
1044+ headroom = max(0, limit // 3)
1045+ start = max(0, center - headroom)
1046+ end = min(len(text), start + limit)
1047+ start = max(0, end - limit)
1048+ return text[start:end], False
1049+ 
1050+ 
1051+def _response_evidence_view(
1052+ raw_output: str,
1053+ *,
1054+ evidence_id: str,
1055+ critical_terms: Sequence[str],
1056+) -> dict[str, Any]:
1057+ source_chars = len(raw_output)
1058+ return {
1059+ "raw_evidence_ref": evidence_id,
1060+ "source": "public_normalized_execution_trace.raw_tool_response",
1061+ "source_char_count": source_chars,
1062+ "display_excerpt_complete": source_chars <= 1_200,
1063+ "display_omission_origin": "none" if source_chars <= 1_200 else "analyzer_evidence_compactor",
1064+ "task_agent_observed_display_omission_marker": False,
1065+ "critical_spans": extract_critical_evidence_spans(raw_output, critical_terms),
1066+ }
1067+ 
1068+ 
1069+def _parse_and_compact(value: Any) -> Any:
1070+ if not isinstance(value, str):
1071+ return _compact_value(value)
1072+ text = value.strip()
1073+ if not text:
1074+ return ""
1075+ try:
1076+ parsed = json.loads(text)
1077+ except json.JSONDecodeError:
1078+ return _head_tail(text, 2_000)
1079+ return _compact_value(parsed)
1080+ 
1081+ 
1082+def _compact_value(value: Any, *, depth: int = 0) -> Any:
1083+ if depth >= 5:
1084+ return _compact_text(value, 500)
1085+ if isinstance(value, Mapping):
1086+ items = list(value.items())
1087+ compact = {str(key): _compact_value(item, depth=depth + 1) for key, item in items[:30]}
1088+ if len(items) > 30:
1089+ compact["_omitted_field_count"] = len(items) - 30
1090+ return compact
1091+ if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
1092+ items = list(value)
1093+ compact_items = [_compact_value(item, depth=depth + 1) for item in items[:30]]
1094+ if len(items) > 30:
1095+ compact_items.append({"_omitted_item_count": len(items) - 30})
1096+ return compact_items
1097+ if isinstance(value, str):
1098+ return _head_tail(value, 1_200)
1099+ if _is_scalar(value):
1100+ return value
1101+ return _compact_text(value, 500)
1102+ 
1103+ 
1104+def _aligned_list(value: Any, expected_length: int) -> list[Any]:
1105+ return list(value) if isinstance(value, list) and len(value) == expected_length else []
1106+ 
1107+ 
1108+def _is_mutating_tool(tool: str) -> bool:
1109+ return bool(_MUTATING_TOOL_PATTERN.search(tool))
1110+ 
1111+ 
1112+def _call_failed(call: Mapping[str, Any]) -> bool:
1113+ if str(call.get("error") or "").strip():
1114+ return True
1115+ return bool(_FAILED_RESPONSE_PATTERN.search(str(call.get("_raw_output") or call.get("response") or "")))
1116+ 
1117+ 
1118+def _call_mutates_state(call: Mapping[str, Any]) -> bool:
1119+ tool = str(call.get("tool") or "")
1120+ if _is_mutating_tool(tool):
1121+ return True
1122+ if tool.lower() not in {"bash", "shell", "exec", "exec_command"}:
1123+ return False
1124+ return bool(_BASH_MUTATION_PATTERN.search(_call_command(call)))
1125+ 
1126+ 
1127+def _call_has_content_evidence(call: Mapping[str, Any]) -> bool:
1128+ if _call_failed(call):
1129+ return False
1130+ tool = str(call.get("tool") or "").lower()
1131+ raw_output = str(call.get("_raw_output") or call.get("response") or "")
1132+ if len(raw_output) < 300:
1133+ return False
1134+ if tool.startswith("read") or tool in {"open_file", "view_file"}:
1135+ return True
1136+ return tool in {"bash", "shell", "exec", "exec_command"} and bool(_BASH_CONTENT_PATTERN.search(_call_command(call)))
1137+ 
1138+ 
1139+def _call_command(call: Mapping[str, Any]) -> str:
1140+ raw_input = call.get("_raw_input") or call.get("request") or ""
1141+ if isinstance(raw_input, Mapping):
1142+ return str(raw_input.get("command") or raw_input.get("cmd") or "")
1143+ if not isinstance(raw_input, str):
1144+ return ""
1145+ try:
1146+ parsed = json.loads(raw_input)
1147+ except json.JSONDecodeError:
1148+ return raw_input
1149+ if not isinstance(parsed, Mapping):
1150+ return raw_input
1151+ return str(parsed.get("command") or parsed.get("cmd") or "")
1152+ 
1153+ 
1154+def _is_scalar(value: Any) -> bool:
1155+ return value is None or isinstance(value, str | int | float | bool)
1156+ 
1157+ 
1158+def _is_number(value: Any) -> bool:
1159+ return isinstance(value, int | float) and not isinstance(value, bool)
1160+ 
1161+ 
1162+def _compact_text(value: Any, limit: int) -> str:
1163+ return _head_tail(str(value or ""), limit)
1164+ 
1165+ 
1166+def _head_tail(text: str, limit: int) -> str:
1167+ if len(text) <= limit:
1168+ return text
1169+ head = max(1, int(limit * 0.7))
1170+ tail = max(1, limit - head)
1171+ omitted = len(text) - head - tail
1172+ marker = _COMPACTION_MARKER.format(omitted=omitted)
1173+ return f"{text[:head]}\n{marker}\n{text[-tail:]}"
1174+ 
1175+ 
1176+def _read_mapping(path: Path) -> dict[str, Any]:
1177+ if not path.is_file():
1178+ return {}
1179+ try:
1180+ value = json.loads(path.read_text(encoding="utf-8"))
1181+ except (OSError, json.JSONDecodeError):
1182+ return {}
1183+ return dict(value) if isinstance(value, Mapping) else {}
@@ -0,0 +1,1672 @@
1+# coding: utf-8
2+# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
3+"""Bounded, read-only evidence acquisition for causal RSI diagnosis."""
4+ 
5+from __future__ import annotations
6+ 
7+import ast
8+import hashlib
9+import json
10+import math
11+import os
12+import re
13+import zipfile
14+from collections.abc import Mapping, Sequence
15+from pathlib import Path
16+from typing import Any
17+from xml.etree import ElementTree
18+ 
19+from openjiuwen.rsi.evaluation_result_analyzer.case_reader import CaseAnalysisInput
20+ 
21+_ALLOWED_OPERATIONS = {
22+ "check_relation",
23+ "compare_numeric_change",
24+ "compare_runs",
25+ "inspect_artifact",
26+ "inspect_evaluation",
27+ "read_artifact_window",
28+ "read_repository_file",
29+ "read_event",
30+ "search_repository",
31+ "search_trace",
32+}
33+_MAX_HYPOTHESES = 6
34+_MAX_REQUESTS = 12
35+_MAX_SEARCH_RESULTS = 5
36+_MAX_EVENT_CHARS = 12_000
37+_MAX_ARTIFACT_FILE_CHARS = 200_000
38+_MAX_ARTIFACT_FILES = 100
39+_MAX_ARTIFACT_WINDOW_CHARS = 12_000
40+_MAX_AUTOMATIC_ARTIFACT_WINDOWS = 12
41+_MAX_AUTOMATIC_ARTIFACT_SOURCES = 4
42+_MAX_AUTOMATIC_ARTIFACT_CHARS = 96_000
43+_MAX_STRUCTURED_ARTIFACTS_PER_REQUEST = 16
44+_MAX_REPOSITORY_FILES = 2_000
45+_MAX_STRUCTURED_CELLS = 20_000
46+_MAX_STRUCTURED_PAGES = 100
47+_TEXT_ARTIFACT_SUFFIXES = {
48+ ".csv",
49+ ".diff",
50+ ".json",
51+ ".log",
52+ ".md",
53+ ".patch",
54+ ".txt",
55+ ".xml",
56+ ".yaml",
57+ ".yml",
58+}
59+_STRUCTURED_ARTIFACT_SUFFIXES = {".docx", ".pdf", ".pptx", ".xlsx"}
60+_STRUCTURED_QUERY_SUFFIX_HINTS = {
61+ ".docx": ("docx", "word", "paragraph", "document", "文档", "段落"),
62+ ".pdf": ("pdf", "page", "document", "文档", "页面"),
63+ ".pptx": ("pptx", "powerpoint", "slide", "deck", "presentation", "幻灯片", "演示"),
64+ ".xlsx": (
65+ "xlsx",
66+ "excel",
67+ "workbook",
68+ "worksheet",
69+ "spreadsheet",
70+ "cell",
71+ "formula",
72+ "工作簿",
73+ "工作表",
74+ "单元格",
75+ "公式",
76+ ),
77+}
78+_TERM_PATTERN = re.compile(r"[A-Za-z][A-Za-z0-9_.:/-]{1,}|\d+(?:\.\d+)?|[\u4e00-\u9fff]{2,8}")
79+_EXPLICIT_NUMERIC_DELTA_PATTERN = re.compile(
80+ r"(?:\b(?:before[-\s]*(?:vs\.?|versus|and)[-\s]*after|numeric\s+(?:change|delta)|"
81+ r"formula\s+(?:change|delta))\b|(?:数值|公式)(?:变化|差值)|前后(?:数值|公式))",
82+ re.IGNORECASE,
83+)
84+_IMPLICIT_NUMERIC_DELTA_PATTERN = re.compile(
85+ r"(?:[%%]|\b(?:percentage\s+points?|percent|delta|difference|increase[sd]?|decrease[sd]?|"
86+ r"subtract(?:ed|ing)?|add(?:ed|ing)?|formula|ratio|rate)\b|(?:百分点|百分比|增(?:加|长)|"
87+ r"减少|差值|公式|比率|比例))",
88+ re.IGNORECASE,
89+)
90+_STOPWORDS = {
91+ "about",
92+ "after",
93+ "agent",
94+ "before",
95+ "case",
96+ "complete",
97+ "content",
98+ "document",
99+ "evidence",
100+ "failure",
101+ "file",
102+ "from",
103+ "full",
104+ "inspect",
105+ "into",
106+ "missing",
107+ "output",
108+ "result",
109+ "read",
110+ "show",
111+ "should",
112+ "source",
113+ "task",
114+ "that",
115+ "the",
116+ "this",
117+ "tool",
118+ "trace",
119+ "with",
120+}
121+_ABSENCE_CLAIM_PATTERN = re.compile(
122+ r"\b(?:absent|absence|missing|lacks?|without|does\s+not\s+(?:contain|include|show)|"
123+ r"no\s+(?:evidence|record|field|entry|occurrence))\b|(?:缺少|缺失|不存在|未包含|没有)",
124+ re.IGNORECASE,
125+)
126+_EXISTENCE_CLAIM_PATTERN = re.compile(
127+ r"\b(?:contains?|includes?|present|exists?|records?|shows?|states?)\b|(?:包含|存在|记录|显示|说明)",
128+ re.IGNORECASE,
129+)
130+ 
131+ 
132+def causal_hypothesis_semantic_id(claim: str, falsified_if: str) -> str:
133+ """Return a stable identity for one causal statement, independent of local labels."""
134+ canonical = json.dumps(
135+ {
136+ "claim": _normalized_semantics(claim),
137+ "falsified_if": _normalized_semantics(falsified_if),
138+ },
139+ ensure_ascii=True,
140+ sort_keys=True,
141+ separators=(",", ":"),
142+ )
143+ return f"chs:{hashlib.sha256(canonical.encode('utf-8')).hexdigest()[:24]}"
144+ 
145+ 
146+def normalize_causal_investigation(
147+ value: Mapping[str, Any] | None,
148+ *,
149+ failed_requirement_ids: Sequence[str] = (),
150+ max_requests: int = _MAX_REQUESTS,
151+ max_hypotheses: int = _MAX_HYPOTHESES,
152+ min_hypotheses: int = 1,
153+ min_hypotheses_per_requirement: int = 0,
154+ require_evidence_per_hypothesis: bool = False,
155+) -> dict[str, Any] | None:
156+ """Validate and bound a model-proposed causal investigation plan."""
157+ if not isinstance(value, Mapping):
158+ return None
159+ raw = value.get("causal_investigation")
160+ if not isinstance(raw, Mapping):
161+ raw = value.get("investigation")
162+ if not isinstance(raw, Mapping):
163+ raw = value
164+ if not isinstance(raw, Mapping):
165+ return None
166+ hypothesis_limit = max(1, min(_MAX_HYPOTHESES, int(max_hypotheses)))
167+ raw_hypotheses = raw.get("hypotheses")
168+ if not isinstance(raw_hypotheses, list) or not raw_hypotheses:
169+ return None
170+ 
171+ allowed_requirements = {str(item) for item in failed_requirement_ids if str(item)}
172+ hypotheses: list[dict[str, Any]] = []
173+ hypothesis_ids: set[str] = set()
174+ hypothesis_semantics: set[tuple[str, str]] = set()
175+ raw_requests: list[tuple[str, Mapping[str, Any]]] = []
176+ for index, item in enumerate(raw_hypotheses[:hypothesis_limit], start=1):
177+ if not isinstance(item, Mapping):
178+ continue
179+ claim = str(item.get("claim", "") or "").strip()
180+ falsified_if = str(item.get("falsified_if", "") or "").strip()
181+ if not claim or not falsified_if:
182+ continue
183+ semantics = (_normalized_semantics(claim), _normalized_semantics(falsified_if))
184+ if semantics in hypothesis_semantics:
185+ continue
186+ hypothesis_semantics.add(semantics)
187+ hypothesis_id = str(item.get("hypothesis_id", "") or f"h{index}").strip()
188+ if not hypothesis_id or hypothesis_id in hypothesis_ids:
189+ hypothesis_id = f"h{index}"
190+ hypothesis_ids.add(hypothesis_id)
191+ explains = _string_list(item.get("explains_requirement_ids"))
192+ if allowed_requirements:
193+ explains = [requirement_id for requirement_id in explains if requirement_id in allowed_requirements]
194+ requests = item.get("evidence_requests")
195+ raw_hypothesis_requests = requests if isinstance(requests, list) else []
196+ declared_numeric_check = item.get("numeric_change_check_required")
197+ # A model may underestimate its own verification obligation. Explicit
198+ # ``false`` therefore cannot disable a controller-detected numeric
199+ # before/after claim. This is a control-plane decision, not a prompt
200+ # preference: textual arithmetic is never accepted as execution.
201+ hypothesis_text = f"{claim}\n{falsified_if}"
202+ numeric_change_check_required = bool(declared_numeric_check) or bool(
203+ _EXPLICIT_NUMERIC_DELTA_PATTERN.search(hypothesis_text)
204+ or _IMPLICIT_NUMERIC_DELTA_PATTERN.search(hypothesis_text)
205+ )
206+ if any(
207+ isinstance(request, Mapping) and request.get("operation") == "compare_numeric_change"
208+ for request in raw_hypothesis_requests
209+ ):
210+ numeric_change_check_required = True
211+ hypotheses.append(
212+ {
213+ "hypothesis_id": hypothesis_id,
214+ "hypothesis_semantic_id": causal_hypothesis_semantic_id(claim, falsified_if),
215+ "claim": claim,
216+ "explains_requirement_ids": explains,
217+ "current_support": _string_list(item.get("current_support"))[:8],
218+ "falsified_if": falsified_if,
219+ "numeric_change_check_required": numeric_change_check_required,
220+ }
221+ )
222+ raw_requests.extend(
223+ (hypothesis_id, request) for request in raw_hypothesis_requests if isinstance(request, Mapping)
224+ )
225+ 
226+ top_level_requests = raw.get("evidence_requests")
227+ if isinstance(top_level_requests, list):
228+ raw_requests.extend(("", request) for request in top_level_requests if isinstance(request, Mapping))
229+ if len(hypotheses) < max(1, min(hypothesis_limit, int(min_hypotheses))):
230+ return None
231+ required_alternatives = max(0, min(hypothesis_limit, int(min_hypotheses_per_requirement)))
232+ if allowed_requirements and required_alternatives:
233+ hypothesis_coverage = {
234+ requirement_id: sum(requirement_id in hypothesis["explains_requirement_ids"] for hypothesis in hypotheses)
235+ for requirement_id in allowed_requirements
236+ }
237+ if any(count < required_alternatives for count in hypothesis_coverage.values()):
238+ return None
239+ 
240+ request_limit = min(_MAX_REQUESTS, max(0, int(max_requests)))
241+ requests: list[dict[str, Any]] = []
242+ request_index_by_fingerprint: dict[str, int] = {}
243+ used_request_ids: set[str] = set()
244+ for default_hypothesis_id, request in raw_requests:
245+ normalized = _normalize_request(
246+ request,
247+ default_hypothesis_id=default_hypothesis_id,
248+ known_hypothesis_ids=hypothesis_ids,
249+ index=len(requests) + 1,
250+ )
251+ if normalized is None:
252+ continue
253+ fingerprint = _request_execution_fingerprint(normalized)
254+ duplicate_index = request_index_by_fingerprint.get(fingerprint)
255+ if duplicate_index is not None:
256+ existing = requests[duplicate_index]
257+ existing["hypothesis_ids"] = list(
258+ dict.fromkeys(
259+ [
260+ *_string_list(existing.get("hypothesis_ids")),
261+ *_string_list(normalized.get("hypothesis_ids")),
262+ ]
263+ )
264+ )
265+ continue
266+ if request_limit == 0 or len(requests) >= request_limit:
267+ # Keep scanning so later declarations can still bind another
268+ # hypothesis to an already retained shared probe.
269+ continue
270+ request_id = str(normalized.get("request_id", "") or f"q{len(requests) + 1}")
271+ if request_id in used_request_ids:
272+ stem = request_id
273+ suffix = 2
274+ while request_id in used_request_ids:
275+ request_id = f"{stem}_{suffix}"
276+ suffix += 1
277+ normalized["request_id"] = request_id
278+ used_request_ids.add(request_id)
279+ request_index_by_fingerprint[fingerprint] = len(requests)
280+ requests.append(normalized)
281+ 
282+ if require_evidence_per_hypothesis:
283+ covered: set[str] = set()
284+ for request in requests:
285+ covered.update(_string_list(request.get("hypothesis_ids")))
286+ if not hypothesis_ids.issubset(covered):
287+ return None
288+ 
289+ hypotheses_by_id = {item["hypothesis_id"]: item for item in hypotheses}
290+ for request in requests:
291+ if request.get("operation") != "inspect_artifact" or request.get("proof_obligation"):
292+ continue
293+ claim_text = " ".join(
294+ str(hypotheses_by_id[hypothesis_id].get("claim", "") or "")
295+ for hypothesis_id in _string_list(request.get("hypothesis_ids"))
296+ if hypothesis_id in hypotheses_by_id
297+ )
298+ request["proof_obligation"] = (
299+ "absence"
300+ if _ABSENCE_CLAIM_PATTERN.search(claim_text)
301+ else "existence"
302+ if claim_text and _EXISTENCE_CLAIM_PATTERN.search(claim_text)
303+ else "coverage"
304+ )
305+ 
306+ return {
307+ "schema_version": 1,
308+ "hypotheses": hypotheses,
309+ "evidence_requests": requests,
310+ "ready_without_more_evidence": bool(raw.get("ready_without_more_evidence")) and not requests,
311+ }
312+ 
313+ 
314+def _request_execution_fingerprint(request: Mapping[str, Any]) -> str:
315+ """Identify one controller operation independently of model-local labels."""
316+ execution = {key: value for key, value in request.items() if key not in {"request_id", "hypothesis_ids", "purpose"}}
317+ return json.dumps(execution, ensure_ascii=True, sort_keys=True, separators=(",", ":"))
318+ 
319+ 
320+def execute_causal_investigation(
321+ case: CaseAnalysisInput,
322+ investigation: Mapping[str, Any],
323+ *,
324+ prior_candidate_feedback: Mapping[str, Any] | None = None,
325+ evidence_root: str | Path | None = None,
326+) -> dict[str, Any]:
327+ """Execute only controller-owned evidence operations for one public case."""
328+ trace_data = _read_json(Path(case.result_path).parent / "judge" / "normalized_trace.json")
329+ events = _trace_events(trace_data)
330+ repository_dir = _repository_dir(evidence_root)
331+ discovered_repository_paths: set[str] = set()
332+ artifact_text_cache: dict[Path, str] = {}
333+ results: list[dict[str, Any]] = []
334+ for request in investigation.get("evidence_requests", []):
335+ if not isinstance(request, Mapping):
336+ continue
337+ operation = str(request.get("operation", "") or "")
338+ if operation == "check_relation":
339+ evidence = _check_relation(request)
340+ elif operation == "compare_numeric_change":
341+ evidence = _compare_numeric_change(request)
342+ elif operation == "search_trace":
343+ evidence = _search_trace(events, request)
344+ elif operation == "read_event":
345+ evidence = _read_event(events, request)
346+ elif operation == "inspect_artifact":
347+ evidence = _inspect_artifact(case, request, text_cache=artifact_text_cache)
348+ elif operation == "read_artifact_window":
349+ evidence = _read_artifact_window(case, request, text_cache=artifact_text_cache)
350+ elif operation == "inspect_evaluation":
351+ evidence = _inspect_evaluation(case, request)
352+ elif operation == "search_repository":
353+ evidence = _search_repository(repository_dir, request)
354+ discovered_repository_paths.update(
355+ str(item.get("relative_path", "") or "")
356+ for item in evidence.get("files", [])
357+ if isinstance(item, Mapping) and str(item.get("relative_path", "") or "")
358+ )
359+ elif operation == "read_repository_file":
360+ evidence = _read_repository_file(
361+ repository_dir,
362+ request,
363+ discovered_paths=discovered_repository_paths,
364+ )
365+ elif operation == "compare_runs":
366+ evidence = _compare_runs(prior_candidate_feedback, request)
367+ else:
368+ continue
369+ results.append(
370+ {
371+ "request_id": str(request.get("request_id", "") or ""),
372+ "hypothesis_ids": _string_list(request.get("hypothesis_ids")),
373+ "operation": operation,
374+ "purpose": str(request.get("purpose", "") or ""),
375+ "proof_obligation": str(request.get("proof_obligation", "") or ""),
376+ **evidence,
377+ }
378+ )
379+ 
380+ automatic_requests, closure_results, closure = _close_incomplete_artifact_evidence(
381+ case,
382+ investigation,
383+ results,
384+ text_cache=artifact_text_cache,
385+ )
386+ results.extend(closure_results)
387+ 
388+ return {
389+ "schema_version": 1,
390+ "policy": {
391+ "controller_owned": True,
392+ "read_only": True,
393+ "arbitrary_shell_or_path_access": False,
394+ "display_omissions_are_not_task_agent_observations": True,
395+ },
396+ "hypotheses": list(investigation.get("hypotheses", [])),
397+ "request_count": len(investigation.get("evidence_requests", [])) + len(automatic_requests),
398+ "model_request_count": len(investigation.get("evidence_requests", [])),
399+ "automatic_request_count": len(automatic_requests),
400+ "automatic_requests": automatic_requests,
401+ "completed_request_count": len(results),
402+ "artifact_evidence_closure": closure,
403+ "results": results,
404+ }
405+ 
406+ 
407+def _close_incomplete_artifact_evidence(
408+ case: CaseAnalysisInput,
409+ investigation: Mapping[str, Any],
410+ initial_results: Sequence[Mapping[str, Any]],
411+ *,
412+ text_cache: dict[Path, str],
413+) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, Any]]:
414+ """Deterministically complete bounded artifact sources exposed by search.
415+ 
416+ ``inspect_artifact`` is discovery, not proof that content is absent. Once a
417+ causal request selects a physical source but returns an incomplete excerpt,
418+ the controller owns the mechanical continuation. This avoids asking the
419+ model to rediscover an already known source/offset and gives absence claims
420+ continuous coverage from character zero to EOF.
421+ """
422+ existing_ids = {
423+ str(item.get("request_id", "") or "") for item in initial_results if str(item.get("request_id", "") or "")
424+ }
425+ # Explicit windows count toward coverage and must not be read again.
426+ covered_ranges: dict[str, list[tuple[int, int]]] = {}
427+ source_counts: dict[str, int] = {}
428+ for item in initial_results:
429+ if str(item.get("operation", "") or "") != "read_artifact_window":
430+ continue
431+ if str(item.get("availability", "") or "") != "available":
432+ continue
433+ source = str(item.get("source", "") or "")
434+ start = _optional_nonnegative_int(item.get("source_char_start"))
435+ end = _optional_nonnegative_int(item.get("source_char_end"))
436+ count = _optional_nonnegative_int(item.get("source_char_count"))
437+ if not source or start is None or end is None:
438+ continue
439+ if end >= start:
440+ covered_ranges.setdefault(source, []).append((start, end))
441+ if count is not None:
442+ source_counts[source] = count
443+ 
444+ candidates: list[dict[str, Any]] = []
445+ candidate_by_source: dict[str, dict[str, Any]] = {}
446+ for item in initial_results:
447+ if str(item.get("operation", "") or "") != "inspect_artifact":
448+ continue
449+ if str(item.get("availability", "") or "") != "available":
450+ continue
451+ hypothesis_ids = _string_list(item.get("hypothesis_ids"))
452+ if not hypothesis_ids:
453+ continue
454+ if str(item.get("proof_obligation", "") or "") == "existence":
455+ # A physical match is a complete witness for an existence claim.
456+ # Reading unrelated tail content cannot strengthen that obligation.
457+ continue
458+ matches = item.get("matches", [])
459+ for match in matches[:1] if isinstance(matches, list) else []:
460+ if not isinstance(match, Mapping):
461+ continue
462+ source = str(match.get("source", "") or "")
463+ if not source:
464+ continue
465+ raw_spans = match.get("exact_spans", [])
466+ spans = raw_spans if isinstance(raw_spans, list) else []
467+ incomplete = any(isinstance(span, Mapping) and not bool(span.get("window_complete")) for span in spans)
468+ if not incomplete:
469+ continue
470+ parent_request_id = str(item.get("request_id", "") or "")
471+ existing_candidate = candidate_by_source.get(source)
472+ if existing_candidate is not None:
473+ existing_candidate["hypothesis_ids"] = list(
474+ dict.fromkeys([*existing_candidate["hypothesis_ids"], *hypothesis_ids])
475+ )
476+ existing_candidate["parent_request_ids"] = list(
477+ dict.fromkeys([*existing_candidate["parent_request_ids"], parent_request_id])
478+ )
479+ continue
480+ candidate = {
481+ "source": source,
482+ "logical_source": str(match.get("logical_source", "") or source),
483+ "hypothesis_ids": hypothesis_ids,
484+ "parent_request_id": parent_request_id,
485+ "parent_request_ids": [parent_request_id] if parent_request_id else [],
486+ "purpose": str(item.get("purpose", "") or ""),
487+ }
488+ candidate_by_source[source] = candidate
489+ candidates.append(candidate)
490+ if len(candidates) >= _MAX_AUTOMATIC_ARTIFACT_SOURCES:
491+ break
492+ if len(candidates) >= _MAX_AUTOMATIC_ARTIFACT_SOURCES:
493+ break
494+ 
495+ automatic_requests: list[dict[str, Any]] = []
496+ closure_results: list[dict[str, Any]] = []
497+ source_records: list[dict[str, Any]] = []
498+ total_chars = 0
499+ window_budget = _MAX_AUTOMATIC_ARTIFACT_WINDOWS
500+ for candidate in candidates:
501+ source = candidate["source"]
502+ cursor = _continuous_prefix_end(covered_ranges.get(source, []))
503+ completed = bool(source_counts.get(source) is not None and cursor >= source_counts[source])
504+ source_windows = 0
505+ source_chars = 0
506+ last_reason = "already_covered" if completed else ""
507+ while not completed and window_budget > 0 and total_chars < _MAX_AUTOMATIC_ARTIFACT_CHARS:
508+ remaining_chars = _MAX_AUTOMATIC_ARTIFACT_CHARS - total_chars
509+ max_chars = min(_MAX_ARTIFACT_WINDOW_CHARS, remaining_chars)
510+ request_id = _unique_automatic_request_id(
511+ candidate["parent_request_id"],
512+ source,
513+ cursor,
514+ existing_ids,
515+ )
516+ request = {
517+ "request_id": request_id,
518+ "hypothesis_ids": list(candidate["hypothesis_ids"]),
519+ "operation": "read_artifact_window",
520+ "relative_path": source,
521+ "source_char_start": cursor,
522+ "max_chars": max_chars,
523+ "purpose": (
524+ "controller-owned continuation of an incomplete artifact search; "
525+ "establish continuous source coverage before reasoning about absence"
526+ ),
527+ "automatic": True,
528+ "parent_request_id": candidate["parent_request_id"],
529+ "parent_request_ids": list(candidate["parent_request_ids"]),
530+ }
531+ evidence = _read_artifact_window(case, request, text_cache=text_cache)
532+ result = {
533+ **request,
534+ **evidence,
535+ }
536+ automatic_requests.append(request)
537+ closure_results.append(result)
538+ existing_ids.add(request_id)
539+ window_budget -= 1
540+ source_windows += 1
541+ if str(evidence.get("availability", "") or "") != "available":
542+ last_reason = str(evidence.get("reason", "") or evidence.get("availability", "") or "unavailable")
543+ break
544+ start = _optional_nonnegative_int(evidence.get("source_char_start"))
545+ end = _optional_nonnegative_int(evidence.get("source_char_end"))
546+ count = _optional_nonnegative_int(evidence.get("source_char_count"))
547+ if start is None or end is None:
548+ last_reason = "non_contiguous_controller_window"
549+ break
550+ if count is None:
551+ last_reason = "non_contiguous_controller_window"
552+ break
553+ if start != cursor or end <= start:
554+ last_reason = "non_contiguous_controller_window"
555+ break
556+ read_chars = end - start
557+ total_chars += read_chars
558+ source_chars += read_chars
559+ covered_ranges.setdefault(source, []).append((start, end))
560+ source_counts[source] = count
561+ next_cursor = _continuous_prefix_end(covered_ranges[source])
562+ if next_cursor <= cursor:
563+ last_reason = "no_forward_progress"
564+ break
565+ cursor = next_cursor
566+ completed = cursor >= count
567+ last_reason = "complete" if completed else "continuation_required"
568+ if not completed and not last_reason:
569+ last_reason = "budget_exhausted"
570+ if not completed and (window_budget <= 0 or total_chars >= _MAX_AUTOMATIC_ARTIFACT_CHARS):
571+ last_reason = "budget_exhausted"
572+ source_records.append(
573+ {
574+ "source": source,
575+ "logical_source": candidate["logical_source"],
576+ "parent_request_id": candidate["parent_request_id"],
577+ "continuous_source_char_end": cursor,
578+ "source_char_count": source_counts.get(source),
579+ "window_count": source_windows,
580+ "read_char_count": source_chars,
581+ "complete": completed,
582+ "status": last_reason,
583+ }
584+ )
585+ 
586+ attempted = bool(candidates)
587+ all_complete = attempted and all(bool(item.get("complete")) for item in source_records)
588+ budget_exhausted = any(item.get("status") == "budget_exhausted" for item in source_records)
589+ return (
590+ automatic_requests,
591+ closure_results,
592+ {
593+ "attempted": attempted,
594+ "status": (
595+ "completed"
596+ if all_complete
597+ else "budget_exhausted"
598+ if budget_exhausted
599+ else "incomplete"
600+ if attempted
601+ else "not_needed"
602+ ),
603+ "candidate_source_count": len(candidates),
604+ "completed_source_count": sum(bool(item.get("complete")) for item in source_records),
605+ "automatic_window_count": len(automatic_requests),
606+ "read_char_count": total_chars,
607+ "limits": {
608+ "max_sources": _MAX_AUTOMATIC_ARTIFACT_SOURCES,
609+ "max_windows": _MAX_AUTOMATIC_ARTIFACT_WINDOWS,
610+ "max_chars": _MAX_AUTOMATIC_ARTIFACT_CHARS,
611+ "max_chars_per_window": _MAX_ARTIFACT_WINDOW_CHARS,
612+ },
613+ "sources": source_records,
614+ },
615+ )
616+ 
617+ 
618+def _continuous_prefix_end(ranges: Sequence[tuple[int, int]]) -> int:
619+ cursor = 0
620+ for start, end in sorted(ranges):
621+ if start > cursor:
622+ break
623+ if end > cursor:
624+ cursor = end
625+ return cursor
626+ 
627+ 
628+def _unique_automatic_request_id(
629+ parent_request_id: str,
630+ source: str,
631+ start: int,
632+ existing_ids: set[str],
633+) -> str:
634+ stem = re.sub(r"[^a-zA-Z0-9_.-]+", "_", parent_request_id).strip("_") or "artifact"
635+ digest = hashlib.sha256(source.encode("utf-8")).hexdigest()[:8]
636+ candidate = f"{stem}.auto.{digest}.{start}"
637+ suffix = 2
638+ while candidate in existing_ids:
639+ candidate = f"{stem}.auto.{digest}.{start}.{suffix}"
640+ suffix += 1
641+ return candidate
642+ 
643+ 
644+def _normalize_request(
645+ request: Mapping[str, Any],
646+ *,
647+ default_hypothesis_id: str,
648+ known_hypothesis_ids: set[str],
649+ index: int,
650+) -> dict[str, Any] | None:
651+ operation = str(request.get("operation", "") or "").strip().casefold()
652+ if operation not in _ALLOWED_OPERATIONS:
653+ return None
654+ query = str(request.get("query", "") or "").strip()
655+ if operation in {"search_trace", "inspect_artifact", "inspect_evaluation", "search_repository"} and not query:
656+ return None
657+ requested_path = request.get("relative_path")
658+ if operation == "read_artifact_window" and not _safe_relative_path(requested_path):
659+ # Inspection results expose the selected identity as ``source``. Accept
660+ # that exact controller-issued identity when a refinement model feeds it
661+ # back, while retaining the same bounded relative-path validation.
662+ requested_path = request.get("source")
663+ if operation in {"read_artifact_window", "read_repository_file"} and not _safe_relative_path(requested_path):
664+ return None
665+ if operation == "check_relation" and not str(request.get("expression", "") or "").strip():
666+ return None
667+ if operation == "compare_numeric_change" and not all(
668+ str(request.get(key, "") or "").strip() for key in ("before_expression", "after_expression")
669+ ):
670+ return None
671+ hypothesis_ids = _string_list(request.get("hypothesis_ids"))
672+ if default_hypothesis_id:
673+ hypothesis_ids.append(default_hypothesis_id)
674+ hypothesis_ids = list(dict.fromkeys(item for item in hypothesis_ids if item in known_hypothesis_ids))
675+ normalized: dict[str, Any] = {
676+ "request_id": str(request.get("request_id", "") or f"q{index}").strip() or f"q{index}",
677+ "hypothesis_ids": hypothesis_ids,
678+ "operation": operation,
679+ "purpose": str(request.get("purpose", "") or "").strip(),
680+ }
681+ if query:
682+ normalized["query"] = query[:1_000]
683+ if operation == "inspect_artifact":
684+ artifact_hint = _safe_relative_path(request.get("relative_path") or request.get("source"))
685+ if artifact_hint:
686+ normalized["relative_path"] = artifact_hint
687+ proof_obligation = str(request.get("proof_obligation", "") or "").strip().casefold()
688+ if proof_obligation in {"existence", "absence", "coverage"}:
689+ normalized["proof_obligation"] = proof_obligation
690+ if operation == "check_relation":
691+ expected = _finite_number(request.get("expected"))
692+ if expected is None:
693+ return None
694+ normalized.update(
695+ {
696+ "expression": str(request.get("expression", "") or "").strip()[:200],
697+ "operator": str(request.get("operator", "approximately_equal") or "approximately_equal")
698+ .strip()
699+ .casefold(),
700+ "expected": expected,
701+ "tolerance": _bounded_tolerance(request.get("tolerance")),
702+ }
703+ )
704+ if operation == "compare_numeric_change":
705+ expected_delta = _finite_number(request.get("expected_delta"))
706+ if expected_delta is None:
707+ return None
708+ normalized.update(
709+ {
710+ "before_expression": str(request.get("before_expression", "") or "").strip()[:200],
711+ "after_expression": str(request.get("after_expression", "") or "").strip()[:200],
712+ "expected_delta": expected_delta,
713+ "tolerance": _bounded_tolerance(request.get("tolerance")),
714+ }
715+ )
716+ if operation == "search_trace":
717+ normalized["max_results"] = min(
718+ _MAX_SEARCH_RESULTS,
719+ max(1, _positive_int(request.get("max_results"), default=3)),
720+ )
721+ if operation == "search_repository":
722+ normalized["max_results"] = min(
723+ _MAX_SEARCH_RESULTS,
724+ max(1, _positive_int(request.get("max_results"), default=3)),
725+ )
726+ if operation == "read_repository_file":
727+ normalized["relative_path"] = _safe_relative_path(request.get("relative_path"))
728+ if operation == "read_artifact_window":
729+ normalized["relative_path"] = _safe_relative_path(requested_path)
730+ source_char_start = _optional_nonnegative_int(request.get("source_char_start")) or 0
731+ source_char_end = _optional_nonnegative_int(request.get("source_char_end"))
732+ requested_max_chars = request.get("max_chars")
733+ if requested_max_chars is None and source_char_end is not None and source_char_end > source_char_start:
734+ requested_max_chars = source_char_end - source_char_start
735+ normalized["source_char_start"] = source_char_start
736+ normalized["max_chars"] = min(
737+ _MAX_ARTIFACT_WINDOW_CHARS,
738+ max(1, _positive_int(requested_max_chars, default=_MAX_ARTIFACT_WINDOW_CHARS)),
739+ )
740+ if operation == "read_event":
741+ message_index = _optional_nonnegative_int(request.get("message_index"))
742+ if message_index is None:
743+ return None
744+ normalized["trace_id"] = str(request.get("trace_id", "") or "").strip()
745+ normalized["message_index"] = message_index
746+ tool_call_index = _optional_nonnegative_int(request.get("tool_call_index"))
747+ if tool_call_index is not None:
748+ normalized["tool_call_index"] = tool_call_index
749+ return normalized
750+ 
751+ 
752+def _check_relation(request: Mapping[str, Any]) -> dict[str, Any]:
753+ """Evaluate a small numeric discriminator without executing model code."""
754+ expression = str(request.get("expression", "") or "").strip()
755+ expected = _finite_number(request.get("expected"))
756+ operator = str(request.get("operator", "approximately_equal") or "approximately_equal").casefold()
757+ tolerance = _bounded_tolerance(request.get("tolerance"))
758+ if expected is None:
759+ return {"availability": "invalid", "reason": "expected_must_be_a_finite_number"}
760+ try:
761+ value = _safe_numeric_expression(expression)
762+ except (SyntaxError, TypeError, ValueError, ZeroDivisionError, OverflowError) as exc:
763+ return {
764+ "availability": "invalid",
765+ "expression": expression,
766+ "reason": f"invalid_numeric_expression:{type(exc).__name__}",
767+ }
768+ 
769+ if operator in {"approximately_equal", "equal"}:
770+ holds = math.isclose(value, expected, rel_tol=0.0, abs_tol=tolerance)
771+ elif operator == "not_equal":
772+ holds = not math.isclose(value, expected, rel_tol=0.0, abs_tol=tolerance)
773+ elif operator == "less_than":
774+ holds = value < expected
775+ elif operator == "less_than_or_equal":
776+ holds = value <= expected or math.isclose(value, expected, rel_tol=0.0, abs_tol=tolerance)
777+ elif operator == "greater_than":
778+ holds = value > expected
779+ elif operator == "greater_than_or_equal":
780+ holds = value >= expected or math.isclose(value, expected, rel_tol=0.0, abs_tol=tolerance)
781+ else:
782+ return {
783+ "availability": "invalid",
784+ "expression": expression,
785+ "value": value,
786+ "reason": "unsupported_relation_operator",
787+ }
788+ return {
789+ "availability": "available",
790+ "expression": expression,
791+ "value": value,
792+ "operator": operator,
793+ "expected": expected,
794+ "tolerance": tolerance,
795+ "holds": holds,
796+ }
797+ 
798+ 
799+def _compare_numeric_change(request: Mapping[str, Any]) -> dict[str, Any]:
800+ """Compute after-before so formula changes are compared to their real baseline."""
801+ before_expression = str(request.get("before_expression", "") or "").strip()
802+ after_expression = str(request.get("after_expression", "") or "").strip()
803+ expected_delta = _finite_number(request.get("expected_delta"))
804+ tolerance = _bounded_tolerance(request.get("tolerance"))
805+ if expected_delta is None:
806+ return {"availability": "invalid", "reason": "expected_delta_must_be_a_finite_number"}
807+ try:
808+ before = _safe_numeric_expression(before_expression)
809+ after = _safe_numeric_expression(after_expression)
810+ except (SyntaxError, TypeError, ValueError, ZeroDivisionError, OverflowError) as exc:
811+ return {
812+ "availability": "invalid",
813+ "before_expression": before_expression,
814+ "after_expression": after_expression,
815+ "reason": f"invalid_numeric_expression:{type(exc).__name__}",
816+ }
817+ delta = after - before
818+ return {
819+ "availability": "available",
820+ "before_expression": before_expression,
821+ "after_expression": after_expression,
822+ "before_value": before,
823+ "after_value": after,
824+ "computed_delta": delta,
825+ "expected_delta": expected_delta,
826+ "tolerance": tolerance,
827+ "holds": math.isclose(delta, expected_delta, rel_tol=0.0, abs_tol=tolerance),
828+ }
829+ 
830+ 
831+def _safe_numeric_expression(expression: str) -> float:
832+ """Evaluate only finite numeric literals and bounded arithmetic operators."""
833+ if not expression or len(expression) > 200:
834+ raise ValueError("expression_length")
835+ tree = ast.parse(expression, mode="eval")
836+ if sum(1 for _ in ast.walk(tree)) > 40:
837+ raise ValueError("expression_complexity")
838+ 
839+ def _evaluate(node: ast.AST) -> float:
840+ if isinstance(node, ast.Expression):
841+ return _evaluate(node.body)
842+ if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)) and not isinstance(node.value, bool):
843+ value = float(node.value)
844+ elif isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.UAdd, ast.USub)):
845+ operand = _evaluate(node.operand)
846+ value = operand if isinstance(node.op, ast.UAdd) else -operand
847+ elif isinstance(node, ast.BinOp) and isinstance(node.op, (ast.Add, ast.Sub, ast.Mult, ast.Div)):
848+ left = _evaluate(node.left)
849+ right = _evaluate(node.right)
850+ if isinstance(node.op, ast.Add):
851+ value = left + right
852+ elif isinstance(node.op, ast.Sub):
853+ value = left - right
854+ elif isinstance(node.op, ast.Mult):
855+ value = left * right
856+ else:
857+ value = left / right
858+ else:
859+ raise ValueError("unsupported_expression_node")
860+ if not math.isfinite(value) or abs(value) > 1e100:
861+ raise ValueError("non_finite_or_unbounded_result")
862+ return value
863+ 
864+ return _evaluate(tree)
865+ 
866+ 
867+def _finite_number(value: Any) -> float | None:
868+ if isinstance(value, bool) or not isinstance(value, (int, float)):
869+ return None
870+ number = float(value)
871+ return number if math.isfinite(number) else None
872+ 
873+ 
874+def _bounded_tolerance(value: Any) -> float:
875+ number = _finite_number(value)
876+ if number is None:
877+ return 1e-9
878+ return min(max(number, 0.0), 1.0)
879+ 
880+ 
881+def _trace_events(trace_data: Mapping[str, Any]) -> list[dict[str, Any]]:
882+ events: list[dict[str, Any]] = []
883+ traces = trace_data.get("traces")
884+ if not isinstance(traces, list):
885+ return events
886+ for trace in traces:
887+ if not isinstance(trace, Mapping):
888+ continue
889+ trace_id = str(trace.get("trace_id", "") or "")
890+ messages = trace.get("messages")
891+ if not isinstance(messages, list):
892+ continue
893+ for sequence, message in enumerate(messages):
894+ if not isinstance(message, Mapping):
895+ continue
896+ raw_index = message.get("message_index", sequence)
897+ message_index = _optional_nonnegative_int(raw_index)
898+ if message_index is None:
899+ message_index = sequence
900+ tool_calls = [dict(call) for call in message.get("tool_calls", []) if isinstance(call, Mapping)]
901+ events.append(
902+ {
903+ "trace_id": trace_id,
904+ "message_index": message_index,
905+ "step_pointer": str(message.get("step_pointer", "") or ""),
906+ "role": str(message.get("role", "") or ""),
907+ "content": str(message.get("content", "") or ""),
908+ "tool_calls": tool_calls,
909+ }
910+ )
911+ return events
912+ 
913+ 
914+def _search_trace(events: list[dict[str, Any]], request: Mapping[str, Any]) -> dict[str, Any]:
915+ query = str(request.get("query", "") or "")
916+ terms = _query_terms(query)
917+ ranked: list[tuple[int, int, dict[str, Any]]] = []
918+ for sequence, event in enumerate(events):
919+ searchable = json.dumps(event, ensure_ascii=False, separators=(",", ":")).casefold()
920+ matched = [term for term in terms if term in searchable]
921+ if matched:
922+ ranked.append((len(set(matched)), sequence, event))
923+ limit = min(_MAX_SEARCH_RESULTS, max(1, _positive_int(request.get("max_results"), default=3)))
924+ selected = sorted(ranked, key=lambda item: (-item[0], item[1]))[:limit]
925+ return {
926+ "availability": "available" if selected else "not_found",
927+ "query": query,
928+ "matched_event_count": len(ranked),
929+ "events": [_search_event_view(event, terms) for _, _, event in selected],
930+ }
931+ 
932+ 
933+def _read_event(events: list[dict[str, Any]], request: Mapping[str, Any]) -> dict[str, Any]:
934+ trace_id = str(request.get("trace_id", "") or "")
935+ message_index = _optional_nonnegative_int(request.get("message_index"))
936+ matches = [
937+ event
938+ for event in events
939+ if event["message_index"] == message_index and (not trace_id or event["trace_id"] == trace_id)
940+ ]
941+ if not matches:
942+ return {
943+ "availability": "not_found",
944+ "trace_id": trace_id,
945+ "message_index": message_index,
946+ }
947+ if not trace_id and len(matches) > 1:
948+ return {
949+ "availability": "ambiguous",
950+ "reason": "trace_id_required_for_non_unique_message_index",
951+ "message_index": message_index,
952+ "candidate_trace_ids": sorted({str(item.get("trace_id", "") or "") for item in matches}),
953+ }
954+ event = matches[0]
955+ tool_call_index = _optional_nonnegative_int(request.get("tool_call_index"))
956+ calls = event["tool_calls"]
957+ if tool_call_index is not None:
958+ calls = [calls[tool_call_index]] if tool_call_index < len(calls) else []
959+ return {
960+ "availability": "available",
961+ "event": {
962+ "trace_id": event["trace_id"],
963+ "message_index": event["message_index"],
964+ "step_pointer": event["step_pointer"],
965+ "role": event["role"],
966+ "content": _bounded_exact_text(event["content"], _MAX_EVENT_CHARS),
967+ "tool_calls": [
968+ {
969+ "tool_call_index": index,
970+ "name": str(call.get("name", "") or ""),
971+ "input": _bounded_exact_text(str(call.get("input", "") or ""), _MAX_EVENT_CHARS),
972+ "output": _bounded_exact_text(str(call.get("output", "") or ""), _MAX_EVENT_CHARS),
973+ "error": _bounded_exact_text(str(call.get("error", "") or ""), 4_000),
974+ }
975+ for index, call in enumerate(calls)
976+ ],
977+ },
978+ }
979+ 
980+ 
981+def _inspect_artifact(
982+ case: CaseAnalysisInput,
983+ request: Mapping[str, Any],
984+ *,
985+ text_cache: dict[Path, str] | None = None,
986+) -> dict[str, Any]:
987+ """Search only physically materialized task artifacts.
988+ 
989+ Evaluation metadata is deliberately excluded. Previously a criterion or
990+ judge explanation could satisfy an ``inspect_artifact`` request even when
991+ the source document/spreadsheet was absent, which turned a repeated outcome
992+ description into apparent causal evidence.
993+ """
994+ query = str(request.get("query", "") or "")
995+ artifact_hint = str(request.get("relative_path", "") or "").strip()
996+ purpose = str(request.get("purpose", "") or "").strip()
997+ selection_query = f"{query} {artifact_hint} {purpose}".strip()
998+ terms = _query_terms(query)
999+ identity_terms = _query_terms(f"{artifact_hint} {purpose}".strip())
1000+ case_dir = _windows_long_path(Path(case.result_path).parent)
1001+ sources: list[tuple[str, str, str]] = []
1002+ artifacts_dir = case_dir / "artifacts"
1003+ if artifacts_dir.is_dir():
1004+ aliases = _artifact_path_aliases(case, artifacts_dir)
1005+ files = [
1006+ path
1007+ for path in sorted(artifacts_dir.rglob("*"))
1008+ if path.is_file() and path.suffix.casefold() in _TEXT_ARTIFACT_SUFFIXES
1009+ ][:_MAX_ARTIFACT_FILES]
1010+ for path in files:
1011+ resolved = path.resolve()
1012+ if not resolved.is_relative_to(case_dir):
1013+ continue
1014+ source = f"artifacts/{path.relative_to(artifacts_dir).as_posix()}"
1015+ sources.append((source, aliases.get(resolved, source), _read_text(path, _MAX_ARTIFACT_FILE_CHARS)))
1016+ structured_files = [
1017+ path
1018+ for path in sorted(artifacts_dir.rglob("*"))
1019+ if path.is_file() and path.suffix.casefold() in _STRUCTURED_ARTIFACT_SUFFIXES
1020+ ]
1021+ structured_files = _select_structured_artifact_files(structured_files, selection_query, aliases=aliases)
1022+ for path in structured_files:
1023+ resolved = path.resolve()
1024+ if not resolved.is_relative_to(case_dir):
1025+ continue
1026+ if text_cache is not None and resolved in text_cache:
1027+ text = text_cache[resolved]
1028+ else:
1029+ text = _structured_artifact_text(path)
1030+ if text_cache is not None:
1031+ text_cache[resolved] = text
1032+ if text:
1033+ source = f"artifacts/{path.relative_to(artifacts_dir).as_posix()}"
1034+ sources.append((source, aliases.get(resolved, source), text))
1035+ 
1036+ if not sources:
1037+ return {
1038+ "availability": "not_available",
1039+ "reason": "physical_artifact_snapshot_not_available",
1040+ "query": query,
1041+ "matches": [],
1042+ }
1043+ 
1044+ if artifact_hint:
1045+ hint_terms = _query_terms(artifact_hint)
1046+ identity_scores = [
1047+ (_query_match_score(logical_source, hint_terms), logical_source) for _, logical_source, _ in sources
1048+ ]
1049+ best_identity_score = max((score for score, _ in identity_scores), default=0.0)
1050+ if best_identity_score > 0:
1051+ best_logical_sources = {
1052+ logical_source for score, logical_source in identity_scores if score == best_identity_score
1053+ }
1054+ sources = [item for item in sources if item[1] in best_logical_sources]
1055+ 
1056+ matches: list[tuple[float, str, str, str]] = []
1057+ for source, logical_source, text in sources:
1058+ lowered = text.casefold()
1059+ matched = [term for term in terms if term in lowered]
1060+ path_matched = [term for term in terms if term in logical_source.casefold()]
1061+ if matched or path_matched:
1062+ score = (
1063+ _query_match_score(text, terms)
1064+ + 2.0 * _query_match_score(logical_source, terms)
1065+ + 4.0 * _query_match_score(logical_source, identity_terms)
1066+ )
1067+ matches.append((score, source, logical_source, text))
1068+ selected = sorted(matches, key=lambda item: (-item[0], item[1]))[:_MAX_SEARCH_RESULTS]
1069+ return {
1070+ "availability": "available" if selected else "not_found",
1071+ "query": query,
1072+ "matches": [
1073+ {
1074+ "source": source,
1075+ "logical_source": logical_source,
1076+ "exact_spans": _exact_match_spans(text, terms, max_spans=3) or _leading_text_span(text),
1077+ }
1078+ for _, source, logical_source, text in selected
1079+ ],
1080+ }
1081+ 
1082+ 
1083+def _artifact_path_aliases(case: CaseAnalysisInput, artifacts_dir: Path) -> dict[Path, str]:
1084+ """Map bounded snapshot paths back to their original logical names."""
1085+ snapshot = case.evaluation_metadata.get("analysis_artifact_snapshot", {})
1086+ rows = snapshot.get("files", []) if isinstance(snapshot, Mapping) else []
1087+ aliases: dict[Path, str] = {}
1088+ for row in rows if isinstance(rows, list) else []:
1089+ if not isinstance(row, Mapping):
1090+ continue
1091+ stored = _safe_relative_path(row.get("path"))
1092+ logical = str(row.get("source_path", "") or "").strip()
1093+ if not stored or not logical:
1094+ continue
1095+ for candidate in (artifacts_dir / stored, artifacts_dir / "workspace" / stored):
1096+ resolved = candidate.resolve()
1097+ if resolved.is_relative_to(artifacts_dir) and resolved.is_file():
1098+ aliases[resolved] = logical
1099+ break
1100+ return aliases
1101+ 
1102+ 
1103+def _select_structured_artifact_files(
1104+ files: Sequence[Path],
1105+ query: str,
1106+ *,
1107+ aliases: Mapping[Path, str] | None = None,
1108+) -> list[Path]:
1109+ """Bound structured parsing and prefer file types named by the request."""
1110+ lowered_query = query.casefold()
1111+ hinted_suffixes = {
1112+ suffix
1113+ for suffix, hints in _STRUCTURED_QUERY_SUFFIX_HINTS.items()
1114+ if any(hint in lowered_query for hint in hints)
1115+ }
1116+ candidates = [path for path in files if not hinted_suffixes or path.suffix.casefold() in hinted_suffixes]
1117+ if len(candidates) <= _MAX_STRUCTURED_ARTIFACTS_PER_REQUEST:
1118+ return candidates
1119+ 
1120+ query_terms = _query_terms(query)
1121+ ranked = sorted(
1122+ candidates,
1123+ key=lambda path: (
1124+ -_query_match_score(
1125+ f"{path.as_posix()} {(aliases or {}).get(path.resolve(), '')}",
1126+ query_terms,
1127+ ),
1128+ path.stat().st_size,
1129+ path.as_posix(),
1130+ ),
1131+ )
1132+ return ranked[:_MAX_STRUCTURED_ARTIFACTS_PER_REQUEST]
1133+ 
1134+ 
1135+def _read_artifact_window(
1136+ case: CaseAnalysisInput,
1137+ request: Mapping[str, Any],
1138+ *,
1139+ text_cache: dict[Path, str] | None = None,
1140+) -> dict[str, Any]:
1141+ """Read one exact, bounded window from a previously named task artifact."""
1142+ relative_path = _safe_relative_path(request.get("relative_path"))
1143+ if relative_path.startswith("artifacts/"):
1144+ relative_path = relative_path.removeprefix("artifacts/")
1145+ if not relative_path:
1146+ return {"availability": "invalid", "reason": "invalid_relative_path"}
1147+ case_dir = _windows_long_path(Path(case.result_path).parent)
1148+ artifacts_dir = (case_dir / "artifacts").resolve()
1149+ aliases = _artifact_path_aliases(case, artifacts_dir)
1150+ path = (artifacts_dir / relative_path).resolve()
1151+ logical_source = aliases.get(path)
1152+ if not path.is_relative_to(artifacts_dir) or not path.is_file():
1153+ workspace_path = (artifacts_dir / "workspace" / relative_path).resolve()
1154+ if workspace_path.is_relative_to(artifacts_dir) and workspace_path.is_file():
1155+ path = workspace_path
1156+ logical_source = aliases.get(path)
1157+ else:
1158+ requested = relative_path.removeprefix("workspace/")
1159+ logical_matches = [
1160+ (stored, logical)
1161+ for stored, logical in aliases.items()
1162+ if logical.replace("\\", "/").removeprefix("workspace/") == requested
1163+ ]
1164+ if not logical_matches:
1165+ logical_matches = _unambiguous_logical_artifact_matches(requested, aliases)
1166+ if len(logical_matches) != 1:
1167+ return {"availability": "not_found", "relative_path": relative_path}
1168+ path, logical_source = logical_matches[0]
1169+ if not path.is_relative_to(artifacts_dir) or not path.is_file():
1170+ return {"availability": "not_found", "relative_path": relative_path}
1171+ suffix = path.suffix.casefold()
1172+ if suffix in _STRUCTURED_ARTIFACT_SUFFIXES:
1173+ if text_cache is not None and path in text_cache:
1174+ content = text_cache[path]
1175+ else:
1176+ content = _structured_artifact_text(path)
1177+ if text_cache is not None:
1178+ text_cache[path] = content
1179+ elif suffix in _TEXT_ARTIFACT_SUFFIXES:
1180+ content = _read_text(path, _MAX_ARTIFACT_FILE_CHARS)
1181+ else:
1182+ return {"availability": "unsupported", "relative_path": relative_path}
1183+ if not content:
1184+ return {"availability": "not_available", "relative_path": relative_path}
1185+ start = min(
1186+ len(content),
1187+ _optional_nonnegative_int(request.get("source_char_start")) or 0,
1188+ )
1189+ max_chars = min(
1190+ _MAX_ARTIFACT_WINDOW_CHARS,
1191+ max(1, _positive_int(request.get("max_chars"), default=_MAX_ARTIFACT_WINDOW_CHARS)),
1192+ )
1193+ end = min(len(content), start + max_chars)
1194+ physical_relative_path = path.relative_to(artifacts_dir).as_posix()
1195+ return {
1196+ "availability": "available",
1197+ "source": f"artifacts/{physical_relative_path}",
1198+ "logical_source": logical_source or aliases.get(path, f"artifacts/{physical_relative_path}"),
1199+ "source_char_start": start,
1200+ "source_char_end": end,
1201+ "source_char_count": len(content),
1202+ "text": content[start:end],
1203+ "window_complete": start == 0 and end == len(content),
1204+ "next_source_char_start": end if end < len(content) else None,
1205+ "omission_origin": "controller_read_window" if start or end < len(content) else "none",
1206+ }
1207+ 
1208+ 
1209+def _unambiguous_logical_artifact_matches(
1210+ requested: str,
1211+ aliases: Mapping[Path, str],
1212+) -> list[tuple[Path, str]]:
1213+ """Recover a named artifact when presentation-only path encoding changed."""
1214+ requested_terms = {term for term in _query_terms(Path(requested).name) if len(term) >= 3}
1215+ if len(requested_terms) < 2:
1216+ return []
1217+ ranked: list[tuple[int, Path, str]] = []
1218+ for stored, logical in aliases.items():
1219+ logical_terms = {term for term in _query_terms(Path(logical).name) if len(term) >= 3}
1220+ overlap = len(requested_terms & logical_terms)
1221+ if overlap:
1222+ ranked.append((overlap, stored, logical))
1223+ if not ranked:
1224+ return []
1225+ best = max(score for score, _, _ in ranked)
1226+ minimum = max(2, (len(requested_terms) + 1) // 2)
1227+ winners = [(stored, logical) for score, stored, logical in ranked if score == best and score >= minimum]
1228+ return winners if len(winners) == 1 else []
1229+ 
1230+ 
1231+def _inspect_evaluation(case: CaseAnalysisInput, request: Mapping[str, Any]) -> dict[str, Any]:
1232+ """Search evaluator-owned result metadata without calling it an artifact."""
1233+ query = str(request.get("query", "") or "")
1234+ terms = _query_terms(query)
1235+ sources = [
1236+ ("case.evaluation_metadata", json.dumps(case.evaluation_metadata, ensure_ascii=False, indent=2)),
1237+ ("case.result", _read_text(Path(case.result_path), _MAX_ARTIFACT_FILE_CHARS)),
1238+ ]
1239+ matches: list[tuple[int, str, str]] = []
1240+ for source, content in sources:
1241+ lowered = content.casefold()
1242+ matched = [term for term in terms if term in lowered]
1243+ if matched:
1244+ matches.append((len(set(matched)), source, content))
1245+ selected = sorted(matches, key=lambda item: (-item[0], item[1]))[:_MAX_SEARCH_RESULTS]
1246+ return {
1247+ "availability": "available" if selected else "not_found",
1248+ "evidence_class": "evaluation_metadata",
1249+ "query": query,
1250+ "matches": [
1251+ {
1252+ "source": source,
1253+ "exact_spans": _exact_match_spans(content, terms, max_spans=3),
1254+ }
1255+ for _, source, content in selected
1256+ ],
1257+ }
1258+ 
1259+ 
1260+def _repository_dir(evidence_root: str | Path | None) -> Path | None:
1261+ if evidence_root is None:
1262+ return None
1263+ root = Path(evidence_root).expanduser().resolve()
1264+ repository = (root / "repository").resolve()
1265+ return repository if repository.is_dir() and repository.is_relative_to(root) else None
1266+ 
1267+ 
1268+def _search_repository(repository_dir: Path | None, request: Mapping[str, Any]) -> dict[str, Any]:
1269+ if repository_dir is None:
1270+ return {"availability": "not_available", "reason": "repository_snapshot_not_available"}
1271+ query = str(request.get("query", "") or "")
1272+ terms = _query_terms(query)
1273+ ranked: list[tuple[int, str, str]] = []
1274+ for path in _repository_text_files(repository_dir):
1275+ text = _read_text(path, _MAX_ARTIFACT_FILE_CHARS)
1276+ lowered = text.casefold()
1277+ matched = [term for term in terms if term in lowered]
1278+ if matched:
1279+ relative = path.relative_to(repository_dir).as_posix()
1280+ ranked.append((len(set(matched)), relative, text))
1281+ limit = min(_MAX_SEARCH_RESULTS, max(1, _positive_int(request.get("max_results"), default=3)))
1282+ selected = sorted(ranked, key=lambda item: (-item[0], item[1]))[:limit]
1283+ return {
1284+ "availability": "available" if selected else "not_found",
1285+ "query": query,
1286+ "matched_file_count": len(ranked),
1287+ "files": [
1288+ {"relative_path": relative, "exact_spans": _exact_match_spans(text, terms, max_spans=3)}
1289+ for _, relative, text in selected
1290+ ],
1291+ }
1292+ 
1293+ 
1294+def _read_repository_file(
1295+ repository_dir: Path | None,
1296+ request: Mapping[str, Any],
1297+ *,
1298+ discovered_paths: set[str],
1299+) -> dict[str, Any]:
1300+ if repository_dir is None:
1301+ return {"availability": "not_available", "reason": "repository_snapshot_not_available"}
1302+ relative_path = _safe_relative_path(request.get("relative_path"))
1303+ if not relative_path:
1304+ return {"availability": "invalid", "reason": "invalid_relative_path"}
1305+ if relative_path not in discovered_paths:
1306+ return {
1307+ "availability": "invalid",
1308+ "reason": "relative_path_not_returned_by_prior_search_repository",
1309+ "relative_path": relative_path,
1310+ }
1311+ path = (repository_dir / relative_path).resolve()
1312+ if not path.is_relative_to(repository_dir) or not path.is_file():
1313+ return {"availability": "not_found", "relative_path": relative_path}
1314+ if path.suffix.casefold() not in _TEXT_ARTIFACT_SUFFIXES | {
1315+ ".c",
1316+ ".cc",
1317+ ".cpp",
1318+ ".go",
1319+ ".h",
1320+ ".hpp",
1321+ ".java",
1322+ ".js",
1323+ ".jsx",
1324+ ".py",
1325+ ".rs",
1326+ ".sh",
1327+ ".ts",
1328+ ".tsx",
1329+ }:
1330+ return {"availability": "unsupported", "relative_path": relative_path}
1331+ return {
1332+ "availability": "available",
1333+ "relative_path": relative_path,
1334+ "content": _bounded_exact_text(_read_text(path, _MAX_ARTIFACT_FILE_CHARS), _MAX_EVENT_CHARS),
1335+ }
1336+ 
1337+ 
1338+def _repository_text_files(repository_dir: Path) -> list[Path]:
1339+ suffixes = _TEXT_ARTIFACT_SUFFIXES | {
1340+ ".c",
1341+ ".cc",
1342+ ".cpp",
1343+ ".go",
1344+ ".h",
1345+ ".hpp",
1346+ ".java",
1347+ ".js",
1348+ ".jsx",
1349+ ".py",
1350+ ".rs",
1351+ ".sh",
1352+ ".ts",
1353+ ".tsx",
1354+ }
1355+ return [
1356+ path
1357+ for path in sorted(repository_dir.rglob("*"))
1358+ if path.is_file() and path.suffix.casefold() in suffixes and path.resolve().is_relative_to(repository_dir)
1359+ ][:_MAX_REPOSITORY_FILES]
1360+ 
1361+ 
1362+def _safe_relative_path(value: Any) -> str:
1363+ text = str(value or "").strip().replace("\\", "/")
1364+ if not text or text.startswith("/") or re.match(r"^[A-Za-z]:", text):
1365+ return ""
1366+ parts = [part for part in text.split("/") if part not in {"", "."}]
1367+ if not parts or any(part == ".." for part in parts):
1368+ return ""
1369+ return "/".join(parts)
1370+ 
1371+ 
1372+def _normalized_semantics(value: str) -> str:
1373+ return " ".join(re.findall(r"[a-z0-9_]+|[\u4e00-\u9fff]", value.casefold()))
1374+ 
1375+ 
1376+def _structured_artifact_text(path: Path) -> str:
1377+ try:
1378+ suffix = path.suffix.casefold()
1379+ if suffix == ".xlsx":
1380+ return _xlsx_text(path)
1381+ if suffix == ".docx":
1382+ return _docx_text(path)
1383+ if suffix == ".pdf":
1384+ return _pdf_text(path)
1385+ if suffix == ".pptx":
1386+ return _pptx_text(path)
1387+ except Exception: # malformed optional artifacts are unavailable evidence
1388+ return ""
1389+ return ""
1390+ 
1391+ 
1392+def _xlsx_text(path: Path) -> str:
1393+ from openpyxl import load_workbook
1394+ 
1395+ workbook = load_workbook(path, read_only=True, data_only=False)
1396+ lines: list[str] = []
1397+ total_chars = 0
1398+ cell_count = 0
1399+ try:
1400+ for worksheet in workbook.worksheets:
1401+ lines.append(f"[sheet:{worksheet.title}]")
1402+ for row in worksheet.iter_rows(
1403+ max_row=min(worksheet.max_row, 2_000),
1404+ max_col=min(worksheet.max_column, 200),
1405+ ):
1406+ for cell in row:
1407+ if cell.value is not None:
1408+ line = f"{cell.coordinate}={cell.value}"
1409+ lines.append(line)
1410+ total_chars += len(line)
1411+ cell_count += 1
1412+ if total_chars >= _MAX_ARTIFACT_FILE_CHARS or cell_count >= _MAX_STRUCTURED_CELLS:
1413+ return "\n".join(lines)
1414+ finally:
1415+ workbook.close()
1416+ return "\n".join(lines)
1417+ 
1418+ 
1419+def _docx_text(path: Path) -> str:
1420+ from docx import Document
1421+ 
1422+ document = Document(path)
1423+ lines: list[str] = []
1424+ total_chars = 0
1425+ for paragraph in document.paragraphs:
1426+ if not paragraph.text:
1427+ continue
1428+ lines.append(paragraph.text)
1429+ total_chars += len(paragraph.text)
1430+ if total_chars >= _MAX_ARTIFACT_FILE_CHARS:
1431+ return "\n".join(lines)[:_MAX_ARTIFACT_FILE_CHARS]
1432+ for table_index, table in enumerate(document.tables, start=1):
1433+ for row_index, row in enumerate(table.rows, start=1):
1434+ values = [cell.text for cell in row.cells]
1435+ line = f"[table:{table_index}:row:{row_index}] " + " | ".join(values)
1436+ lines.append(line)
1437+ total_chars += len(line)
1438+ if total_chars >= _MAX_ARTIFACT_FILE_CHARS:
1439+ return "\n".join(lines)[:_MAX_ARTIFACT_FILE_CHARS]
1440+ return "\n".join(lines)[:_MAX_ARTIFACT_FILE_CHARS]
1441+ 
1442+ 
1443+def _pdf_text(path: Path) -> str:
1444+ import pdfplumber
1445+ 
1446+ lines: list[str] = []
1447+ with pdfplumber.open(path) as document:
1448+ for page_index, page in enumerate(document.pages[:_MAX_STRUCTURED_PAGES], start=1):
1449+ lines.append(f"[page:{page_index}]")
1450+ lines.append(page.extract_text() or "")
1451+ if sum(len(item) for item in lines) >= _MAX_ARTIFACT_FILE_CHARS:
1452+ break
1453+ return "\n".join(lines)[:_MAX_ARTIFACT_FILE_CHARS]
1454+ 
1455+ 
1456+def _pptx_text(path: Path) -> str:
1457+ lines: list[str] = []
1458+ with zipfile.ZipFile(path) as archive:
1459+ slide_names = sorted(name for name in archive.namelist() if re.fullmatch(r"ppt/slides/slide\d+\.xml", name))[
1460+ :_MAX_STRUCTURED_PAGES
1461+ ]
1462+ for slide_name in slide_names:
1463+ root = ElementTree.fromstring(archive.read(slide_name))
1464+ texts = [element.text or "" for element in root.iter() if element.tag.endswith("}t")]
1465+ lines.append(f"[{slide_name}] " + " ".join(texts))
1466+ return "\n".join(lines)[:_MAX_ARTIFACT_FILE_CHARS]
1467+ 
1468+ 
1469+def _compare_runs(
1470+ prior_candidate_feedback: Mapping[str, Any] | None,
1471+ request: Mapping[str, Any],
1472+) -> dict[str, Any]:
1473+ if not prior_candidate_feedback:
1474+ return {"availability": "not_available", "reason": "no_paired_candidate_feedback"}
1475+ rendered = json.dumps(prior_candidate_feedback, ensure_ascii=False, indent=2)
1476+ query = str(request.get("query", "") or "")
1477+ terms = _query_terms(query) if query else []
1478+ return {
1479+ "availability": "available",
1480+ "query": query,
1481+ "paired_feedback": (
1482+ {"exact_spans": _exact_match_spans(rendered, terms, max_spans=5)}
1483+ if terms
1484+ else _bounded_exact_text(rendered, _MAX_EVENT_CHARS)
1485+ ),
1486+ }
1487+ 
1488+ 
1489+def _search_event_view(event: Mapping[str, Any], terms: Sequence[str]) -> dict[str, Any]:
1490+ return {
1491+ "trace_id": event.get("trace_id"),
1492+ "message_index": event.get("message_index"),
1493+ "step_pointer": event.get("step_pointer"),
1494+ "role": event.get("role"),
1495+ "content_spans": _exact_match_spans(str(event.get("content", "") or ""), terms, max_spans=2),
1496+ "tool_calls": [
1497+ {
1498+ "tool_call_index": index,
1499+ "name": str(call.get("name", "") or ""),
1500+ "input_spans": _exact_match_spans(str(call.get("input", "") or ""), terms, max_spans=2),
1501+ "output_spans": _exact_match_spans(str(call.get("output", "") or ""), terms, max_spans=3),
1502+ "error_spans": _exact_match_spans(str(call.get("error", "") or ""), terms, max_spans=2),
1503+ }
1504+ for index, call in enumerate(event.get("tool_calls", []))
1505+ ],
1506+ }
1507+ 
1508+ 
1509+def _exact_match_spans(
1510+ text: str,
1511+ terms: Sequence[str],
1512+ *,
1513+ max_spans: int,
1514+ span_chars: int = 2_000,
1515+) -> list[dict[str, Any]]:
1516+ if not text or not terms:
1517+ return []
1518+ lowered = text.casefold()
1519+ candidates: list[tuple[float, int, int, list[str]]] = []
1520+ for term in terms:
1521+ start_at = 0
1522+ for _ in range(16):
1523+ position = lowered.find(term, start_at)
1524+ if position < 0:
1525+ break
1526+ start = max(0, position - span_chars // 3)
1527+ end = min(len(text), start + span_chars)
1528+ start = max(0, end - span_chars)
1529+ window = lowered[start:end]
1530+ matched_terms = [candidate for candidate in terms if candidate in window]
1531+ score = sum(_term_weight(candidate) for candidate in set(matched_terms))
1532+ score += min(1.0, len(matched_terms) / 10)
1533+ candidates.append((score, start, end, matched_terms))
1534+ start_at = position + max(1, len(term))
1535+ spans: list[dict[str, Any]] = []
1536+ occupied: list[tuple[int, int]] = []
1537+ for _, start, end, matched_terms in sorted(candidates, key=lambda item: (-item[0], item[1])):
1538+ if any(start < old_end and end > old_start for old_start, old_end in occupied):
1539+ continue
1540+ occupied.append((start, end))
1541+ spans.append(
1542+ {
1543+ "source_char_start": start,
1544+ "source_char_end": end,
1545+ "source_char_count": len(text),
1546+ "matched_term": matched_terms[0],
1547+ "matched_terms": list(dict.fromkeys(matched_terms)),
1548+ "text": text[start:end],
1549+ "window_complete": start == 0 and end == len(text),
1550+ "omission_origin": "controller_search_window" if start or end < len(text) else "none",
1551+ }
1552+ )
1553+ if len(spans) >= max_spans:
1554+ break
1555+ return spans
1556+ 
1557+ 
1558+def _leading_text_span(text: str, *, span_chars: int = 2_000) -> list[dict[str, Any]]:
1559+ if not text:
1560+ return []
1561+ end = min(len(text), span_chars)
1562+ return [
1563+ {
1564+ "source_char_start": 0,
1565+ "source_char_end": end,
1566+ "source_char_count": len(text),
1567+ "matched_term": "logical_source",
1568+ "matched_terms": ["logical_source"],
1569+ "text": text[:end],
1570+ "window_complete": end == len(text),
1571+ "omission_origin": "controller_search_window" if end < len(text) else "none",
1572+ }
1573+ ]
1574+ 
1575+ 
1576+def _query_match_score(text: str, terms: Sequence[str]) -> float:
1577+ lowered = text.casefold()
1578+ return sum(_term_weight(term) for term in set(terms) if term in lowered)
1579+ 
1580+ 
1581+def _term_weight(term: str) -> float:
1582+ weight = 1.0 + min(len(term), 24) / 24
1583+ if any(character.isdigit() for character in term):
1584+ weight += 1.5
1585+ if any(character in term for character in "_./:-"):
1586+ weight += 0.5
1587+ return weight
1588+ 
1589+ 
1590+def _bounded_exact_text(text: str, limit: int) -> dict[str, Any]:
1591+ if len(text) <= limit:
1592+ return {
1593+ "text": text,
1594+ "source_char_count": len(text),
1595+ "complete": True,
1596+ "omission_origin": "none",
1597+ }
1598+ return {
1599+ "text": text[:limit],
1600+ "source_char_count": len(text),
1601+ "complete": False,
1602+ "omission_origin": "controller_bound",
1603+ "omitted_source_chars": len(text) - limit,
1604+ }
1605+ 
1606+ 
1607+def _query_terms(query: str) -> list[str]:
1608+ terms: list[str] = []
1609+ seen: set[str] = set()
1610+ for match in _TERM_PATTERN.findall(query):
1611+ normalized = match.casefold()
1612+ if normalized in _STOPWORDS or normalized in seen:
1613+ continue
1614+ seen.add(normalized)
1615+ terms.append(normalized)
1616+ if len(terms) >= 24:
1617+ break
1618+ return terms
1619+ 
1620+ 
1621+def _read_json(path: Path) -> dict[str, Any]:
1622+ try:
1623+ value = json.loads(path.read_text(encoding="utf-8"))
1624+ except (OSError, json.JSONDecodeError):
1625+ return {}
1626+ return value if isinstance(value, dict) else {}
1627+ 
1628+ 
1629+def _read_text(path: Path, limit: int) -> str:
1630+ try:
1631+ with path.open("r", encoding="utf-8", errors="replace") as stream:
1632+ return stream.read(limit)
1633+ except OSError:
1634+ return ""
1635+ 
1636+ 
1637+def _windows_long_path(path: Path) -> Path:
1638+ """Return an extended Windows path for deep evaluation artifact trees."""
1639+ resolved = str(path.resolve(strict=False))
1640+ if os.name != "nt" or resolved.startswith("\\\\?\\"):
1641+ return Path(resolved)
1642+ if resolved.startswith("\\\\"):
1643+ return Path("".join(("\\\\?\\UNC\\", resolved.lstrip("\\"))))
1644+ return Path("".join(("\\\\?\\", resolved)))
1645+ 
1646+ 
1647+def _string_list(value: Any) -> list[str]:
1648+ if not isinstance(value, (list, tuple, set)):
1649+ return []
1650+ return [str(item).strip() for item in value if str(item).strip()]
1651+ 
1652+ 
1653+def _positive_int(value: Any, *, default: int) -> int:
1654+ try:
1655+ parsed = int(value)
1656+ except (TypeError, ValueError):
1657+ return default
1658+ return parsed if parsed > 0 else default
1659+ 
1660+ 
1661+def _optional_nonnegative_int(value: Any) -> int | None:
1662+ try:
1663+ parsed = int(value)
1664+ except (TypeError, ValueError):
1665+ return None
1666+ return parsed if parsed >= 0 else None
1667+ 
1668+ 
1669+__all__ = [
1670+ "execute_causal_investigation",
1671+ "normalize_causal_investigation",
1672+]
Mopenjiuwen/rsi/evaluator/__init__.py+16-0文件内容审核中,请稍后刷新重试
Aopenjiuwen/rsi/evaluator/optimization_signals.py+89-0文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/evaluator/team_evaluator.py+30-6文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/evaluator/trajectory_usage.py+31-14文件内容审核中,请稍后刷新重试
Aopenjiuwen/rsi/improver_evolution/__init__.py+35-0文件内容审核中,请稍后刷新重试
Aopenjiuwen/rsi/improver_evolution/policy.py+529-0文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/member_optimizer/action_groups/definitions.py+1-0文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/member_optimizer/action_groups/policy.py+59-4文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/member_optimizer/agents/prompts/action_planning.md+37-14文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/member_optimizer/lever.py+2-0文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/member_optimizer/optimizer.py+14-2文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/member_optimizer/path_layout.py+6-2文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/member_optimizer/verification.py+24-17文件内容审核中,请稍后刷新重试
Mopenjiuwen/rsi/model_call.py+91-3文件内容审核中,请稍后刷新重试
Ascripts/rsi/evobench_deepagent_harness/harness.json+33-0文件内容审核中,请稍后刷新重试
Ascripts/rsi/evobench_deepagent_harness/system_prompt.md+18-0文件内容审核中,请稍后刷新重试
Atests/unit_tests/rsi/test_candidate_feedback.py+577-0文件内容审核中,请稍后刷新重试
Mtests/unit_tests/rsi/test_config.py+82-0文件内容审核中,请稍后刷新重试
Mtests/unit_tests/rsi/test_data_loader.py+12-0文件内容审核中,请稍后刷新重试
Mtests/unit_tests/rsi/test_evaluation_result_analyzer.py+2791-41文件内容审核中,请稍后刷新重试
Mtests/unit_tests/rsi/test_evaluator.py+84-0文件内容审核中,请稍后刷新重试
Atests/unit_tests/rsi/test_evidence_compactor.py+790-0文件内容审核中,请稍后刷新重试
Atests/unit_tests/rsi/test_improver_feedback_analysis.py+391-0文件内容审核中,请稍后刷新重试
Atests/unit_tests/rsi/test_improver_meta_validation.py+408-0文件内容审核中,请稍后刷新重试
Atests/unit_tests/rsi/test_improver_policy.py+397-0文件内容审核中,请稍后刷新重试
Mtests/unit_tests/rsi/test_member_optimizer.py+870-8文件内容审核中,请稍后刷新重试
Mtests/unit_tests/rsi/test_member_optimizer_path_layout.py+14-0文件内容审核中,请稍后刷新重试
Mtests/unit_tests/rsi/test_model_call.py+103-0文件内容审核中,请稍后刷新重试
Atests/unit_tests/rsi/test_optimization_signals.py+66-0文件内容审核中,请稍后刷新重试
Mtests/unit_tests/rsi/test_single_harness_iterative.py+2396-15文件内容审核中,请稍后刷新重试