jeevesagent.core

Layer-free primitives: types, protocols, errors, IDs.

Submodules

Exceptions

AuthenticationError

Invalid, missing, or revoked API credentials.

BudgetExceeded

A run was halted because a budget limit was hit.

CancelledByUser

A user-driven interruption (signal, timeout) ended the run.

ConfigError

Invalid or unresolvable configuration passed to Agent.

ContentFilterError

The provider's safety system blocked the request or response.

FreshnessError

A certified value failed its freshness policy.

InvalidRequestError

The request was malformed or violated the provider's API

IsolationWarning

Emitted when a memory query is likely to silently miss data

JeevesAgentError

Base class for all harness errors.

LineageError

A certified value failed its lineage policy.

MCPError

An MCP transport, handshake, or protocol error.

MemoryStoreError

The memory backend failed an operation.

ModelError

A call to the underlying model adapter failed.

OutputValidationError

The model's final answer did not validate against the supplied

PermanentModelError

A model call failed in a way that retrying will not fix.

PermissionDenied

A tool call was denied by the permission layer or a user hook.

RateLimitError

The provider returned a 429 / quota-exhausted response.

RuntimeJournalError

The durable runtime journal is unreadable or inconsistent.

SandboxError

The sandbox refused or failed to execute a tool.

ToolError

A tool invocation failed at the tool's own boundary.

TransientModelError

A model call failed in a way that may succeed on retry.

Classes

AuditEntry

An immutable, signed entry in the audit log.

Budget

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

BudgetStatus

Result of a budget check before each step.

CertifiedValue

A value carrying provenance metadata for freshness/lineage checks.

Embedder

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

Episode

A single (input, decisions, tool calls, output) tuple from history.

Event

A single observable record from a running session.

EventKind

Enum where members are also (and must be) strings

Fact

A semantic claim extracted from one or more episodes.

HookHost

Aggregator over user-registered lifecycle callbacks.

Memory

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

MemoryBlock

An in-context memory block, pinned to every prompt.

Message

A single chat message in the model's conversation.

Model

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

ModelChunk

A single chunk from a streaming model call.

PermissionDecision

Outcome of a permission check or pre-tool hook.

Permissions

Decides whether a tool call is allowed.

Role

Enum where members are also (and must be) strings

RunContext

Typed, immutable context for one agent run.

RunResult

Final outcome of an Agent.run call.

Runtime

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

RuntimeSession

Handle to an open durable session held by a Runtime.

Sandbox

Isolation layer for tool execution.

Secrets

Resolution and redaction of named secrets.

Span

A trace span handle. Concrete telemetry adapters return their own

Telemetry

OpenTelemetry-compatible tracing/metrics surface.

ToolCall

A model-emitted request to invoke a tool.

ToolDef

Schema description of a tool the model can call.

ToolEvent

Tool registry change notification (MCP listChanged etc.).

ToolHost

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

ToolResult

Outcome of a tool invocation.

Usage

Token and cost accounting for a model call.

set_run_context

Context manager that installs a RunContext for the

Functions

deterministic_hash(→ str)

Stable hash of arbitrary JSON-serializable parts.

get_run_context(→ RunContext)

Return the RunContext for the currently-running agent.

new_id(→ str)

Return a fresh ULID, optionally prefixed for readability.

Package Contents

exception jeevesagent.core.AuthenticationError(message: str, *, cause: BaseException | None = None)[source]

Bases: PermanentModelError

Invalid, missing, or revoked API credentials.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.BudgetExceeded(reason: str)[source]

Bases: JeevesAgentError

A run was halted because a budget limit was hit.

Initialize self. See help(type(self)) for accurate signature.

reason
exception jeevesagent.core.CancelledByUser[source]

Bases: JeevesAgentError

A user-driven interruption (signal, timeout) ended the run.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.ConfigError[source]

Bases: JeevesAgentError

Invalid or unresolvable configuration passed to Agent.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.ContentFilterError(message: str, *, cause: BaseException | None = None)[source]

Bases: PermanentModelError

The provider’s safety system blocked the request or response.

Typically a permanent failure for the same prompt; users may rephrase but the framework should not silently retry.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.FreshnessError[source]

Bases: JeevesAgentError

