echo_env / src /core /harness /events.py
sergiopaniego's picture
sergiopaniego HF Staff
Redeploy echo_env from OpenEnv main (openenv v0.8.0)
0b2c941 verified
Raw History Blame Contribute Delete
3.74 kB
# SPDX-License-Identifier: BSD-3-Clause
"""Event and response types for agentic harness turns (RFC 005)."""
from __future__ import annotations
import time
from enum import Enum
from typing import Any, Literal
from pydantic import BaseModel, Field
class HarnessEventType(str, Enum):
"""Types of events emitted while a harness processes one turn."""
LLM_REQUEST = "llm_request"
LLM_RESPONSE = "llm_response"
LLM_CHUNK = "llm_chunk"
TOOL_CALL = "tool_call"
TOOL_RESULT = "tool_result"
TEXT_OUTPUT = "text_output"
ERROR = "error"
TURN_COMPLETE = "turn_complete"
class HarnessEvent(BaseModel):
"""
A single event from a harness turn.
Concrete adapters map their native event streams to this schema so the
orchestrator can inspect what happened during a turn regardless of which
harness produced it.
Args:
type (`HarnessEventType`):
The event type.
timestamp (`float`, *optional*, defaults to the current Unix time):
When the event occurred.
data (`dict[str, Any]`, *optional*):
Event-type-specific payload. Expected keys by type:
`LLM_REQUEST`: `messages`, `model`; `LLM_RESPONSE`: `content`,
`usage`; `LLM_CHUNK`: `content`, `index`; `TOOL_CALL`:
`tool_name`, `arguments`; `TOOL_RESULT`: `tool_name`, `result`,
`error`; `TEXT_OUTPUT`: `text`; `ERROR`: `message`,
`recoverable`; `TURN_COMPLETE`: `response` and optionally `done`.
"""
type: HarnessEventType
timestamp: float = Field(default_factory=time.time)
data: dict[str, Any] = Field(default_factory=dict)
class HarnessResponse(BaseModel):
"""
Complete response from a single conversational turn.
Args:
response (`str`):
The harness's text response for this turn.
events (`list[HarnessEvent]`, *optional*):
All events emitted during this turn.
done (`bool`, *optional*, defaults to `False`):
Whether the harness considers the task complete.
"""
response: str
events: list[HarnessEvent] = Field(default_factory=list)
done: bool = False
class HarnessClientMessage(BaseModel):
"""
One client frame on the production `/harness` WebSocket.
Args:
type (`str`):
Must be `"message"`.
content (`str`):
The user message for this conversational turn.
"""
type: Literal["message"]
content: str
class HarnessProtocolError(BaseModel):
"""
A recoverable rejection of a client frame on `/harness`.
No turn was started. The client can correct its message and retry on the
same connection. The distinct `protocol_error` discriminator keeps this
response separate from terminal harness `error` events.
Args:
data (`dict[str, Any]`):
Error details including `message` and `code`.
"""
type: Literal["protocol_error"] = "protocol_error"
data: dict[str, Any]
def events_to_metadata(events: list[HarnessEvent]) -> list[dict[str, Any]]:
"""
Convert harness events to JSON-serializable dicts for observation metadata.
This is the sanctioned way to place events into
[`~openenv.core.env_server.types.Observation`] metadata, guaranteeing the
payload survives wire serialization.
Args:
events (`list[HarnessEvent]`):
Events to convert.
Returns:
`list[dict]` of JSON-serializable event payloads.
"""
return [event.model_dump(mode="json") for event in events]
__all__ = [
"HarnessClientMessage",
"HarnessEvent",
"HarnessEventType",
"HarnessResponse",
"HarnessProtocolError",
"events_to_metadata",
]