///|
/// MemoryPort: session memory — the port a memory system plugs into
/// (nowledge-mem today, OpenViking and any other scheme tomorrow). Four
/// slots: `inbound` for session-opening recall, `store`/`search`/`delete`
/// for the durable record. Core never labels or re-renders provider output —
/// providers own their text format (community convention e.g. devkit
/// envelopes); core owns only timing, placement, and stability.
pub(open) trait MemoryPort {
  /// Session-opening memory context tailored to the user's first request.
  /// Core calls it at most once per session lifetime per process, ONLY when
  /// the loaded session transcript is empty (the session's first turn).
  /// `None` or an empty body contributes nothing; a non-empty result
  /// freezes into the transcript as ONE user message placed before the real
  /// first user input — never rewritten, never re-read. The slot is `async`
  /// so adapter IO propagates through async; synchronous implementations
  /// write plain `fn` bodies.
  async fn inbound(Self, session_id~ : String, request~ : String) -> String? raise @error.MemoryError

  /// Persist one entry with provider-convention metadata; returns the
  /// provider's id or a pending ticket for it. Whether the write is durable
  /// on return is the provider's business.
  async fn store(Self, content~ : String, metadata~ : Map[String, Json]) -> String raise @error.MemoryError

  /// Query the store; returns provider-rendered text (`None` = no hits).
  /// Omitting `top_k` leaves the hit count to the provider.
  async fn search(Self, query~ : String, top_k? : Int) -> String? raise @error.MemoryError

  /// Remove the memory stored under `id` — a passive interface for
  /// product-side logic (e.g. an observer detecting stale memory). Whether
  /// an unknown id raises is the provider's call.
  async fn delete(Self, id : String) -> Unit raise @error.MemoryError
}