feat: Add agent workspace system and runtime management
- Add agent core modules (agent_core, factory, registry, skill_loader) - Add runtime system for agent execution management - Add REST API for agents, workspaces, and runtime control - Add process supervisor for agent lifecycle management - Add workspace template system with agent profiles - Add frontend RuntimeView and runtime API integration - Add per-agent skill workspaces for smoke_fullstack run - Refactor skill system with active/installed separation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
41
backend/process/models.py
Normal file
41
backend/process/models.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Data models for lightweight process supervision."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
class ProcessRunState(str, Enum):
|
||||
"""Execution state for supervised runs."""
|
||||
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProcessRun:
|
||||
"""Represents a supervised process run."""
|
||||
|
||||
run_id: str
|
||||
command: str
|
||||
scope_key: str
|
||||
state: ProcessRunState = ProcessRunState.PENDING
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
created_at: datetime = field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = field(default_factory=datetime.utcnow)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"run_id": self.run_id,
|
||||
"command": self.command,
|
||||
"scope_key": self.scope_key,
|
||||
"state": self.state.value,
|
||||
"metadata": self.metadata,
|
||||
"created_at": self.created_at.isoformat(),
|
||||
"updated_at": self.updated_at.isoformat(),
|
||||
}
|
||||
35
backend/process/registry.py
Normal file
35
backend/process/registry.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Registry for managing supervised process metadata."""
|
||||
|
||||
from threading import Lock
|
||||
from typing import Dict, Iterable, Optional
|
||||
|
||||
from .models import ProcessRun
|
||||
|
||||
|
||||
class RunRegistry:
|
||||
"""In-memory registry for tracked process runs."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._runs: Dict[str, ProcessRun] = {}
|
||||
self._lock = Lock()
|
||||
|
||||
def add(self, run: ProcessRun) -> None:
|
||||
with self._lock:
|
||||
self._runs[run.run_id] = run
|
||||
|
||||
def get(self, run_id: str) -> Optional[ProcessRun]:
|
||||
with self._lock:
|
||||
return self._runs.get(run_id)
|
||||
|
||||
def list(self) -> Iterable[ProcessRun]:
|
||||
with self._lock:
|
||||
return list(self._runs.values())
|
||||
|
||||
def update(self, run: ProcessRun) -> None:
|
||||
with self._lock:
|
||||
self._runs[run.run_id] = run
|
||||
|
||||
def remove(self, run_id: str) -> None:
|
||||
with self._lock:
|
||||
self._runs.pop(run_id, None)
|
||||
61
backend/process/supervisor.py
Normal file
61
backend/process/supervisor.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Minimal supervisor for scripted tasks and long-running utilities."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
|
||||
from .models import ProcessRun, ProcessRunState
|
||||
from .registry import RunRegistry
|
||||
|
||||
|
||||
class ProcessSupervisor:
|
||||
"""Tracks supervised runs without executing real processes yet."""
|
||||
|
||||
def __init__(self, registry: Optional[RunRegistry] = None) -> None:
|
||||
self.registry = registry or RunRegistry()
|
||||
|
||||
def spawn(
|
||||
self,
|
||||
run_id: str,
|
||||
command: str,
|
||||
scope_key: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> ProcessRun:
|
||||
run = ProcessRun(
|
||||
run_id=run_id,
|
||||
command=command,
|
||||
scope_key=scope_key,
|
||||
metadata=metadata or {},
|
||||
)
|
||||
run.state = ProcessRunState.RUNNING
|
||||
run.updated_at = datetime.utcnow()
|
||||
self.registry.add(run)
|
||||
return run
|
||||
|
||||
def update_state(
|
||||
self,
|
||||
run_id: str,
|
||||
state: ProcessRunState,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> Optional[ProcessRun]:
|
||||
run = self.registry.get(run_id)
|
||||
if not run:
|
||||
return None
|
||||
run.state = state
|
||||
run.metadata.update(metadata or {})
|
||||
run.updated_at = datetime.utcnow()
|
||||
self.registry.update(run)
|
||||
return run
|
||||
|
||||
def cancel(self, run_id: str, reason: Optional[str] = None) -> Optional[ProcessRun]:
|
||||
run = self.registry.get(run_id)
|
||||
if not run:
|
||||
return None
|
||||
run.state = ProcessRunState.CANCELLED
|
||||
run.metadata.setdefault("cancel_reason", reason or "manual")
|
||||
run.updated_at = datetime.utcnow()
|
||||
self.registry.update(run)
|
||||
return run
|
||||
|
||||
def list_runs(self) -> Iterable[ProcessRun]:
|
||||
return self.registry.list()
|
||||
Reference in New Issue
Block a user