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.
This commit is contained in:
Elliot Chen
2026-06-05 22:35:51 +08:00
commit 518b8eca85
636 changed files with 160553 additions and 0 deletions

View File

@ -0,0 +1,147 @@
"""Tests for :class:`AgentSkillWriter` — directory + progressive disclosure."""
from __future__ import annotations
from pathlib import Path
import pytest
from everos.core.persistence import MarkdownReader, MemoryRoot
from everos.infra.persistence.markdown import (
AgentSkillFrontmatter,
AgentSkillWriter,
)
def _make_fm(**overrides: object) -> AgentSkillFrontmatter:
"""Build an AgentSkillFrontmatter with sensible defaults for tests."""
base: dict[str, object] = {
"id": "agent_x_skill_alpha",
"agent_id": "agent_x",
"name": "alpha",
"description": "A test skill.",
"confidence": 0.5,
"maturity_score": 0.5,
}
base.update(overrides)
return AgentSkillFrontmatter(**base) # type: ignore[arg-type]
@pytest.fixture
def root(tmp_path: Path) -> MemoryRoot:
return MemoryRoot(tmp_path)
@pytest.fixture
def writer(root: MemoryRoot) -> AgentSkillWriter:
return AgentSkillWriter(root)
async def test_write_main_creates_directory_layout(
root: MemoryRoot, writer: AgentSkillWriter
) -> None:
fm = _make_fm()
path = await writer.write_main(
"agent_x", "alpha", frontmatter=fm, body="Step 1: do thing."
)
expected = root.agents_dir() / "agent_x" / "skills" / "skill_alpha" / "SKILL.md"
assert path == expected
assert expected.is_file()
async def test_write_main_writes_frontmatter_and_body(
root: MemoryRoot, writer: AgentSkillWriter
) -> None:
fm = _make_fm(
description="Contract risk scan.",
confidence=0.88,
maturity_score=0.82,
source_case_ids=["case_a", "case_b"],
cluster_id="cl_x",
)
await writer.write_main("agent_x", "alpha", frontmatter=fm, body="The body.")
parsed = await MarkdownReader.read(
root.agents_dir() / "agent_x" / "skills" / "skill_alpha" / "SKILL.md"
)
assert parsed.frontmatter["name"] == "alpha"
assert parsed.frontmatter["description"] == "Contract risk scan."
assert parsed.frontmatter["confidence"] == 0.88
assert parsed.frontmatter["maturity_score"] == 0.82
assert parsed.frontmatter["source_case_ids"] == ["case_a", "case_b"]
assert parsed.frontmatter["cluster_id"] == "cl_x"
assert parsed.body.rstrip("\n") == "The body."
async def test_write_main_is_upsert_full_replace(
root: MemoryRoot, writer: AgentSkillWriter
) -> None:
"""Second call overwrites both frontmatter and body — no append."""
fm1 = _make_fm(description="v1", maturity_score=0.4)
await writer.write_main("agent_x", "alpha", frontmatter=fm1, body="body v1")
fm2 = _make_fm(description="v2", maturity_score=0.7)
await writer.write_main("agent_x", "alpha", frontmatter=fm2, body="body v2")
parsed = await MarkdownReader.read(
root.agents_dir() / "agent_x" / "skills" / "skill_alpha" / "SKILL.md"
)
assert parsed.frontmatter["description"] == "v2"
assert parsed.frontmatter["maturity_score"] == 0.7
assert parsed.body.rstrip("\n") == "body v2"
# No "body v1" residue from the previous version.
assert "body v1" not in parsed.body
async def test_write_reference_uses_md_extension(
root: MemoryRoot, writer: AgentSkillWriter
) -> None:
path = await writer.write_reference(
"agent_x", "alpha", "termination_clauses", "## Termination\n..."
)
expected = (
root.agents_dir()
/ "agent_x"
/ "skills"
/ "skill_alpha"
/ "references"
/ "termination_clauses.md"
)
assert path == expected
assert path.read_text(encoding="utf-8").startswith("## Termination")
async def test_write_script_keeps_full_filename(
root: MemoryRoot, writer: AgentSkillWriter
) -> None:
path = await writer.write_script("agent_x", "alpha", "redline.py", "print('hi')\n")
expected = (
root.agents_dir()
/ "agent_x"
/ "skills"
/ "skill_alpha"
/ "scripts"
/ "redline.py"
)
assert path == expected
assert path.read_text(encoding="utf-8") == "print('hi')\n"
def test_main_path_does_not_create_anything(
root: MemoryRoot, writer: AgentSkillWriter
) -> None:
"""``main_path`` is a pure path resolver — no IO."""
p = writer.main_path("agent_x", "alpha")
assert p.name == "SKILL.md"
assert not root.agents_dir().exists()
async def test_write_main_normalises_trailing_newline(
root: MemoryRoot, writer: AgentSkillWriter
) -> None:
"""Body without a trailing newline still ends in exactly one newline."""
fm = _make_fm()
await writer.write_main("agent_x", "alpha", frontmatter=fm, body="no-newline-end")
text = (
root.agents_dir() / "agent_x" / "skills" / "skill_alpha" / "SKILL.md"
).read_text(encoding="utf-8")
assert text.endswith("no-newline-end\n")

