Files
Artemis-Eapil/artemis/graph/state.py
T
somew1nd 206a9ce921 feat: initial release of ARTEMIS Android Agent & Automation Platform
ARTEMIS is an autonomous multimodal Android agent and mobile UI automation
platform engineered for physical devices and emulators. It combines state-of-the-art
multimodal Vision-Language Models (VLMs) with hierarchical closed-loop planning,
resilient self-healing, and deep Android subsystem integration.

Key Features & Architectural Highlights:
- Dual-Engine Orchestration:
  * Pro Engine: Multi-agent closed-loop (Planner -> Operator -> Validator/Failure Analyzer -> Summarizer) powered by LangGraph.
  * Flash Engine: Ultra-fast reactive loop for deterministic, low-latency UI execution (<35 steps).
- Multimodal Perception & Grounding:
  * Hybrid visual screenshot grounding, real-time OCR extraction, and XML accessibility hierarchy parsing.
  * Dynamic-First, Coordinate-Fallback locating strategy for maximum resilience against UI drift.
- Autonomous Self-Healing & Diagnostics:
  * Failure analyzer and safety net for popup dismissal, crash recovery, and auto-retry.
  * Diagnostic sub-agents for log analysis, video recording inspection, and object detection.
- Unified CLI & Python SDK:
  * Rich CLI with commands: init, doctor, run, trace, batch, bench, server, and mcp.
  * Fluent Python SDK with Pydantic structured output models and package locking.
- Multi-Provider LLM Architecture:
  * Support for Google Gemini, OpenAI, Anthropic Claude, Vertex AI, OpenRouter, xAI Grok, and local endpoints (Ollama/vLLM).
  * Centralized configuration in config/artemis.jsonc.
- Native MCP & Observability:
  * Model Context Protocol (MCP) server endpoints for AI IDEs.
  * Full trace recording with before/after screenshots, action overlays, and execution replays.

Change-Id: I430b93ee2b76b54cc5a3c6eeccd15d2cf29830a1
2026-08-13 11:46:47 -07:00

135 lines
4.7 KiB
Python

# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Annotated, Any
from langchain_core.messages import AnyMessage
from langgraph.graph import add_messages
from artemis.config import AgentNode
from artemis.context import ArtemisContext
from artemis.utils.logger import get_logger
from pydantic import BaseModel, ConfigDict
logger = get_logger(__name__)
def take_last(a, b):
"""Reducer function keeping the latest value."""
return b
class State(BaseModel):
model_config = ConfigDict(
arbitrary_types_allowed=True,
extra="allow",
)
messages: Annotated[list[AnyMessage], "Sequential messages", add_messages]
remaining_steps: Annotated[int | None, "Remaining steps before the task is completed"] = None
# planner related keys
initial_goal: Annotated[str, "Initial goal given by the user"]
injected_instruction: Annotated[
str | None,
"Injected instruction from user for micro-adjustment",
take_last,
] = None
# operator related keys (perception)
latest_ui_hierarchy: Annotated[
list[dict] | None, "Latest UI hierarchy of the device", take_last
]
latest_screenshot: Annotated[str | None, "Path to the latest screenshot", take_last]
focused_app_info: Annotated[str | None, "Focused app info", take_last]
device_date: Annotated[str | None, "Date of the device", take_last]
# operator related keys (decisions)
structured_decisions: Annotated[
str | None,
"Structured decisions made by the operator, for the validator to follow",
take_last,
]
operator_raw_thinking: Annotated[
str | None,
"Raw thinking process of the operator",
take_last,
] = None
operator_native_thinking: Annotated[
str | None,
"Native/implicit thinking process of the operator",
take_last,
] = None
short_term_memory: Annotated[
str | None,
"Short term memory / scratchpad for the operator",
take_last,
] = None
complete_subgoals_by_ids: Annotated[
list[str],
"List of subgoal IDs to complete",
take_last,
]
current_agent: Annotated[str | None, "Current active agent", take_last] = None
operator_replan_reason: Annotated[str | None, "Reason for operator replan", take_last] = None
subgoal_plan: Annotated[list[Any] | None, "Subgoal plan", take_last] = None
operator_tactical_plan: Annotated[list[Any] | None, "Operator tactical plan", take_last] = None
# validator related keys
validator_messages: Annotated[list[AnyMessage], "Sequential Validator messages", add_messages]
last_execution_result: Annotated[
dict | None, "Last execution result from validator", take_last
] = None
current_step_id: Annotated[str | None, "Current step ID from data engine", take_last] = None
operator_raw_data: Annotated[
dict | None,
"Raw perception data from operator (screenshot, xml, ocr)",
take_last,
] = None
checker_success: Annotated[
bool | None,
"True if checker succeeded or not triggered, False if failed",
take_last,
] = None
subagent_calls: Annotated[
list[str], "List of sub-agent calls in current session", take_last
] = []
operator_replied: Annotated[bool | None, "True if operator replied to checker", take_last] = (
None
)
operator_tool_limit_exceeded: Annotated[
bool | None,
"True if operator exceeded tool call limit in the previous turn",
take_last,
] = None
indexed_points: Annotated[
list[list[int]] | None,
"Active coordinate mappings matching indexed text elements",
take_last,
] = None
indexed_elements: Annotated[
list[dict] | None,
"Active interactable elements matching indexed text elements,"
" containing text, bounds, class, and center coordinates.",
take_last,
] = None
async def asanitize_update(
self,
ctx: ArtemisContext,
update: dict,
agent: AgentNode | None = None,
):
"""Sanitizes the state update to ensure it is valid and apply side effect logic where required."""
return update