refactor: unify agent history and harden runtime diagnostics
Preserve user guidance through history compression, stabilize Flash index targets, and retire withdrawn checks without applying stale verdicts. Improve runtime diagnostics, tool responses, and history rendering. Simplify documentation and comments. Validation: 2076 tests passed in the full suite; 381 affected tests and 26 frontend tests passed after cleanup. Ruff and the quality ratchet passed.
This commit is contained in:
1 parent
e983180b5b
commit
5d92939d91
127 files changed
+13738
-1988
No files matched your search
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"system": "You are a precise, evidence-driven verification expert (Checker). You audit declared check items against recorded execution evidence and issue verdicts. You never operate the device, never modify any state, and never negotiate with the executing agent — you only observe, probe read-only state where allowed, and judge.",
|
||||
"base_rules": "# Verdict Rules (always apply)\n- Quote every check item verbatim in `item_text` — never paraphrase.\n- Every verdict MUST carry concrete evidence: a specific probe output, a specific note excerpt, or a specific on-screen/screenshot observation. A `failed` verdict without concrete evidence is invalid and will be downgraded.\n- `inconclusive` means the evidence needed for a judgment was never captured or is genuinely ambiguous. When evidence was never collected, output `inconclusive` and state explicitly that the evidence is missing — do NOT output `failed` for lack of evidence.\n- Judge only what the check item declares. Do not invent additional criteria and do not relax the declared ones.",
|
||||
"verify_semantics": "# `verify` items (completion acceptance criteria)\n- A `verify` item asks: was the anchored subgoal actually accomplished to this standard?\n- `failed` verify verdicts trigger a repair loop, so a failed verdict MUST include a `suggestion`: a concrete, actionable hint telling the executor what is missing or wrong.\n- The release decision (whether execution proceeds) is computed outside of you and is fail-open on errors; your job is only the truthful verdict value.",
|
||||
"assert_semantics": "# `assert` items (test assertions)\n- An `assert` item is a test expectation, not an instruction. A failed assertion is a legitimate, valuable test result: record it truthfully with evidence. Nothing will (or should) try to repair it.\n- Never mark an assert `failed` merely because evidence is absent — absence of captured evidence is `inconclusive` with an explicit note that the evidence is missing.\n- `suggestion` is not needed for assert verdicts.",
|
||||
"verify_semantics": "# `verify` items (completion acceptance criteria)\n- A `verify` item asks: was the anchored subgoal actually accomplished to this standard?\n- A `failed` verify verdict can send its milestone back for repair, so it must include a `suggestion`: a concrete, actionable hint telling the executor what is missing or wrong.\n- The release decision (whether execution proceeds) is computed outside of you and is fail-open on errors; your job is only the truthful verdict value.",
|
||||
"assert_semantics": "# `assert` items (test assertions)\n- An `assert` item is a test expectation, not an instruction. A failed assertion is a legitimate, valuable test result: record it truthfully with evidence. Nothing will (or should) try to repair it.\n- Never mark an assert `failed` merely because evidence is absent — absence of captured evidence is `inconclusive` with an explicit note that the evidence is missing.\n- Precondition first: before judging an assert, confirm from the execution history (step replay, notes, anchor evidence) that the action(s) meant to trigger the asserted behavior were actually and correctly performed by the executor (the toggle was really tapped, the intended screen was really reached).\n- If that precondition is not established — the executor never performed the trigger, performed the wrong action, or the evidence cannot show it — output `inconclusive` and state explicitly that the precondition failed / the triggering action is not confirmed as executed. Never output `failed` here: a failed assert is attributed to the device under test, and an execution problem must not be reported as a device defect.\n- Output `failed` only when the precondition is established AND the device state, probe output, or recorded evidence still contradicts the expectation. The evidence must cite the concrete step(s) that performed the trigger plus the probe output / observation that contradicts the expectation, so the verdict reads as a device-defect diagnosis.\n- `suggestion` is not needed for assert verdicts.",
|
||||
"anchor_guide": "# Evidence anchoring (checkpoint audit)\n- You are auditing a subgoal at its completion moment. The authoritative evidence is the execution history around the anchor step (the anchor step's pre-action screenshot and the preceding step's post-action state) plus persistently probeable device state.\n- For transient expectations (toasts, dialogs, momentary states) and ordering expectations, you MUST inspect the recorded history near the anchor step — the live device has already moved on and is NOT valid evidence for them.\n- Prefer persistent state probes and recorded notes over inference from summaries.",
|
||||
"final_guide": "# Final review (task exit audit)\n- Audit against the USER'S ORIGINAL GOAL, not merely the plan's checkboxes.\n- `@end` items: judge them using the FINAL device state provided to you.\n- `on_complete` items: cite the verdict ledger — an already-recorded PASS/FAIL from the item's checkpoint attempt is authoritative (an assert that ever failed stays failed). For items never checked, judge from the historical evidence near their subgoal's completion; if that evidence was never captured, output `inconclusive` stating the evidence is missing. NEVER re-judge an `on_complete` item from the current screen: the current screen only proves the present, not the past moment the item was declared for.\n- `unmet_subgoals`: list, verbatim, any plan subgoal marked complete whose verify criteria are demonstrably not met. Leave it empty otherwise; failed asserts alone do NOT make a subgoal unmet.",
|
||||
"final_guide": "# Final review (task exit audit)\n- Audit against the user's original goal as amended by the user guidance received during the run (listed in the prompt), not merely the plan's checkboxes. Guidance outranks the plan and its check lines: judge a check line the guidance retired or changed by the guidance, not by its original wording.\n- `@end` items: judge them using the FINAL device state provided to you.\n- `on_complete` items: cite the verdict ledger — an already-recorded PASS/FAIL from the item's checkpoint attempt is authoritative (an assert that ever failed stays failed). For items never checked, judge from the historical evidence near their subgoal's completion; if that evidence was never captured, output `inconclusive` stating the evidence is missing. NEVER re-judge an `on_complete` item from the current screen: the current screen only proves the present, not the past moment the item was declared for.\n- `unmet_subgoals`: list, verbatim, any plan subgoal marked complete whose verify criteria are demonstrably not met. Leave it empty otherwise; failed asserts alone do NOT make a subgoal unmet.",
|
||||
"probe_guide": "# Device probes (read-only)\n- `probe_device` runs a fixed, enumerated set of read-only state probes. Prefer probing persistent state over inferring it: alarms (`alarms`), battery status (`battery`), foreground activities (`foreground`), notifications (`notifications`), a settings value (`setting` with `namespace` + `key`), a content provider query (`content` with `uri`), installed packages (`packages`), a system property (`prop` with `key`).\n- Probes outside this table do not exist; you cannot run arbitrary commands."
|
||||
}
|
||||
@@ -319,21 +319,54 @@ def build_checker_tools(
|
||||
# --- History formatting --------------------------------------------------------------
|
||||
|
||||
|
||||
def _format_history(ctx: ArtemisContext, limit: int = 60) -> str:
|
||||
def _load_steps(ctx: ArtemisContext) -> list[dict] | str:
|
||||
"""The agent-friendly step records, or the reason they are unavailable."""
|
||||
if not ctx.data_engine:
|
||||
return "No execution history available."
|
||||
try:
|
||||
steps = ctx.data_engine.get_agent_friendly_steps() or []
|
||||
return list(ctx.data_engine.get_agent_friendly_steps() or [])
|
||||
except Exception as e:
|
||||
return f"Failed to load execution history: {e}"
|
||||
|
||||
|
||||
def _step_offset_label(step: dict, session_start: float | None) -> str:
|
||||
"""Session-relative ``T+mm:ss`` label of a step: the clock every agent shares.
|
||||
|
||||
The recorded timestamp against the session start is authoritative; a
|
||||
record that only carries the engine's ``"97.7s"`` label is converted.
|
||||
"""
|
||||
from artemis.memory.transcript import format_session_offset
|
||||
|
||||
ts = step.get("timestamp")
|
||||
if isinstance(session_start, (int, float)) and isinstance(ts, (int, float)):
|
||||
return format_session_offset(float(ts) - float(session_start))
|
||||
rel = str(step.get("relative_time") or "").strip()
|
||||
if rel.startswith("T+"):
|
||||
return rel
|
||||
if rel.endswith("s"):
|
||||
try:
|
||||
return format_session_offset(float(rel[:-1]))
|
||||
except ValueError:
|
||||
pass
|
||||
return "T+??:??"
|
||||
|
||||
|
||||
def _format_history(ctx: ArtemisContext, limit: int = 60, steps: list[dict] | None = None) -> str:
|
||||
if steps is None:
|
||||
steps = _load_steps(ctx)
|
||||
if isinstance(steps, str):
|
||||
return steps
|
||||
if not steps:
|
||||
return "No execution history available."
|
||||
session_start = getattr(ctx.data_engine, "session_start_time", None)
|
||||
lines = []
|
||||
for s in steps[-limit:]:
|
||||
num = s.get("step_number")
|
||||
rel = s.get("relative_time", "")
|
||||
summary = s.get("summary") or ""
|
||||
summary = (s.get("summary") or "").strip()
|
||||
action = s.get("action_taken")
|
||||
if not summary and not action:
|
||||
# Nothing to audit on this step: an empty row is only noise.
|
||||
continue
|
||||
action_str = ""
|
||||
if action and not summary:
|
||||
# The shared renderer keeps the target's provenance visible: a
|
||||
@@ -343,8 +376,25 @@ def _format_history(ctx: ArtemisContext, limit: int = 60) -> str:
|
||||
action_str = format_actions_clean(action)[:160]
|
||||
except Exception:
|
||||
action_str = str(action)[:160]
|
||||
lines.append(f"- Step {num} ({rel}): {summary or action_str}".rstrip())
|
||||
return "\n".join(lines)
|
||||
label = _step_offset_label(s, session_start)
|
||||
lines.append(f"- Step {num} ({label}): {summary or action_str}".rstrip())
|
||||
return "\n".join(lines) or "No execution history available."
|
||||
|
||||
|
||||
def _format_user_guidance(ctx: ArtemisContext, steps: list[dict] | None = None) -> str:
|
||||
"""Every mid-run user instruction, read from the step it was stamped on."""
|
||||
if steps is None:
|
||||
steps = _load_steps(ctx)
|
||||
if isinstance(steps, str) or not steps:
|
||||
return "(none)"
|
||||
session_start = getattr(ctx.data_engine, "session_start_time", None)
|
||||
lines = []
|
||||
for s in steps:
|
||||
instruction = (s.get("extra_metadata") or {}).get("injected_instruction")
|
||||
if isinstance(instruction, str) and instruction.strip():
|
||||
label = _step_offset_label(s, session_start)
|
||||
lines.append(f'- Step {s.get("step_number")} ({label}): "{instruction.strip()}"')
|
||||
return "\n".join(lines) or "(none)"
|
||||
|
||||
|
||||
def _format_check_items(check_items: list) -> str:
|
||||
@@ -773,14 +823,16 @@ async def run_final_check(
|
||||
)
|
||||
|
||||
screenshot_b64, minimal_list = await _capture_final_screen(ctx)
|
||||
steps = _load_steps(ctx)
|
||||
|
||||
human_text = (
|
||||
f"# User's Original Goal\n{goal}\n\n"
|
||||
f"# User guidance received during the run\n{_format_user_guidance(ctx, steps)}\n\n"
|
||||
f"# Final Task Plan\n{plan_text or '(no plan)'}\n\n"
|
||||
f"# Declared Check Items\n{_format_check_items(items)}\n\n"
|
||||
f"# Verdict Ledger (checkpoint results, authoritative for on_complete"
|
||||
f" items)\n{_format_ledger(ledger)}\n\n"
|
||||
f"# Execution History (summaries)\n{_format_history(ctx)}\n\n"
|
||||
f"# Execution History (summaries)\n{_format_history(ctx, steps=steps)}\n\n"
|
||||
f"# Final Screen Elements\n{minimal_list}"
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user