A certified value failed its freshness policy.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.InvalidRequestError(message: str, *, cause: BaseException | None = None)[source]

Bases: PermanentModelError

The request was malformed or violated the provider’s API contract — bad parameters, oversized prompt, unknown model name, etc. Fix the request, don’t retry.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.IsolationWarning[source]

Bases: UserWarning

Emitted when a memory query is likely to silently miss data because the caller forgot to pass user_id.

Concrete trigger: a backend’s recall / recall_facts runs with user_id=None against a store whose persisted records include at least one non-None user_id — the partition is safe (the anonymous bucket and named-user buckets are isolated), but the developer probably wired up multi-tenancy somewhere and forgot to pass user_id here, so they will see suspiciously empty recall results.

Subclass of UserWarning so it goes through Python’s standard warnings filter machinery — apps can silence, promote-to-error, or log it however they want, e.g.:

import warnings
from jeevesagent import IsolationWarning
warnings.simplefilter("error", IsolationWarning)  # raise on hit

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.JeevesAgentError[source]

Bases: Exception

Base class for all harness errors.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.LineageError[source]

Bases: JeevesAgentError

A certified value failed its lineage policy.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.MCPError[source]

Bases: JeevesAgentError

An MCP transport, handshake, or protocol error.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.MemoryStoreError[source]

Bases: JeevesAgentError

The memory backend failed an operation.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.ModelError(message: str, *, cause: BaseException | None = None)[source]

Bases: JeevesAgentError

A call to the underlying model adapter failed.

Base of the model-error taxonomy: catch this to handle every model failure regardless of whether it is transient or permanent. The SDK exception that triggered the classification is attached via __cause__ (and cause) so debug code can still inspect the raw error.

Initialize self. See help(type(self)) for accurate signature.

cause = None
exception jeevesagent.core.OutputValidationError(message: str, *, raw: str, schema: type, cause: BaseException | None = None)[source]

Bases: JeevesAgentError

The model’s final answer did not validate against the supplied output_schema.

Raised by Agent.run() when the caller passed output_schema= and the model’s final assistant text could not be parsed/validated as the requested Pydantic model — even after the optional one-shot “retry with the validation error” turn.

Carries the raw model output (raw), the underlying Pydantic pydantic.ValidationError (cause, also exposed via __cause__), and the schema that was being targeted (schema) so callers can build whatever recovery strategy they need (re-prompt with extra examples, fall back to free-text, etc.).

Initialize self. See help(type(self)) for accurate signature.

cause = None
raw
schema
exception jeevesagent.core.PermanentModelError(message: str, *, cause: BaseException | None = None)[source]

Bases: ModelError

A model call failed in a way that retrying will not fix.

Wrong API key, malformed request, content-filter rejection, deprecated model name, etc. The retry layer raises these immediately without backoff so callers can fail fast and surface the real problem.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.PermissionDenied(tool: str, reason: str)[source]

Bases: JeevesAgentError

A tool call was denied by the permission layer or a user hook.

Initialize self. See help(type(self)) for accurate signature.

reason
tool
exception jeevesagent.core.RateLimitError(message: str, *, retry_after: float | None = None, cause: BaseException | None = None)[source]

Bases: TransientModelError

The provider returned a 429 / quota-exhausted response.

Carries retry_after when the provider supplied one. Subclass of TransientModelError so generic transient handlers cover it; catch RateLimitError specifically when you need to surface “slow down” to the caller (e.g. propagate a 429 to your own clients).

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.RuntimeJournalError[source]

Bases: JeevesAgentError

The durable runtime journal is unreadable or inconsistent.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.SandboxError[source]

Bases: JeevesAgentError

The sandbox refused or failed to execute a tool.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.ToolError[source]

Bases: JeevesAgentError

A tool invocation failed at the tool’s own boundary.

Initialize self. See help(type(self)) for accurate signature.

exception jeevesagent.core.TransientModelError(message: str, *, retry_after: float | None = None, cause: BaseException | None = None)[source]

Bases: ModelError

A model call failed in a way that may succeed on retry.

Covers HTTP 5xx responses, network errors, timeouts, and provider-side rate limits. The retry layer treats this family as retryable and applies backoff.