View File

@ -0,0 +1,182 @@
"""Tests for ``BaseDailyWriter`` skeleton.
Uses a dummy ``UserScopedFrontmatter`` subclass to exercise the path
resolution + entry-id construction + today-by-default logic without
pulling in any concrete business schema.
"""
from __future__ import annotations
import datetime as dt
from pathlib import Path
from typing import ClassVar, Literal
import pytest
from everos.component.utils.datetime import today_with_timezone
from everos.core.persistence import (
AgentScopedFrontmatter,
MarkdownReader,
MemoryRoot,
UserScopedFrontmatter,
)
from everos.infra.persistence.markdown.writers import BaseDailyWriter
class _UserDemoFrontmatter(UserScopedFrontmatter):
ENTRY_ID_PREFIX: ClassVar[str] = "demo"
DIR_NAME: ClassVar[str] = "demos"
FILE_PREFIX: ClassVar[str] = "demo"
type: Literal["user_demo"] = "user_demo"
class _AgentDemoFrontmatter(AgentScopedFrontmatter):
ENTRY_ID_PREFIX: ClassVar[str] = "ademo"
DIR_NAME: ClassVar[str] = "demos"
FILE_PREFIX: ClassVar[str] = "demo"
type: Literal["agent_demo"] = "agent_demo"
class _UserDemoWriter(BaseDailyWriter):
schema = _UserDemoFrontmatter
class _AgentDemoWriter(BaseDailyWriter):
schema = _AgentDemoFrontmatter
@pytest.fixture
def root(tmp_path: Path) -> MemoryRoot:
return MemoryRoot(tmp_path)
def test_constructor_rejects_missing_schema(root: MemoryRoot) -> None:
class _NoSchema(BaseDailyWriter):
pass
with pytest.raises(TypeError, match="schema"):
_NoSchema(root)
def test_constructor_rejects_schema_missing_classvars(root: MemoryRoot) -> None:
class _IncompleteFrontmatter(UserScopedFrontmatter):
# Missing ENTRY_ID_PREFIX / DIR_NAME / FILE_PREFIX.
type: Literal["incomplete"] = "incomplete"
class _IncompleteWriter(BaseDailyWriter):
schema = _IncompleteFrontmatter
with pytest.raises(TypeError, match="ENTRY_ID_PREFIX"):
_IncompleteWriter(root)
async def test_append_writes_to_user_track(root: MemoryRoot) -> None:
writer = _UserDemoWriter(root)
eid = await writer.append("u_jason", "first", date=dt.date(2026, 4, 22))
assert eid.prefix == "demo"
assert eid.date == dt.date(2026, 4, 22)
assert eid.seq == 1
expected = root.users_dir() / "u_jason" / "demos" / "demo-2026-04-22.md"
assert expected.exists()
parsed = await MarkdownReader.read(expected)
assert parsed.entries[0].id == "demo_20260422_00000001"
assert parsed.entries[0].body == "first"
async def test_append_writes_to_agent_track(root: MemoryRoot) -> None:
writer = _AgentDemoWriter(root)
eid = await writer.append("agent_zhangsan", "trace", date=dt.date(2026, 4, 22))
assert eid.prefix == "ademo"
expected = root.agents_dir() / "agent_zhangsan" / "demos" / "demo-2026-04-22.md"
assert expected.exists()
async def test_append_increments_seq_across_calls(root: MemoryRoot) -> None:
writer = _UserDemoWriter(root)
eids = [
await writer.append("u_jason", f"body {i}", date=dt.date(2026, 4, 22))
for i in range(3)
]
assert [e.seq for e in eids] == [1, 2, 3]
async def test_append_date_defaults_to_today(root: MemoryRoot) -> None:
"""Omitting ``date`` falls back to today_with_timezone()."""
writer = _UserDemoWriter(root)
eid = await writer.append("u_jason", "body")
today = today_with_timezone()
assert eid.date == today
expected = root.users_dir() / "u_jason" / "demos" / f"demo-{today.isoformat()}.md"
assert expected.exists()
async def test_append_passes_frontmatter_updates(root: MemoryRoot) -> None:
writer = _UserDemoWriter(root)
await writer.append(
"u_jason",
"body",
date=dt.date(2026, 4, 22),
frontmatter_updates={"file_type": "user_demo_daily", "entry_count": 1},
)
path = root.users_dir() / "u_jason" / "demos" / "demo-2026-04-22.md"
parsed = await MarkdownReader.read(path)
assert parsed.frontmatter["file_type"] == "user_demo_daily"
assert parsed.frontmatter["entry_count"] == 1
async def test_current_count_hook_can_be_overridden(root: MemoryRoot) -> None:
"""Subclass override of ``_current_count`` controls seq."""
class _ConstantCount(BaseDailyWriter):
schema = _UserDemoFrontmatter
async def _current_count(self, path): # noqa: ANN001
return 41 # always claim 41 existing entries
writer = _ConstantCount(root)
eid = await writer.append("u_jason", "body", date=dt.date(2026, 4, 22))
assert eid.seq == 42 # 41 + 1
async def test_frontmatter_updates_hook_supplies_defaults(root: MemoryRoot) -> None:
"""Subclass override of ``_frontmatter_updates`` populates frontmatter."""
class _WithDefaults(BaseDailyWriter):
schema = _UserDemoFrontmatter
def _frontmatter_updates(self, scope_id, date, *, next_count): # noqa: ANN001
return {
"user_id": scope_id,
"entry_count": next_count,
"marker": "from-hook",
}
writer = _WithDefaults(root)
await writer.append("u_jason", "body", date=dt.date(2026, 4, 22))
path = root.users_dir() / "u_jason" / "demos" / "demo-2026-04-22.md"
parsed = await MarkdownReader.read(path)
assert parsed.frontmatter["marker"] == "from-hook"
assert parsed.frontmatter["entry_count"] == 1
assert parsed.frontmatter["user_id"] == "u_jason"
async def test_explicit_frontmatter_updates_skip_hook(root: MemoryRoot) -> None:
"""Caller-supplied ``frontmatter_updates`` overrides the hook entirely."""
class _WithDefaults(BaseDailyWriter):
schema = _UserDemoFrontmatter
def _frontmatter_updates(self, scope_id, date, *, next_count): # noqa: ANN001
return {"marker": "from-hook"}
writer = _WithDefaults(root)
await writer.append(
"u_jason",
"body",
date=dt.date(2026, 4, 22),
frontmatter_updates={"marker": "explicit"},
)
path = root.users_dir() / "u_jason" / "demos" / "demo-2026-04-22.md"
parsed = await MarkdownReader.read(path)
assert parsed.frontmatter["marker"] == "explicit"

