feat: harden agent verification and runtime contracts

Introduce two-gate checker settlement, explicit graph visibility and checkpoints, structured LLM reliability, centralized action specifications, and corresponding tests and design documentation.
This commit is contained in:
somew1nd committed 2026-08-31 20:42:24 -07:00
1 parent ddfa420945
commit cbe8f4f0c6
106 files changed
+11542 -4445

No files matched your search

+21 -15
View File
@@ -22,6 +22,7 @@ from artemis.context import ArtemisContext
from artemis.controllers.unified_controller import UnifiedMobileController
from artemis.data_engine.trace import trace
from artemis.graph.state import State
from artemis.graph.visibility import strict_state
from artemis.utils.logger import get_logger
from artemis.utils.ocr_api import is_ocr_configured, perform_ocr
from artemis.utils.ocr_xml_fusion import (
@@ -71,15 +72,23 @@ def _should_skip_settling(state: State) -> bool:
@trace(type="agent", name="perception")
def _check_injected_instruction_file(base_dir: str) -> str | None:
def _check_injected_instruction_file(base_dir: str) -> dict | None:
"""Consumes a pending injected-instruction payload, if any.
Returns the payload dict: `instruction` (str | None) plus the explicit
`release_loop` stop signal (bool) declared by the injecting caller.
"""
instruction_path = Path(base_dir) / "injected_instruction.json"
if instruction_path.exists():
with open(instruction_path, encoding="utf-8") as f:
data = json.load(f)
injected_instruction = data.get("instruction")
instruction_path.unlink()
logger.info(f"Read and deleted injected instruction: '{injected_instruction}'")
return injected_instruction
payload = {
"instruction": data.get("instruction"),
"release_loop": bool(data.get("release_loop")),
}
logger.info(f"Read and deleted injected instruction payload: {payload}")
return payload
return None
@@ -88,6 +97,7 @@ async def perception_node(state: State, ctx: ArtemisContext) -> dict:
Always executes at the start of an operator cycle.
"""
state = strict_state(state, "perception")
logger.info("Entering Perception Node...")
if not ctx.data_engine.current_step_id:
@@ -96,10 +106,14 @@ async def perception_node(state: State, ctx: ArtemisContext) -> dict:
# Check for injected instruction without blocking event loop
injected_instruction = None
user_stop_requested = False
if ctx.data_engine and ctx.data_engine.base_dir:
injected_instruction = await asyncio.to_thread(
injected_payload = await asyncio.to_thread(
_check_injected_instruction_file, str(ctx.data_engine.base_dir)
)
if injected_payload:
injected_instruction = injected_payload.get("instruction")
user_stop_requested = bool(injected_payload.get("release_loop"))
controller = UnifiedMobileController(ctx)
@@ -171,16 +185,6 @@ async def perception_node(state: State, ctx: ArtemisContext) -> dict:
)
logger.info(f"Offloaded perception data saving to background with image_name: {image_name}")
# Cache latest perception data on ArtemisContext for downstream zero-latency reuse (e.g. Checker)
ctx.latest_perception_data = {
"screenshot_b64": latest_screenshot_b64,
"latest_ui_hierarchy": fused_xml,
"xml_hierarchy": xml_hierarchy if xml_hierarchy is not None else [],
"ocr_results": ocr_results,
"width": device_data.width,
"height": device_data.height,
}
# Return state update
update = {
"latest_screenshot": screenshot_path,
@@ -193,5 +197,7 @@ async def perception_node(state: State, ctx: ArtemisContext) -> dict:
"height": device_data.height,
},
"injected_instruction": injected_instruction,
# sticky_or reducer: once latched True, later False updates keep it True
"user_stop_requested": user_stop_requested,
}
return update