jeevesagent.core.protocols
==========================

.. py:module:: jeevesagent.core.protocols

.. autoapi-nested-parse::

   Protocol definitions for every module boundary.

   These structural types are the contract surface of the harness. Every
   implementation — first-party or third-party — satisfies one of these. The
   loop and the agent only depend on the protocols, never on concrete
   implementations.

   The protocols are intentionally async-only: every method that performs
   I/O is a coroutine, every stream is an :class:`AsyncIterator`, every
   resource is an :class:`AsyncContextManager`.



Classes
-------

.. autoapisummary::

   jeevesagent.core.protocols.Budget
   jeevesagent.core.protocols.Embedder
   jeevesagent.core.protocols.HookHost
   jeevesagent.core.protocols.Memory
   jeevesagent.core.protocols.Model
   jeevesagent.core.protocols.Permissions
   jeevesagent.core.protocols.Runtime
   jeevesagent.core.protocols.RuntimeSession
   jeevesagent.core.protocols.Sandbox
   jeevesagent.core.protocols.Secrets
   jeevesagent.core.protocols.Telemetry
   jeevesagent.core.protocols.ToolHost


Module Contents
---------------

.. py:class:: Budget

   Bases: :py:obj:`Protocol`


   Resource governance — tokens, calls, cost, wall clock.


   .. py:method:: allows_step() -> jeevesagent.core.types.BudgetStatus
      :async:



   .. py:method:: consume(*, tokens_in: int, tokens_out: int, cost_usd: float) -> None
      :async:



.. py:class:: Embedder

   Bases: :py:obj:`Protocol`


   Text-to-vector embedding model used by the memory subsystem.


   .. py:method:: embed(text: str) -> list[float]
      :async:



   .. py:method:: embed_batch(texts: list[str]) -> list[list[float]]
      :async:



   .. py:attribute:: dimensions
      :type:  int


   .. py:attribute:: name
      :type:  str


.. py:class:: HookHost

   Bases: :py:obj:`Protocol`


   Aggregator over user-registered lifecycle callbacks.


   .. py:method:: on_event(event: jeevesagent.core.types.Event) -> None
      :async:



   .. py:method:: post_tool(call: jeevesagent.core.types.ToolCall, result: jeevesagent.core.types.ToolResult) -> None
      :async:



   .. py:method:: pre_tool(call: jeevesagent.core.types.ToolCall) -> jeevesagent.core.types.PermissionDecision
      :async:



.. py:class:: Memory

   Bases: :py:obj:`Protocol`


   Tiered memory: working blocks, episodic store, semantic graph.


   .. py:method:: append_block(name: str, content: str) -> None
      :async:


      Append to a named block, creating it if absent.



   .. py:method:: consolidate() -> None
      :async:


      Background: extract semantic facts from recent episodes.



   .. py:method:: recall(query: str, *, kind: str = 'episodic', limit: int = 5, time_range: tuple[datetime.datetime, datetime.datetime] | None = None, user_id: str | None = None) -> list[jeevesagent.core.types.Episode]
      :async:


      Retrieve episodes (or facts, when ``kind='semantic'``).

      When ``user_id`` is supplied, results are restricted to
      episodes stored with that exact ``user_id`` value. ``None``
      is its own bucket (the "anonymous / single-tenant"
      namespace) — episodes stored with ``user_id=None`` are never
      visible to a query with ``user_id="alice"`` and vice versa.
      Backends MUST honour this filter to preserve the framework's
      multi-tenant safety contract.



   .. py:method:: recall_facts(query: str, *, limit: int = 5, valid_at: datetime.datetime | None = None, user_id: str | None = None) -> list[jeevesagent.core.types.Fact]
      :async:


      Retrieve bi-temporal facts matching ``query``.

      Backends that don't expose a fact store return ``[]``. The agent
      loop calls this directly rather than duck-typing on
      ``memory.facts`` so backends without fact support don't need
      any opt-out mechanism.

      ``user_id`` filters by namespace partition with the same
      semantics as :meth:`recall`: ``None`` is its own bucket and
      does not cross-contaminate with non-None values.



   .. py:method:: remember(episode: jeevesagent.core.types.Episode) -> str
      :async:


      Persist an episode. Returns the episode ID.



   .. py:method:: session_messages(session_id: str, *, user_id: str | None = None, limit: int = 20) -> list[jeevesagent.core.types.Message]
      :async:


      Return the most-recent ``limit`` user/assistant turns from
      the conversation identified by ``session_id``, in order
      (oldest first).

      This is the conversation-continuity primitive — the agent
      loop calls it at the top of every run so that reusing a
      ``session_id`` actually continues the chat (the model sees
      previous turns as real :class:`Message` history) rather than
      starting fresh and relying solely on semantic recall.

      ``user_id`` MUST be respected by backends as a hard
      namespace partition: messages persisted under one
      ``user_id`` are never visible to a query scoped to a
      different one. Backends without persisted message logs
      return ``[]`` — the agent loop falls back to the
      semantic-recall path in that case.



   .. py:method:: update_block(name: str, content: str) -> None
      :async:


      Replace the contents of a named block.



   .. py:method:: working() -> list[jeevesagent.core.types.MemoryBlock]
      :async:


      All in-context blocks. Pinned to every prompt.



