Introduce Devii agents subsystem with supervisor, policy, and evals

- Add agents package: controller, runner, store, tools, actions, evals, policy
- Wire agents into Devii service, session, registry, CLI, and dispatcher
- Extend database schema and config for agent persistence
- Add news and telegram bridge updates plus landing UI and docs
- Cover agents, news, and telegram flows with unit tests
This commit is contained in:
2026-10-26 02:54:44 +01:00
parent 407673bf57
commit 3bfde87fdb
50 changed files with 4102 additions and 78 deletions
+190
View File
@@ -0,0 +1,190 @@
# retoor <retoor@molodetz.nl>
import multiprocessing
import tempfile
import dataset
from devplacepy.services.devii.agents.store import (
AgentStore,
claim,
due_rows,
prune_terminal,
snapshot_for,
)
from devplacepy.services.devii.tasks.store import memory_db
from devplacepy.services.devii.tasks.schedule import now_utc, to_iso
from devplacepy.utils import generate_uid
def _record(**overrides):
now = to_iso(now_utc())
record = {
"uid": generate_uid(),
"channel": "main",
"category": "research",
"label": None,
"task": "work",
"output_format": "text",
"tools_extra": "[]",
"effort": "standard",
"task_scope": 0,
"status": "queued",
"progress": "queued",
"result": None,
"error": None,
"checks": 0,
"nudges_used": 0,
"retries_used": 0,
"error_streak": 0,
"iterations": 0,
"tokens": 0,
"cost_usd": 0.0,
"needs_question": None,
"pending_ask_id": None,
"check_at": now,
"last_progress_at": now,
"created_at": now,
"updated_at": now,
}
record.update(overrides)
return record
def _store(db=None):
return AgentStore(memory_db() if db is None else db, "user", "owner-1")
def test_create_get_list_update_scope():
store = _store()
first = _record()
store.create(first)
assert store.get(first["uid"])["task"] == "work"
assert store.get("missing") is None
other = AgentStore(store.db, "user", "owner-2")
assert other.get(first["uid"]) is None
store.create(_record(status="running"))
assert len(store.list()) == 2
assert len(store.list(status="running")) == 1
store.update(first["uid"], {"status": "cancelled"})
assert store.get(first["uid"])["status"] == "cancelled"
def test_count_active_ignores_terminal():
store = _store()
for status in ("queued", "running", "needs_user", "done", "cancelled"):
store.create(_record(status=status))
assert store.count_active() == 3
def test_due_rows_and_claim():
db = memory_db()
store = AgentStore(db, "user", "owner-1")
now = to_iso(now_utc())
store.create(_record(status="queued", check_at=now))
store.create(_record(status="done", check_at=now))
rows = due_rows(db, now, 10)
assert [row["status"] for row in rows] == ["queued"]
assert claim(db, rows[0]["uid"], now) is True
assert claim(db, rows[0]["uid"], now) is False
assert store.get(rows[0]["uid"])["status"] == "checking"
def test_complete_only_wins_from_live_status():
store = _store()
record = _record(status="running")
store.create(record)
won = store.complete(
record["uid"],
{
"status": "done",
"result": "ok",
"error": None,
"iterations": 3,
"tokens": 100,
"cost_usd": 0.01,
"updated_at": to_iso(now_utc()),
},
)
assert won is True
assert store.get(record["uid"])["result"] == "ok"
lost = store.complete(
record["uid"],
{
"status": "done",
"result": "late",
"error": None,
"iterations": 4,
"tokens": 200,
"cost_usd": 0.02,
"updated_at": to_iso(now_utc()),
},
)
assert lost is False
assert store.get(record["uid"])["result"] == "ok"
def test_prune_terminal_keeps_live_rows():
db = memory_db()
store = AgentStore(db, "user", "owner-1")
store.create(_record(status="done", updated_at="2020-01-01T00:00:00"))
store.create(_record(status="running", updated_at="2020-01-01T00:00:00"))
assert prune_terminal(db, "2021-01-01T00:00:00") == 1
assert len(store.list()) == 1
def test_snapshot_for_measures_age_and_idle():
row = _record(created_at="2026-01-01T00:00:00", last_progress_at="2026-01-01T00:10:00")
from datetime import datetime, timezone
snap = snapshot_for(row, datetime(2026, 1, 1, 0, 20, tzinfo=timezone.utc))
assert snap["age_seconds"] == 1200
assert snap["idle_seconds"] == 600
assert snap["answered"] is False
def _claim_worker(path, uid, queue):
import sqlalchemy
db = dataset.connect(
f"sqlite:///{path}", engine_kwargs={"connect_args": {"timeout": 30}}
)
try:
for _ in range(50):
try:
queue.put(claim(db, uid, to_iso(now_utc())))
return
except sqlalchemy.exc.OperationalError as exc:
if "locked" not in str(exc).lower():
raise
queue.put(False)
finally:
queue.put(None)
def test_claim_race_exactly_one_winner():
with tempfile.NamedTemporaryFile(suffix=".db") as handle:
db = dataset.connect(
f"sqlite:///{handle.name}",
engine_kwargs={"connect_args": {"timeout": 30}},
)
store = AgentStore(db, "user", "owner-1")
record = _record()
store.create(record)
uid = record["uid"]
ctx = multiprocessing.get_context("fork")
queue = ctx.Queue()
workers = [
ctx.Process(target=_claim_worker, args=(handle.name, uid, queue))
for _ in range(8)
]
for worker in workers:
worker.start()
for worker in workers:
worker.join(60)
assert worker.exitcode == 0
results = []
while len(results) < 16:
results.append(queue.get(timeout=30))
wins = [item for item in results if item is True]
assert len(wins) == 1