View File

@ -0,0 +1,344 @@
"""Tests for AtomicFact / Foresight / AgentCase daily-log writers.
The 4 daily-log kinds (episode + these 3) all share ``BaseDailyWriter``
plumbing — exhaustive chassis tests live in ``test_base.py`` and
``test_episode_writer.py`` indirectly via the e2e flows. Here we focus
on the per-kind path resolution + frontmatter shape that each
subclass owns: ``schema``, ``_frontmatter_updates``, and the
writer ↔ reader round-trip on a fresh tmp memory_root.
"""
from __future__ import annotations
import datetime as _dt
from pathlib import Path
import pytest
from everos.core.persistence import MarkdownReader, MemoryRoot
from everos.infra.persistence.markdown import (
AgentCaseReader,
AgentCaseWriter,
AtomicFactReader,
AtomicFactWriter,
ForesightReader,
ForesightWriter,
)
@pytest.fixture
def memory_root(tmp_path: Path) -> MemoryRoot:
mr = MemoryRoot(tmp_path)
mr.ensure()
return mr
# ── AtomicFact ────────────────────────────────────────────────────────────
async def test_atomic_fact_writer_round_trip(memory_root: MemoryRoot) -> None:
writer = AtomicFactWriter(memory_root)
today = _dt.date(2026, 5, 15)
eid = await writer.append_entry(
"u1",
inline={
"owner_id": "u1",
"session_id": "s1",
"timestamp": "2026-05-15T10:00:00+00:00",
"parent_id": "mc_1",
"sender_ids": ["u1"],
},
sections={"Fact": "Alice prefers Italian."},
date=today,
)
path = (
memory_root.users_dir() / "u1" / ".atomic_facts" / "atomic_fact-2026-05-15.md"
)
parsed = await MarkdownReader.read(path)
# frontmatter
fm = parsed.frontmatter
assert fm["id"] == "atomic_fact_log_u1_2026-05-15"
assert fm["type"] == "atomic_fact_daily"
assert fm["file_type"] == "atomic_fact_daily"
assert fm["user_id"] == "u1"
assert fm["track"] == "user"
assert fm["date"] == "2026-05-15"
assert fm["entry_count"] == 1
# entry body
assert len(parsed.entries) == 1
entry = parsed.entries[0]
assert entry.id == eid.format()
structured = entry.as_structured()
assert structured.inline["owner_id"] == "u1"
assert structured.inline["parent_id"] == "mc_1"
assert structured.sections["Fact"] == "Alice prefers Italian."
# reader is symmetric
reader = AtomicFactReader(memory_root)
assert reader.path_for("u1", today) == path
found = await reader.find_structured("u1", eid)
assert found is not None
assert found.sections["Fact"] == "Alice prefers Italian."
async def test_atomic_fact_writer_appends_multiple(memory_root: MemoryRoot) -> None:
writer = AtomicFactWriter(memory_root)
today = _dt.date(2026, 5, 15)
eid1 = await writer.append_entry(
"u1",
inline={
"owner_id": "u1",
"session_id": "s1",
"timestamp": "2026-05-15T10:00:00+00:00",
"parent_id": "mc_1",
},
sections={"Fact": "fact 1"},
date=today,
)
eid2 = await writer.append_entry(
"u1",
inline={
"owner_id": "u1",
"session_id": "s1",
"timestamp": "2026-05-15T11:00:00+00:00",
"parent_id": "mc_2",
},
sections={"Fact": "fact 2"},
date=today,
)
assert eid1.format() != eid2.format()
assert eid2.format().endswith("0002")
# ── Foresight ─────────────────────────────────────────────────────────────
async def test_foresight_writer_round_trip(memory_root: MemoryRoot) -> None:
writer = ForesightWriter(memory_root)
today = _dt.date(2026, 5, 15)
eid = await writer.append_entry(
"u1",
inline={
"owner_id": "u1",
"session_id": "s1",
"timestamp": "2026-05-15T10:00:00+00:00",
"parent_id": "mc_1",
"start_time": "2026-05-15T12:00:00+00:00",
"end_time": "2026-05-15T13:00:00+00:00",
"duration_days": 1,
},
sections={
"Foresight": "User will book lunch at noon.",
"Evidence": "Past calendar pattern.",
},
date=today,
)
path = memory_root.users_dir() / "u1" / ".foresights" / "foresight-2026-05-15.md"
parsed = await MarkdownReader.read(path)
fm = parsed.frontmatter
assert fm["id"] == "foresight_log_u1_2026-05-15"
assert fm["type"] == "foresight_daily"
structured = parsed.entries[0].as_structured()
assert structured.sections["Foresight"] == "User will book lunch at noon."
assert structured.sections["Evidence"] == "Past calendar pattern."
assert structured.inline["duration_days"] == "1"
assert structured.inline["start_time"].startswith("2026-05-15T12:00:00")
reader = ForesightReader(memory_root)
found = await reader.find_structured("u1", eid)
assert found is not None
assert found.sections["Evidence"] == "Past calendar pattern."
# ── AgentCase ─────────────────────────────────────────────────────────────
async def test_agent_case_writer_round_trip(memory_root: MemoryRoot) -> None:
writer = AgentCaseWriter(memory_root)
today = _dt.date(2026, 5, 15)
eid = await writer.append_entry(
"a1",
inline={
"owner_id": "a1",
"session_id": "s1",
"timestamp": "2026-05-15T10:00:00+00:00",
"parent_id": "mc_agent",
"quality_score": 0.87,
},
sections={
"TaskIntent": "Scan contract for indemnity gaps.",
"Approach": "1. read sections;\n2. flag clauses;\n3. cross-check cap.",
"KeyInsight": "Indemnity cap missing in section 4.",
},
date=today,
)
path = memory_root.agents_dir() / "a1" / ".cases" / "agent_case-2026-05-15.md"
parsed = await MarkdownReader.read(path)
fm = parsed.frontmatter
assert fm["id"] == "agent_case_log_a1_2026-05-15"
assert fm["type"] == "agent_case_daily"
assert fm["agent_id"] == "a1"
assert fm["track"] == "agent"
structured = parsed.entries[0].as_structured()
assert structured.inline["quality_score"] == "0.87"
assert structured.sections["TaskIntent"].startswith("Scan contract")
assert structured.sections["Approach"].startswith("1. read sections")
assert structured.sections["KeyInsight"].startswith("Indemnity cap missing")
reader = AgentCaseReader(memory_root)
assert reader.path_for("a1", today) == path
found = await reader.find_structured("a1", eid)
assert found is not None
assert found.sections["TaskIntent"].startswith("Scan contract")
# ── round-trip with cascade handler (md → LanceDB row mapping) ─────────────
async def test_atomic_fact_writer_output_feeds_handler(
memory_root: MemoryRoot,
) -> None:
"""The writer's md is exactly what AtomicFactHandler expects to read."""
from everos.component.embedding import EmbeddingProvider
from everos.component.tokenizer import Tokenizer
from everos.memory.cascade.handlers import AtomicFactHandler, HandlerDeps
from everos.memory.cascade.handlers._daily_log_base import ParsedEntry
class _T(Tokenizer):
def tokenize(self, t): # type: ignore[no-untyped-def]
return [x for x in t.split() if x]
def tokenize_batch(self, ts): # type: ignore[no-untyped-def]
return [self.tokenize(x) for x in ts]
class _E(EmbeddingProvider):
dim = 1024
async def embed(self, t): # type: ignore[no-untyped-def]
return [0.0] * self.dim
async def embed_batch(self, ts): # type: ignore[no-untyped-def]
return [await self.embed(x) for x in ts]
today = _dt.date(2026, 5, 15)
eid = await AtomicFactWriter(memory_root).append_entry(
"u1",
inline={
"owner_id": "u1",
"session_id": "s1",
"timestamp": "2026-05-15T10:00:00+00:00",
"parent_id": "mc_1",
"sender_ids": ["u1"],
},
sections={"Fact": "Alice prefers Italian."},
date=today,
)
path = (
memory_root.users_dir() / "u1" / ".atomic_facts" / "atomic_fact-2026-05-15.md"
)
rel = path.relative_to(memory_root.root).as_posix()
parsed = await MarkdownReader.read(path)
entry = parsed.entries[0]
handler = AtomicFactHandler(
HandlerDeps(memory_root=memory_root, embedder=_E(), tokenizer=_T())
)
structured = entry.as_structured()
pe = ParsedEntry(entry.id, structured, handler._content_sha256(structured))
row = await handler._build_row(
owner_id="u1", owner_type="user", md_path=rel, entry=pe
)
assert row.id == f"u1_{eid.format()}"
assert row.fact == "Alice prefers Italian."
assert row.parent_id == "mc_1"
assert row.sender_ids == ["u1"]
assert len(row.vector) == 1024
# ── Display-tz contract for frontmatter timestamps (Gap #5) ────────────
async def test_atomic_fact_frontmatter_last_appended_at_carries_display_tz_offset(
memory_root: MemoryRoot,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``last_appended_at`` in markdown frontmatter renders in the display tz.
Markdown frontmatter is a display-side artefact (users read the file
directly), so ``last_appended_at`` must use
:func:`get_now_with_timezone` not :func:`get_utc_now`. Pins that
contract end-to-end: configure ``EVEROS_MEMORY__TIMEZONE=Asia/Shanghai``,
write an entry, read the .md file, assert the literal string ends
with ``+08:00``.
Repeats the same check for ``ForesightWriter`` and
``AgentCaseWriter`` — they share ``BaseDailyWriter`` plumbing so a
regression on one would likely affect all three, but pinning each
rules out per-subclass shadowing of ``_frontmatter_updates``.
"""
from everos.component.utils import datetime as _dt_module
from everos.config import load_settings
monkeypatch.setenv("EVEROS_MEMORY__TIMEZONE", "Asia/Shanghai")
load_settings.cache_clear()
_dt_module._display_tz.cache_clear()
today = _dt.date(2026, 5, 15)
# AtomicFact
af_writer = AtomicFactWriter(memory_root)
await af_writer.append_entry(
"u1",
inline={
"owner_id": "u1",
"session_id": "s1",
"timestamp": "2026-05-15T10:00:00+00:00",
"parent_id": "mc_1",
"sender_ids": ["u1"],
},
sections={"Fact": "x"},
date=today,
)
af_path = (
memory_root.users_dir() / "u1" / ".atomic_facts" / "atomic_fact-2026-05-15.md"
)
af_fm = (await MarkdownReader.read(af_path)).frontmatter
assert af_fm["last_appended_at"].endswith("+08:00"), af_fm["last_appended_at"]
# Foresight
fs_writer = ForesightWriter(memory_root)
await fs_writer.append_entry(
"u1",
inline={
"owner_id": "u1",
"session_id": "s1",
"timestamp": "2026-05-15T10:00:00+00:00",
"scope": "today",
"horizon_days": 1,
},
sections={"Foresight": "x"},
date=today,
)
fs_path = memory_root.users_dir() / "u1" / ".foresights" / "foresight-2026-05-15.md"
fs_fm = (await MarkdownReader.read(fs_path)).frontmatter
assert fs_fm["last_appended_at"].endswith("+08:00"), fs_fm["last_appended_at"]
# AgentCase
ac_writer = AgentCaseWriter(memory_root)
await ac_writer.append_entry(
"a1",
inline={
"owner_id": "a1",
"session_id": "s1",
"timestamp": "2026-05-15T10:00:00+00:00",
"quality_score": 0.9,
},
sections={"Task intent": "x", "Approach": "y"},
date=today,
)
ac_path = memory_root.agents_dir() / "a1" / ".cases" / "agent_case-2026-05-15.md"
ac_fm = (await MarkdownReader.read(ac_path)).frontmatter
assert ac_fm["last_appended_at"].endswith("+08:00"), ac_fm["last_appended_at"]

View File

@ -0,0 +1,166 @@
"""Tests for :class:`ProfileWriter` — single-file rewrite layout."""
from __future__ import annotations
from pathlib import Path
from typing import ClassVar, Literal
import pytest
from everos.core.persistence import (
AgentScopedFrontmatter,
BaseFrontmatter,
MarkdownReader,
MemoryRoot,
UserScopedFrontmatter,
)
from everos.infra.persistence.markdown.writers import ProfileWriter
class _UserProfileFM(UserScopedFrontmatter):
PROFILE_FILENAME: ClassVar[str] = "user.md"
type: Literal["demo_user_profile"] = "demo_user_profile"
display_name: str = ""
bio: str = ""
class _AgentProfileFM(AgentScopedFrontmatter):
PROFILE_FILENAME: ClassVar[str] = "agent.md"
type: Literal["demo_agent_profile"] = "demo_agent_profile"
name: str = ""
@pytest.fixture
def root(tmp_path: Path) -> MemoryRoot:
return MemoryRoot(tmp_path)
@pytest.fixture
def writer(root: MemoryRoot) -> ProfileWriter:
return ProfileWriter(root)
async def test_write_creates_user_profile(
root: MemoryRoot, writer: ProfileWriter
) -> None:
fm = _UserProfileFM(
id="demo_user_profile_u_jason",
type="demo_user_profile",
user_id="u_jason",
display_name="Jason",
bio="hiker.",
)
path = await writer.write("u_jason", frontmatter=fm, body="Long-form profile.")
expected = root.users_dir() / "u_jason" / "user.md"
assert path == expected
assert expected.is_file()
async def test_write_creates_agent_profile(
root: MemoryRoot, writer: ProfileWriter
) -> None:
fm = _AgentProfileFM(
id="demo_agent_profile_agent_x",
type="demo_agent_profile",
agent_id="agent_x",
name="zhang_legal",
)
path = await writer.write("agent_x", frontmatter=fm, body="Agent playbook.")
expected = root.agents_dir() / "agent_x" / "agent.md"
assert path == expected
assert expected.is_file()
async def test_write_writes_frontmatter_and_body(
root: MemoryRoot, writer: ProfileWriter
) -> None:
fm = _UserProfileFM(
id="demo_user_profile_u_jason",
type="demo_user_profile",
user_id="u_jason",
display_name="Jason",
bio="weekend hiker.",
)
await writer.write("u_jason", frontmatter=fm, body="The body.")
parsed = await MarkdownReader.read(root.users_dir() / "u_jason" / "user.md")
assert parsed.frontmatter["display_name"] == "Jason"
assert parsed.frontmatter["bio"] == "weekend hiker."
assert parsed.body.rstrip("\n") == "The body."
async def test_write_is_upsert_full_replace(
root: MemoryRoot, writer: ProfileWriter
) -> None:
"""Second call overwrites both frontmatter and body — no append."""
fm1 = _UserProfileFM(
id="demo_user_profile_u_jason",
type="demo_user_profile",
user_id="u_jason",
display_name="Jason v1",
bio="v1",
)
await writer.write("u_jason", frontmatter=fm1, body="body v1")
fm2 = _UserProfileFM(
id="demo_user_profile_u_jason",
type="demo_user_profile",
user_id="u_jason",
display_name="Jason v2",
bio="v2",
)
await writer.write("u_jason", frontmatter=fm2, body="body v2")
parsed = await MarkdownReader.read(root.users_dir() / "u_jason" / "user.md")
assert parsed.frontmatter["display_name"] == "Jason v2"
assert parsed.frontmatter["bio"] == "v2"
assert parsed.body.rstrip("\n") == "body v2"
assert "v1" not in parsed.body
def test_path_for_does_not_create_files(
root: MemoryRoot, writer: ProfileWriter
) -> None:
"""``path_for`` is a pure path resolver — no IO."""
p = writer.path_for("u_jason", schema=_UserProfileFM)
assert p == root.users_dir() / "u_jason" / "user.md"
assert not p.exists()
assert not root.users_dir().exists()
async def test_write_normalises_trailing_newline(
root: MemoryRoot, writer: ProfileWriter
) -> None:
fm = _UserProfileFM(
id="demo_user_profile_u_jason",
type="demo_user_profile",
user_id="u_jason",
)
await writer.write("u_jason", frontmatter=fm, body="no-newline-end")
text = (root.users_dir() / "u_jason" / "user.md").read_text(encoding="utf-8")
assert text.endswith("no-newline-end\n")
async def test_write_rejects_schema_missing_profile_filename(
writer: ProfileWriter,
) -> None:
"""Schema without ``PROFILE_FILENAME`` ClassVar raises a clear error."""
class _BadSchema(UserScopedFrontmatter):
type: Literal["bad"] = "bad"
fm = _BadSchema(id="x", type="bad", user_id="u_jason")
with pytest.raises(TypeError, match="PROFILE_FILENAME"):
await writer.write("u_jason", frontmatter=fm, body="body")
async def test_write_rejects_schema_missing_scope_dir(writer: ProfileWriter) -> None:
"""Schema without scope mixin (empty ``SCOPE_DIR``) raises a clear error."""
class _ScopelessSchema(BaseFrontmatter):
PROFILE_FILENAME: ClassVar[str] = "profile.md"
type: Literal["scopeless"] = "scopeless"
fm = _ScopelessSchema(id="x", type="scopeless")
with pytest.raises(TypeError, match="SCOPE_DIR"):
await writer.write("x", frontmatter=fm, body="body")