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.
70 lines
2.2 KiB
Python
70 lines
2.2 KiB
Python
"""Episode daily-log writer — md is the SoT for Episode memories.
|
|
|
|
Stays in the chassis style: caller hands in pre-built ``inline`` and
|
|
``sections`` dicts plus the scope id (``owner_id``). Domain →
|
|
structured-entry shaping lives in the calling pipeline (cf. architecture
|
|
rule: ``infra`` may not import ``memory``).
|
|
|
|
This milestone assumes well-behaved callers (no retransmit dedupe needed).
|
|
The writer just appends; the chassis manages the in-file ``entry_id``
|
|
sequence, which is the single source of identity for an md entry.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime as _dt
|
|
from collections.abc import Mapping
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import anyio
|
|
|
|
from everos.component.utils.datetime import (
|
|
get_now_with_timezone,
|
|
to_iso_format,
|
|
)
|
|
from everos.core.persistence import MarkdownReader
|
|
|
|
from ..mds import EpisodeDailyFrontmatter
|
|
from .base import BaseDailyWriter
|
|
|
|
|
|
class EpisodeWriter(BaseDailyWriter):
|
|
"""Daily-log writer for the Episode schema (md = SoT).
|
|
|
|
``append_entry`` / ``append_entries`` come from
|
|
:class:`BaseDailyWriter`; the ``entry_id`` (``ep_<YYYYMMDD>_<NNNN>``)
|
|
is the in-file identity allocated under the per-path lock. Callers
|
|
can derive a globally-unique id from ``(owner_id, entry_id)``
|
|
without persisting any algo-side uuid.
|
|
"""
|
|
|
|
schema = EpisodeDailyFrontmatter
|
|
|
|
# ── Frontmatter override (entry_count + last_appended_at) ────────────
|
|
|
|
def _frontmatter_updates(
|
|
self,
|
|
scope_id: str,
|
|
date: _dt.date,
|
|
*,
|
|
next_count: int,
|
|
) -> Mapping[str, Any] | None:
|
|
return {
|
|
"id": f"episode_log_{scope_id}_{date.isoformat()}",
|
|
"type": "episode_daily",
|
|
"file_type": "episode_daily",
|
|
"schema_version": 1,
|
|
"user_id": scope_id,
|
|
"track": "user",
|
|
"date": date.isoformat(),
|
|
"entry_count": next_count,
|
|
"last_appended_at": to_iso_format(get_now_with_timezone()),
|
|
}
|
|
|
|
async def _current_count(self, path: Path) -> int:
|
|
if not await anyio.Path(path).is_file():
|
|
return 0
|
|
parsed = await MarkdownReader.read(path)
|
|
return parsed.frontmatter.get("entry_count", 0)
|