Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions raven/cli/_memory_warn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""User-visible warning when the memory backend fails to start.

Under the TUI, loguru is redirected to a file, so a start failure (e.g. a
memory identity that EverOS rejects) silently disables long-term memory with
nothing on screen. Mirror the embedding-unavailable warning in
``everos/backend.py`` and print to stderr so every entry point (TUI, agent
REPL, gateway) sees the same degraded-memory notice, not just the two that
already surface the traceback via stderr.
"""

from __future__ import annotations

from rich.console import Console


def warn_memory_start_failed(exc: BaseException) -> None:
"""Print a stderr notice that long-term memory is off this session."""
Console(stderr=True).print(
"[yellow]Memory backend failed to start; long-term memory is off this session.[/yellow]\n"
f"[dim]Run `raven onboard` to reconfigure, or check the log ({type(exc).__name__}).[/dim]"
)
10 changes: 8 additions & 2 deletions raven/cli/agent_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,13 @@ async def run_once():
if backend is not None:
try:
await backend.start()
except Exception:
except Exception as exc:
logger.exception(
"memory backend start failed; continuing with legacy memory path",
)
from raven.cli._memory_warn import warn_memory_start_failed

warn_memory_start_failed(exc)
try:
# Build inside the running loop: Scheduler pins its home loop in
# __init__, so build_repl must not run in the sync prologue.
Expand Down Expand Up @@ -496,10 +499,13 @@ async def run_interactive():
if backend is not None:
try:
await backend.start()
except Exception:
except Exception as exc:
logger.exception(
"memory backend start failed; continuing with legacy memory path",
)
from raven.cli._memory_warn import warn_memory_start_failed

warn_memory_start_failed(exc)
# agent_loop.run() is now a lifecycle keep-alive (executor /
# debug server / MCP up, then idle); all turns go through the
# spine. Gathered on teardown.
Expand Down
5 changes: 4 additions & 1 deletion raven/cli/gateway_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,13 @@ async def run():
if backend is not None:
try:
await backend.start()
except Exception:
except Exception as exc:
_logger.exception(
"memory backend start failed; continuing with legacy memory path",
)
from raven.cli._memory_warn import warn_memory_start_failed

warn_memory_start_failed(exc)
try:
# Spine assembly for the gateway's host sources (cron submits
# through it, replies route to channels via a per-channel outlet).
Expand Down
5 changes: 4 additions & 1 deletion raven/cli/tui_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,12 +706,15 @@ def _agent_loop_factory():
async def _start_backend() -> None:
try:
await agent_loop.backend.start() # type: ignore[union-attr]
except Exception:
except Exception as exc:
from loguru import logger as _logger

_logger.exception(
"tui: memory backend start failed; continuing with degraded memory path",
)
from raven.cli._memory_warn import warn_memory_start_failed

warn_memory_start_failed(exc)
_strip_tty_stream_handlers()

asyncio.create_task(_start_backend())
Expand Down
19 changes: 19 additions & 0 deletions tests/test_memory_warn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""warn_memory_start_failed must surface on stderr (visible under the TUI)."""

from __future__ import annotations

import pytest

from raven.cli._memory_warn import warn_memory_start_failed


def test_warn_memory_start_failed_writes_degraded_notice_to_stderr(
capsys: pytest.CaptureFixture[str],
) -> None:
warn_memory_start_failed(ValueError("memory.userId='..' is not accepted"))

captured = capsys.readouterr()
combined = captured.err + captured.out

assert "long-term memory is off" in combined
assert "ValueError" in combined