retry_after (in seconds) carries a provider-supplied hint when one is available — e.g. an Retry-After HTTP header on a 429 response. The retry layer respects the larger of the policy’s computed backoff and retry_after so we never wait less than the provider asked for.

Initialize self. See help(type(self)) for accurate signature.

retry_after = None
class jeevesagent.core.AuditEntry(/, **data: Any)[source]

Bases: pydantic.BaseModel

An immutable, signed entry in the audit log.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

action: str
actor: str
model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

payload: dict[str, Any]
seq: int
session_id: str
signature: str
timestamp: datetime.datetime
class jeevesagent.core.Budget[source]

Bases: Protocol

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

async allows_step() jeevesagent.core.types.BudgetStatus[source]
async consume(*, tokens_in: int, tokens_out: int, cost_usd: float) None[source]
class jeevesagent.core.BudgetStatus(/, **data: Any)[source]

Bases: pydantic.BaseModel

Result of a budget check before each step.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

classmethod blocked_(reason: str) BudgetStatus[source]
classmethod ok_() BudgetStatus[source]
classmethod warn_(reason: str) BudgetStatus[source]
property blocked: bool
model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

reason: str | None = None
state: Literal['ok', 'warn', 'blocked']
property warn: bool
class jeevesagent.core.CertifiedValue(/, **data: Any)[source]

Bases: pydantic.BaseModel

A value carrying provenance metadata for freshness/lineage checks.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

fetched_at: datetime.datetime
lineage: tuple[str, Ellipsis] = ()
model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

schema_version: str = '1'
source: str
valid_until: datetime.datetime | None = None
value: Any
class jeevesagent.core.Embedder[source]

Bases: Protocol

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

async embed(text: str) list[float][source]
async embed_batch(texts: list[str]) list[list[float]][source]
dimensions: int
name: str
class jeevesagent.core.Episode(/, **data: Any)[source]

Bases: pydantic.BaseModel

A single (input, decisions, tool calls, output) tuple from history.

user_id is the framework-managed namespace partition. Episodes persisted with one user_id value are never visible to memory recall queries scoped to a different user_id. None is its own bucket — the “anonymous / single-tenant” namespace — and does not see episodes belonging to a non-None user_id (and vice versa). Set automatically from RunContext by the agent loop; pass explicitly when constructing episodes outside a run.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

format() str[source]
embedding: list[float] | None = None
id: str = None
input: str
occurred_at: datetime.datetime = None
output: str
session_id: str
tool_calls: list[ToolCall] = None
user_id: str | None = None
class jeevesagent.core.Event(/, **data: Any)[source]

Bases: pydantic.BaseModel

A single observable record from a running session.

Carries a discriminator (kind) plus a free-form payload. Construct via the class methods to ensure consistent shapes.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

classmethod architecture_event(session_id: str, name: str, **data: Any) Event[source]

Generic architecture-progress event.

name is a namespaced string identifying the source architecture and the kind of progress (e.g. "self_refine.critique", "reflexion.lesson_persisted", "router.classified"). data is merged into the payload alongside name so consumers can pattern-match on name and read structured fields off the rest.

classmethod budget_exceeded(session_id: str, status: BudgetStatus) Event[source]
classmethod budget_warning(session_id: str, status: BudgetStatus) Event[source]
classmethod completed(session_id: str, result: Any) Event[source]
classmethod error(session_id: str, exc: BaseException) Event[source]
classmethod model_chunk(session_id: str, chunk: ModelChunk) Event[source]
classmethod started(session_id: str, prompt: str) Event[source]
classmethod tool_call(session_id: str, call: ToolCall) Event[source]
classmethod tool_result(session_id: str, result: ToolResult) Event[source]
at: datetime.datetime = None
kind: EventKind
payload: dict[str, Any] = None
session_id: str
class jeevesagent.core.EventKind[source]

Bases: enum.StrEnum

Enum where members are also (and must be) strings

Initialize self. See help(type(self)) for accurate signature.

ARCHITECTURE_EVENT = 'architecture_event'

Generic architecture-progress event. Carries a namespaced name in the payload (e.g. "self_refine.critique", "reflexion.lesson_persisted", "router.classified") so each architecture can stream its own progress signal without expanding EventKind.

