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:
@ -0,0 +1,61 @@
|
||||
"""``AgentMemoryPipeline.run`` — empty short-circuit + per-cell event emit."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from everalgo.types import ChatMessage, MemCell
|
||||
|
||||
from everos.memory import IngestResult
|
||||
from everos.memory.events import AgentPipelineStarted
|
||||
from everos.memory.extract.pipeline.agent_memory import AgentMemoryPipeline
|
||||
|
||||
|
||||
class _FakeEngine:
|
||||
"""Captures emitted events; mirrors ``OfflineEngine.emit`` async signature."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.events: list[AgentPipelineStarted] = []
|
||||
|
||||
async def emit(self, event: AgentPipelineStarted) -> None:
|
||||
self.events.append(event)
|
||||
|
||||
|
||||
def _make_cell(n_items: int, ts: int = 1_700_000_000_000) -> MemCell:
|
||||
items = [
|
||||
ChatMessage(
|
||||
id=f"m{i}",
|
||||
role="user",
|
||||
sender_id="u1",
|
||||
sender_name="u",
|
||||
content="hi",
|
||||
timestamp=ts,
|
||||
)
|
||||
for i in range(n_items)
|
||||
]
|
||||
return MemCell(items=items, timestamp=ts)
|
||||
|
||||
|
||||
async def test_empty_cells_short_circuit() -> None:
|
||||
engine = _FakeEngine()
|
||||
pipeline = AgentMemoryPipeline(engine) # type: ignore[arg-type]
|
||||
ingested = IngestResult(session_id="s1", messages=[])
|
||||
out = await pipeline.run(ingested, cells=[], memcell_ids=[])
|
||||
assert out.track == "agent_memory"
|
||||
assert out.status == "accumulated"
|
||||
assert out.message_count == 0
|
||||
assert engine.events == []
|
||||
|
||||
|
||||
async def test_emits_one_event_per_cell() -> None:
|
||||
engine = _FakeEngine()
|
||||
pipeline = AgentMemoryPipeline(engine) # type: ignore[arg-type]
|
||||
ingested = IngestResult(session_id="s1", messages=[])
|
||||
cells = [_make_cell(n_items=2), _make_cell(n_items=3)]
|
||||
memcell_ids = ["mc_a", "mc_b"]
|
||||
out = await pipeline.run(ingested, cells=cells, memcell_ids=memcell_ids)
|
||||
|
||||
assert out.track == "agent_memory"
|
||||
assert out.status == "extracted"
|
||||
assert out.message_count == 5 # 2 + 3
|
||||
assert [e.memcell_id for e in engine.events] == ["mc_a", "mc_b"]
|
||||
assert all(e.session_id == "s1" for e in engine.events)
|
||||
assert all(isinstance(e, AgentPipelineStarted) for e in engine.events)
|
||||
@ -0,0 +1,123 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as _dt
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from everalgo.types import ChatMessage, MemCell
|
||||
from everalgo.types import Episode as AlgoEpisode
|
||||
|
||||
from everos.core.persistence import EntryId
|
||||
from everos.memory import IngestResult
|
||||
from everos.memory.events import EpisodeExtracted, UserPipelineStarted
|
||||
from everos.memory.extract.pipeline.user_memory import UserMemoryPipeline
|
||||
from everos.memory.models import CanonicalMessage
|
||||
|
||||
|
||||
def _sample_memcell() -> MemCell:
|
||||
return MemCell(
|
||||
items=[
|
||||
ChatMessage(
|
||||
id="m1",
|
||||
role="user",
|
||||
content="hello",
|
||||
timestamp=1_700_000_000_000,
|
||||
sender_id="u1",
|
||||
),
|
||||
],
|
||||
timestamp=1_700_000_000_000,
|
||||
)
|
||||
|
||||
|
||||
class _CapturingEngine:
|
||||
def __init__(self) -> None:
|
||||
self.emitted: list[object] = []
|
||||
|
||||
async def emit(self, event: object) -> None:
|
||||
self.emitted.append(event)
|
||||
|
||||
|
||||
async def test_emit_pipeline_started_routes_through_engine() -> None:
|
||||
engine = _CapturingEngine()
|
||||
pipeline = UserMemoryPipeline(
|
||||
episode_writer=MagicMock(),
|
||||
prompt_loader=MagicMock(),
|
||||
llm_client=MagicMock(),
|
||||
engine=engine,
|
||||
)
|
||||
|
||||
cell = _sample_memcell()
|
||||
await pipeline._emit_pipeline_started( # noqa: SLF001 — test introspection
|
||||
memcell_id="mc_a",
|
||||
session_id="s1",
|
||||
app_id="claude_code",
|
||||
project_id="oss",
|
||||
cell=cell,
|
||||
)
|
||||
|
||||
started = [e for e in engine.emitted if isinstance(e, UserPipelineStarted)]
|
||||
assert len(started) == 1
|
||||
assert started[0].memcell_id == "mc_a"
|
||||
assert started[0].session_id == "s1"
|
||||
assert started[0].app_id == "claude_code"
|
||||
assert started[0].project_id == "oss"
|
||||
assert started[0].memcell is cell
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_emit_episode_extracted_after_md_write() -> None:
|
||||
"""Each per-sender Episode write emits EpisodeExtracted with the md entry id."""
|
||||
engine = _CapturingEngine()
|
||||
episode_writer = MagicMock()
|
||||
episode_writer.append_entry = AsyncMock(
|
||||
return_value=EntryId(prefix="ep", date=_dt.date(2026, 5, 17), seq=1)
|
||||
)
|
||||
episode_writer.path_for = MagicMock(
|
||||
return_value="users/u1/episodes/episode-2026-05-17.md"
|
||||
)
|
||||
prompt_loader = MagicMock()
|
||||
prompt_loader.load = MagicMock(return_value="<prompt>")
|
||||
llm_client = MagicMock()
|
||||
|
||||
pipeline = UserMemoryPipeline(
|
||||
episode_writer=episode_writer,
|
||||
prompt_loader=prompt_loader,
|
||||
llm_client=llm_client,
|
||||
engine=engine,
|
||||
)
|
||||
|
||||
cell = _sample_memcell()
|
||||
ingested = IngestResult(
|
||||
session_id="s1",
|
||||
messages=[
|
||||
CanonicalMessage(
|
||||
message_id="m1",
|
||||
session_id="s1",
|
||||
sender_id="u1",
|
||||
role="user",
|
||||
timestamp=_dt.datetime.fromtimestamp(1_700_000_000, tz=_dt.UTC),
|
||||
text="hello",
|
||||
)
|
||||
],
|
||||
)
|
||||
algo_ep = AlgoEpisode(
|
||||
owner_id="u1", episode="they said hello", timestamp=1_700_000_000_000
|
||||
)
|
||||
with patch.object( # noqa: SLF001
|
||||
pipeline._ep_ext, "aextract", new=AsyncMock(return_value=algo_ep)
|
||||
):
|
||||
outcome = await pipeline.run(
|
||||
ingested=ingested,
|
||||
cells=[cell],
|
||||
memcell_ids=["mc_a"],
|
||||
per_cell_all_senders=[["u1"]],
|
||||
)
|
||||
|
||||
assert outcome.status == "extracted"
|
||||
extracted = [e for e in engine.emitted if isinstance(e, EpisodeExtracted)]
|
||||
assert len(extracted) == 1
|
||||
assert extracted[0].memcell_id == "mc_a"
|
||||
assert extracted[0].episode_entry_id == "ep_20260517_00000001"
|
||||
assert extracted[0].episode_text == "they said hello"
|
||||
assert extracted[0].episode_timestamp_ms == 1_700_000_000_000
|
||||
assert extracted[0].owner_id == "u1"
|
||||
Reference in New Issue
Block a user