.. py:class:: Model

   Bases: :py:obj:`Protocol`


   LLM provider interface. One adapter per lab (Anthropic, OpenAI, ...).

   The required surface is ``stream(...)`` — every adapter must
   implement it. Adapters MAY additionally override ``complete(...)``
   with a non-streaming (single-shot) call; if not, ``complete``
   falls back to consuming the stream internally and assembling the
   full response, which is correct but slower (per-chunk wire +
   parsing overhead). Architectures use ``complete`` on the
   non-streaming hot path (``agent.run()``) and ``stream`` when a
   consumer is reading from ``agent.stream()``.


   .. py:method:: stream(messages: list[jeevesagent.core.types.Message], *, tools: list[jeevesagent.core.types.ToolDef] | None = None, temperature: float = 1.0, max_tokens: int | None = None) -> collections.abc.AsyncIterator[jeevesagent.core.types.ModelChunk]

      Stream completion chunks. Each chunk is text, tool_call, or finish.



   .. py:attribute:: name
      :type:  str


.. py:class:: Permissions

   Bases: :py:obj:`Protocol`


   Decides whether a tool call is allowed.


   .. py:method:: check(call: jeevesagent.core.types.ToolCall, *, context: collections.abc.Mapping[str, Any]) -> jeevesagent.core.types.PermissionDecision
      :async:



.. py:class:: Runtime

   Bases: :py:obj:`Protocol`


   Durable execution. Wraps every side effect in a journal entry.


   .. py:method:: session(session_id: str) -> contextlib.AbstractAsyncContextManager[RuntimeSession]

      Open or resume a durable session.



   .. py:method:: signal(session_id: str, name: str, payload: Any) -> None
      :async:


      Send an external signal (e.g., human approval) to a session.



   .. py:method:: step(name: str, fn: collections.abc.Callable[Ellipsis, collections.abc.Awaitable[Any]], *args: Any, idempotency_key: str | None = None, **kwargs: Any) -> Any
      :async:


      Execute ``fn`` as a journaled step. Replays cached on resume.



   .. py:method:: stream_step(name: str, fn: collections.abc.Callable[Ellipsis, collections.abc.AsyncIterator[Any]], *args: Any, **kwargs: Any) -> collections.abc.AsyncIterator[Any]

      Execute a streaming step. Replays the aggregate on resume.



   .. py:attribute:: name
      :type:  str


.. py:class:: RuntimeSession

   Bases: :py:obj:`Protocol`


   Handle to an open durable session held by a :class:`Runtime`.


   .. py:method:: deliver(name: str, payload: Any) -> None
      :async:



   .. py:attribute:: id
      :type:  str


.. py:class:: Sandbox

   Bases: :py:obj:`Protocol`


   Isolation layer for tool execution.


   .. py:method:: execute(tool: jeevesagent.core.types.ToolDef, args: collections.abc.Mapping[str, Any]) -> jeevesagent.core.types.ToolResult
      :async:



   .. py:method:: with_filesystem(root: str) -> contextlib.AbstractAsyncContextManager[None]

      Temporary filesystem sandbox for the duration of the context.



.. py:class:: Secrets

   Bases: :py:obj:`Protocol`


   Resolution and redaction of named secrets.


   .. py:method:: redact(text: str) -> str


   .. py:method:: resolve(ref: str) -> str
      :async:



   .. py:method:: store(ref: str, value: str) -> None
      :async:



.. py:class:: Telemetry

   Bases: :py:obj:`Protocol`


   OpenTelemetry-compatible tracing/metrics surface.


   .. py:method:: emit_metric(name: str, value: float, **attrs: Any) -> None
      :async:



   .. py:method:: trace(name: str, **attrs: Any) -> contextlib.AbstractAsyncContextManager[jeevesagent.core.types.Span]


.. py:class:: ToolHost

   Bases: :py:obj:`Protocol`


   MCP-aware tool registry. Lazy-loads schemas on demand.


   .. py:method:: call(tool: str, args: collections.abc.Mapping[str, Any], *, call_id: str = '') -> jeevesagent.core.types.ToolResult
      :async:


      Invoke ``tool`` with ``args``. The ``call_id`` is propagated into
      the returned :class:`ToolResult` so the loop can correlate
      results with the originating model-emitted call.



   .. py:method:: list_tools(*, query: str | None = None) -> list[jeevesagent.core.types.ToolDef]
      :async:



   .. py:method:: watch() -> collections.abc.AsyncIterator[jeevesagent.core.types.ToolEvent]

      Notifications when the tool list changes (MCP listChanged).



