Claude
Skills
Sign in
Back

temet-run-tui-patterns

Included with Lifetime
$97 forever

Builds temet-run TUI components following project patterns for daemon monitoring, agent management, and observability integration. Use when implementing agent dashboards, status displays, log viewers, multi-agent layouts, IPC integration, and OpenTelemetry instrumentation in Textual widgets. Specific to temet-run's architecture and requirements.

AI Agents

What this skill does


# temet-run TUI Patterns

## Purpose
Implement temet-run TUI components following the project's functional programming principles, type safety, observability patterns, and daemon/agent architecture. This skill covers patterns specific to monitoring autonomous agents.

## Quick Start

```python
from temet_run.config import Settings
from temet_run.tui.widgets import AgentListWidget
from textual.app import App, ComposeResult

class TemedRunTUI(App):
    """temet-run TUI dashboard."""

    def __init__(self) -> None:
        super().__init__()
        self._settings = Settings()

    def compose(self) -> ComposeResult:
        yield AgentListWidget(
            settings=self._settings,
            id="agent-list",
        )

    async def on_mount(self) -> None:
        """Initialize dashboard."""
        self._settings = Settings()
        agent_list = self.query_one("#agent-list", AgentListWidget)
        await agent_list.refresh_agents()
```

## Instructions

### Step 1: Integrate with Settings Configuration

Use temet-run's Pydantic Settings for configuration:

```python
from temet_run.config import Settings
from textual.widgets import Static
from textual.app import ComposeResult

class DaemonMonitorWidget(Static):
    """Monitor daemon using temet-run settings."""

    def __init__(self, **kwargs: object) -> None:
        super().__init__(**kwargs)
        try:
            self._settings = Settings()  # Loads from .env and environment
        except Exception as e:
            self._settings = None
            self._error = str(e)

    def render(self) -> str:
        """Render daemon paths."""
        if not self._settings:
            return f"Configuration error: {self._error}"

        return (
            f"PID Dir: {self._settings.daemon_pid_file.parent}\n"
            f"Socket Dir: {self._settings.daemon_socket_path.parent}\n"
            f"Log Dir: {self._settings.log_dir}\n"
            f"Memory Dir: {self._settings.memory_dir}"
        )

    def get_agent_paths(self, agent_id: str) -> dict[str, Path]:
        """Get paths for specific agent."""
        if not self._settings:
            return {}

        return {
            "pid_file": self._settings.get_agent_pid_file(agent_id),
            "socket": self._settings.get_agent_socket_path(agent_id),
            "log": self._settings.get_agent_log_file(agent_id),
        }
```

**Settings Pattern:**
- Settings loads from `.env` and environment variables
- Use `Settings()` to create validated config
- Call `get_agent_pid_file(agent_id)` to get agent-specific paths
- Handle `ValidationError` for invalid configuration

### Step 2: Implement Agent Discovery and Status Checking

Monitor running agents using PID files:

```python
import os
from pathlib import Path
from textual.widgets import Static
from temet_run.config import Settings
from dataclasses import dataclass

@dataclass(frozen=True)
class AgentStatus:
    """Agent status information."""
    agent_id: str
    is_running: bool
    pid: int | None
    status: str  # "running", "idle", "busy", "stopped"

class AgentDiscoveryMixin:
    """Mixin for discovering running agents."""

    def __init__(self, settings: Settings) -> None:
        self._settings = settings

    def _find_running_agents(self) -> list[AgentStatus]:
        """Discover all running agents from PID files.

        Returns:
            List of running agent statuses.
        """
        statuses: list[AgentStatus] = []
        pid_dir = self._settings.daemon_pid_file.parent

        if not pid_dir.exists():
            return statuses

        # Find all temet-run PID files
        pid_files = list(pid_dir.glob("temet-run-*.pid"))

        for pid_file in sorted(pid_files):
            # Extract agent_id from filename: temet-run-{agent_id}.pid
            agent_id = pid_file.stem.replace("temet-run-", "")

            # Read PID and check if process exists
            try:
                pid_str = pid_file.read_text().strip()
                pid = int(pid_str)

                # Signal 0 check: raises ProcessLookupError if not running
                os.kill(pid, 0)

                status = AgentStatus(
                    agent_id=agent_id,
                    is_running=True,
                    pid=pid,
                    status="running",  # TODO: Get actual status via IPC
                )
                statuses.append(status)

            except (ValueError, OSError, ProcessLookupError):
                # PID file invalid or process not running
                statuses.append(AgentStatus(
                    agent_id=agent_id,
                    is_running=False,
                    pid=None,
                    status="stopped",
                ))

        return statuses
```

**Discovery Pattern:**
- Check PID files in `settings.daemon_pid_file.parent`
- Files named `temet-run-{agent_id}.pid`
- Use `os.kill(pid, 0)` to check if process exists
- Handle stale PID files gracefully

### Step 3: Integrate IPC for Agent Communication

Use IPCClient to communicate with running agents:

```python
import asyncio
from temet_run.ipc.client import IPCClient
from textual.widgets import Static

class AgentCommandWidget(Static):
    """Send commands to agent via IPC."""

    def __init__(self, agent_id: str, socket_path: Path, **kwargs: object) -> None:
        super().__init__(**kwargs)
        self._agent_id = agent_id
        self._socket_path = socket_path
        self._response = ""

    async def execute_command(self, prompt: str) -> str:
        """Execute command on agent via IPC.

        Args:
            prompt: User prompt/command.

        Returns:
            Agent response text.

        Raises:
            FileNotFoundError: If agent socket not found.
            ConnectionError: If IPC connection fails.
        """
        if not self._socket_path.exists():
            msg = f"Agent socket not found: {self._socket_path}"
            raise FileNotFoundError(msg)

        response_parts: list[str] = []

        try:
            async with IPCClient(self._socket_path) as client:
                # Stream responses from agent
                async for response in client.execute_command(prompt=prompt):
                    response_type = response.get("type")

                    if response_type == "message_response":
                        content = response.get("content", "")
                        if isinstance(content, str):
                            response_parts.append(content)

                    elif response_type == "status_response":
                        status = response.get("status")
                        if status == "completed":
                            self._response = "".join(response_parts)
                            return self._response
                        elif status == "failed":
                            error = response.get("error", "Unknown error")
                            raise RuntimeError(f"Agent error: {error}")

        except Exception as e:
            msg = f"IPC communication failed: {e}"
            raise RuntimeError(msg) from e

        return self._response

    async def get_agent_status(self) -> dict[str, object]:
        """Get agent status via IPC.

        Returns:
            Status dict with agent information.
        """
        async with IPCClient(self._socket_path) as client:
            response = await client.get_status()
            return response
```

**IPC Pattern:**
- Check socket exists before connecting
- Use `async with IPCClient(path)` for safe connection
- Stream responses for long operations
- Handle connection errors gracefully
- Log errors via observability system

### Step 4: Implement Observability Instrumentation

Add OpenTelemetry tracing to TUI widgets:

```python
from temet_run.observability import get_logger, instrument_async
from textual.widgets import Static
import asyncio

logger = get_logger(__name__)

class ObservedWidget(Static):
    """Widget with OpenTelemet

Related in AI Agents