BUDGET_EXCEEDED = 'budget_exceeded'
BUDGET_WARNING = 'budget_warning'
COMPLETED = 'completed'
ERROR = 'error'
MEMORY_RECALL = 'memory_recall'
MEMORY_WRITE = 'memory_write'
MODEL_CHUNK = 'model_chunk'
PERMISSION_ASK = 'permission_ask'
PERMISSION_DECISION = 'permission_decision'
STARTED = 'started'
TOOL_CALL = 'tool_call'
TOOL_RESULT = 'tool_result'
class jeevesagent.core.Fact(/, **data: Any)[source]

Bases: pydantic.BaseModel

A semantic claim extracted from one or more episodes.

Bi-temporal: valid_from/valid_until tracks when the fact was true in the world; recorded_at tracks when we learned it.

user_id is the framework-managed namespace partition. Facts persisted with one user_id value are never visible to recall queries scoped to a different user_id. Set automatically from RunContext by the agent loop / consolidator; pass explicitly when constructing facts outside a run.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

format() str[source]
confidence: float = 1.0
id: str = None
object: str
predicate: str
recorded_at: datetime.datetime = None
sources: list[str] = None
subject: str
user_id: str | None = None
valid_from: datetime.datetime = None
valid_until: datetime.datetime | None = None
class jeevesagent.core.HookHost[source]

Bases: Protocol

Aggregator over user-registered lifecycle callbacks.

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) jeevesagent.core.types.PermissionDecision[source]
class jeevesagent.core.Memory[source]

Bases: Protocol

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

async append_block(name: str, content: str) None[source]

Append to a named block, creating it if absent.

async consolidate() None[source]

Background: extract semantic facts from recent episodes.

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_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.

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 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 recall(): None is 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 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 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.

async update_block(name: str, content: str) None[source]

Replace the contents of a named block.

async working() list[jeevesagent.core.types.MemoryBlock][source]

All in-context blocks. Pinned to every prompt.

class jeevesagent.core.MemoryBlock(/, **data: Any)[source]

Bases: pydantic.BaseModel

An in-context memory block, pinned to every prompt.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

format() str[source]
content: str
name: str
pinned_order: int = 0
updated_at: datetime.datetime = None
class jeevesagent.core.Message(/, **data: Any)[source]

Bases: pydantic.BaseModel

A single chat message in the model’s conversation.

tool_calls is populated on assistant messages that emitted tool calls in the previous turn — real provider adapters (Anthropic tool_use blocks, OpenAI tool_calls array) need to reconstruct the right wire format from this.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

content: str
model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str | None = None
role: Role
tool_call_id: str | None = None
tool_calls: tuple[ToolCall, Ellipsis] = ()
class jeevesagent.core.Model[source]

Bases: 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().

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.

name: str
class jeevesagent.core.ModelChunk(/, **data: Any)[source]

Bases: pydantic.BaseModel

A single chunk from a streaming model call.

Discriminated by kind. Exactly one of the optional fields is set depending on the kind.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

finish_reason: str | None = None
kind: Literal['text', 'tool_call', 'finish']
text: str | None = None
tool_call: ToolCall | None = None
usage: Usage | None = None
class jeevesagent.core.PermissionDecision(/, **data: Any)[source]

Bases: pydantic.BaseModel

Outcome of a permission check or pre-tool hook.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

classmethod allow_(reason: str | None = None) PermissionDecision[source]
classmethod ask_(reason: str | None = None) PermissionDecision[source]
classmethod deny_(reason: str) PermissionDecision[source]
property allow: bool
property ask: bool
decision: Literal['allow', 'deny', 'ask']
property deny: bool
model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

reason: str | None = None
class jeevesagent.core.Permissions[source]

Bases: Protocol

Decides whether a tool call is allowed.

async check(call: jeevesagent.core.types.ToolCall, *, context: collections.abc.Mapping[str, Any]) jeevesagent.core.types.PermissionDecision[source]
class jeevesagent.core.Role[source]

Bases: enum.StrEnum

Enum where members are also (and must be) strings

Initialize self. See help(type(self)) for accurate signature.

ASSISTANT = 'assistant'
SYSTEM = 'system'
TOOL = 'tool'
USER = 'user'
class jeevesagent.core.RunContext[source]

