1
0
This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/src/timmy/agent_core/interface.py

383 lines
11 KiB
Python

"""TimAgent Interface — The substrate-agnostic agent contract.
This is the foundation for embodiment. Whether Timmy runs on:
- A server with Ollama (today)
- A Raspberry Pi with sensors
- A Boston Dynamics Spot robot
- A VR avatar
The interface remains constant. Implementation varies.
Architecture:
perceive() → reason → act()
↑ ↓
←←← remember() ←←←←←←┘
All methods return effects that can be logged, audited, and replayed.
"""
import uuid
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum, auto
from typing import Any, Optional
class PerceptionType(Enum):
"""Types of sensory input an agent can receive."""
TEXT = auto() # Natural language
IMAGE = auto() # Visual input
AUDIO = auto() # Sound/speech
SENSOR = auto() # Temperature, distance, etc.
MOTION = auto() # Accelerometer, gyroscope
NETWORK = auto() # API calls, messages
INTERNAL = auto() # Self-monitoring (battery, temp)
class ActionType(Enum):
"""Types of actions an agent can perform."""
TEXT = auto() # Generate text response
SPEAK = auto() # Text-to-speech
MOVE = auto() # Physical movement
GRIP = auto() # Manipulate objects
CALL = auto() # API/network call
EMIT = auto() # Signal/light/sound
SLEEP = auto() # Power management
class AgentCapability(Enum):
"""High-level capabilities a TimAgent may possess."""
REASONING = "reasoning"
CODING = "coding"
WRITING = "writing"
ANALYSIS = "analysis"
VISION = "vision"
SPEECH = "speech"
NAVIGATION = "navigation"
MANIPULATION = "manipulation"
LEARNING = "learning"
COMMUNICATION = "communication"
@dataclass(frozen=True)
class AgentIdentity:
"""Immutable identity for an agent instance.
This persists across sessions and substrates. If Timmy moves
from cloud to robot, the identity follows.
"""
id: str
name: str
version: str
created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
@classmethod
def generate(cls, name: str, version: str = "1.0.0") -> "AgentIdentity":
"""Generate a new unique identity."""
return cls(
id=str(uuid.uuid4()),
name=name,
version=version,
)
@dataclass
class Perception:
"""A sensory input to the agent.
Substrate-agnostic representation. A camera image and a
LiDAR point cloud are both Perception instances.
"""
type: PerceptionType
data: Any # Content depends on type
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
source: str = "unknown" # e.g., "camera_1", "microphone", "user_input"
metadata: dict = field(default_factory=dict)
@classmethod
def text(cls, content: str, source: str = "user") -> "Perception":
"""Factory for text perception."""
return cls(
type=PerceptionType.TEXT,
data=content,
source=source,
)
@classmethod
def sensor(cls, kind: str, value: float, unit: str = "") -> "Perception":
"""Factory for sensor readings."""
return cls(
type=PerceptionType.SENSOR,
data={"kind": kind, "value": value, "unit": unit},
source=f"sensor_{kind}",
)
@dataclass
class Action:
"""An action the agent intends to perform.
Actions are effects — they describe what should happen,
not how. The substrate implements the "how."
"""
type: ActionType
payload: Any # Action-specific data
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
confidence: float = 1.0 # 0-1, agent's certainty
deadline: Optional[str] = None # When action must complete
@classmethod
def respond(cls, text: str, confidence: float = 1.0) -> "Action":
"""Factory for text response action."""
return cls(
type=ActionType.TEXT,
payload=text,
confidence=confidence,
)
@classmethod
def move(cls, vector: tuple[float, float, float], speed: float = 1.0) -> "Action":
"""Factory for movement action (x, y, z meters)."""
return cls(
type=ActionType.MOVE,
payload={"vector": vector, "speed": speed},
)
@dataclass
class Memory:
"""A stored experience or fact.
Memories are substrate-agnostic. A conversation history
and a video recording are both Memory instances.
"""
id: str
content: Any
created_at: str
access_count: int = 0
last_accessed: Optional[str] = None
importance: float = 0.5 # 0-1, for pruning decisions
tags: list[str] = field(default_factory=list)
def touch(self) -> None:
"""Mark memory as accessed."""
self.access_count += 1
self.last_accessed = datetime.now(timezone.utc).isoformat()
@dataclass
class Communication:
"""A message to/from another agent or human."""
sender: str
recipient: str
content: Any
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
protocol: str = "direct" # e.g., "http", "websocket", "speech"
encrypted: bool = False
class TimAgent(ABC):
"""Abstract base class for all Timmy agent implementations.
This is the substrate-agnostic interface. Implementations:
- OllamaAgent: LLM-based reasoning (today)
- RobotAgent: Physical embodiment (future)
- SimulationAgent: Virtual environment (future)
Usage:
agent = OllamaAgent(identity) # Today's implementation
perception = Perception.text("Hello Timmy")
memory = agent.perceive(perception)
action = agent.reason("How should I respond?")
result = agent.act(action)
agent.remember(memory) # Store for future
"""
def __init__(self, identity: AgentIdentity) -> None:
self._identity = identity
self._capabilities: set[AgentCapability] = set()
self._state: dict[str, Any] = {}
@property
def identity(self) -> AgentIdentity:
"""Return this agent's immutable identity."""
return self._identity
@property
def capabilities(self) -> set[AgentCapability]:
"""Return set of supported capabilities."""
return self._capabilities.copy()
def has_capability(self, capability: AgentCapability) -> bool:
"""Check if agent supports a capability."""
return capability in self._capabilities
@abstractmethod
def perceive(self, perception: Perception) -> Memory:
"""Process sensory input and create a memory.
This is the entry point for all agent interaction.
A text message, camera frame, or temperature reading
all enter through perceive().
Args:
perception: Sensory input
Returns:
Memory: Stored representation of the perception
"""
pass
@abstractmethod
def reason(self, query: str, context: list[Memory]) -> Action:
"""Reason about a situation and decide on action.
This is where "thinking" happens. The agent uses its
substrate-appropriate reasoning (LLM, neural net, rules)
to decide what to do.
Args:
query: What to reason about
context: Relevant memories for context
Returns:
Action: What the agent decides to do
"""
pass
@abstractmethod
def act(self, action: Action) -> Any:
"""Execute an action in the substrate.
This is where the abstract action becomes concrete:
- TEXT → Generate LLM response
- MOVE → Send motor commands
- SPEAK → Call TTS engine
Args:
action: The action to execute
Returns:
Result of the action (substrate-specific)
"""
pass
@abstractmethod
def remember(self, memory: Memory) -> None:
"""Store a memory for future retrieval.
The storage mechanism depends on substrate:
- Cloud: SQLite, vector DB
- Robot: Local flash storage
- Hybrid: Synced with conflict resolution
Args:
memory: Experience to store
"""
pass
@abstractmethod
def recall(self, query: str, limit: int = 5) -> list[Memory]:
"""Retrieve relevant memories.
Args:
query: What to search for
limit: Maximum memories to return
Returns:
List of relevant memories, sorted by relevance
"""
pass
@abstractmethod
def communicate(self, message: Communication) -> bool:
"""Send/receive communication with another agent.
Args:
message: Message to send
Returns:
True if communication succeeded
"""
pass
def get_state(self) -> dict[str, Any]:
"""Get current agent state for monitoring/debugging."""
return {
"identity": self._identity,
"capabilities": list(self._capabilities),
"state": self._state.copy(),
}
def shutdown(self) -> None:
"""Graceful shutdown. Persist state, close connections."""
# Override in subclass for cleanup
pass
class AgentEffect:
"""Log entry for agent actions — for audit and replay.
The complete history of an agent's life can be captured
as a sequence of AgentEffects. This enables:
- Debugging: What did the agent see and do?
- Audit: Why did it make that decision?
- Replay: Reconstruct agent state from log
- Training: Learn from agent experiences
"""
def __init__(self, log_path: Optional[str] = None) -> None:
self._effects: list[dict] = []
self._log_path = log_path
def log_perceive(self, perception: Perception, memory_id: str) -> None:
"""Log a perception event."""
self._effects.append(
{
"type": "perceive",
"perception_type": perception.type.name,
"source": perception.source,
"memory_id": memory_id,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
)
def log_reason(self, query: str, action_type: ActionType) -> None:
"""Log a reasoning event."""
self._effects.append(
{
"type": "reason",
"query": query,
"action_type": action_type.name,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
)
def log_act(self, action: Action, result: Any) -> None:
"""Log an action event."""
self._effects.append(
{
"type": "act",
"action_type": action.type.name,
"confidence": action.confidence,
"result_type": type(result).__name__,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
)
def export(self) -> list[dict]:
"""Export effect log for analysis."""
return self._effects.copy()