Files
EverOS/src/everos/infra/ome/_stores/idle.py
Elliot Chen 518b8eca85 chore: initialize EverOS 1.0.0
md-first memory extraction framework for AI agents.

Markdown is the single source of truth; SQLite holds state and LanceDB
provides the rebuildable vector + BM25 + scalar index. The codebase follows
a single-direction DDD layering (entrypoints -> service -> memory -> infra,
with component / core / config cross-cutting) enforced by import-linter.

Engineering surface:
- Coding conventions in .claude/rules/ (path-scoped) and workflows in
  .claude/skills/ (/commit, /new-branch, /pr).
- GitHub Actions CI runs make lint + test + integration; pre-commit mirrors
  the gates locally (ruff, hygiene hooks, gitlint commit-msg).
- Commit messages follow Conventional Commits, enforced by gitlint.
- make lint also enforces datetime two-zone discipline and OpenAPI drift.
2026-06-06 07:33:17 +08:00

65 lines
2.6 KiB
Python

"""IdleStore — last_activity_ts rows backing the Idle trigger.
All writes pass through ``to_iso_format`` over a tz-aware datetime, so
``last_activity_ts`` is a fixed-format ISO 8601 string whose
lexicographic order matches temporal order — :meth:`scan_idle` relies
on this to keep the column un-wrapped in its predicate so SQLite can
use ``idx_idle_scan``.
"""
from __future__ import annotations
from datetime import datetime, timedelta
from everos.component.utils.datetime import from_iso_format, to_iso_format
from everos.infra.ome._stores.storage import OMEStorage
class IdleStore:
"""SQLite-backed last-activity tracker for the ``Idle`` trigger."""
def __init__(self, storage: OMEStorage) -> None:
self._storage = storage
async def touch(self, strategy_name: str, bucket_key: str, *, at: datetime) -> None:
"""UPSERT ``last_activity_ts = at`` for ``(strategy_name, bucket_key)``."""
async with self._storage.connect() as conn:
await conn.execute(
"INSERT INTO idle_store "
"(strategy_name, bucket_key, last_activity_ts) "
"VALUES (?, ?, ?) "
"ON CONFLICT(strategy_name, bucket_key) DO UPDATE SET "
"last_activity_ts = excluded.last_activity_ts",
(strategy_name, bucket_key, to_iso_format(at)),
)
await conn.commit()
async def scan_idle(
self, strategy_name: str, *, idle_seconds: int, now: datetime
) -> list[str]:
"""Return bucket_keys with ``last_activity_ts`` older than ``idle_seconds``."""
# Cutoff on the RHS so the indexed column stays un-wrapped.
cutoff = to_iso_format(now - timedelta(seconds=idle_seconds))
async with self._storage.connect() as conn:
cur = await conn.execute(
"SELECT bucket_key FROM idle_store "
"WHERE strategy_name = ? AND last_activity_ts <= ? "
"ORDER BY last_activity_ts ASC",
(strategy_name, cutoff),
)
rows = await cur.fetchall()
return [r[0] for r in rows]
async def get_last_activity(
self, strategy_name: str, bucket_key: str
) -> datetime | None:
"""Return the stored ``last_activity_ts`` (``None`` if never touched)."""
async with self._storage.connect() as conn:
cur = await conn.execute(
"SELECT last_activity_ts FROM idle_store "
"WHERE strategy_name = ? AND bucket_key = ?",
(strategy_name, bucket_key),
)
row = await cur.fetchone()
return from_iso_format(row[0]) if row else None