Typed, immutable context for one agent run.

Set once at the start of Agent.run() and propagated to every architecture, tool, hook, sub-agent, and memory operation via a contextvars.ContextVar. The framework treats user_id and session_id as first-class fields (typed, namespaced); metadata is an opaque bag for app-specific keys the framework does not interpret.

Construct one directly when you need to spawn work outside an active run with explicit scope:

ctx = RunContext(user_id="alice", session_id="conv_42")
async with set_run_context(ctx):
    await my_tool(...)

Inside an agent run, prefer get_run_context() over constructing a new one — that gives you the live context the framework set up.

get(key: str, default: Any = None) Any[source]

Shorthand for self.metadata.get(key, default).

with_overrides(*, user_id: str | None | _Sentinel = _Sentinel.UNSET, session_id: str | None | _Sentinel = _Sentinel.UNSET, run_id: str | _Sentinel = _Sentinel.UNSET, metadata: collections.abc.Mapping[str, Any] | _Sentinel = _Sentinel.UNSET) RunContext[source]

Return a new context with selected fields replaced.

Used by multi-agent architectures when spawning sub-agents that need to inherit most of the parent’s context but with a derived session_id or augmented metadata. The sentinel makes “leave this field unchanged” distinguishable from “explicitly set this field to None”.

metadata: collections.abc.Mapping[str, Any]

Free-form application context. Use this for keys the framework does not need to understand — locale, request id, feature flags, tenant id beyond user_id, etc. Read inside tools / hooks via get_run_context().metadata.

run_id: str = ''

Unique identifier for this single Agent.run() invocation. Distinct from session_id (which identifies a conversation that may span many runs). Auto-set by Agent.run(); an explicit value passed in by the caller is overridden.

session_id: str | None = None

Conversation thread identifier. Reusing the same session_id across calls signals “continue this conversation” — the framework will rehydrate prior session messages so the model sees real chat history, not just memory recall. None means “fresh conversation”; the framework auto-generates one inside Agent.run() if not supplied.

user_id: str | None = None

Namespace for memory recall + persistence. None is the “anonymous / single-tenant” bucket; episodes / facts stored with user_id=None never see episodes / facts stored with a non-None user_id and vice versa. The framework treats this as a hard partition key, not a soft filter.

class jeevesagent.core.RunResult(/, **data: Any)[source]

Bases: pydantic.BaseModel

Final outcome of an Agent.run call.

output is always the raw assistant text (the JSON itself when a structured-output schema was supplied). parsed is the validated Pydantic instance — populated only when the caller passed output_schema= to Agent.run(). Use whichever fits the call site:

# free-form text run
result = await agent.run("summarise this PDF")
print(result.output)

# structured-output run
result = await agent.run(prompt, output_schema=Invoice)
invoice: Invoice = result.parsed   # typed, validated

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

cost_usd: float = 0.0
property duration: datetime.timedelta

Wall-clock latency between started_at and finished_at.

finished_at: datetime.datetime
interrupted: bool = False
interruption_reason: str | None = None
model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

output: str
parsed: Any | None = None

The validated Pydantic instance when output_schema= was supplied to Agent.run(); None otherwise. Typed as Any to keep the runtime type free; the call site has the schema and can cast or annotate as needed.

session_id: str
started_at: datetime.datetime
tokens_in: int = 0
tokens_out: int = 0
property total_tokens: int

tokens_in + tokens_out.

Type:

Convenience

turns: int
class jeevesagent.core.Runtime[source]

Bases: Protocol

Durable 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 fn as 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.

name: str
class jeevesagent.core.RuntimeSession[source]

Bases: Protocol

Handle to an open durable session held by a Runtime.

async deliver(name: str, payload: Any) None[source]
id: str
class jeevesagent.core.Sandbox[source]

Bases: Protocol

Isolation 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.Secrets[source]

Bases: Protocol

Resolution and redaction of named secrets.

redact(text: str) str[source]
async resolve(ref: str) str[source]
async store(ref: str, value: str) None[source]
class jeevesagent.core.Span(/, **data: Any)[source]

Bases: pydantic.BaseModel

