merge(runtime): reconcile task state and device cleanup
This commit is contained in:
commit
5907a28e14
43 files changed
+931
-2938
No files matched your search
@@ -12,40 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Core framework contracts and lifecycle primitives."""
|
||||
"""Core framework primitives.
|
||||
|
||||
from artemis.core.context import ExecutionContext, DeviceInfoContext
|
||||
from artemis.core.state import ExecutionContextState, ExecutionStatus, StepRecord
|
||||
from artemis.core.events import Event, EventBus, HookType, global_event_bus
|
||||
from artemis.core.exceptions import (
|
||||
ArtemisException,
|
||||
DeviceDriverException,
|
||||
DeviceNotFoundException,
|
||||
AgentExecutionException,
|
||||
ToolExecutionException,
|
||||
LLMProviderException,
|
||||
StateTransitionException,
|
||||
)
|
||||
from artemis.core.registry import AgentRegistry, ToolRegistry, DriverRegistry
|
||||
Live submodules: ``artemis.core.tool_declaration`` and ``artemis.core.diagnostics``
|
||||
(imported directly by module path by their consumers).
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"ExecutionContext",
|
||||
"DeviceInfoContext",
|
||||
"ExecutionContextState",
|
||||
"ExecutionStatus",
|
||||
"StepRecord",
|
||||
"Event",
|
||||
"EventBus",
|
||||
"HookType",
|
||||
"global_event_bus",
|
||||
"ArtemisException",
|
||||
"DeviceDriverException",
|
||||
"DeviceNotFoundException",
|
||||
"AgentExecutionException",
|
||||
"ToolExecutionException",
|
||||
"LLMProviderException",
|
||||
"StateTransitionException",
|
||||
"AgentRegistry",
|
||||
"ToolRegistry",
|
||||
"DriverRegistry",
|
||||
]
|
||||
__all__: list[str] = []
|
||||
@@ -1,62 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""Clean Execution Context for ARTEMIS."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class DeviceInfoContext(BaseModel):
|
||||
"""Device metadata and configuration."""
|
||||
|
||||
device_id: str = Field(default="default-device", description="ADB serial or device identifier")
|
||||
platform: str = Field(
|
||||
default="android", description="Platform type ('android', 'cloud', 'mock')"
|
||||
)
|
||||
width: int = Field(default=1080, description="Screen width in pixels")
|
||||
height: int = Field(default=2400, description="Screen height in pixels")
|
||||
|
||||
|
||||
class ExecutionContext:
|
||||
"""Central context holding device driver, session identifiers, and telemetry tracer."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
task_goal: str,
|
||||
device_id: str = "default-device",
|
||||
trace_id: str | None = None,
|
||||
traces_path: Path | str | None = None,
|
||||
locked_package: str | None = None,
|
||||
platform: str = "android",
|
||||
):
|
||||
self.task_goal = task_goal
|
||||
self.trace_id = trace_id or str(uuid4())
|
||||
self.traces_path = Path(traces_path) if traces_path else Path("traces") / self.trace_id
|
||||
self.locked_package = locked_package
|
||||
self.device = DeviceInfoContext(device_id=device_id, platform=platform)
|
||||
self.driver: Any | None = None
|
||||
self.telemetry: Any | None = None
|
||||
# Fully-wired ArtemisContext (data_engine, adb clients, execution setup).
|
||||
# Required by the live (non-mock) paths of GraphRunner/ReactiveRunner.
|
||||
self.artemis_context: Any | None = None
|
||||
self.custom_attributes: dict[str, Any] = {}
|
||||
|
||||
def set_attribute(self, key: str, value: Any) -> None:
|
||||
self.custom_attributes[key] = value
|
||||
|
||||
def get_attribute(self, key: str, default: Any = None) -> Any:
|
||||
return self.custom_attributes.get(key, default)
|
||||
@@ -1,77 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""Event Bus and Lifecycle Hooks System for ARTEMIS."""
|
||||
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from enum import Enum
|
||||
import inspect
|
||||
import time
|
||||
from typing import Any
|
||||
from pydantic import BaseModel, Field
|
||||
from artemis.utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class HookType(str, Enum):
|
||||
BEFORE_TASK = "before_task"
|
||||
AFTER_TASK = "after_task"
|
||||
BEFORE_STEP = "before_step"
|
||||
AFTER_STEP = "after_step"
|
||||
ON_ACTION = "on_action"
|
||||
ON_ERROR = "on_error"
|
||||
ON_SCREEN_CHANGE = "on_screen_change"
|
||||
|
||||
|
||||
class Event(BaseModel):
|
||||
"""Encapsulates an event payload emitted through the event bus."""
|
||||
|
||||
event_type: HookType = Field(..., description="Type of the lifecycle event")
|
||||
timestamp: float = Field(default_factory=time.time, description="Event occurrence timestamp")
|
||||
trace_id: str = Field(..., description="Active session trace identifier")
|
||||
payload: dict[str, Any] = Field(default_factory=dict, description="Event data dictionary")
|
||||
|
||||
|
||||
class EventBus:
|
||||
"""Asynchronous event bus supporting subscription and broadcast of lifecycle events."""
|
||||
|
||||
def __init__(self):
|
||||
self._subscribers: dict[HookType, list[Callable[..., Any]]] = defaultdict(list)
|
||||
|
||||
def subscribe(self, hook: HookType, callback: Callable[..., Any]) -> None:
|
||||
"""Registers a listener callback for a specific lifecycle event."""
|
||||
self._subscribers[hook].append(callback)
|
||||
|
||||
def unsubscribe(self, hook: HookType, callback: Callable[..., Any]) -> None:
|
||||
"""Removes a registered callback."""
|
||||
if callback in self._subscribers[hook]:
|
||||
self._subscribers[hook].remove(callback)
|
||||
|
||||
async def emit(self, event: Event) -> None:
|
||||
"""Asynchronously dispatches an event to all subscribed listeners."""
|
||||
listeners = self._subscribers.get(event.event_type, [])
|
||||
for listener in listeners:
|
||||
try:
|
||||
if inspect.iscoroutinefunction(listener):
|
||||
await listener(event)
|
||||
else:
|
||||
listener(event)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error executing event listener for {event.event_type}: {e}")
|
||||
|
||||
|
||||
# Global singleton event bus instance
|
||||
global_event_bus = EventBus()
|
||||
@@ -1,57 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""Standardized exception hierarchy for ARTEMIS."""
|
||||
|
||||
|
||||
class ArtemisException(Exception):
|
||||
"""Base exception for all ARTEMIS framework errors."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DeviceDriverException(ArtemisException):
|
||||
"""Raised when hardware, emulator, or ADB interactions fail."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DeviceNotFoundException(DeviceDriverException):
|
||||
"""Raised when the specified target device cannot be located or connected."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AgentExecutionException(ArtemisException):
|
||||
"""Raised when an autonomous agent fails during reasoning or execution."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ToolExecutionException(ArtemisException):
|
||||
"""Raised when a tool action fails or receives invalid arguments."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class LLMProviderException(ArtemisException):
|
||||
"""Raised when an upstream LLM/VLM API call fails or times out."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class StateTransitionException(ArtemisException):
|
||||
"""Raised when graph execution encounters an invalid state transition."""
|
||||
|
||||
pass
|
||||
@@ -1,46 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""Global Plugin and Component Registry for Agents, Tools, and Drivers."""
|
||||
|
||||
from typing import Any, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class ComponentRegistry:
|
||||
"""Generic typed component registry supporting runtime dynamic plugin loading."""
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self._items: dict[str, Any] = {}
|
||||
|
||||
def register(self, key: str, item: Any) -> Any:
|
||||
self._items[key.lower()] = item
|
||||
return item
|
||||
|
||||
def get(self, key: str) -> Any | None:
|
||||
return self._items.get(key.lower())
|
||||
|
||||
def list_all(self) -> dict[str, Any]:
|
||||
return dict(self._items)
|
||||
|
||||
def contains(self, key: str) -> bool:
|
||||
return key.lower() in self._items
|
||||
|
||||
|
||||
# Global Registries
|
||||
AgentRegistry = ComponentRegistry("Agents")
|
||||
ToolRegistry = ComponentRegistry("Tools")
|
||||
DriverRegistry = ComponentRegistry("Drivers")
|
||||
@@ -1,70 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""Core Execution State and Lifecycle Data Structures."""
|
||||
|
||||
from enum import Enum
|
||||
import time
|
||||
from typing import Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ExecutionStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
SUCCESS = "success"
|
||||
FAILED = "failed"
|
||||
PAUSED = "paused"
|
||||
INTERRUPTED = "interrupted"
|
||||
|
||||
|
||||
class StepRecord(BaseModel):
|
||||
"""Encapsulates a single step's observation, thought, and action."""
|
||||
|
||||
step_number: int = Field(..., description="1-indexed step turn counter")
|
||||
thought: str | None = Field(default=None, description="Reasoning text")
|
||||
action_name: str | None = Field(default=None, description="Triggered action name")
|
||||
action_params: dict[str, Any] = Field(default_factory=dict, description="Action arguments")
|
||||
result: str | None = Field(default=None, description="Action execution result output")
|
||||
screenshot_path: str | None = Field(default=None, description="Saved step screenshot file URI")
|
||||
timestamp: float = Field(default_factory=time.time, description="Timestamp of step start")
|
||||
duration_seconds: float = Field(default=0.0, description="Step processing duration in seconds")
|
||||
|
||||
|
||||
class ExecutionContextState(BaseModel):
|
||||
"""Full execution state passed across agents and runners."""
|
||||
|
||||
task_goal: str = Field(..., description="Initial user task objective")
|
||||
trace_id: str = Field(..., description="Unique task trace identifier")
|
||||
status: ExecutionStatus = Field(
|
||||
default=ExecutionStatus.RUNNING, description="Current execution lifecycle status"
|
||||
)
|
||||
current_turn: int = Field(default=0, description="Current step turn count")
|
||||
max_turns: int = Field(default=30, description="Maximum allowed step turns before timeout")
|
||||
steps: list[StepRecord] = Field(
|
||||
default_factory=list, description="Chronological execution step history"
|
||||
)
|
||||
active_plan: list[str] = Field(
|
||||
default_factory=list, description="Decomposed milestone plan items"
|
||||
)
|
||||
current_step_index: int = Field(default=0, description="Current index within active_plan")
|
||||
metadata: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Custom metadata and state parameters"
|
||||
)
|
||||
error_message: str | None = Field(
|
||||
default=None, description="Failure reason if status is FAILED"
|
||||
)
|
||||
final_output: Any | None = Field(
|
||||
default=None, description="Final synthesized structured output from Outputter or runner"
|
||||
)
|
||||
Reference in new issue
Block a user