Spaces:
Running
Running
| """ayllu.daemon — always-on-but-cheap listener, learned from the tribe soul-daemon. | |
| The tribe's insight: a persona can be "always on & listening" without burning tokens, | |
| because an idle tick does only a free file/inbox check and a model is invoked ONLY when | |
| a real message arrives. a11oy keeps that insight and adds a hard bound: at most | |
| `max_wakes` model-invoking wakes per run. The daemon itself never calls a model — it | |
| tells the caller WHEN to (via ayllu.loop.run_turn), keeping cost and control explicit. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| class Daemon: | |
| def __init__(self, persona, *, max_wakes: int = 8) -> None: | |
| self.persona = persona | |
| self.max_wakes = int(max_wakes) | |
| self.wakes = 0 | |
| self.idle_ticks = 0 | |
| def tick(self, inbox: list) -> dict[str, Any]: | |
| """One cheap tick. `inbox` is the already-read list of pending messages. | |
| Returns whether the persona should wake (and thus incur a model call). An | |
| empty inbox is FREE — no model, no cost. | |
| """ | |
| if not inbox: | |
| self.idle_ticks += 1 | |
| return {"woke": False, "cost": "free", "idle_ticks": self.idle_ticks} | |
| if self.wakes >= self.max_wakes: | |
| return {"woke": False, "reason": "max_wakes reached (bounded)", | |
| "pending": len(inbox)} | |
| self.wakes += 1 | |
| return {"woke": True, "message": inbox[0], "wakes": self.wakes, | |
| "pending_after": len(inbox) - 1} | |
| def stats(self) -> dict[str, Any]: | |
| return {"persona": getattr(self.persona, "name", None), | |
| "wakes": self.wakes, "idle_ticks": self.idle_ticks, | |
| "max_wakes": self.max_wakes} | |