A trace span handle. Concrete telemetry adapters return their own representation; this is the value-object contract for in-process use.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

attributes: dict[str, Any] = None
name: str
span_id: str
started_at: datetime.datetime = None
trace_id: str
class jeevesagent.core.Telemetry[source]

Bases: Protocol

OpenTelemetry-compatible tracing/metrics surface.

async emit_metric(name: str, value: float, **attrs: Any) None[source]
trace(name: str, **attrs: Any) contextlib.AbstractAsyncContextManager[jeevesagent.core.types.Span][source]
class jeevesagent.core.ToolCall(/, **data: Any)[source]

Bases: pydantic.BaseModel

A model-emitted request to invoke a tool.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

idempotency_key() str[source]
is_destructive() bool[source]
args: dict[str, Any] = None
destructive: bool = False
id: str = None
tool: str
tool_def: ToolDef | None = None
class jeevesagent.core.ToolDef(/, **data: Any)[source]

Bases: pydantic.BaseModel

Schema description of a tool the model can call.

Mirrors the JSON-Schema-flavored shape used across MCP and provider APIs.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

description: str
input_schema: dict[str, Any] = None
model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str
server: str | None = None
class jeevesagent.core.ToolEvent(/, **data: Any)[source]

Bases: pydantic.BaseModel

Tool registry change notification (MCP listChanged etc.).

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

at: datetime.datetime = None
kind: Literal['added', 'removed', 'updated']
server: str | None = None
tool: str
class jeevesagent.core.ToolHost[source]

Bases: Protocol

MCP-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 tool with args. The call_id is propagated into the returned ToolResult so the loop can correlate results with the originating model-emitted call.

async list_tools(*, query: str | None = None) list[jeevesagent.core.types.ToolDef][source]
watch() collections.abc.AsyncIterator[jeevesagent.core.types.ToolEvent][source]

Notifications when the tool list changes (MCP listChanged).

class jeevesagent.core.ToolResult(/, **data: Any)[source]

Bases: pydantic.BaseModel

Outcome of a tool invocation.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

classmethod denied_(call_id: str, reason: str, **kwargs: Any) ToolResult[source]
classmethod error_(call_id: str, message: str, **kwargs: Any) ToolResult[source]
classmethod success(call_id: str, output: Any, **kwargs: Any) ToolResult[source]
call_id: str
denied: bool = False
duration_ms: float | None = None
error: str | None = None
ok: bool
output: Any = None
reason: str | None = None
started_at: datetime.datetime = None
class jeevesagent.core.Usage(/, **data: Any)[source]

Bases: pydantic.BaseModel

Token and cost accounting for a model call.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

cost_usd: float = 0.0
input_tokens: int = 0
model_config

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

output_tokens: int = 0
class jeevesagent.core.set_run_context(context: RunContext)[source]

Context manager that installs a RunContext for the duration of an async with block.

The framework uses this internally inside Agent.run() to expose the live context to tools and hooks. Application code rarely needs it, but it is the supported way to invoke a tool outside an agent loop with explicit scope — for example in background workers that share tool implementations with the agent:

async with set_run_context(RunContext(user_id="alice")):
    await some_tool(...)

Behaves correctly under structured concurrency: nested async with blocks restore the prior context on exit, and anyio task-group spawns inherit the active context automatically.

jeevesagent.core.deterministic_hash(*parts: Any) str[source]

Stable hash of arbitrary JSON-serializable parts.

Used as an idempotency key for journaled steps. The hash is stable across processes and Python versions because the input is canonicalised via json.dumps(..., sort_keys=True).

jeevesagent.core.get_run_context() RunContext[source]

Return the RunContext for the currently-running agent.

Inside an active Agent.run() call this returns the live context with user_id, session_id, run_id, and metadata populated. Outside any active run (test code, direct @tool invocation, REPL exploration) this returns the default empty RunContext — never raises.

Tools that need scope information call this rather than taking extra parameters:

@tool
async def fetch_user_orders() -> str:
    ctx = get_run_context()
    return await db.query("orders", user_id=ctx.user_id)
jeevesagent.core.new_id(prefix: str = '') str[source]

Return a fresh ULID, optionally prefixed for readability.

>>> new_id("ep").startswith("ep_")
True