""" Cell model for Lazarus Pit v2.0. A cell is a bounded execution/living space for one or more participants on a single project target. Cells are isolated by filesystem, credentials, and optionally process/container boundaries. """ import json import shutil import uuid from dataclasses import dataclass, field, asdict from datetime import datetime, timedelta from enum import Enum, auto from pathlib import Path from typing import Dict, List, Optional, Set class CellState(Enum): PROPOSED = "proposed" ACTIVE = "active" IDLE = "idle" CLOSING = "closing" ARCHIVED = "archived" DESTROYED = "destroyed" class CellRole(Enum): EXECUTOR = "executor" # Can read/write code, run tools, push commits OBSERVER = "observer" # Read-only cell access DIRECTOR = "director" # Can assign, close, publish back to Gitea @dataclass class CellMember: identity: str # e.g. "agent:allegro", "bot:kimi", "human:Alexander" role: CellRole joined_at: str @dataclass class Cell: cell_id: str project: str # e.g. "Timmy_Foundation/timmy-config#262" owner: str # e.g. "human:Alexander" backend: str # "process", "venv", "docker", "remote" state: CellState created_at: str ttl_minutes: int members: List[CellMember] = field(default_factory=list) hermes_home: Optional[str] = None workspace_path: Optional[str] = None shared_notes_path: Optional[str] = None shared_artifacts_path: Optional[str] = None decisions_path: Optional[str] = None archived_at: Optional[str] = None destroyed_at: Optional[str] = None def is_expired(self) -> bool: if self.state in (CellState.CLOSING, CellState.ARCHIVED, CellState.DESTROYED): return False created = datetime.fromisoformat(self.created_at.replace("Z", "+00:00")) return datetime.now().astimezone() > created + timedelta(minutes=self.ttl_minutes) def to_dict(self) -> dict: d = asdict(self) d["state"] = self.state.value d["members"] = [ {**asdict(m), "role": m.role.value} for m in self.members ] return d @classmethod def from_dict(cls, d: dict) -> "Cell": d = dict(d) d["state"] = CellState(d["state"]) d["members"] = [ CellMember( identity=m["identity"], role=CellRole(m["role"]), joined_at=m["joined_at"], ) for m in d.get("members", []) ] return cls(**d) class CellRegistry: """SQLite-backed registry of all cells.""" def __init__(self, db_path: Optional[str] = None): self.db_path = Path(db_path or "/root/.lazarus_registry.sqlite") self.db_path.parent.mkdir(parents=True, exist_ok=True) self._init_db() def _init_db(self): import sqlite3 with sqlite3.connect(str(self.db_path)) as conn: conn.execute( """ CREATE TABLE IF NOT EXISTS cells ( cell_id TEXT PRIMARY KEY, project TEXT NOT NULL, owner TEXT NOT NULL, backend TEXT NOT NULL, state TEXT NOT NULL, created_at TEXT NOT NULL, ttl_minutes INTEGER NOT NULL, members TEXT, hermes_home TEXT, workspace_path TEXT, shared_notes_path TEXT, shared_artifacts_path TEXT, decisions_path TEXT, archived_at TEXT, destroyed_at TEXT ) """ ) def save(self, cell: Cell): import sqlite3 with sqlite3.connect(str(self.db_path)) as conn: conn.execute( """ INSERT OR REPLACE INTO cells ( cell_id, project, owner, backend, state, created_at, ttl_minutes, members, hermes_home, workspace_path, shared_notes_path, shared_artifacts_path, decisions_path, archived_at, destroyed_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( cell.cell_id, cell.project, cell.owner, cell.backend, cell.state.value, cell.created_at, cell.ttl_minutes, json.dumps(cell.to_dict()["members"]), cell.hermes_home, cell.workspace_path, cell.shared_notes_path, cell.shared_artifacts_path, cell.decisions_path, cell.archived_at, cell.destroyed_at, ), ) def load(self, cell_id: str) -> Optional[Cell]: import sqlite3 with sqlite3.connect(str(self.db_path)) as conn: row = conn.execute( "SELECT * FROM cells WHERE cell_id = ?", (cell_id,) ).fetchone() if not row: return None keys = [ "cell_id", "project", "owner", "backend", "state", "created_at", "ttl_minutes", "members", "hermes_home", "workspace_path", "shared_notes_path", "shared_artifacts_path", "decisions_path", "archived_at", "destroyed_at", ] d = dict(zip(keys, row)) d["members"] = json.loads(d["members"]) return Cell.from_dict(d) def list_active(self) -> List[Cell]: import sqlite3 with sqlite3.connect(str(self.db_path)) as conn: rows = conn.execute( "SELECT * FROM cells WHERE state NOT IN ('destroyed', 'archived')" ).fetchall() keys = [ "cell_id", "project", "owner", "backend", "state", "created_at", "ttl_minutes", "members", "hermes_home", "workspace_path", "shared_notes_path", "shared_artifacts_path", "decisions_path", "archived_at", "destroyed_at", ] cells = [] for row in rows: d = dict(zip(keys, row)) d["members"] = json.loads(d["members"]) cells.append(Cell.from_dict(d)) return cells def delete(self, cell_id: str): import sqlite3 with sqlite3.connect(str(self.db_path)) as conn: conn.execute("DELETE FROM cells WHERE cell_id = ?", (cell_id,)) class CellPackager: """Creates and destroys per-cell filesystem isolation.""" ROOT = Path("/tmp/lazarus-cells") def __init__(self, root: Optional[str] = None): self.root = Path(root or self.ROOT) def create(self, cell_id: str, backend: str = "process") -> Cell: cell_home = self.root / cell_id workspace = cell_home / "workspace" shared_notes = cell_home / "shared" / "notes.md" shared_artifacts = cell_home / "shared" / "artifacts" decisions = cell_home / "shared" / "decisions.jsonl" hermes_home = cell_home / ".hermes" for p in [workspace, shared_artifacts, hermes_home]: p.mkdir(parents=True, exist_ok=True) if not shared_notes.exists(): shared_notes.write_text(f"# Cell {cell_id} — Shared Notes\n\n") if not decisions.exists(): decisions.write_text("") now = datetime.now().astimezone().isoformat() return Cell( cell_id=cell_id, project="", owner="", backend=backend, state=CellState.PROPOSED, created_at=now, ttl_minutes=60, hermes_home=str(hermes_home), workspace_path=str(workspace), shared_notes_path=str(shared_notes), shared_artifacts_path=str(shared_artifacts), decisions_path=str(decisions), ) def destroy(self, cell_id: str) -> bool: cell_home = self.root / cell_id if cell_home.exists(): shutil.rmtree(cell_home) return True return False