| """
|
| Paradigm 3: Filesystem Inode State (Zero-Byte File Gates)
|
| Part of SOVEREIGN PYTHON LLM ENGINE
|
|
|
| Boolean state flags stored as directory entries.
|
| File existence = 1, absence = 0.
|
| No file content ever written or read β pure stat() calls.
|
|
|
| Structure:
|
| {base_dir}/
|
| {agent_id}/
|
| flags/
|
| IDLE β exists = agent is idle
|
| THINKING β exists = agent is thinking
|
| ACTING β exists = agent is acting
|
| OBSERVING β exists = agent is observing
|
| REFLECTING β exists = agent is reflecting
|
| DONE β exists = agent is done
|
| ERROR β exists = agent errored
|
| step/
|
| 0000000000000005 β filename = current step number (zero-padded)
|
| locks/
|
| tool:filesystem.read β exists = this tool is checked out
|
|
|
| Why this is fast:
|
| - os.stat() is a single kernel syscall
|
| - Directory entries live in kernel dcache (hot in memory)
|
| - No open(), read(), write(), close() β just stat() + mkdir() + unlink()
|
| - Concurrent readers are safe (no file locking needed for boolean flags)
|
| - Corruption-proof: filesystem guarantees atomic rename/unlink
|
|
|
| Use cases:
|
| - Agent state machine flags (7 states)
|
| - Tool checkout tracking (which tools are in use)
|
| - Mutex locks (file existence = lock held)
|
| - Rate limit gates (file mtime = last call timestamp)
|
| """
|
|
|
| from __future__ import annotations
|
|
|
| import os
|
| import time
|
| from pathlib import Path
|
| from typing import Iterator
|
|
|
|
|
|
|
|
|
|
|
|
|
| class InodeGate:
|
| """
|
| Single boolean state gate backed by a zero-byte file.
|
|
|
| set() β touch file (create if not exists)
|
| clear() β unlink file (delete if exists)
|
| get() β stat file (True if exists)
|
| mtime() β file modification time (useful for rate limiting)
|
| """
|
|
|
| def __init__(self, path: Path):
|
| self.path = path
|
| self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
| def set(self) -> None:
|
| self.path.touch(exist_ok=True)
|
|
|
| def clear(self) -> None:
|
| try:
|
| self.path.unlink()
|
| except FileNotFoundError:
|
| pass
|
|
|
| def get(self) -> bool:
|
| return self.path.exists()
|
|
|
| def toggle(self) -> bool:
|
| if self.get():
|
| self.clear()
|
| return False
|
| else:
|
| self.set()
|
| return True
|
|
|
| def mtime(self) -> float | None:
|
| try:
|
| return self.path.stat().st_mtime
|
| except FileNotFoundError:
|
| return None
|
|
|
| def age_seconds(self) -> float | None:
|
| mt = self.mtime()
|
| return (time.time() - mt) if mt is not None else None
|
|
|
| def __bool__(self) -> bool:
|
| return self.get()
|
|
|
| def __repr__(self) -> str:
|
| return f"InodeGate({self.path.name}={'1' if self.get() else '0'})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| AGENT_FLAGS = [
|
| "IDLE", "THINKING", "ACTING", "OBSERVING",
|
| "REFLECTING", "DONE", "ERROR"
|
| ]
|
|
|
|
|
| class InodeStateMachine:
|
| """
|
| Agent state machine using zero-byte files as boolean flags.
|
|
|
| State transitions are atomic at the filesystem level:
|
| transition(remove={"THINKING"}, add={"ACTING"}) uses
|
| a rename-based swap to avoid intermediate invalid states.
|
|
|
| Directory structure:
|
| {base}/{agent_id}/flags/IDLE
|
| {base}/{agent_id}/flags/THINKING
|
| ...
|
| {base}/{agent_id}/step/{zero_padded_step}
|
| {base}/{agent_id}/locks/{lock_name}
|
|
|
| Usage:
|
| ism = InodeStateMachine(base_dir, agent_id="react_1")
|
| ism.transition(remove={"IDLE"}, add={"THINKING"})
|
| ism.set_step(1)
|
| print(ism.active_flags()) # ["THINKING"]
|
| """
|
|
|
| def __init__(self, base_dir: Path, agent_id: str):
|
| self.agent_id = agent_id
|
| self._root = base_dir / agent_id
|
| self._flags = self._root / "flags"
|
| self._step_dir = self._root / "step"
|
| self._locks = self._root / "locks"
|
|
|
|
|
| for d in (self._flags, self._step_dir, self._locks):
|
| d.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
| if not any(True for _ in self._flags.iterdir()):
|
| (self._flags / "IDLE").touch()
|
|
|
|
|
|
|
| def set_flag(self, flag: str) -> None:
|
| (self._flags / flag.upper()).touch(exist_ok=True)
|
|
|
| def clear_flag(self, flag: str) -> None:
|
| try:
|
| (self._flags / flag.upper()).unlink()
|
| except FileNotFoundError:
|
| pass
|
|
|
| def has_flag(self, flag: str) -> bool:
|
| return (self._flags / flag.upper()).exists()
|
|
|
| def active_flags(self) -> list[str]:
|
| return sorted(p.name for p in self._flags.iterdir() if p.is_file())
|
|
|
| def transition(self, remove: set[str], add: set[str]) -> None:
|
| """
|
| Atomic-ish transition: clear old flags, set new flags.
|
| Uses individual unlink/touch β not a true atomic swap,
|
| but fast enough for single-process agents.
|
| For true atomicity, use rename-based swap (future extension).
|
| """
|
| for flag in remove:
|
| self.clear_flag(flag)
|
| for flag in add:
|
| self.set_flag(flag)
|
|
|
| def clear_all(self) -> None:
|
| for p in self._flags.iterdir():
|
| if p.is_file():
|
| p.unlink()
|
|
|
|
|
|
|
| def set_step(self, step: int) -> None:
|
| """Store current step as filename (zero-padded 16 digits)."""
|
|
|
| for p in self._step_dir.iterdir():
|
| if p.is_file():
|
| p.unlink()
|
|
|
| (self._step_dir / f"{step:016d}").touch()
|
|
|
| def get_step(self) -> int:
|
| """Read current step from directory entry."""
|
| files = sorted(p.name for p in self._step_dir.iterdir() if p.is_file())
|
| if not files:
|
| return 0
|
| return int(files[-1])
|
|
|
|
|
|
|
| def acquire_lock(self, lock_name: str) -> bool:
|
| """
|
| Try to acquire a named lock.
|
| Returns True if acquired, False if already held.
|
| Uses exclusive creation (O_CREAT | O_EXCL at filesystem level).
|
| """
|
| lock_path = self._locks / lock_name.replace('/', '_').replace(':', '-')
|
| try:
|
| fd = os.open(str(lock_path), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
| os.close(fd)
|
| return True
|
| except FileExistsError:
|
| return False
|
|
|
| def release_lock(self, lock_name: str) -> None:
|
| lock_path = self._locks / lock_name.replace('/', '_').replace(':', '-')
|
| try:
|
| lock_path.unlink()
|
| except FileNotFoundError:
|
| pass
|
|
|
| def held_locks(self) -> list[str]:
|
| return [p.name for p in self._locks.iterdir() if p.is_file()]
|
|
|
|
|
|
|
| def rate_gate(self, gate_name: str, min_interval_s: float) -> bool:
|
| """
|
| Rate limit gate using file mtime.
|
| Returns True if allowed (enough time elapsed), False if rate-limited.
|
| Touching the gate file resets the timer.
|
| """
|
| gate = InodeGate(self._locks / f"rate_{gate_name}")
|
| age = gate.age_seconds()
|
|
|
| if age is None or age >= min_interval_s:
|
| gate.set()
|
| return True
|
|
|
| return False
|
|
|
|
|
|
|
| def snapshot(self) -> dict:
|
| return {
|
| "agent_id": self.agent_id,
|
| "active_flags": self.active_flags(),
|
| "step": self.get_step(),
|
| "held_locks": self.held_locks(),
|
| }
|
|
|
| def destroy(self) -> None:
|
| """Remove all inode state for this agent."""
|
| import shutil
|
| shutil.rmtree(self._root, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| class InodeRegistry:
|
| """
|
| Registry of all active agents tracked via inode state.
|
| Scan base_dir to discover all agents without any central index.
|
| The directory listing IS the index.
|
| """
|
|
|
| def __init__(self, base_dir: Path):
|
| self.base_dir = base_dir
|
| base_dir.mkdir(parents=True, exist_ok=True)
|
|
|
| def get_agent(self, agent_id: str) -> InodeStateMachine:
|
| return InodeStateMachine(self.base_dir, agent_id)
|
|
|
| def list_agents(self) -> list[str]:
|
| return [
|
| d.name for d in self.base_dir.iterdir()
|
| if d.is_dir() and (d / "flags").exists()
|
| ]
|
|
|
| def all_snapshots(self) -> list[dict]:
|
| snapshots = []
|
| for agent_id in self.list_agents():
|
| ism = self.get_agent(agent_id)
|
| snapshots.append(ism.snapshot())
|
| return snapshots
|
|
|
| def active_agents(self) -> list[str]:
|
| """Agents that are not IDLE and not DONE."""
|
| result = []
|
| for agent_id in self.list_agents():
|
| ism = self.get_agent(agent_id)
|
| flags = set(ism.active_flags())
|
| if flags and not flags.issubset({"IDLE", "DONE"}):
|
| result.append(agent_id)
|
| return result
|
|
|