jeevesagent.core.protocols¶
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 AsyncIterator, every
resource is an AsyncContextManager.
Classes¶
Resource governance — tokens, calls, cost, wall clock. |
|
Text-to-vector embedding model used by the memory subsystem. |
|
Aggregator over user-registered lifecycle callbacks. |
|
Tiered memory: working blocks, episodic store, semantic graph. |
|
LLM provider interface. One adapter per lab (Anthropic, OpenAI, ...). |
|
Decides whether a tool call is allowed. |
|
Durable execution. Wraps every side effect in a journal entry. |
|
Handle to an open durable session held by a |
|
Isolation layer for tool execution. |
|
Resolution and redaction of named secrets. |
|
OpenTelemetry-compatible tracing/metrics surface. |
|
MCP-aware tool registry. Lazy-loads schemas on demand. |
Module Contents¶
- class jeevesagent.core.protocols.Budget[source]¶
Bases:
ProtocolResource governance — tokens, calls, cost, wall clock.
user_id(M9): the agent loop forwards the liveRunContext’s user_id into everyallows_stepandconsumecall so multi-tenant budget impls can enforce per-user caps. Implementations that don’t track per-user usage may ignore the kwarg; the framework falls back gracefully when the kwarg isn’t accepted.- async allows_step(*, user_id: str | None = None) jeevesagent.core.types.BudgetStatus[source]¶
- class jeevesagent.core.protocols.Embedder[source]¶
Bases:
ProtocolText-to-vector embedding model used by the memory subsystem.
- class jeevesagent.core.protocols.HookHost[source]¶
Bases:
ProtocolAggregator over user-registered lifecycle callbacks.
user_id(M9): same contract asPermissions— forwarded from the live RunContext so per-user hooks can route. Legacy hook impls without the kwarg fall back viaexcept TypeErrorin the agent loop.- async on_event(event: jeevesagent.core.types.Event) None[source]¶
- async post_tool(call: jeevesagent.core.types.ToolCall, result: jeevesagent.core.types.ToolResult) None[source]¶
- async pre_tool(call: jeevesagent.core.types.ToolCall, *, user_id: str | None = None) jeevesagent.core.types.PermissionDecision[source]¶
- class jeevesagent.core.protocols.Memory[source]¶
Bases:
ProtocolTiered memory: working blocks, episodic store, semantic graph.
- async append_block(name: str, content: str, *, user_id: str | None = None) None[source]¶
Append to a named block in
user_id’s namespace, creating it if absent.
- async export(*, user_id: str | None = None) jeevesagent.core.types.MemoryExport[source]¶
Full data dump for
user_id— GDPR / data portability.Returns every episode, every fact, and the export timestamp. Serialise with
MemoryExport.model_dump_json()for download or downstream processing. Honours theuser_idpartition: never includes data belonging to a different user.
- async forget(*, user_id: str | None = None, session_id: str | None = None, before: datetime.datetime | None = None) int[source]¶
Erase memory for a user — GDPR / “right to be forgotten”.
With
user_idonly: erase EVERYTHING (episodes + facts + session messages + working blocks) belonging to that user. Withsession_id: erase only that conversation thread for that user. Withbefore: erase only data older than the timestamp (other args still scope it). All filters AND together.Returns the total number of records deleted (episodes + facts; backends without precise counts may return their best estimate).
user_id=Noneerases the anonymous bucket only — same partition rule asrecall(). To erase everything across all users, callers must enumerate users and callforgetper-user; the framework deliberately makes the “delete every user” path explicit so it’s not done by accident.
- async profile(*, user_id: str | None = None) jeevesagent.core.types.MemoryProfile[source]¶
Summary of what this memory knows about
user_id.Returns counts (episodes, facts), the most-recent sessions the user touched, the last-seen timestamp, and a sample of the most-recently-recorded facts. Suitable for rendering a “what does the bot know about me?” view to the end user, or for an ops dashboard.
Backends MUST honour
user_idas a hard partition — passing one user’s id never returns counts derived from another user’s data.
- async 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][source]¶
Retrieve episodes (or facts, when
kind='semantic').When
user_idis supplied, results are restricted to episodes stored with that exactuser_idvalue.Noneis its own bucket (the “anonymous / single-tenant” namespace) — episodes stored withuser_id=Noneare never visible to a query withuser_id="alice"and vice versa. Backends MUST honour this filter to preserve the framework’s multi-tenant safety contract.
- async recall_facts(query: str, *, limit: int = 5, valid_at: datetime.datetime | None = None, user_id: str | None = None) list[jeevesagent.core.types.Fact][source]¶
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 onmemory.factsso backends without fact support don’t need any opt-out mechanism.user_idfilters by namespace partition with the same semantics asrecall():Noneis its own bucket and does not cross-contaminate with non-None values.
- async remember(episode: jeevesagent.core.types.Episode) str[source]¶
Persist an episode. Returns the episode ID.
- async session_messages(session_id: str, *, user_id: str | None = None, limit: int = 20) list[jeevesagent.core.types.Message][source]¶
Return the most-recent
limituser/assistant turns from the conversation identified bysession_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_idactually continues the chat (the model sees previous turns as realMessagehistory) rather than starting fresh and relying solely on semantic recall.user_idMUST be respected by backends as a hard namespace partition: messages persisted under oneuser_idare 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.
- async update_block(name: str, content: str, *, user_id: str | None = None) None[source]¶
Replace the contents of a named block in
user_id’s namespace.Noneis the anonymous bucket.
- async working(*, user_id: str | None = None) list[jeevesagent.core.types.MemoryBlock][source]¶
All in-context blocks for
user_id. Pinned to every prompt.Like every other memory primitive, working blocks are user-partitioned: blocks set under one
user_idare invisible to a query scoped to a different one. Backends MUST honour this — passing alice’s user_id never returns bob’s pinned blocks.
- class jeevesagent.core.protocols.Model[source]¶
Bases:
ProtocolLLM provider interface. One adapter per lab (Anthropic, OpenAI, …).
The required surface is
stream(...)— every adapter must implement it. Adapters MAY additionally overridecomplete(...)with a non-streaming (single-shot) call; if not,completefalls back to consuming the stream internally and assembling the full response, which is correct but slower (per-chunk wire + parsing overhead). Architectures usecompleteon the non-streaming hot path (agent.run()) andstreamwhen a consumer is reading fromagent.stream().- 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][source]¶
Stream completion chunks. Each chunk is text, tool_call, or finish.
- class jeevesagent.core.protocols.Permissions[source]¶
Bases:
ProtocolDecides whether a tool call is allowed.
user_id(M9): the agent loop forwards the liveRunContext’s user_id so multi-tenant permission impls (e.g.PerUserPermissions) can route to per-user policies. Implementations that don’t care about the user can ignore the kwarg; the framework’s fallbackexcept TypeErrorcovers legacy impls.- async check(call: jeevesagent.core.types.ToolCall, *, context: collections.abc.Mapping[str, Any], user_id: str | None = None) jeevesagent.core.types.PermissionDecision[source]¶
- class jeevesagent.core.protocols.Runtime[source]¶
Bases:
ProtocolDurable execution. Wraps every side effect in a journal entry.
- session(session_id: str) contextlib.AbstractAsyncContextManager[RuntimeSession][source]¶
Open or resume a durable session.
- async signal(session_id: str, name: str, payload: Any) None[source]¶
Send an external signal (e.g., human approval) to a session.
- async step(name: str, fn: collections.abc.Callable[Ellipsis, collections.abc.Awaitable[Any]], *args: Any, idempotency_key: str | None = None, **kwargs: Any) Any[source]¶
Execute
fnas a journaled step. Replays cached on resume.
- stream_step(name: str, fn: collections.abc.Callable[Ellipsis, collections.abc.AsyncIterator[Any]], *args: Any, **kwargs: Any) collections.abc.AsyncIterator[Any][source]¶
Execute a streaming step. Replays the aggregate on resume.
- class jeevesagent.core.protocols.RuntimeSession[source]¶
Bases:
ProtocolHandle to an open durable session held by a
Runtime.
- class jeevesagent.core.protocols.Sandbox[source]¶
Bases:
ProtocolIsolation layer for tool execution.
- async execute(tool: jeevesagent.core.types.ToolDef, args: collections.abc.Mapping[str, Any]) jeevesagent.core.types.ToolResult[source]¶
- with_filesystem(root: str) contextlib.AbstractAsyncContextManager[None][source]¶
Temporary filesystem sandbox for the duration of the context.
- class jeevesagent.core.protocols.Secrets[source]¶
Bases:
ProtocolResolution and redaction of named secrets.
resolve/storeare async because most production secrets backends (Vault, AWS Secrets Manager, GCP Secret Manager) talk over the network.lookup_syncexists for the constructor-time path: when the framework needs to wire an API key into a model adapter before any event loop is running (e.g.OpenAIModel(...)from insideAgent.__init__). Concrete impls returningNonefromlookup_syncfor refs that can’t be resolved synchronously are fine — callers should fall back toos.environor to the explicitapi_key=argument.- lookup_sync(ref: str) str | None[source]¶
Synchronous best-effort lookup, for constructor-time callers that can’t await. Returns
Nonewhen the ref isn’t available synchronously (e.g. the impl needs a network round-trip). Default impls injeevesagent. security.secretscover env-var and in-memory lookups.
- class jeevesagent.core.protocols.Telemetry[source]¶
Bases:
ProtocolOpenTelemetry-compatible tracing/metrics surface.
- trace(name: str, **attrs: Any) contextlib.AbstractAsyncContextManager[jeevesagent.core.types.Span][source]¶
- class jeevesagent.core.protocols.ToolHost[source]¶
Bases:
ProtocolMCP-aware tool registry. Lazy-loads schemas on demand.
- async call(tool: str, args: collections.abc.Mapping[str, Any], *, call_id: str = '') jeevesagent.core.types.ToolResult[source]¶
Invoke
toolwithargs. Thecall_idis propagated into the returnedToolResultso the loop can correlate results with the originating model-emitted call.
- watch() collections.abc.AsyncIterator[jeevesagent.core.types.ToolEvent][source]¶
Notifications when the tool list changes (MCP listChanged).