jeevesagent
===========

.. py:module:: jeevesagent

.. autoapi-nested-parse::

   JeevesAgent — model-agnostic, MCP-native agent harness.



Submodules
----------

.. toctree::
   :maxdepth: 1

   /api/jeevesagent/agent/index
   /api/jeevesagent/architecture/index
   /api/jeevesagent/core/index
   /api/jeevesagent/data/index
   /api/jeevesagent/governance/index
   /api/jeevesagent/graph/index
   /api/jeevesagent/jeeves/index
   /api/jeevesagent/loader/index
   /api/jeevesagent/mcp/index
   /api/jeevesagent/memory/index
   /api/jeevesagent/model/index
   /api/jeevesagent/observability/index
   /api/jeevesagent/runtime/index
   /api/jeevesagent/security/index
   /api/jeevesagent/skills/index
   /api/jeevesagent/team/index
   /api/jeevesagent/tools/index
   /api/jeevesagent/vectorstore/index


Exceptions
----------

.. autoapisummary::

   jeevesagent.AuthenticationError
   jeevesagent.BudgetExceeded
   jeevesagent.CancelledByUser
   jeevesagent.ConfigError
   jeevesagent.ContentFilterError
   jeevesagent.FreshnessError
   jeevesagent.InvalidRequestError
   jeevesagent.IsolationWarning
   jeevesagent.JeevesAgentError
   jeevesagent.LineageError
   jeevesagent.MCPError
   jeevesagent.MemoryStoreError
   jeevesagent.ModelError
   jeevesagent.OutputValidationError
   jeevesagent.PathEscapeError
   jeevesagent.PermanentModelError
   jeevesagent.PermissionDenied
   jeevesagent.RateLimitError
   jeevesagent.RuntimeJournalError
   jeevesagent.SandboxError
   jeevesagent.SkillError
   jeevesagent.ToolError
   jeevesagent.TransientModelError


Classes
-------

.. autoapisummary::

   jeevesagent.ActorCritic
   jeevesagent.Agent
   jeevesagent.AgentGraph
   jeevesagent.AgentSession
   jeevesagent.AllowAll
   jeevesagent.AnthropicModel
   jeevesagent.Architecture
   jeevesagent.AuditEntry
   jeevesagent.AuditLog
   jeevesagent.Blackboard
   jeevesagent.BlackboardArchitecture
   jeevesagent.BlackboardEntry
   jeevesagent.Budget
   jeevesagent.BudgetStatus
   jeevesagent.CertifiedValue
   jeevesagent.ChromaFactStore
   jeevesagent.ChromaMemory
   jeevesagent.ChromaVectorStore
   jeevesagent.CohereEmbedder
   jeevesagent.ConsolidationWorker
   jeevesagent.Consolidator
   jeevesagent.Dependencies
   jeevesagent.EchoModel
   jeevesagent.Embedder
   jeevesagent.Episode
   jeevesagent.Event
   jeevesagent.EventKind
   jeevesagent.FAISSVectorStore
   jeevesagent.Fact
   jeevesagent.FactStore
   jeevesagent.FileAuditLog
   jeevesagent.FilesystemSandbox
   jeevesagent.FreshnessPolicy
   jeevesagent.Handoff
   jeevesagent.HashEmbedder
   jeevesagent.HookHost
   jeevesagent.HookRegistry
   jeevesagent.InMemoryAuditLog
   jeevesagent.InMemoryFactStore
   jeevesagent.InMemoryJournalStore
   jeevesagent.InMemoryMemory
   jeevesagent.InMemoryVectorStore
   jeevesagent.InProcRuntime
   jeevesagent.InProcessToolHost
   jeevesagent.JeevesConfig
   jeevesagent.JeevesGateway
   jeevesagent.JournalStore
   jeevesagent.JournaledRuntime
   jeevesagent.LineagePolicy
   jeevesagent.LiteLLMModel
   jeevesagent.MCPClient
   jeevesagent.MCPRegistry
   jeevesagent.MCPServerSpec
   jeevesagent.Memory
   jeevesagent.MemoryBlock
   jeevesagent.Message
   jeevesagent.Mode
   jeevesagent.Model
   jeevesagent.ModelChunk
   jeevesagent.MultiAgentDebate
   jeevesagent.NoSandbox
   jeevesagent.NoTelemetry
   jeevesagent.OTelTelemetry
   jeevesagent.OpenAIEmbedder
   jeevesagent.OpenAIModel
   jeevesagent.PermissionDecision
   jeevesagent.Permissions
   jeevesagent.Plan
   jeevesagent.PlanAndExecute
   jeevesagent.PlanStep
   jeevesagent.PostgresFactStore
   jeevesagent.PostgresJournalStore
   jeevesagent.PostgresMemory
   jeevesagent.PostgresRuntime
   jeevesagent.PostgresVectorStore
   jeevesagent.ReAct
   jeevesagent.ReWOO
   jeevesagent.ReWOOPlan
   jeevesagent.ReWOOStep
   jeevesagent.ReWOOStepResult
   jeevesagent.RedisFactStore
   jeevesagent.RedisMemory
   jeevesagent.Reflexion
   jeevesagent.RetryPolicy
   jeevesagent.Role
   jeevesagent.Router
   jeevesagent.RouterRoute
   jeevesagent.RunContext
   jeevesagent.RunResult
   jeevesagent.Runtime
   jeevesagent.RuntimeSession
   jeevesagent.Sandbox
   jeevesagent.ScriptedModel
   jeevesagent.ScriptedTurn
   jeevesagent.SearchResult
   jeevesagent.Secrets
   jeevesagent.SelfRefine
   jeevesagent.Skill
   jeevesagent.SkillMetadata
   jeevesagent.SkillRegistry
   jeevesagent.SkillSource
   jeevesagent.Span
   jeevesagent.SqliteFactStore
   jeevesagent.SqliteJournalStore
   jeevesagent.SqliteRuntime
   jeevesagent.StandardPermissions
   jeevesagent.StepResult
   jeevesagent.SubprocessSandbox
   jeevesagent.Supervisor
   jeevesagent.Swarm
   jeevesagent.Team
   jeevesagent.Telemetry
   jeevesagent.ThoughtNode
   jeevesagent.Tool
   jeevesagent.ToolCall
   jeevesagent.ToolDef
   jeevesagent.ToolEvent
   jeevesagent.ToolHost
   jeevesagent.ToolResult
   jeevesagent.TreeOfThoughts
   jeevesagent.Usage
   jeevesagent.VectorMemory
   jeevesagent.VectorStore
   jeevesagent.VoyageEmbedder
   jeevesagent.set_run_context


Functions
---------

.. autoapisummary::

   jeevesagent.bash_tool
   jeevesagent.build_graph
   jeevesagent.classify_model_error
   jeevesagent.default_workdir
   jeevesagent.deterministic_hash
   jeevesagent.edit_tool
   jeevesagent.filesystem_tools
   jeevesagent.get_run_context
   jeevesagent.new_id
   jeevesagent.read_tool
   jeevesagent.resolve_architecture
   jeevesagent.run_architecture
   jeevesagent.tool
   jeevesagent.write_graph
   jeevesagent.write_tool


Package Contents
----------------

.. py:exception:: AuthenticationError(message: str, *, cause: BaseException | None = None)

   Bases: :py:obj:`PermanentModelError`


   Invalid, missing, or revoked API credentials.

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


.. py:exception:: BudgetExceeded(reason: str)

   Bases: :py:obj:`JeevesAgentError`


   A run was halted because a budget limit was hit.

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


   .. py:attribute:: reason


.. py:exception:: CancelledByUser

   Bases: :py:obj:`JeevesAgentError`


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

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


.. py:exception:: ConfigError

   Bases: :py:obj:`JeevesAgentError`


   Invalid or unresolvable configuration passed to ``Agent``.

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


.. py:exception:: ContentFilterError(message: str, *, cause: BaseException | None = None)

   Bases: :py:obj:`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.


.. py:exception:: FreshnessError

   Bases: :py:obj:`JeevesAgentError`


   A certified value failed its freshness policy.

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


.. py:exception:: InvalidRequestError(message: str, *, cause: BaseException | None = None)

   Bases: :py:obj:`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.


.. py:exception:: IsolationWarning

   Bases: :py:obj:`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 :class:`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.


.. py:exception:: JeevesAgentError

   Bases: :py:obj:`Exception`


   Base class for all harness errors.

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


.. py:exception:: LineageError

   Bases: :py:obj:`JeevesAgentError`


   A certified value failed its lineage policy.

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


.. py:exception:: MCPError

   Bases: :py:obj:`JeevesAgentError`


   An MCP transport, handshake, or protocol error.

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


.. py:exception:: MemoryStoreError

   Bases: :py:obj:`JeevesAgentError`


   The memory backend failed an operation.

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


.. py:exception:: ModelError(message: str, *, cause: BaseException | None = None)

   Bases: :py:obj:`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.


   .. py:attribute:: cause
      :value: None



.. py:exception:: OutputValidationError(message: str, *, raw: str, schema: type, cause: BaseException | None = None)

   Bases: :py:obj:`JeevesAgentError`


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

   Raised by :meth:`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
   :class:`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.


   .. py:attribute:: cause
      :value: None



   .. py:attribute:: raw


   .. py:attribute:: schema


.. py:exception:: PathEscapeError

   Bases: :py:obj:`ValueError`


   Raised when a tool argument resolves outside its workdir.

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


.. py:exception:: PermanentModelError(message: str, *, cause: BaseException | None = None)

   Bases: :py:obj:`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.


.. py:exception:: PermissionDenied(tool: str, reason: str)

   Bases: :py:obj:`JeevesAgentError`


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

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


   .. py:attribute:: reason


   .. py:attribute:: tool


.. py:exception:: RateLimitError(message: str, *, retry_after: float | None = None, cause: BaseException | None = None)

   Bases: :py:obj:`TransientModelError`


   The provider returned a 429 / quota-exhausted response.

   Carries ``retry_after`` when the provider supplied one. Subclass
   of :class:`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.


.. py:exception:: RuntimeJournalError

   Bases: :py:obj:`JeevesAgentError`


   The durable runtime journal is unreadable or inconsistent.

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


.. py:exception:: SandboxError

   Bases: :py:obj:`JeevesAgentError`


   The sandbox refused or failed to execute a tool.

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


.. py:exception:: SkillError

   Bases: :py:obj:`ValueError`


   Raised on invalid skill construction or frontmatter.

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


.. py:exception:: ToolError

   Bases: :py:obj:`JeevesAgentError`


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

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


.. py:exception:: TransientModelError(message: str, *, retry_after: float | None = None, cause: BaseException | None = None)

   Bases: :py:obj:`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.


   .. py:attribute:: retry_after
      :value: None



.. py:class:: ActorCritic(*, actor: jeevesagent.agent.api.Agent, critic: jeevesagent.agent.api.Agent, max_rounds: int = 3, approval_threshold: float = 0.9, critique_template: str | None = None, refine_template: str | None = None)

   Actor + adversarial critic with optional different models.

   Constructor parameters:

   * ``actor`` (required): the generating :class:`Agent`. Sees the
     original prompt on round 0 and a refine prompt on subsequent
     rounds.
   * ``critic`` (required): the reviewing :class:`Agent`. Sees the
     original prompt + the actor's current output and produces
     structured JSON critique.
   * ``max_rounds``: cap on critique-refine cycles after the
     initial generation. Default 3.
   * ``approval_threshold``: terminate when ``critique.score`` is
     at or above this value. Default 0.9.
   * ``critique_template`` / ``refine_template``: override the
     default prompts. Templates use ``{prompt}``, ``{output}``,
     ``{critique}``, ``{issues_bulleted}``.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'actor-critic'



.. py:class:: Agent(instructions: str, *, model: jeevesagent.core.protocols.Model | str | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, budget: jeevesagent.core.protocols.Budget | None = None, permissions: jeevesagent.core.protocols.Permissions | None = None, hooks: jeevesagent.security.hooks.HookRegistry | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object] | None = None, telemetry: jeevesagent.core.protocols.Telemetry | None = None, audit_log: jeevesagent.security.audit.AuditLog | None = None, max_turns: int = DEFAULT_MAX_TURNS, auto_consolidate: bool = False, architecture: jeevesagent.architecture.Architecture | str | None = None, skills: list[Any] | None = None, retry_policy: jeevesagent.governance.retry.RetryPolicy | None = None)

   A fully-async, MCP-native, model-agnostic agent harness.


   .. py:method:: add_tool(item: jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]) -> jeevesagent.tools.registry.Tool

      Register a tool after construction.

      Convenience for plugin-style code that adds tools after the
      ``Agent`` exists. Only works when the underlying tool host is
      an :class:`InProcessToolHost` (the default — and the only host
      that has a writable registry today).

      Returns the constructed :class:`Tool` so callers can introspect
      the auto-derived schema.



   .. py:method:: after_tool(fn: jeevesagent.security.hooks.PostToolHook) -> jeevesagent.security.hooks.PostToolHook

      Register a best-effort post-tool callback.



   .. py:method:: before_tool(fn: jeevesagent.security.hooks.PreToolHook) -> jeevesagent.security.hooks.PreToolHook

      Register a pre-tool hook. First denial wins; allow otherwise.



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


      Manually trigger memory consolidation.

      Returns the number of new facts the consolidator extracted,
      or ``0`` when the memory backend doesn't expose a fact store.

      Useful when ``auto_consolidate=False`` (the default) and you
      want to batch consolidation at a controlled cadence — e.g.
      once a day, or before shutdown.



   .. py:method:: from_config(path: str | pathlib.Path, *, model: jeevesagent.core.protocols.Model | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | None = None) -> Agent
      :classmethod:


      Construct an ``Agent`` from a TOML config file.

      Designed for ops/devops users who want declarative agent
      config separate from code. Supports the textual / numeric
      bits — instructions, model spec (string), max_turns,
      auto_consolidate, budget — and lets callers pass concrete
      instances for the things TOML can't reasonably express
      (real ``Memory``, ``Runtime``, custom ``Model``, tools).

      Example ``agent.toml``::

          instructions = "You are a research assistant."
          model = "claude-opus-4-7"
          max_turns = 100
          auto_consolidate = true

          [budget]
          max_tokens = 200_000
          max_cost_usd = 5.0
          max_wall_clock_minutes = 10
          soft_warning_at = 0.8

      Then::

          agent = Agent.from_config("agent.toml")



   .. py:method:: from_dict(cfg: dict[str, Any], *, model: jeevesagent.core.protocols.Model | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | None = None) -> Agent
      :classmethod:


      Construct an ``Agent`` from a parsed config dict.

      Same shape as :meth:`from_config` but skips the file read.
      Useful when the config comes from somewhere other than a TOML
      file — environment variables, a Pydantic settings model, a
      ``yaml.safe_load`` result, an HTTP API, etc.

      Recognised keys (all optional except ``instructions`` and
      ``model``):

      * ``instructions: str`` — required
      * ``model: str`` — required (or pass ``model=`` kwarg)
      * ``max_turns: int``
      * ``auto_consolidate: bool``
      * ``budget: dict`` with any of ``max_tokens``,
        ``max_input_tokens``, ``max_output_tokens``, ``max_cost_usd``,
        ``max_wall_clock_minutes``, ``soft_warning_at``



   .. py:method:: generate_graph(path: str | pathlib.Path | None = None, *, title: str | None = None) -> str
      :async:


      Render this agent's structure as a Mermaid graph.

      Walks the agent + its architecture + all sub-agents +
      every agent's tools, producing a graph that captures the
      full team, tool attachments, and architecture-specific
      relationships (delegate / handoff / classify / etc.).

      Returns the Mermaid text. If ``path`` is provided, also
      writes to disk — extension determines the format:

      * ``.mmd`` — raw Mermaid source
      * ``.md``  — Markdown with the diagram in a ``mermaid``
        fence (renders on GitHub, IDE markdown previews,
        Jupyter)
      * ``.png`` / ``.svg`` — rendered via ``mermaid.ink``;
        falls back to ``.mmd`` next to the path on network
        failure

      Example::

          mermaid_text = await agent.generate_graph("graph.md")
          print(mermaid_text)

      Pass ``title=`` to override the diagram title (defaults to
      the file's stem, or ``"Agent"`` if no path is given).



   .. py:method:: recall(query: str, *, kind: str = 'episodic', limit: int = 5) -> list[Any]
      :async:


      Convenience wrapper around ``self.memory.recall(query, ...)``.

      Returns episodes matching ``query``. For semantic / fact-store
      recall, use ``self.memory.facts.recall_text(...)`` directly.



   .. py:method:: remove_tool(name: str) -> bool

      Unregister a tool by name. Returns ``True`` if a tool was
      removed, ``False`` if no tool with that name was registered.

      Same constraint as :meth:`add_tool`: only works with
      :class:`InProcessToolHost`.



   .. py:method:: resume(session_id: str, prompt: str, *, user_id: str | None = None, metadata: collections.abc.Mapping[str, Any] | None = None, context: jeevesagent.core.context.RunContext | None = None, extra_tools: list[jeevesagent.tools.registry.Tool] | None = None, emit: Emit | None = None, output_schema: type[pydantic.BaseModel] | None = None, output_validation_retries: int = 1) -> jeevesagent.core.types.RunResult
      :async:


      Resume a previously-interrupted run from its journal.

      Equivalent to ``agent.run(prompt, session_id=session_id, ...)``
      with the same kwarg surface as :meth:`run`. Exists as a
      separate method so the intent is explicit at the call site
      — when a durable :class:`Runtime` (e.g. :class:`SqliteRuntime`)
      is configured, completed steps replay from the journal
      instead of re-executing.



   .. py:method:: run(prompt: str, *, user_id: str | None = None, session_id: str | None = None, metadata: collections.abc.Mapping[str, Any] | None = None, context: jeevesagent.core.context.RunContext | None = None, extra_tools: list[jeevesagent.tools.registry.Tool] | None = None, emit: Emit | None = None, output_schema: type[pydantic.BaseModel] | None = None, output_validation_retries: int = 1) -> jeevesagent.core.types.RunResult
      :async:


      Run the agent to completion and return its :class:`RunResult`.

      ``user_id`` is the namespace partition for memory recall and
      persistence — episodes and facts stored with one ``user_id``
      are never visible to a query scoped to a different ``user_id``.
      ``None`` is the "anonymous / single-tenant" bucket. See
      :class:`~jeevesagent.RunContext` for the partitioning
      contract.

      Pass ``session_id`` to resume a journaled run — when paired with
      a durable runtime (e.g. :class:`SqliteRuntime`), already-completed
      steps replay from the journal instead of re-executing. Without a
      durable runtime, ``session_id`` just labels the run.

      ``metadata`` is a free-form bag for application context the
      framework does not interpret (locale, request id, feature
      flags). Tools and hooks read it via
      ``get_run_context().metadata``.

      ``context`` accepts a fully-formed :class:`RunContext` instead
      of the individual kwargs — useful when passing context through
      multi-agent boundaries that received their parent's context as
      a single object. When both ``context`` and the individual
      kwargs are provided, the kwargs override the corresponding
      fields on ``context``.

      ``extra_tools`` injects additional :class:`Tool`\ s for this
      run only — the agent's configured ``ToolHost`` is wrapped so
      the model sees the extras alongside whatever tools were
      registered at construction. Used by multi-agent architectures
      that need to inject coordination tools (e.g. Swarm's
      ``handoff(target, message)``) into a peer agent's loop without
      permanently mutating that agent's static configuration.

      ``emit`` is an awaitable callback invoked once per
      :class:`Event` produced during the run (model chunks, tool
      calls, tool results, architecture progress, errors, ...).
      Default ``None`` drops events on the floor (regular ``run``
      semantics — return only the final ``RunResult``). Multi-agent
      architectures pass an emit that forwards a sub-Agent's events
      into the parent's stream, so calls like ``await
      worker.run(prompt, emit=parent_send)`` surface the worker's
      token-by-token streaming to the outermost ``agent.stream(...)``
      consumer.

      ``output_schema`` requests a structured, validated final
      answer. Pass any Pydantic ``BaseModel`` subclass and the
      framework will (1) append a JSON-schema directive to the
      system prompt instructing the model to emit a final answer
      that matches, (2) parse the final assistant text against the
      schema, and (3) populate :attr:`RunResult.parsed` with the
      validated instance. ``RunResult.output`` keeps the raw text
      so you can log or display it. Up to
      ``output_validation_retries`` extra turns are spent
      recovering from a parse failure (the model is given the
      validation error as feedback and asked to try again); if it
      still fails after the retry budget, the run raises
      :class:`~jeevesagent.OutputValidationError`. Set retries to
      0 to fail fast.



   .. py:method:: stream(prompt: str, *, user_id: str | None = None, session_id: str | None = None, metadata: collections.abc.Mapping[str, Any] | None = None, context: jeevesagent.core.context.RunContext | None = None, extra_tools: list[jeevesagent.tools.registry.Tool] | None = None, output_schema: type[pydantic.BaseModel] | None = None, output_validation_retries: int = 1) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:


      Stream :class:`Event`\ s as the loop produces them.

      The loop runs as a background task; events are pushed through a
      bounded memory stream so a slow consumer applies backpressure.
      Breaking out of the iteration cancels the producer cleanly.
      ``session_id`` works the same as :meth:`run`'s — pass an
      existing one to resume against a durable runtime's journal.
      ``extra_tools`` works the same as :meth:`run`'s.



   .. py:method:: tools_list() -> list[str]
      :async:


      Return the names of all currently-registered tools.

      Convenience that works for any :class:`ToolHost`. Calls
      ``tool_host.list_tools()`` under the hood and returns just the
      names; use ``self.tool_host.list_tools()`` directly for the
      full :class:`ToolDef` records.



   .. py:method:: with_tool(fn: collections.abc.Callable[Ellipsis, object]) -> collections.abc.Callable[Ellipsis, object]

      Decorator-style equivalent of :meth:`add_tool`.

      Usage::

          @agent.with_tool
          async def search(query: str) -> str:
              '''Search a knowledge base.'''
              return f"results for {query}"

      Returns the original function unchanged (so it can still be
      called normally), and registers it as a tool on the agent's
      underlying :class:`InProcessToolHost`. Same constraint as
      :meth:`add_tool`: the host must be writable.



   .. py:property:: architecture
      :type: jeevesagent.architecture.Architecture


      The configured :class:`Architecture` strategy.

      Default is :class:`~jeevesagent.architecture.ReAct`. Pass
      ``architecture=`` to ``Agent(...)`` to override.


   .. py:property:: budget
      :type: jeevesagent.core.protocols.Budget


      The configured :class:`Budget`.


   .. py:property:: hooks
      :type: jeevesagent.core.protocols.HookHost



   .. py:property:: instructions
      :type: str


      The system prompt the agent runs with.

      Surfaced as a public property so multi-agent architectures
      (e.g. :class:`~jeevesagent.architecture.Supervisor`) can read
      each worker's intended role when composing instructions for
      the supervising model.


   .. py:property:: memory
      :type: jeevesagent.core.protocols.Memory


      The configured :class:`Memory` backend.


   .. py:property:: model
      :type: jeevesagent.core.protocols.Model


      The configured :class:`Model` adapter.


   .. py:property:: permissions
      :type: jeevesagent.core.protocols.Permissions


      The configured :class:`Permissions` policy.


   .. py:property:: runtime
      :type: jeevesagent.core.protocols.Runtime


      The configured :class:`Runtime`.


   .. py:property:: skills
      :type: Any | None


      The :class:`SkillRegistry` of skills registered on this
      agent (or ``None`` if no skills were configured). Useful for
      inspecting / mutating the skill set after construction.


   .. py:property:: tool_host
      :type: jeevesagent.core.protocols.ToolHost


      The configured :class:`ToolHost`.


.. py:class:: AgentGraph

   Renderable graph of an agent's structure.


   .. py:method:: to_mermaid() -> str

      Render to a Mermaid ``flowchart TB`` block.

      Output is plain Mermaid (no ``%%{init}%%`` directives) so it
      renders consistently across GitHub, IDE previews, and
      ``mermaid.ink``.



   .. py:attribute:: edges
      :type:  list[_Edge]
      :value: []



   .. py:attribute:: nodes
      :type:  list[_Node]
      :value: []



   .. py:attribute:: subgraphs
      :type:  list[_Subgraph]
      :value: []



   .. py:attribute:: title
      :type:  str
      :value: 'Agent'



.. py:class:: AgentSession

   Mutable per-run state shared between :class:`Agent` and an
   :class:`Architecture`.

   The :class:`Agent` constructs this once per run, the architecture
   mutates it as iteration progresses, and the :class:`Agent` reads
   the final state to build a :class:`RunResult`.

   ``metadata`` is a free-form dict architectures use for things
   that don't deserve their own field — multi-agent architectures
   stash worker handoff state, planners stash plans, etc.


   .. py:attribute:: cumulative_usage
      :type:  jeevesagent.core.types.Usage


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


   .. py:attribute:: instructions
      :type:  str


   .. py:attribute:: interrupted
      :type:  bool
      :value: False



   .. py:attribute:: interruption_reason
      :type:  str | None
      :value: None



   .. py:attribute:: messages
      :type:  list[jeevesagent.core.types.Message]
      :value: []



   .. py:attribute:: metadata
      :type:  dict[str, Any]


   .. py:attribute:: output
      :type:  str
      :value: ''



   .. py:attribute:: turns
      :type:  int
      :value: 0



.. py:class:: AllowAll

   Trivial permission policy: every call is allowed.

   The default for :class:`Agent` when no permissions are configured.


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



.. py:class:: AnthropicModel(model: str = 'claude-opus-4-7', *, client: Any = None, api_key: str | None = None, max_tokens: int = DEFAULT_MAX_TOKENS)

   Talks to Claude via :class:`anthropic.AsyncAnthropic`.


   .. py:method:: complete(messages: list[jeevesagent.core.types.Message], *, tools: list[jeevesagent.core.types.ToolDef] | None = None, temperature: float = 1.0, max_tokens: int | None = None) -> tuple[str, list[jeevesagent.core.types.ToolCall], jeevesagent.core.types.Usage, str]
      :async:


      Single-shot non-streaming completion.

      Calls ``client.messages.create(...)`` (no ``stream=True``,
      no ``stream`` context manager) — Anthropic returns the full
      ``Message`` in one HTTP response. We walk its ``content``
      blocks once to assemble ``(text, tool_calls, usage,
      stop_reason)``. Used by the non-streaming hot path
      (``agent.run()``); ``agent.stream()`` keeps using
      :meth:`stream`.

      Falls back to consuming :meth:`stream` if the underlying
      client raises (test fakes that only support streaming, or
      transports that don't honour single-shot creation).



   .. 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]
      :async:



   .. py:attribute:: name
      :value: 'claude-opus-4-7'



.. py:class:: Architecture

   Bases: :py:obj:`Protocol`


   Strategy interface for driving the agent loop.

   Implementations are async generators: they ``yield`` :class:`Event`
   values for every milestone they want surfaced (model chunks, tool
   calls, tool results, budget warnings, errors, architecture-specific
   progress events).

   See ``Subagent.md`` for the catalogue of architectures and the
   design rationale behind the protocol shape.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]

      Sub-Agents this architecture composes, keyed by role name.

      Used by multi-agent architectures (Supervisor, Actor-Critic,
      Debate, Router, Blackboard, Swarm) to expose their workers for
      introspection (logging, telemetry, eval). Single-agent
      architectures return ``{}``.



   .. py:method:: run(session: AgentSession, deps: Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]

      Drive iteration; yield events as they happen.

      The architecture mutates ``session`` (turns, output,
      cumulative_usage, messages, interrupted, interruption_reason,
      metadata) as it iterates and yields :class:`Event`\ s for the
      caller to forward (or ignore, in non-streaming runs).

      Implementations are *async generators* — declared
      ``async def run(...) -> AsyncIterator[Event]:`` with ``yield``
      statements in the body.



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


.. py:class:: AuditEntry(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:attribute:: action
      :type:  str


   .. py:attribute:: actor
      :type:  str


   .. py:attribute:: model_config

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


   .. py:attribute:: payload
      :type:  dict[str, Any]


   .. py:attribute:: seq
      :type:  int


   .. py:attribute:: session_id
      :type:  str


   .. py:attribute:: signature
      :type:  str


   .. py:attribute:: timestamp
      :type:  datetime.datetime


.. py:class:: AuditLog

   Bases: :py:obj:`Protocol`


   The append-only signed log surface.


   .. py:method:: append(*, session_id: str, actor: str, action: str, payload: dict[str, Any]) -> jeevesagent.core.types.AuditEntry
      :async:



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



.. py:class:: Blackboard

   Public + per-agent private state for the architecture.


   .. py:method:: post(author: str, content: str, *, kind: str = 'contribution', private_to: str | None = None) -> BlackboardEntry


   .. py:method:: render_for(agent_name: str) -> str

      Format the blackboard state as a string for ``agent_name``.

      Includes every public entry and the agent's own private
      scratchpad if any.



   .. py:attribute:: private
      :type:  dict[str, list[BlackboardEntry]]


   .. py:attribute:: public
      :type:  list[BlackboardEntry]
      :value: []



.. py:class:: BlackboardArchitecture(*, agents: dict[str, jeevesagent.agent.api.Agent], coordinator: jeevesagent.agent.api.Agent | None = None, decider: jeevesagent.agent.api.Agent | None = None, max_rounds: int = 10, coordinator_instructions: str | None = None, decider_instructions: str | None = None)

   Coordinator + agents + decider, mediated by a shared
   blackboard.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'blackboard'



.. py:class:: BlackboardEntry

   One contribution on the blackboard.


   .. py:attribute:: author
      :type:  str


   .. py:attribute:: content
      :type:  str


   .. py:attribute:: kind
      :type:  str
      :value: 'contribution'



   .. py:attribute:: timestamp
      :type:  datetime.datetime


.. 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:: BudgetStatus(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:method:: blocked_(reason: str) -> BudgetStatus
      :classmethod:



   .. py:method:: ok_() -> BudgetStatus
      :classmethod:



   .. py:method:: warn_(reason: str) -> BudgetStatus
      :classmethod:



   .. py:property:: blocked
      :type: bool



   .. py:attribute:: model_config

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


   .. py:attribute:: reason
      :type:  str | None
      :value: None



   .. py:attribute:: state
      :type:  Literal['ok', 'warn', 'blocked']


   .. py:property:: warn
      :type: bool



.. py:class:: CertifiedValue(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:attribute:: fetched_at
      :type:  datetime.datetime


   .. py:attribute:: lineage
      :type:  tuple[str, Ellipsis]
      :value: ()



   .. py:attribute:: model_config

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


   .. py:attribute:: schema_version
      :type:  str
      :value: '1'



   .. py:attribute:: source
      :type:  str


   .. py:attribute:: valid_until
      :type:  datetime.datetime | None
      :value: None



   .. py:attribute:: value
      :type:  Any


.. py:class:: ChromaFactStore(client: Any, *, embedder: jeevesagent.core.protocols.Embedder | None = None, collection_name: str = DEFAULT_FACTS_COLLECTION)

   Bi-temporal fact store backed by a Chroma collection.


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



   .. py:method:: all_facts() -> list[jeevesagent.core.types.Fact]
      :async:



   .. py:method:: append(fact: jeevesagent.core.types.Fact) -> str
      :async:



   .. py:method:: ephemeral(*, embedder: jeevesagent.core.protocols.Embedder | None = None, collection_name: str = DEFAULT_FACTS_COLLECTION) -> ChromaFactStore
      :classmethod:



   .. py:method:: local(persist_directory: str, *, embedder: jeevesagent.core.protocols.Embedder | None = None, collection_name: str = DEFAULT_FACTS_COLLECTION) -> ChromaFactStore
      :classmethod:



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



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



   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder



.. py:class:: ChromaMemory(client: Any, *, embedder: jeevesagent.core.protocols.Embedder | None = None, collection_name: str = DEFAULT_COLLECTION, fact_store: Any | None = None)

   Memory backed by ``chromadb``.

   Construct via :meth:`local` for an on-disk persistent client or
   :meth:`ephemeral` for a process-local in-memory client.


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



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



   .. py:method:: ephemeral(*, embedder: jeevesagent.core.protocols.Embedder | None = None, collection_name: str = DEFAULT_COLLECTION, with_facts: bool = False, facts_collection_name: str = 'jeeves_facts') -> ChromaMemory
      :classmethod:


      In-memory client (lost on process exit). Great for tests.



   .. py:method:: local(persist_directory: str, *, embedder: jeevesagent.core.protocols.Embedder | None = None, collection_name: str = DEFAULT_COLLECTION, with_facts: bool = False, facts_collection_name: str = 'jeeves_facts') -> ChromaMemory
      :classmethod:


      Persistent on-disk client at ``persist_directory``.

      ``with_facts=True`` attaches a :class:`ChromaFactStore` rooted
      at the same client so facts persist alongside episodes in the
      same on-disk store.



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



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



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



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



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



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



   .. py:attribute:: facts
      :type:  Any | None
      :value: None



.. py:class:: ChromaVectorStore(embedder: jeevesagent.core.protocols.Embedder, *, collection_name: str = 'jeeves_vectors', persist_directory: str | None = None, client: Any = None)

   Vector store backed by ``chromadb``.


   .. py:method:: add(chunks: list[jeevesagent.loader.base.Chunk], ids: list[str] | None = None) -> list[str]
      :async:



   .. py:method:: count() -> int
      :async:



   .. py:method:: delete(ids: list[str]) -> None
      :async:



   .. py:method:: from_chunks(chunks: list[jeevesagent.loader.base.Chunk], *, embedder: jeevesagent.core.protocols.Embedder, ids: list[str] | None = None, collection_name: str = 'jeeves_vectors', persist_directory: str | None = None, client: Any = None) -> ChromaVectorStore
      :classmethod:

      :async:


      One-shot: construct a ChromaVectorStore + add ``chunks``.



   .. py:method:: from_texts(texts: list[str], *, embedder: jeevesagent.core.protocols.Embedder, metadatas: list[dict[str, Any]] | None = None, ids: list[str] | None = None, collection_name: str = 'jeeves_vectors', persist_directory: str | None = None, client: Any = None) -> ChromaVectorStore
      :classmethod:

      :async:


      One-shot: construct a ChromaVectorStore from raw text
      strings (each becomes a :class:`Chunk` with the matching
      metadata dict, or empty if ``metadatas`` is None).



   .. py:method:: get_by_ids(ids: list[str]) -> list[jeevesagent.loader.base.Chunk]
      :async:



   .. py:method:: search(query: str, *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[jeevesagent.vectorstore.base.SearchResult]
      :async:



   .. py:method:: search_by_vector(vector: list[float], *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[jeevesagent.vectorstore.base.SearchResult]
      :async:



   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder



   .. py:attribute:: name
      :value: 'chroma'



.. py:class:: CohereEmbedder(model: str = 'embed-english-v3.0', *, client: Any | None = None, api_key: str | None = None, input_type: str = 'search_document')

   Embeddings via Cohere's ``cohere`` SDK.

   Models and dimensions:

   * ``embed-english-v3.0`` / ``embed-multilingual-v3.0`` -> 1024
   * ``embed-english-light-v3.0`` / ``embed-multilingual-light-v3.0`` -> 384

   ``input_type`` is required by Cohere v3 models:

   * ``"search_document"`` (default) — corpus / fact-store entries
   * ``"search_query"`` — retrieval queries
   * ``"classification"`` / ``"clustering"`` for non-retrieval uses


   .. 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
      :value: 'embed-english-v3.0'



.. py:class:: ConsolidationWorker(memory: jeevesagent.core.protocols.Memory, *, interval_seconds: float = 60.0, on_consolidated: OnConsolidatedCb | None = None, on_error: OnErrorCb | None = None)

   Periodic consolidator for any :class:`Memory` backend.


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


      Sleep ``interval_seconds`` then consolidate. Repeat until
      cancelled.

      Spawn this in an :func:`anyio.create_task_group` — the cancel
      scope at scope exit terminates the worker cooperatively.



   .. py:method:: run_once() -> int
      :async:


      Run a single consolidation pass. Returns the number of new
      facts extracted (``0`` when no fact store / nothing changed).

      Errors in ``memory.consolidate()`` are routed to ``on_error``
      and **not** re-raised, so callers can use this in a polling
      loop without wrapping it in their own try/except.



   .. py:property:: iterations
      :type: int


      Number of consolidate cycles attempted (test introspection).


   .. py:property:: total_extracted
      :type: int


      Cumulative count of facts extracted across all cycles.


.. py:class:: Consolidator(*, model: jeevesagent.core.protocols.Model, system_prompt: str = DEFAULT_SYSTEM_PROMPT, max_facts_per_episode: int = 20)

   Wraps a :class:`Model` to extract :class:`Fact` rows from episodes.


   .. py:method:: consolidate(episodes: collections.abc.Iterable[jeevesagent.core.types.Episode], *, store: jeevesagent.memory.facts.FactStore) -> list[jeevesagent.core.types.Fact]
      :async:


      Process ``episodes``; append extracted facts to ``store``;
      return the new :class:`Fact` instances in extraction order.

      Uses ``store.append_many`` when available so the underlying
      store can batch the embedder calls (one ``embed_batch`` API
      round-trip instead of N individual ``embed`` calls). Falls
      back to per-fact ``append`` for stores that haven't
      implemented ``append_many``.



.. py:class:: Dependencies

   Bundled protocol implementations passed to every architecture.

   Constructed once per run from the :class:`Agent`'s configured
   backends. Architectures treat this as read-only — they call
   methods on the contained protocols but don't mutate the struct
   itself.

   Multi-agent architectures (Supervisor, Router, etc.) will grow
   helper methods on this class — ``fresh_session``,
   ``scope_for_worker``, ``with_extra_tools``, ``spawn_child`` — as
   they land in v0.5+. v0.3 keeps it as a passive struct.


   .. py:attribute:: audit_log
      :type:  jeevesagent.security.audit.AuditLog | None


   .. py:attribute:: budget
      :type:  jeevesagent.core.protocols.Budget


   .. py:attribute:: context
      :type:  jeevesagent.core.context.RunContext

      Typed scope for the run — ``user_id`` (memory namespace),
      ``session_id`` (conversation thread), ``run_id`` (this specific
      invocation), and ``metadata`` (free-form app context). See
      :class:`~jeevesagent.RunContext` for the per-field semantics.


   .. py:attribute:: fast_audit
      :type:  bool
      :value: True


      Skip ``_audit(...)`` calls when ``audit_log`` is ``None``.


   .. py:attribute:: fast_budget
      :type:  bool
      :value: True


      Skip ``budget.allows_step()`` and ``budget.consume(...)``
      when budget is ``NoBudget``.


   .. py:attribute:: fast_hooks
      :type:  bool
      :value: True


      Skip ``hooks.pre_tool`` / ``hooks.post_tool`` dispatch when
      no hooks have been registered.


   .. py:attribute:: fast_permissions
      :type:  bool
      :value: True


      Skip per-tool ``permissions.check(...)`` when permissions is
      the no-op ``AllowAll``.


   .. py:attribute:: fast_runtime
      :type:  bool
      :value: True


      Inline ``await fn(*args)`` (skipping ``runtime.step(...)``
      wrapping + idempotency-key derivation) when runtime is
      ``InProcRuntime``.


   .. py:attribute:: fast_telemetry
      :type:  bool
      :value: True


      Skip ``telemetry.trace(...)`` contextmanagers + ``emit_metric``
      calls when ``telemetry`` is ``NoTelemetry``.


   .. py:attribute:: hooks
      :type:  jeevesagent.security.hooks.HookRegistry


   .. py:attribute:: max_turns
      :type:  int


   .. py:attribute:: memory
      :type:  jeevesagent.core.protocols.Memory


   .. py:attribute:: model
      :type:  jeevesagent.core.protocols.Model


   .. py:attribute:: permissions
      :type:  jeevesagent.core.protocols.Permissions


   .. py:attribute:: runtime
      :type:  jeevesagent.core.protocols.Runtime


   .. py:attribute:: streaming
      :type:  bool
      :value: False


      Whether a downstream consumer is reading from
      ``agent.stream()``. When True, architectures should preserve
      real-time event-arrival semantics so a consumer that breaks
      out of the iterator triggers prompt cancellation. When False
      (the default for ``agent.run()``), architectures may batch
      events for fewer task-group / channel allocations on the
      hot path.


   .. py:attribute:: telemetry
      :type:  jeevesagent.core.protocols.Telemetry


   .. py:attribute:: tools
      :type:  jeevesagent.core.protocols.ToolHost


.. py:class:: EchoModel(*, prefix: str = 'Echo: ', chunk_delay_s: float = 0.0, cost_per_token: float = 0.0)

   Echo-style model for tests and demos.


   .. py:method:: complete(messages: list[jeevesagent.core.types.Message], *, tools: list[jeevesagent.core.types.ToolDef] | None = None, temperature: float = 1.0, max_tokens: int | None = None) -> tuple[str, list[jeevesagent.core.types.ToolCall], jeevesagent.core.types.Usage, str]
      :async:


      Single-shot echo. Returns the echoed user prompt as one
      string with synthetic usage. No per-token chunking — used by
      the non-streaming hot path (``agent.run()``).



   .. 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]
      :async:



   .. py:attribute:: name
      :type:  str
      :value: 'echo'



.. 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:: Episode(/, **data: Any)

   Bases: :py:obj:`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 :class:`~jeevesagent.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.


   .. py:method:: format() -> str


   .. py:attribute:: embedding
      :type:  list[float] | None
      :value: None



   .. py:attribute:: id
      :type:  str
      :value: None



   .. py:attribute:: input
      :type:  str


   .. py:attribute:: occurred_at
      :type:  datetime.datetime
      :value: None



   .. py:attribute:: output
      :type:  str


   .. py:attribute:: session_id
      :type:  str


   .. py:attribute:: tool_calls
      :type:  list[ToolCall]
      :value: None



   .. py:attribute:: user_id
      :type:  str | None
      :value: None



.. py:class:: Event(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:method:: architecture_event(session_id: str, name: str, **data: Any) -> Event
      :classmethod:


      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.



   .. py:method:: budget_exceeded(session_id: str, status: BudgetStatus) -> Event
      :classmethod:



   .. py:method:: budget_warning(session_id: str, status: BudgetStatus) -> Event
      :classmethod:



   .. py:method:: completed(session_id: str, result: Any) -> Event
      :classmethod:



   .. py:method:: error(session_id: str, exc: BaseException) -> Event
      :classmethod:



   .. py:method:: model_chunk(session_id: str, chunk: ModelChunk) -> Event
      :classmethod:



   .. py:method:: started(session_id: str, prompt: str) -> Event
      :classmethod:



   .. py:method:: tool_call(session_id: str, call: ToolCall) -> Event
      :classmethod:



   .. py:method:: tool_result(session_id: str, result: ToolResult) -> Event
      :classmethod:



   .. py:attribute:: at
      :type:  datetime.datetime
      :value: None



   .. py:attribute:: kind
      :type:  EventKind


   .. py:attribute:: payload
      :type:  dict[str, Any]
      :value: None



   .. py:attribute:: session_id
      :type:  str


.. py:class:: EventKind

   Bases: :py:obj:`enum.StrEnum`


   Enum where members are also (and must be) strings

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


   .. py:attribute:: ARCHITECTURE_EVENT
      :value: '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 :class:`EventKind`.


   .. py:attribute:: BUDGET_EXCEEDED
      :value: 'budget_exceeded'



   .. py:attribute:: BUDGET_WARNING
      :value: 'budget_warning'



   .. py:attribute:: COMPLETED
      :value: 'completed'



   .. py:attribute:: ERROR
      :value: 'error'



   .. py:attribute:: MEMORY_RECALL
      :value: 'memory_recall'



   .. py:attribute:: MEMORY_WRITE
      :value: 'memory_write'



   .. py:attribute:: MODEL_CHUNK
      :value: 'model_chunk'



   .. py:attribute:: PERMISSION_ASK
      :value: 'permission_ask'



   .. py:attribute:: PERMISSION_DECISION
      :value: 'permission_decision'



   .. py:attribute:: STARTED
      :value: 'started'



   .. py:attribute:: TOOL_CALL
      :value: 'tool_call'



   .. py:attribute:: TOOL_RESULT
      :value: 'tool_result'



.. py:class:: FAISSVectorStore(embedder: jeevesagent.core.protocols.Embedder, *, dimension: int | None = None, index_factory_string: str = 'HNSW32', metric: str = 'ip')

   Vector store backed by ``faiss-cpu``.


   .. py:method:: add(chunks: list[jeevesagent.loader.base.Chunk], ids: list[str] | None = None) -> list[str]
      :async:



   .. py:method:: count() -> int
      :async:



   .. py:method:: delete(ids: list[str]) -> None
      :async:



   .. py:method:: from_chunks(chunks: list[jeevesagent.loader.base.Chunk], *, embedder: jeevesagent.core.protocols.Embedder, ids: list[str] | None = None, dimension: int | None = None, index_factory_string: str = 'HNSW32', metric: str = 'ip') -> FAISSVectorStore
      :classmethod:

      :async:


      One-shot: construct a FAISSVectorStore + add ``chunks``.



   .. py:method:: from_texts(texts: list[str], *, embedder: jeevesagent.core.protocols.Embedder, metadatas: list[dict[str, Any]] | None = None, ids: list[str] | None = None, dimension: int | None = None, index_factory_string: str = 'HNSW32', metric: str = 'ip') -> FAISSVectorStore
      :classmethod:

      :async:


      One-shot: construct a FAISSVectorStore from raw text
      strings (each becomes a :class:`Chunk` with the matching
      metadata dict, or empty if ``metadatas`` is None).



   .. py:method:: get_by_ids(ids: list[str]) -> list[jeevesagent.loader.base.Chunk]
      :async:



   .. py:method:: search(query: str, *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[jeevesagent.vectorstore.base.SearchResult]
      :async:



   .. py:method:: search_by_vector(vector: list[float], *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[jeevesagent.vectorstore.base.SearchResult]
      :async:



   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder



   .. py:attribute:: name
      :value: 'faiss'



.. py:class:: Fact(/, **data: Any)

   Bases: :py:obj:`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
   :class:`~jeevesagent.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.


   .. py:method:: format() -> str


   .. py:attribute:: confidence
      :type:  float
      :value: 1.0



   .. py:attribute:: id
      :type:  str
      :value: None



   .. py:attribute:: object
      :type:  str


   .. py:attribute:: predicate
      :type:  str


   .. py:attribute:: recorded_at
      :type:  datetime.datetime
      :value: None



   .. py:attribute:: sources
      :type:  list[str]
      :value: None



   .. py:attribute:: subject
      :type:  str


   .. py:attribute:: user_id
      :type:  str | None
      :value: None



   .. py:attribute:: valid_from
      :type:  datetime.datetime
      :value: None



   .. py:attribute:: valid_until
      :type:  datetime.datetime | None
      :value: None



.. py:class:: FactStore

   Bases: :py:obj:`Protocol`


   Storage surface for bi-temporal facts.


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



   .. py:method:: all_facts() -> list[jeevesagent.core.types.Fact]
      :async:



   .. py:method:: append(fact: jeevesagent.core.types.Fact) -> str
      :async:



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



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



.. py:class:: FileAuditLog(path: str | pathlib.Path, *, secret: str = '')

   JSONL append-only audit log with HMAC signatures.

   On construction we read any pre-existing entries to recover the
   highest seq, so a process restart picks up where the last one left
   off.


   .. py:method:: append(*, session_id: str, actor: str, action: str, payload: dict[str, Any]) -> jeevesagent.core.types.AuditEntry
      :async:



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



   .. py:property:: path
      :type: pathlib.Path



.. py:class:: FilesystemSandbox(inner: jeevesagent.core.protocols.ToolHost, *, roots: collections.abc.Iterable[str | pathlib.Path], path_args: collections.abc.Iterable[str] | None = None, auto_detect: bool = True)

   Restrict a tool host's path-typed arguments to declared roots.


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



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



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



   .. py:property:: inner
      :type: jeevesagent.core.protocols.ToolHost



   .. py:property:: roots
      :type: tuple[pathlib.Path, Ellipsis]



.. py:class:: FreshnessPolicy

   Maximum age for certified values from each source.

   ``per_source`` maps a source-prefix (matched with ``startswith``)
   to a ``timedelta``. The first prefix that matches wins. ``default``
   is used when no prefix matches; if also ``None``, the policy
   treats all values as fresh.


   .. py:method:: from_dict(per_source: dict[str, datetime.timedelta] | None = None, *, default: datetime.timedelta | None = None) -> FreshnessPolicy
      :classmethod:



   .. py:method:: max_age_for(source: str) -> datetime.timedelta | None


   .. py:attribute:: default
      :type:  datetime.timedelta | None
      :value: None



   .. py:attribute:: per_source
      :type:  tuple[tuple[str, datetime.timedelta], Ellipsis]
      :value: ()



.. py:class:: Handoff

   Per-peer handoff configuration.

   * ``agent`` — the peer :class:`Agent`.
   * ``input_type`` — optional Pydantic model. When set, the
     generated handoff tool's input schema mirrors this model's
     fields, so the calling model gets a typed schema (instead of
     a string ``message``). The validated payload is exposed to
     ``input_filter`` and surfaces in the ``swarm.handoff`` event.
   * ``input_filter`` — optional callback ``(history, payload)
     → prompt`` for selective context forwarding. Default behavior
     respects the Swarm's ``pass_full_history`` flag.
   * ``description`` — override the generated tool's description.
     Useful when the agent's name is opaque ("billing_v2") but
     the description should be user-friendly.
   * ``tool_name`` — override the auto-generated tool name. Default
     is ``"transfer_to_<key>"`` where ``<key>`` is the peer's key
     in the swarm's ``agents`` dict.


   .. py:attribute:: agent
      :type:  jeevesagent.agent.api.Agent


   .. py:attribute:: description
      :type:  str | None
      :value: None



   .. py:attribute:: input_filter
      :type:  InputFilter | None
      :value: None



   .. py:attribute:: input_type
      :type:  type[pydantic.BaseModel] | None
      :value: None



   .. py:attribute:: tool_name
      :type:  str | None
      :value: None



.. py:class:: HashEmbedder(dimensions: int = DEFAULT_HASH_DIMENSIONS)

   Deterministic SHA256-seeded unit vectors.

   Each text gets a fresh ``random.Random`` seeded by the SHA256 of
   its UTF-8 bytes, then samples ``dimensions`` Gaussian values and
   L2-normalises the result. Same text always produces the same
   vector; different texts produce well-distributed vectors with
   cosine distances that correlate with literal text equality (not
   semantic similarity).

   Use this in tests (fast, no network) and as a default for
   in-memory backends that need *some* vector but don't need real
   semantic recall.


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



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



   .. py:attribute:: dimensions
      :type:  int
      :value: 384



   .. py:attribute:: name
      :type:  str
      :value: 'hash-embedder-384'



.. 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:: HookRegistry

   Implements :class:`~jeevesagent.core.protocols.HookHost`.


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


      Best-effort post-tool callbacks. Failures and timeouts are
      absorbed so they cannot affect the result the loop returns.



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


      Run all pre-tool hooks. First deny wins; otherwise allow.



   .. py:method:: register_event(hook: EventHook) -> EventHook


   .. py:method:: register_post_tool(hook: PostToolHook) -> PostToolHook


   .. py:method:: register_pre_tool(hook: PreToolHook) -> PreToolHook


   .. py:attribute:: event_hooks
      :type:  list[EventHook]
      :value: []



   .. py:attribute:: hook_timeout_s
      :type:  float
      :value: 5.0



   .. py:attribute:: post_tool_hooks
      :type:  list[PostToolHook]
      :value: []



   .. py:attribute:: pre_tool_hooks
      :type:  list[PreToolHook]
      :value: []



.. py:class:: InMemoryAuditLog(*, secret: str = '')

   List-backed signed audit log.


   .. py:method:: all_entries() -> list[jeevesagent.core.types.AuditEntry]
      :async:



   .. py:method:: append(*, session_id: str, actor: str, action: str, payload: dict[str, Any]) -> jeevesagent.core.types.AuditEntry
      :async:



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



.. py:class:: InMemoryFactStore(*, embedder: jeevesagent.core.protocols.Embedder | None = None)

   Dict-backed bi-temporal fact store.

   All operations are coordinated by an :class:`anyio.Lock` so
   concurrent appends from the consolidator and reads from the agent
   loop don't tear the index.

   When an ``embedder`` is supplied, every appended fact's triple
   (``"subject predicate object"``) is embedded and stored alongside
   the fact, and :meth:`recall_text` ranks by cosine similarity
   against the query's embedding. When no embedder is given,
   :meth:`recall_text` falls back to token-overlap matching.


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



   .. py:method:: all_facts() -> list[jeevesagent.core.types.Fact]
      :async:



   .. py:method:: append(fact: jeevesagent.core.types.Fact) -> str
      :async:


      Append a fact, invalidating any superseded predecessors.

      Supersession rule: any existing fact with matching subject +
      predicate, currently valid (``valid_until is None``), and a
      different ``object`` gets its ``valid_until`` set to the new
      fact's ``valid_from``.



   .. py:method:: append_many(facts: collections.abc.Iterable[jeevesagent.core.types.Fact]) -> list[str]
      :async:


      Append a batch of facts. Embedder calls are coalesced via
      :meth:`Embedder.embed_batch` when an embedder is configured —
      one network round-trip for the batch instead of N.



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



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


      Rank facts against ``query``.

      With an embedder configured: cosine-similarity over the query's
      embedding vs each fact triple's stored embedding. Without one:
      token-overlap with a small stop-word list (longer overlaps
      win, ties break by shorter haystack = more specific match).

      ``user_id`` partitions the candidate set as a hard namespace
      boundary — see :class:`Fact` for semantics.



   .. py:method:: snapshot() -> dict[str, jeevesagent.core.types.Fact]


   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder | None



.. py:class:: InMemoryJournalStore

   Dict-backed journal. Process-local; lost on exit.


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



   .. py:method:: get_step(session_id: str, step_name: str) -> JournalEntry | None
      :async:



   .. py:method:: get_stream(session_id: str, step_name: str) -> list[Any] | None
      :async:



   .. py:method:: put_step(session_id: str, step_name: str, value: Any) -> None
      :async:



   .. py:method:: put_stream(session_id: str, step_name: str, chunks: list[Any]) -> None
      :async:



   .. py:method:: step_keys() -> list[tuple[str, str]]


   .. py:method:: stream_keys() -> list[tuple[str, str]]


.. py:class:: InMemoryMemory(*, consolidator: jeevesagent.memory.consolidator.Consolidator | None = None, fact_store: jeevesagent.memory.facts.FactStore | None = None)

   Dict-backed implementation of :class:`Memory`.


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



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


      Process unconsolidated episodes through the configured
      :class:`Consolidator`, appending facts to ``self.facts``.



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



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



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



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


      Return user/assistant pairs from prior runs of this session.

      Materialises each persisted :class:`Episode` for the given
      ``session_id`` (within the ``user_id`` partition) into a
      ``[USER input, ASSISTANT output]`` pair, ordered oldest-first
      and capped at ``limit`` turns total — i.e. up to ``limit / 2``
      Q/A exchanges. Tool-call traces are not replayed; the final
      assistant text per turn is sufficient context for follow-ups.



   .. py:method:: snapshot() -> dict[str, Any]


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



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



   .. py:attribute:: facts
      :type:  jeevesagent.memory.facts.FactStore


.. py:class:: InMemoryVectorStore(embedder: jeevesagent.core.protocols.Embedder)

   In-process vector store backed by a Python list.


   .. py:method:: add(chunks: list[jeevesagent.loader.base.Chunk], ids: list[str] | None = None) -> list[str]
      :async:



   .. py:method:: count() -> int
      :async:



   .. py:method:: delete(ids: list[str]) -> None
      :async:



   .. py:method:: from_chunks(chunks: list[jeevesagent.loader.base.Chunk], *, embedder: jeevesagent.core.protocols.Embedder, ids: list[str] | None = None) -> InMemoryVectorStore
      :classmethod:

      :async:


      One-shot: construct an InMemoryVectorStore + add ``chunks``.



   .. py:method:: from_texts(texts: list[str], *, embedder: jeevesagent.core.protocols.Embedder, metadatas: list[dict[str, Any]] | None = None, ids: list[str] | None = None) -> InMemoryVectorStore
      :classmethod:

      :async:


      One-shot: construct an InMemoryVectorStore from raw text
      strings (each becomes a :class:`Chunk` with the matching
      metadata dict, or empty if ``metadatas`` is None).



   .. py:method:: get_by_ids(ids: list[str]) -> list[jeevesagent.loader.base.Chunk]
      :async:



   .. py:method:: load(path: str | pathlib.Path, *, embedder: jeevesagent.core.protocols.Embedder) -> InMemoryVectorStore
      :classmethod:

      :async:


      Restore a store previously :meth:`save`-d. Pass the same
      embedder kind/dimensions or queries will produce nonsense
      scores.



   .. py:method:: save(path: str | pathlib.Path) -> None
      :async:


      Write the full store (chunks + vectors + ids) to a JSON
      file. The embedder is NOT serialized — supply the same
      embedder when calling :meth:`load`.



   .. py:method:: search(query: str, *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[jeevesagent.vectorstore.base.SearchResult]
      :async:



   .. py:method:: search_by_vector(vector: list[float], *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[jeevesagent.vectorstore.base.SearchResult]
      :async:



   .. py:method:: search_hybrid(query: str, *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, alpha: float = 0.5) -> list[jeevesagent.vectorstore.base.SearchResult]
      :async:


      Hybrid lexical (BM25) + vector search via RRF.

      ``alpha`` is in [0, 1]: 0 = pure BM25, 1 = pure vector,
      0.5 = even weighting (RRF default). Both rankings are
      computed independently and fused by Reciprocal Rank Fusion,
      then the top-``k`` survivors are returned.

      Embeddings catch semantic similarity ("automobile" ↔ "car"),
      BM25 catches exact-term hits (model names, error codes,
      person names) — together they outperform either alone on
      most retrieval benchmarks.



   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder



   .. py:attribute:: name
      :value: 'in-memory'



.. py:class:: InProcRuntime

   No durability. Each step runs immediately.


   .. py:method:: session(session_id: str) -> collections.abc.AsyncIterator[InProcSession]
      :async:



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



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



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


   .. py:attribute:: name
      :value: 'inproc'



.. py:class:: InProcessToolHost(tools: list[Tool | collections.abc.Callable[Ellipsis, Any]] | None = None)

   A dict-backed :class:`~jeevesagent.core.protocols.ToolHost`.


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



   .. py:method:: get(name: str) -> Tool | None


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



   .. py:method:: register(item: Tool | collections.abc.Callable[Ellipsis, Any]) -> Tool


   .. py:method:: unregister(name: str) -> bool

      Remove a tool by name. Returns ``True`` if removed.



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


      In-process registry is static; the generator yields nothing.

      Iterating over an empty tuple keeps this an async generator
      (so the return type is ``AsyncIterator``) without ever producing
      an event at runtime.



.. py:class:: JeevesConfig

   Connection details for the Jeeves Gateway.


   .. py:attribute:: api_key
      :type:  str


   .. py:attribute:: base_url
      :type:  str
      :value: 'https://jeeves.works/mcp'



   .. py:attribute:: server_name
      :type:  str
      :value: 'jeeves'



.. py:class:: JeevesGateway(config: JeevesConfig, *, registry: jeevesagent.mcp.registry.MCPRegistry | None = None)

   ToolHost-shaped wrapper around the Jeeves Gateway.


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



   .. py:method:: as_mcp_server() -> jeevesagent.mcp.spec.MCPServerSpec

      Return the :class:`MCPServerSpec` describing this gateway.



   .. py:method:: as_registry() -> jeevesagent.mcp.registry.MCPRegistry

      Return a one-server :class:`MCPRegistry` rooted at this gateway.



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



   .. py:method:: from_env(*, env_var: str = JEEVES_API_KEY_ENV, base_url: str | None = None, server_name: str = JEEVES_DEFAULT_SERVER_NAME) -> JeevesGateway
      :classmethod:


      Build a gateway from the ``JEEVES_API_KEY`` environment variable.



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



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



   .. py:property:: config
      :type: JeevesConfig



   .. py:property:: server_name
      :type: str



.. py:class:: JournalStore

   Bases: :py:obj:`Protocol`


   Storage surface for the durable runtime.


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



   .. py:method:: get_step(session_id: str, step_name: str) -> JournalEntry | None
      :async:



   .. py:method:: get_stream(session_id: str, step_name: str) -> list[Any] | None
      :async:



   .. py:method:: put_step(session_id: str, step_name: str, value: Any) -> None
      :async:



   .. py:method:: put_stream(session_id: str, step_name: str, chunks: list[Any]) -> None
      :async:



.. py:class:: JournaledRuntime(store: jeevesagent.runtime.journal.JournalStore | None = None)

   Runtime that journals every step's result for replay.

   Pass any :class:`JournalStore` (in-memory for tests, sqlite for
   durable single-process use, future Postgres/DBOS adapters for
   multi-process / multi-host).


   .. py:method:: session(session_id: str) -> collections.abc.AsyncIterator[JournaledSession]
      :async:



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



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



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


   .. py:attribute:: name
      :value: 'journaled'



   .. py:property:: store
      :type: jeevesagent.runtime.journal.JournalStore



.. py:class:: LineagePolicy

   Allow-list of source prefixes for the entire lineage chain.

   A :class:`CertifiedValue` is acceptable if every entry in
   ``value.lineage`` (interpreted as a source prefix) starts with one
   of the allowed prefixes.


   .. py:method:: from_iter(sources: list[str] | tuple[str, Ellipsis]) -> LineagePolicy
      :classmethod:



   .. py:attribute:: allowed_sources
      :type:  frozenset[str]


.. py:class:: LiteLLMModel(model: str, *, api_key: str | None = None, client: Any | None = None, **litellm_kwargs: Any)

   Bases: :py:obj:`jeevesagent.model.openai.OpenAIModel`


   Talks to any LiteLLM-supported provider.

   Inherits chunk normalisation, tool-call delta aggregation, and
   message-conversion from :class:`OpenAIModel` because LiteLLM
   produces OpenAI-shaped outputs.


.. py:class:: MCPClient(spec: jeevesagent.mcp.spec.MCPServerSpec, *, session: Any | None = None)

   One client per MCP server. Holds the live ``ClientSession``.


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


      Tear down the session and underlying transport.



   .. py:method:: call_tool(name: str, args: dict[str, Any]) -> Any
      :async:


      Invoke ``name`` with ``args``. Returns the SDK's CallToolResult.



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


      Open the transport and initialise the session.

      No-op if already connected (or a fake session was injected at
      construction time).



   .. py:method:: list_tools() -> list[Any]
      :async:


      Return whatever the SDK gave us — a list of tool descriptors.

      Each descriptor has ``name``, ``description``, ``inputSchema``.
      We don't translate to :class:`ToolDef` here — the registry does
      that, since it also assigns names with disambiguation.



   .. py:property:: is_connected
      :type: bool



   .. py:property:: name
      :type: str



   .. py:property:: spec
      :type: jeevesagent.mcp.spec.MCPServerSpec



.. py:class:: MCPRegistry(items: list[jeevesagent.mcp.spec.MCPServerSpec | jeevesagent.mcp.client.MCPClient] | None = None)

   Aggregates many :class:`MCPClient` instances into a single ``ToolHost``.


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



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



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


      Connect every client in parallel and rebuild the index.



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



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


      Re-pull tool lists from every client and rebuild the index.



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


      ``listChanged`` notifications. Not yet implemented; yields nothing.



   .. py:property:: server_names
      :type: list[str]



.. py:class:: MCPServerSpec

   How to find and talk to a single MCP server.

   Construct via the class methods :meth:`stdio` or :meth:`http` rather
   than the bare constructor — they enforce the right combination of
   fields per transport.


   .. py:method:: http(name: str, url: str, headers: dict[str, str] | None = None, *, description: str = '') -> MCPServerSpec
      :classmethod:


      Connect to ``url`` via Streamable HTTP transport.



   .. py:method:: stdio(name: str, command: str, args: list[str] | tuple[str, Ellipsis] | None = None, env: dict[str, str] | None = None, *, description: str = '') -> MCPServerSpec
      :classmethod:


      Spawn ``command`` as a subprocess and speak JSON-RPC over its stdio.



   .. py:attribute:: args
      :type:  tuple[str, Ellipsis]
      :value: ()



   .. py:attribute:: command
      :type:  str | None
      :value: None



   .. py:attribute:: description
      :type:  str
      :value: ''



   .. py:attribute:: env
      :type:  tuple[tuple[str, str], Ellipsis]
      :value: ()



   .. py:attribute:: headers
      :type:  tuple[tuple[str, str], Ellipsis]
      :value: ()



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


   .. py:attribute:: transport
      :type:  Literal['stdio', 'http']


   .. py:attribute:: url
      :type:  str | None
      :value: None



.. 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:: MemoryBlock(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:method:: format() -> str


   .. py:attribute:: content
      :type:  str


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


   .. py:attribute:: pinned_order
      :type:  int
      :value: 0



   .. py:attribute:: updated_at
      :type:  datetime.datetime
      :value: None



.. py:class:: Message(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:attribute:: content
      :type:  str


   .. py:attribute:: model_config

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


   .. py:attribute:: name
      :type:  str | None
      :value: None



   .. py:attribute:: role
      :type:  Role


   .. py:attribute:: tool_call_id
      :type:  str | None
      :value: None



   .. py:attribute:: tool_calls
      :type:  tuple[ToolCall, Ellipsis]
      :value: ()



.. py:class:: Mode

   Bases: :py:obj:`enum.StrEnum`


   Enum where members are also (and must be) strings

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


   .. py:attribute:: ACCEPT_EDITS
      :value: 'acceptEdits'



   .. py:attribute:: BYPASS
      :value: 'bypassPermissions'



   .. py:attribute:: DEFAULT
      :value: 'default'



.. 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:: ModelChunk(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:attribute:: finish_reason
      :type:  str | None
      :value: None



   .. py:attribute:: kind
      :type:  Literal['text', 'tool_call', 'finish']


   .. py:attribute:: text
      :type:  str | None
      :value: None



   .. py:attribute:: tool_call
      :type:  ToolCall | None
      :value: None



   .. py:attribute:: usage
      :type:  Usage | None
      :value: None



.. py:class:: MultiAgentDebate(*, debaters: list[jeevesagent.agent.api.Agent], judge: jeevesagent.agent.api.Agent | None = None, rounds: int = 2, convergence_check: bool = True, convergence_similarity: float = 0.85, debater_instructions: str | None = None, judge_instructions: str | None = None)

   N debaters + optional judge orchestration.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'debate'



.. py:class:: NoSandbox(inner: jeevesagent.core.protocols.ToolHost)

   Pass-through wrapper around a :class:`ToolHost`.


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



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



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



   .. py:property:: inner
      :type: jeevesagent.core.protocols.ToolHost



.. py:class:: NoTelemetry

   No-op telemetry. Very cheap; safe to call on every loop step.


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



   .. py:method:: trace(name: str, **attrs: Any) -> collections.abc.AsyncIterator[jeevesagent.core.types.Span]
      :async:



.. py:class:: OTelTelemetry(*, tracer_provider: Any | None = None, meter_provider: Any | None = None, instrumentation_name: str = 'jeevesagent')

   OpenTelemetry-backed :class:`~jeevesagent.core.protocols.Telemetry`.


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



   .. py:method:: trace(name: str, **attrs: Any) -> collections.abc.AsyncIterator[jeevesagent.core.types.Span]
      :async:



.. py:class:: OpenAIEmbedder(model: str = 'text-embedding-3-small', *, dimensions: int | None = None, client: Any | None = None, api_key: str | None = None)

   Embeddings via OpenAI's ``embeddings.create`` API.

   Dimensions are fixed by the model:

   * ``text-embedding-3-small`` -> 1536
   * ``text-embedding-3-large`` -> 3072
   * ``text-embedding-ada-002`` -> 1536

   Pass ``dimensions=`` only for ``text-embedding-3-*`` models, which
   support the ``dimensions`` parameter for projection.


   .. 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
      :value: 'text-embedding-3-small'



.. py:class:: OpenAIModel(model: str = 'gpt-4o', *, client: Any = None, api_key: str | None = None, base_url: str | None = None)

   Talks to OpenAI via :class:`openai.AsyncOpenAI`.


   .. py:method:: complete(messages: list[jeevesagent.core.types.Message], *, tools: list[jeevesagent.core.types.ToolDef] | None = None, temperature: float = 1.0, max_tokens: int | None = None) -> tuple[str, list[jeevesagent.core.types.ToolCall], jeevesagent.core.types.Usage, str]
      :async:


      Single-shot completion (no per-chunk yields).

      Tries the OpenAI non-streaming endpoint
      (``stream=False``) first. If that fails — e.g. when a test
      fake client only supports streaming, or a transport doesn't
      honor ``stream=False`` — falls back to consuming
      :meth:`stream` internally and accumulating the result. The
      fallback still saves the per-chunk yield + Event
      construction overhead on the architecture side because
      ReAct calls ``complete`` with a single ``await``.



   .. 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]
      :async:



   .. py:attribute:: name
      :value: 'gpt-4o'



.. py:class:: PermissionDecision(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:method:: allow_(reason: str | None = None) -> PermissionDecision
      :classmethod:



   .. py:method:: ask_(reason: str | None = None) -> PermissionDecision
      :classmethod:



   .. py:method:: deny_(reason: str) -> PermissionDecision
      :classmethod:



   .. py:property:: allow
      :type: bool



   .. py:property:: ask
      :type: bool



   .. py:attribute:: decision
      :type:  Literal['allow', 'deny', 'ask']


   .. py:property:: deny
      :type: bool



   .. py:attribute:: model_config

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


   .. py:attribute:: reason
      :type:  str | None
      :value: None



.. 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:: Plan(/, **data: Any)

   Bases: :py:obj:`pydantic.BaseModel`


   A list of plan steps in execution order.

   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.


   .. py:attribute:: steps
      :type:  list[PlanStep]
      :value: None



.. py:class:: PlanAndExecute(*, max_steps: int = 8, planner_prompt: str | None = None, executor_prompt: str | None = None, synthesizer_prompt: str | None = None)

   Planner → step executor → synthesizer.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'plan-and-execute'



.. py:class:: PlanStep(/, **data: Any)

   Bases: :py:obj:`pydantic.BaseModel`


   One step of a plan.

   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.


   .. py:attribute:: description
      :type:  str


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


.. py:class:: PostgresFactStore(pool: Any, *, embedder: jeevesagent.core.protocols.Embedder | None = None)

   Postgres-backed bi-temporal fact store.


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



   .. py:method:: all_facts() -> list[jeevesagent.core.types.Fact]
      :async:



   .. py:method:: append(fact: jeevesagent.core.types.Fact) -> str
      :async:



   .. py:method:: append_many(facts: collections.abc.Iterable[jeevesagent.core.types.Fact]) -> list[str]
      :async:



   .. py:method:: connect(dsn: str, *, embedder: jeevesagent.core.protocols.Embedder | None = None, min_size: int = 1, max_size: int = 10) -> PostgresFactStore
      :classmethod:

      :async:



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



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



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



   .. py:method:: schema_sql() -> list[str]

      Return the DDL for this fact store's schema.

      Exposed so tests can assert on the SQL strings, and so
      migration scripts can apply the schema in their own
      transaction.



   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder | None



.. py:class:: PostgresJournalStore(pool: Any)

   Postgres-backed journal. Production-grade durable replay.

   Same shape as :class:`SqliteJournalStore` but uses ``asyncpg`` and
   a Postgres database. Designed for users who already run a Postgres
   instance for the rest of their stack (memory, audit, app state)
   and want their durable-runtime journal to live there too.

   Why not a DBOS adapter?

       DBOS Python's workflow model requires ``@DBOS.workflow()`` and
       ``@DBOS.communicator()`` decorators at module-load time. Our
       ``Runtime.step(name, fn, *args)`` API takes arbitrary
       callables at runtime, which doesn't compose cleanly with
       DBOS's static-decoration model. ``PostgresJournalStore``
       gives the same durability guarantee through our existing
       :class:`JournaledRuntime` architecture, with no decorator
       intrusion on user code.


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



   .. py:method:: connect(dsn: str, *, min_size: int = 1, max_size: int = 10) -> PostgresJournalStore
      :classmethod:

      :async:


      Open an asyncpg pool and return the store rooted at it.



   .. py:method:: get_step(session_id: str, step_name: str) -> JournalEntry | None
      :async:



   .. py:method:: get_stream(session_id: str, step_name: str) -> list[Any] | None
      :async:



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



   .. py:method:: put_step(session_id: str, step_name: str, value: Any) -> None
      :async:



   .. py:method:: put_stream(session_id: str, step_name: str, chunks: list[Any]) -> None
      :async:



   .. py:method:: schema_sql() -> list[str]
      :staticmethod:


      Return the DDL needed to bootstrap this store's schema.

      Idempotent; safe to run on every process start.



.. py:class:: PostgresMemory(pool: Any, *, embedder: jeevesagent.core.protocols.Embedder | None = None, namespace: str = DEFAULT_NAMESPACE, fact_store: Any | None = None)

   Postgres-backed :class:`Memory`.

   ``pool`` is an ``asyncpg.Pool`` (or anything with the same API).
   Tests can pass a fake pool whose ``acquire()`` returns a fake
   connection.


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



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



   .. py:method:: connect(dsn: str, *, embedder: jeevesagent.core.protocols.Embedder | None = None, namespace: str = DEFAULT_NAMESPACE, min_size: int = 1, max_size: int = 10, with_facts: bool = False) -> PostgresMemory
      :classmethod:

      :async:


      Open an asyncpg pool and register the pgvector codec.

      When ``with_facts=True`` a :class:`PostgresFactStore` rooted at
      the same pool is attached as ``self.facts`` so the agent loop's
      memory.facts integration just works.



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



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


      Apply :meth:`schema_sql` against the connected pool.

      When a :class:`PostgresFactStore` is attached as ``self.facts``,
      its schema is initialised in the same call.



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



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



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



   .. py:method:: schema_sql() -> list[str]

      Return the DDL needed to bootstrap this backend's schema.

      Exposed so tests can assert on the SQL without running it; also
      usable from migration scripts that want to apply the schema in
      their own transaction.



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



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



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



   .. py:property:: embedding_dimensions
      :type: int



   .. py:attribute:: facts
      :type:  Any | None
      :value: None



   .. py:property:: namespace
      :type: str



.. py:class:: PostgresRuntime(pool: Any)

   Bases: :py:obj:`jeevesagent.runtime.journaled.JournaledRuntime`


   :class:`JournaledRuntime` backed by Postgres for cross-host
   durable replay.


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


      Close the underlying connection pool.



   .. py:method:: connect(dsn: str, *, min_size: int = 1, max_size: int = 10) -> PostgresRuntime
      :classmethod:

      :async:


      Open a fresh asyncpg pool and return the runtime rooted at it.



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


      Create the journal tables if they don't already exist.



   .. py:attribute:: name
      :value: 'postgres'



.. py:class:: PostgresVectorStore(embedder: jeevesagent.core.protocols.Embedder, *, dsn: str, table: str = 'jeeves_vectors', dimension: int | None = None)

   Vector store backed by Postgres + ``pgvector``.


   .. py:method:: add(chunks: list[jeevesagent.loader.base.Chunk], ids: list[str] | None = None) -> list[str]
      :async:



   .. py:method:: count() -> int
      :async:



   .. py:method:: delete(ids: list[str]) -> None
      :async:



   .. py:method:: from_chunks(chunks: list[jeevesagent.loader.base.Chunk], *, embedder: jeevesagent.core.protocols.Embedder, ids: list[str] | None = None, dsn: str, table: str = 'jeeves_vectors', dimension: int | None = None) -> PostgresVectorStore
      :classmethod:

      :async:


      One-shot: construct a PostgresVectorStore + add ``chunks``.



   .. py:method:: from_texts(texts: list[str], *, embedder: jeevesagent.core.protocols.Embedder, metadatas: list[dict[str, Any]] | None = None, ids: list[str] | None = None, dsn: str, table: str = 'jeeves_vectors', dimension: int | None = None) -> PostgresVectorStore
      :classmethod:

      :async:


      One-shot: construct a PostgresVectorStore from raw text
      strings (each becomes a :class:`Chunk` with the matching
      metadata dict, or empty if ``metadatas`` is None).



   .. py:method:: get_by_ids(ids: list[str]) -> list[jeevesagent.loader.base.Chunk]
      :async:



   .. py:method:: init_schema(dimension: int) -> None
      :async:


      Create the table + HNSW index. Idempotent.



   .. py:method:: search(query: str, *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[jeevesagent.vectorstore.base.SearchResult]
      :async:



   .. py:method:: search_by_vector(vector: list[float], *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[jeevesagent.vectorstore.base.SearchResult]
      :async:



   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder



   .. py:attribute:: name
      :value: 'postgres'



.. py:class:: ReAct(*, max_turns: int | None = None)

   Observe-think-act in a tight loop.

   The default architecture for every :class:`Agent`. Other
   architectures wrap or replace this strategy; see ``Subagent.md``.

   ``max_turns`` overrides ``Dependencies.max_turns`` for this
   architecture only — useful when wrapping ReAct inside another
   architecture that sets its own per-leaf cap (Reflexion,
   Plan-and-Execute, etc.). ``None`` means "use whatever the Agent
   was configured with".


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'react'



.. py:class:: ReWOO(*, max_steps: int = 8, planner_prompt: str | None = None, solver_prompt: str | None = None, parallel_levels: bool = True)

   Plan-then-tool-execute with placeholder substitution.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'rewoo'



.. py:class:: ReWOOPlan(/, **data: Any)

   Bases: :py:obj:`pydantic.BaseModel`


   A list of ReWOO steps (no required ordering — dependencies
   are inferred from ``{{En}}`` placeholders).

   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.


   .. py:attribute:: steps
      :type:  list[ReWOOStep]
      :value: None



.. py:class:: ReWOOStep(/, **data: Any)

   Bases: :py:obj:`pydantic.BaseModel`


   One step of a ReWOO plan: id + tool + args.

   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.


   .. py:attribute:: args
      :type:  dict[str, Any]
      :value: None



   .. py:property:: depends_on
      :type: list[str]


      Extract ``{{En}}`` step ids referenced in args.


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


   .. py:attribute:: tool
      :type:  str


.. py:class:: ReWOOStepResult(/, **data: Any)

   Bases: :py:obj:`pydantic.BaseModel`


   !!! abstract "Usage Documentation"
       [Models](../concepts/models.md)

   A base class for creating Pydantic models.

   .. attribute:: __class_vars__

      The names of the class variables defined on the model.

   .. attribute:: __private_attributes__

      Metadata about the private attributes of the model.

   .. attribute:: __signature__

      The synthesized `__init__` [`Signature`][inspect.Signature] of the model.

   .. attribute:: __pydantic_complete__

      Whether model building is completed, or if there are still undefined fields.

   .. attribute:: __pydantic_core_schema__

      The core schema of the model.

   .. attribute:: __pydantic_custom_init__

      Whether the model has a custom `__init__` function.

   .. attribute:: __pydantic_decorators__

      Metadata containing the decorators defined on the model.
      This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.

   .. attribute:: __pydantic_generic_metadata__

      Metadata for generic models; contains data used for a similar purpose to
      __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.

   .. attribute:: __pydantic_parent_namespace__

      Parent namespace of the model, used for automatic rebuilding of models.

   .. attribute:: __pydantic_post_init__

      The name of the post-init method for the model, if defined.

   .. attribute:: __pydantic_root_model__

      Whether the model is a [`RootModel`][pydantic.root_model.RootModel].

   .. attribute:: __pydantic_serializer__

      The `pydantic-core` `SchemaSerializer` used to dump instances of the model.

   .. attribute:: __pydantic_validator__

      The `pydantic-core` `SchemaValidator` used to validate instances of the model.

   .. attribute:: __pydantic_fields__

      A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.

   .. attribute:: __pydantic_computed_fields__

      A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.

   .. attribute:: __pydantic_extra__

      A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
      is set to `'allow'`.

   .. attribute:: __pydantic_fields_set__

      The names of fields explicitly set during instantiation.

   .. attribute:: __pydantic_private__

      Values of private attributes set on the model instance.

   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.


   .. py:attribute:: error
      :type:  str | None
      :value: None



   .. py:attribute:: output
      :type:  str


   .. py:attribute:: step_id
      :type:  str


   .. py:attribute:: tool
      :type:  str


.. py:class:: RedisFactStore(client: Any, *, embedder: jeevesagent.core.protocols.Embedder | None = None, key_prefix: str = DEFAULT_KEY_PREFIX)

   Bi-temporal fact store over plain Redis hashes.


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



   .. py:method:: all_facts() -> list[jeevesagent.core.types.Fact]
      :async:



   .. py:method:: append(fact: jeevesagent.core.types.Fact) -> str
      :async:



   .. py:method:: connect(url: str = 'redis://localhost:6379/0', *, embedder: jeevesagent.core.protocols.Embedder | None = None, key_prefix: str = DEFAULT_KEY_PREFIX) -> RedisFactStore
      :classmethod:

      :async:



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



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



   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder



.. py:class:: RedisMemory(client: Any, *, embedder: jeevesagent.core.protocols.Embedder | None = None, key_prefix: str = DEFAULT_KEY_PREFIX, index_name: str = DEFAULT_INDEX_NAME, use_vector_index: bool = True, fact_store: Any | None = None)

   Redis-backed :class:`Memory`. Use :meth:`connect` to construct.


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



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



   .. py:method:: connect(url: str = 'redis://localhost:6379/0', *, embedder: jeevesagent.core.protocols.Embedder | None = None, key_prefix: str = DEFAULT_KEY_PREFIX, index_name: str = DEFAULT_INDEX_NAME, use_vector_index: bool = True, with_facts: bool = False, fact_key_prefix: str = 'jeeves:fact:') -> RedisMemory
      :classmethod:

      :async:


      Open an async Redis connection.

      ``with_facts=True`` attaches a :class:`RedisFactStore` sharing
      the same client; facts go to ``{fact_key_prefix}*`` keys so
      they don't collide with episode keys.



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



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


      Create the RediSearch HNSW index, if not already present.

      Skipped silently when ``use_vector_index=False`` or when
      RediSearch isn't available on the server.



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



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



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



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



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



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



   .. py:attribute:: facts
      :type:  Any | None
      :value: None



.. py:class:: Reflexion(*, base: jeevesagent.architecture.base.Architecture | None = None, max_attempts: int = 3, threshold: float = 0.8, evaluator_prompt: str | None = None, reflector_prompt: str | None = None, lessons_block_name: str = 'reflexion_lessons', lesson_store: jeevesagent.vectorstore.base.VectorStore | None = None, top_k_lessons: int = 5)

   Wrap a base architecture with evaluator + reflector + lesson
   memory.

   See module docstring for the full mechanism. Constructor
   parameters:

   * ``base`` — architecture to retry. Default :class:`ReAct`.
   * ``max_attempts`` — cap on retries within a single run.
     Default 3.
   * ``threshold`` — minimum evaluator score to terminate as
     success. Default 0.8.
   * ``evaluator_prompt`` / ``reflector_prompt`` — override the
     default system prompts.
   * ``lessons_block_name`` — memory working-block name for
     persisted lessons. Default ``"reflexion_lessons"``. Multiple
     Reflexion-wrapped agents in the same memory should pick
     distinct names.
   * ``lesson_store`` — optional :class:`VectorStore` enabling
     selective recall. When set, lessons are stored as embedded
     chunks and only the top-``top_k_lessons`` most relevant
     lessons are surfaced on each attempt (instead of all past
     lessons). Avoids context bloat as lessons accumulate.
   * ``top_k_lessons`` — how many lessons to recall per attempt
     (selective-recall mode only). Default 5.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'reflexion'



.. py:class:: RetryPolicy

   Exponential-backoff-with-jitter retry schedule.

   The default is sensible for production: up to **3 attempts**
   (one initial + two retries), starting at 1 s, doubling each
   attempt, capped at 30 s, with ±10% jitter so synchronised
   clients don't reform a thundering herd.

   Examples::

       # default — sensible for most apps
       RetryPolicy()

       # disable retries (fail fast)
       RetryPolicy.disabled()

       # aggressive — survives long provider blips
       RetryPolicy.aggressive()

       # tuned to a specific SLO
       RetryPolicy(max_attempts=4, initial_delay_s=0.5, max_delay_s=15)

   The schedule applies *between* attempts: the first call has no
   delay, the second is delayed by ``initial_delay_s`` (± jitter),
   the third by ``initial_delay_s * multiplier`` (± jitter), etc.,
   each capped at ``max_delay_s``. Provider-supplied
   ``Retry-After`` hints (carried on
   :class:`~jeevesagent.RateLimitError.retry_after`) override the
   computed delay when they ask for *more* time — we never sleep
   less than the provider asked for.


   .. py:method:: aggressive() -> RetryPolicy
      :classmethod:


      Up to 6 attempts, faster initial backoff, longer cap.
      Use when the underlying provider is known-flaky and the
      caller prefers slow success over fast failure.



   .. py:method:: disabled() -> RetryPolicy
      :classmethod:


      Single attempt, no retries — fail fast on any error.



   .. py:method:: is_enabled() -> bool

      ``True`` when the policy permits at least one retry.



   .. py:attribute:: initial_delay_s
      :type:  float
      :value: 1.0


      Backoff before the FIRST retry (i.e. between attempts 1 and 2).
      Subsequent retries use ``initial_delay_s * multiplier**n``.


   .. py:attribute:: jitter
      :type:  float
      :value: 0.1


      Fractional ±jitter applied to each computed delay. ``0.1`` =
      ±10%. Set to ``0`` for deterministic backoff (useful in tests).


   .. py:attribute:: max_attempts
      :type:  int
      :value: 3


      Maximum total attempts including the first call. ``1`` means
      no retries; the call either succeeds or raises immediately. The
      minimum-meaningful retry policy is therefore ``max_attempts=2``.


   .. py:attribute:: max_delay_s
      :type:  float
      :value: 30.0


      Cap on any single backoff. Prevents runaway sleeps when
      ``multiplier`` is large or ``max_attempts`` is high.


   .. py:attribute:: multiplier
      :type:  float
      :value: 2.0


      Geometric growth between successive retries. ``2.0`` doubles
      each time; ``1.0`` makes the policy linear (fixed-interval).


.. py:class:: Role

   Bases: :py:obj:`enum.StrEnum`


   Enum where members are also (and must be) strings

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


   .. py:attribute:: ASSISTANT
      :value: 'assistant'



   .. py:attribute:: SYSTEM
      :value: 'system'



   .. py:attribute:: TOOL
      :value: 'tool'



   .. py:attribute:: USER
      :value: 'user'



.. py:class:: Router(*, routes: list[RouterRoute], fallback_route: str | None = None, require_confidence_above: float = 0.0, classifier_prompt: str | None = None)

   Classify input → dispatch to ONE specialist :class:`Agent`.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'router'



.. py:class:: RouterRoute

   One specialist + classification metadata.

   ``name`` is what the classifier emits in its ``route:`` line and
   must be unique within a Router. ``description`` is shown to the
   classifier alongside the name — keep it specific and
   distinguishing so the classifier picks reliably.


   .. py:attribute:: agent
      :type:  jeevesagent.agent.api.Agent


   .. py:attribute:: description
      :type:  str
      :value: ''



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


.. py:class:: RunContext

   Typed, immutable context for one agent run.

   Set once at the start of :meth:`Agent.run` and propagated to
   every architecture, tool, hook, sub-agent, and memory operation
   via a :class:`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:

   .. code-block:: python

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

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


   .. py:method:: get(key: str, default: Any = None) -> Any

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



   .. py:method:: 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

      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``".



   .. py:attribute:: metadata
      :type:  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``.


   .. py:attribute:: run_id
      :type:  str
      :value: ''


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


   .. py:attribute:: session_id
      :type:  str | None
      :value: 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
      :meth:`Agent.run` if not supplied.


   .. py:attribute:: user_id
      :type:  str | None
      :value: 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.


.. py:class:: RunResult(/, **data: Any)

   Bases: :py:obj:`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 :meth:`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.


   .. py:attribute:: cost_usd
      :type:  float
      :value: 0.0



   .. py:property:: duration
      :type: datetime.timedelta


      Wall-clock latency between ``started_at`` and ``finished_at``.


   .. py:attribute:: finished_at
      :type:  datetime.datetime


   .. py:attribute:: interrupted
      :type:  bool
      :value: False



   .. py:attribute:: interruption_reason
      :type:  str | None
      :value: None



   .. py:attribute:: model_config

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


   .. py:attribute:: output
      :type:  str


   .. py:attribute:: parsed
      :type:  Any | None
      :value: None


      The validated Pydantic instance when ``output_schema=`` was
      supplied to :meth:`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.


   .. py:attribute:: session_id
      :type:  str


   .. py:attribute:: started_at
      :type:  datetime.datetime


   .. py:attribute:: tokens_in
      :type:  int
      :value: 0



   .. py:attribute:: tokens_out
      :type:  int
      :value: 0



   .. py:property:: total_tokens
      :type: int


      ``tokens_in + tokens_out``.

      :type: Convenience


   .. py:attribute:: turns
      :type:  int


.. 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:: ScriptedModel(turns: list[ScriptedTurn])

   Model that emits canned responses, one per call to :meth:`stream`.


   .. py:method:: complete(messages: list[jeevesagent.core.types.Message], *, tools: list[jeevesagent.core.types.ToolDef] | None = None, temperature: float = 1.0, max_tokens: int | None = None) -> tuple[str, list[jeevesagent.core.types.ToolCall], jeevesagent.core.types.Usage, str]
      :async:


      Single-shot replay of the next scripted turn.

      Mirrors :meth:`stream` but returns the turn's text +
      tool_calls + usage in one tuple. Used by the non-streaming
      hot path (``agent.run()``); ``agent.stream()`` keeps using
      :meth:`stream` for per-chunk replay.



   .. 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]
      :async:



   .. py:attribute:: name
      :type:  str
      :value: 'scripted'



   .. py:property:: remaining
      :type: int



.. py:class:: ScriptedTurn

   .. py:attribute:: text
      :type:  str
      :value: ''



   .. py:attribute:: tool_calls
      :type:  list[jeevesagent.core.types.ToolCall]
      :value: []



   .. py:attribute:: usage
      :type:  jeevesagent.core.types.Usage


.. py:class:: SearchResult

   One hit from :meth:`VectorStore.search`.

   * ``chunk`` — the matched chunk (with its full metadata).
   * ``score`` — similarity in [-1, 1] for cosine; backend-
     specific for other distance metrics. Higher = more similar.
   * ``id`` — the store-assigned id (so callers can ``delete()``
     or ``get_by_ids()`` later).


   .. py:attribute:: chunk
      :type:  jeevesagent.loader.base.Chunk


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


   .. py:attribute:: score
      :type:  float


.. 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:: SelfRefine(*, base: jeevesagent.architecture.base.Architecture | None = None, max_rounds: int = 3, critic_prompt: str | None = None, refiner_prompt: str | None = None, stop_phrase: str = 'no issues')

   Wrap a base architecture with iterative critique / refine.

   ``base`` defaults to :class:`ReAct`; the round-0 generator runs
   the base architecture's full strategy. Subsequent rounds are
   text-only model calls — no tools, just critique and rewrite.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'self-refine'



.. py:class:: Skill(path: str | pathlib.Path, *, source_label: str | None = None)

   A loadable agent skill.


   .. py:method:: from_text(text: str, *, source_label: str | None = None) -> Skill
      :classmethod:


      Build an inline skill from a SKILL.md-formatted string.

      No filesystem path; bundled scripts and ``tools.py`` aren't
      accessible. Useful for one-off skill definitions in code.



   .. py:method:: list_files() -> list[pathlib.Path]

      Enumerate every file bundled with this skill.



   .. py:method:: load_body() -> str

      Return the full SKILL.md body (without frontmatter).



   .. py:property:: description
      :type: str



   .. py:attribute:: metadata


   .. py:property:: name
      :type: str



   .. py:attribute:: path


   .. py:property:: pending_tools
      :type: list[jeevesagent.tools.registry.Tool]


      The Tool instances this skill will register on load.

      Both Mode B (Python @tool from ``tools.py``) and Mode C
      (subprocess wrappers from frontmatter ``tools:`` manifest)
      contribute to this list. Empty for pure markdown skills.


.. py:class:: SkillMetadata

   Lightweight skill descriptor — what loads at startup.

   The body is NOT in here; it's read on demand via
   :meth:`Skill.load_body`. Keep this small — it lives in the
   system prompt for the entire agent's lifetime.


   .. py:method:: to_catalog_line() -> str

      One-line catalog entry for the system prompt.



   .. py:attribute:: allowed_tools
      :type:  list[str] | None
      :value: None



   .. py:attribute:: compatibility
      :type:  str | None
      :value: None



   .. py:attribute:: declared_tool_count
      :type:  int
      :value: 0



   .. py:attribute:: description
      :type:  str


   .. py:attribute:: extra
      :type:  dict[str, Any]


   .. py:attribute:: has_python_tools
      :type:  bool
      :value: False



   .. py:attribute:: license
      :type:  str | None
      :value: None



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


   .. py:attribute:: source_label
      :type:  str | None
      :value: None



.. py:class:: SkillRegistry(items: collections.abc.Iterable[SkillSpec] | None = None)

   A keyed collection of :class:`Skill` instances.


   .. py:method:: add(skill: jeevesagent.skills.skill.Skill) -> None

      Append (or override) a single skill after construction.



   .. py:method:: catalog_section() -> str

      The markdown bullet list that gets appended to the
      agent's system prompt.

      Empty registry → empty string (so the constructor can
      unconditionally call this without polluting the system
      prompt with a blank "Available skills" header).



   .. py:method:: get(name: str) -> jeevesagent.skills.skill.Skill | None


   .. py:method:: is_loaded(name: str) -> bool

      Whether the skill's pending tools have been registered.



   .. py:method:: load(name: str) -> str

      Return the full body of a skill (the load_skill tool's
      result). Raises :class:`SkillError` for unknown names so
      the model gets a clear error in the tool result.

      Does NOT register pending Tools. For the full load-and-
      register flow, see :meth:`load_with_tools`.



   .. py:method:: load_with_tools(name: str) -> tuple[str, list[jeevesagent.tools.registry.Tool]]

      Return ``(body, newly_pending_tools)`` — the body of the
      skill plus the Tool instances the framework should register
      with the agent's tool host on this load.

      Idempotent: subsequent calls for the same skill return the
      body and an empty tool list, since registration only needs
      to happen once.



   .. py:method:: metadata_map() -> collections.abc.Mapping[str, jeevesagent.skills.skill.SkillMetadata]

      All currently-registered skills' metadata, keyed by name.
      Cheap to compute — used to build the catalog section.



   .. py:method:: names() -> list[str]


   .. py:method:: remove(name: str) -> jeevesagent.skills.skill.Skill | None

      Drop a skill by name. Returns the removed instance or
      ``None`` if no such skill was registered.



.. py:class:: SkillSource

   A folder of skills + an optional label.


   .. py:method:: coerce(item: SkillSource | str | pathlib.Path | tuple[str | pathlib.Path, str]) -> SkillSource
      :classmethod:


      Normalize one user-supplied source spec.

      Accepts:
      * ``SkillSource(...)`` — used as-is
      * ``str`` / ``Path`` — bare path, no label
      * ``(path, label)`` — path with explicit label



   .. py:method:: discover() -> list[jeevesagent.skills.skill.Skill]

      Find every SKILL.md under this source directory.

      Recurses one level (most common layout: ``skills/<name>/SKILL.md``)
      but also handles deeper nesting. Each SKILL.md becomes one
      :class:`Skill` instance with this source's label attached.



   .. py:attribute:: label
      :type:  str | None
      :value: None



   .. py:attribute:: path
      :type:  pathlib.Path


.. py:class:: Span(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:attribute:: attributes
      :type:  dict[str, Any]
      :value: None



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


   .. py:attribute:: span_id
      :type:  str


   .. py:attribute:: started_at
      :type:  datetime.datetime
      :value: None



   .. py:attribute:: trace_id
      :type:  str


.. py:class:: SqliteFactStore(path: str | pathlib.Path, *, embedder: jeevesagent.core.protocols.Embedder | None = None)

   Durable bi-temporal fact store rooted at a sqlite file.


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



   .. py:method:: all_facts() -> list[jeevesagent.core.types.Fact]
      :async:



   .. py:method:: append(fact: jeevesagent.core.types.Fact) -> str
      :async:


      Append a fact, invalidating any superseded predecessors.

      Same supersession rule as :class:`InMemoryFactStore`: if there's
      an existing currently-valid fact with matching subject +
      predicate but different object, set its ``valid_until`` to the
      new fact's ``valid_from``.



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



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



   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder | None



   .. py:property:: path
      :type: pathlib.Path



.. py:class:: SqliteJournalStore(path: str | pathlib.Path)

   SQLite-backed journal. Durable across process restarts.


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



   .. py:method:: get_step(session_id: str, step_name: str) -> JournalEntry | None
      :async:



   .. py:method:: get_stream(session_id: str, step_name: str) -> list[Any] | None
      :async:



   .. py:method:: put_step(session_id: str, step_name: str, value: Any) -> None
      :async:



   .. py:method:: put_stream(session_id: str, step_name: str, chunks: list[Any]) -> None
      :async:



   .. py:property:: path
      :type: pathlib.Path



.. py:class:: SqliteRuntime(path: str | pathlib.Path)

   Bases: :py:obj:`jeevesagent.runtime.journaled.JournaledRuntime`


   :class:`JournaledRuntime` with a :class:`SqliteJournalStore`.


   .. py:attribute:: name
      :value: 'sqlite'



   .. py:property:: path
      :type: pathlib.Path



.. py:class:: StandardPermissions(*, mode: Mode = Mode.DEFAULT, allowed_tools: list[str] | None = None, denied_tools: list[str] | None = None)

   Mode + allow/deny-list permission policy.


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



   .. py:method:: strict() -> StandardPermissions
      :classmethod:


      Convenience: default-mode permissions with no overrides.



.. py:class:: StepResult(/, **data: Any)

   Bases: :py:obj:`pydantic.BaseModel`


   The output of executing one 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.


   .. py:attribute:: description
      :type:  str


   .. py:attribute:: output
      :type:  str


   .. py:attribute:: step_id
      :type:  str


.. py:class:: SubprocessSandbox(inner: jeevesagent.core.protocols.ToolHost, *, timeout_seconds: float = 30.0)

   Run each tool call in a fresh child Python process.


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



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



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



   .. py:property:: inner
      :type: jeevesagent.core.protocols.ToolHost



   .. py:property:: timeout_seconds
      :type: float



.. py:class:: Supervisor(*, workers: dict[str, jeevesagent.agent.api.Agent], base: jeevesagent.architecture.base.Architecture | None = None, instructions_template: str | None = None, delegate_tool_name: str = 'delegate', forward_tool_name: str = 'forward_message')

   Coordinator + workers, glued by a ``delegate`` tool.

   The supervisor's base architecture (default :class:`ReAct`) sees
   a fresh ``delegate(worker, instructions)`` tool that routes calls
   to the named worker :class:`Agent`. Worker outputs come back as
   tool results just like any other tool call.

   Constructor
   -----------
   * ``workers``: dict mapping role-names to fully-built
     :class:`Agent` instances. Names must be valid identifiers
     (the model emits them as the ``worker`` argument).
   * ``base``: the architecture the supervisor itself runs.
     Default :class:`ReAct`. Wrap inside :class:`Reflexion` to
     learn delegation patterns across runs.
   * ``instructions_template``: format string with
     ``{worker_descriptions}``. Default teaches the supervisor
     to delegate effectively. The agent's own ``instructions``
     are *prepended* (so domain context survives).
   * ``delegate_tool_name``: defaults to ``"delegate"``. Customize
     to avoid clashes with user-defined tools that happen to have
     the same name.
   * ``forward_tool_name``: defaults to ``"forward_message"``. The
     supervisor calls this with a worker name to return that
     worker's last output VERBATIM as the supervisor's final
     response. Skips a synthesis round-trip — the
     `langchain.com/blog/benchmarking-multi-agent-architectures`_
     benchmark showed +50% quality on tasks where the supervisor
     would otherwise paraphrase a worker's output.


   .. py:method:: add_worker(name: str, agent: jeevesagent.agent.api.Agent) -> None

      Register a worker between runs.

      Safe to call between :meth:`Agent.run` invocations on the
      agent that owns this supervisor; the new worker becomes
      available for ``delegate(name, ...)`` on the next run.
      Calling mid-run is undefined — the supervisor's prompt is
      composed at run start.



   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: remove_worker(name: str) -> jeevesagent.agent.api.Agent | None

      Unregister a worker by name. Returns the removed Agent
      if it was registered, ``None`` otherwise. Same lifecycle
      rules as :meth:`add_worker`.



   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'supervisor'



.. py:class:: Swarm(*, agents: dict[str, jeevesagent.agent.api.Agent | Handoff], entry_agent: str, max_handoffs: int = 8, detect_cycles: bool = True, pass_full_history: bool = True, handoff_tool_name: str = 'handoff')

   Peer agents passing control through handoff tools.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'swarm'



.. py:class:: Team

   Namespace for multi-agent team builders.

   Every classmethod returns a fully-built :class:`Agent` whose
   architecture is the corresponding multi-agent strategy. The
   returned Agent has the standard ``run`` / ``stream`` / etc.
   interface — call sites don't change between single-agent and
   team agents.


   .. py:method:: actor_critic(actor: jeevesagent.agent.api.Agent, critic: jeevesagent.agent.api.Agent, *, instructions: str = '', model: jeevesagent.core.protocols.Model | str | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, budget: jeevesagent.core.protocols.Budget | None = None, permissions: jeevesagent.core.protocols.Permissions | None = None, hooks: jeevesagent.security.hooks.HookRegistry | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object] | None = None, telemetry: jeevesagent.core.protocols.Telemetry | None = None, audit_log: jeevesagent.security.audit.AuditLog | None = None, max_turns: int = DEFAULT_MAX_TURNS, auto_consolidate: bool = False, skills: list[Any] | None = None, max_rounds: int = 3, approval_threshold: float = 0.9, critique_template: str | None = None, refine_template: str | None = None) -> jeevesagent.agent.api.Agent
      :staticmethod:


      Build an actor-critic pair where the critic reviews the
      actor's output (with structured JSON scoring + rubric) and
      the actor refines below ``approval_threshold``.



   .. py:method:: blackboard(agents: dict[str, jeevesagent.agent.api.Agent], *, coordinator: jeevesagent.agent.api.Agent | None = None, decider: jeevesagent.agent.api.Agent | None = None, instructions: str = '', model: jeevesagent.core.protocols.Model | str | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, budget: jeevesagent.core.protocols.Budget | None = None, permissions: jeevesagent.core.protocols.Permissions | None = None, hooks: jeevesagent.security.hooks.HookRegistry | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object] | None = None, telemetry: jeevesagent.core.protocols.Telemetry | None = None, audit_log: jeevesagent.security.audit.AuditLog | None = None, max_turns: int = DEFAULT_MAX_TURNS, auto_consolidate: bool = False, skills: list[Any] | None = None, max_rounds: int = 10, coordinator_instructions: str | None = None, decider_instructions: str | None = None) -> jeevesagent.agent.api.Agent
      :staticmethod:


      Build a blackboard team where ``agents`` collaborate via
      a shared workspace; an optional ``coordinator`` selects who
      acts each round and an optional ``decider`` decides when
      the work is done.



   .. py:method:: debate(debaters: list[jeevesagent.agent.api.Agent], *, judge: jeevesagent.agent.api.Agent | None = None, instructions: str = '', model: jeevesagent.core.protocols.Model | str | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, budget: jeevesagent.core.protocols.Budget | None = None, permissions: jeevesagent.core.protocols.Permissions | None = None, hooks: jeevesagent.security.hooks.HookRegistry | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object] | None = None, telemetry: jeevesagent.core.protocols.Telemetry | None = None, audit_log: jeevesagent.security.audit.AuditLog | None = None, max_turns: int = DEFAULT_MAX_TURNS, auto_consolidate: bool = False, skills: list[Any] | None = None, rounds: int = 2, convergence_check: bool = True, convergence_similarity: float = 0.85, debater_instructions: str | None = None, judge_instructions: str | None = None) -> jeevesagent.agent.api.Agent
      :staticmethod:


      Build a multi-agent debate where ``debaters`` argue for
      ``rounds`` (with optional convergence early-exit). If
      ``judge`` is provided, the judge synthesizes a final
      answer; otherwise majority vote wins.



   .. py:method:: router(routes: list[jeevesagent.architecture.RouterRoute], *, instructions: str = '', model: jeevesagent.core.protocols.Model | str | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, budget: jeevesagent.core.protocols.Budget | None = None, permissions: jeevesagent.core.protocols.Permissions | None = None, hooks: jeevesagent.security.hooks.HookRegistry | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object] | None = None, telemetry: jeevesagent.core.protocols.Telemetry | None = None, audit_log: jeevesagent.security.audit.AuditLog | None = None, max_turns: int = DEFAULT_MAX_TURNS, auto_consolidate: bool = False, skills: list[Any] | None = None, fallback_route: str | None = None, require_confidence_above: float = 0.0, classifier_prompt: str | None = None) -> jeevesagent.agent.api.Agent
      :staticmethod:


      Build a router that classifies once and dispatches to
      ONE specialist :class:`Agent`. Cheaper than Supervisor for
      tasks with clear specialist boundaries (one classifier call
      + one specialist run, no synthesis pass).



   .. py:method:: supervisor(workers: dict[str, jeevesagent.agent.api.Agent], *, instructions: str = '', model: jeevesagent.core.protocols.Model | str | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, budget: jeevesagent.core.protocols.Budget | None = None, permissions: jeevesagent.core.protocols.Permissions | None = None, hooks: jeevesagent.security.hooks.HookRegistry | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object] | None = None, telemetry: jeevesagent.core.protocols.Telemetry | None = None, audit_log: jeevesagent.security.audit.AuditLog | None = None, max_turns: int = DEFAULT_MAX_TURNS, auto_consolidate: bool = False, skills: list[Any] | None = None, instructions_template: str | None = None, delegate_tool_name: str = 'delegate', forward_tool_name: str = 'forward_message') -> jeevesagent.agent.api.Agent
      :staticmethod:


      Build a coordinator Agent that delegates to ``workers``.

      The coordinator can call ``delegate(worker, instructions)``
      to dispatch a subtask, or ``forward_message(worker)`` to
      return a worker's output verbatim. Multiple delegations in
      one turn run in parallel.



   .. py:method:: swarm(agents: dict[str, jeevesagent.agent.api.Agent | jeevesagent.architecture.swarm.Handoff], entry_agent: str, *, instructions: str = '', model: jeevesagent.core.protocols.Model | str | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, budget: jeevesagent.core.protocols.Budget | None = None, permissions: jeevesagent.core.protocols.Permissions | None = None, hooks: jeevesagent.security.hooks.HookRegistry | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object] | None = None, telemetry: jeevesagent.core.protocols.Telemetry | None = None, audit_log: jeevesagent.security.audit.AuditLog | None = None, max_turns: int = DEFAULT_MAX_TURNS, auto_consolidate: bool = False, skills: list[Any] | None = None, max_handoffs: int = 8, detect_cycles: bool = True, pass_full_history: bool = True, handoff_tool_name: str = 'handoff') -> jeevesagent.agent.api.Agent
      :staticmethod:


      Build a peer-swarm of agents that hand off control via a
      ``handoff`` tool (or per-target ``transfer_to_<name>`` tools
      when peers are wrapped in :class:`Handoff` with an
      ``input_type``).

      ``entry_agent`` is the peer that receives the first message.



.. 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:: ThoughtNode(/, **data: Any)

   Bases: :py:obj:`pydantic.BaseModel`


   One node in the Tree-of-Thoughts search tree.

   Children are stored implicitly (each node has a ``parent_id``).
   The full tree is reconstructable from the node list ToT keeps in
   its session metadata.

   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.


   .. py:attribute:: content
      :type:  str


   .. py:attribute:: depth
      :type:  int


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


   .. py:attribute:: parent_id
      :type:  str | None


   .. py:attribute:: score
      :type:  float
      :value: 0.0



.. py:class:: Tool

   A registered tool: definition plus the callable that executes it.


   .. py:method:: execute(args: collections.abc.Mapping[str, Any]) -> Any
      :async:


      Invoke the underlying callable.

      Async functions are awaited; sync functions are dispatched to a
      worker thread via :func:`anyio.to_thread.run_sync` so they don't
      block the event loop.



   .. py:method:: to_def() -> jeevesagent.core.types.ToolDef


   .. py:attribute:: description
      :type:  str


   .. py:attribute:: destructive
      :type:  bool
      :value: False



   .. py:attribute:: fn
      :type:  collections.abc.Callable[Ellipsis, Any]


   .. py:attribute:: input_schema
      :type:  dict[str, Any]


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


.. py:class:: ToolCall(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:method:: idempotency_key() -> str


   .. py:method:: is_destructive() -> bool


   .. py:attribute:: args
      :type:  dict[str, Any]
      :value: None



   .. py:attribute:: destructive
      :type:  bool
      :value: False



   .. py:attribute:: id
      :type:  str
      :value: None



   .. py:attribute:: tool
      :type:  str


   .. py:attribute:: tool_def
      :type:  ToolDef | None
      :value: None



.. py:class:: ToolDef(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:attribute:: description
      :type:  str


   .. py:attribute:: input_schema
      :type:  dict[str, Any]
      :value: None



   .. py:attribute:: model_config

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


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


   .. py:attribute:: server
      :type:  str | None
      :value: None



.. py:class:: ToolEvent(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:attribute:: at
      :type:  datetime.datetime
      :value: None



   .. py:attribute:: kind
      :type:  Literal['added', 'removed', 'updated']


   .. py:attribute:: server
      :type:  str | None
      :value: None



   .. py:attribute:: tool
      :type:  str


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



.. py:class:: ToolResult(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:method:: denied_(call_id: str, reason: str, **kwargs: Any) -> ToolResult
      :classmethod:



   .. py:method:: error_(call_id: str, message: str, **kwargs: Any) -> ToolResult
      :classmethod:



   .. py:method:: success(call_id: str, output: Any, **kwargs: Any) -> ToolResult
      :classmethod:



   .. py:attribute:: call_id
      :type:  str


   .. py:attribute:: denied
      :type:  bool
      :value: False



   .. py:attribute:: duration_ms
      :type:  float | None
      :value: None



   .. py:attribute:: error
      :type:  str | None
      :value: None



   .. py:attribute:: ok
      :type:  bool


   .. py:attribute:: output
      :type:  Any
      :value: None



   .. py:attribute:: reason
      :type:  str | None
      :value: None



   .. py:attribute:: started_at
      :type:  datetime.datetime
      :value: None



.. py:class:: TreeOfThoughts(*, branch_factor: int = 3, max_depth: int = 3, beam_width: int = 2, solved_threshold: float = 1.0, min_score: float = 0.0, parallel: bool = True, proposer_prompt: str | None = None, evaluator_prompt: str | None = None)

   Branch + evaluate + prune. BFS beam search over thoughts.


   .. py:method:: declared_workers() -> dict[str, jeevesagent.agent.api.Agent]


   .. py:method:: run(session: jeevesagent.architecture.base.AgentSession, deps: jeevesagent.architecture.base.Dependencies, prompt: str) -> collections.abc.AsyncIterator[jeevesagent.core.types.Event]
      :async:



   .. py:attribute:: name
      :value: 'tree-of-thoughts'



.. py:class:: Usage(/, **data: Any)

   Bases: :py:obj:`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.


   .. py:attribute:: cost_usd
      :type:  float
      :value: 0.0



   .. py:attribute:: input_tokens
      :type:  int
      :value: 0



   .. py:attribute:: model_config

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


   .. py:attribute:: output_tokens
      :type:  int
      :value: 0



.. py:class:: VectorMemory(*, embedder: jeevesagent.core.protocols.Embedder | None = None, max_episodes: int | None = None, consolidator: jeevesagent.memory.consolidator.Consolidator | None = None, fact_store: jeevesagent.memory.facts.FactStore | None = None)

   Pure-Python embedding-backed :class:`Memory`.


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



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


      Process unconsolidated episodes through the configured
      :class:`Consolidator`, appending facts to ``self.facts``.

      No-op when no consolidator is configured.



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



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



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



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



   .. py:method:: snapshot() -> dict[str, Any]


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



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



   .. py:property:: embedder
      :type: jeevesagent.core.protocols.Embedder



   .. py:attribute:: facts
      :type:  jeevesagent.memory.facts.FactStore


.. py:class:: VectorStore

   Bases: :py:obj:`Protocol`


   Async protocol for vector stores.

   Six methods cover the lifecycle: add (embed + store), delete,
   search (by query string), search_by_vector (precomputed),
   count, get_by_ids.

   Backends that aren't natively async (FAISS, Chroma) wrap their
   sync calls in :func:`anyio.to_thread.run_sync` so they don't
   block the event loop.


   .. py:method:: add(chunks: list[jeevesagent.loader.base.Chunk], ids: list[str] | None = None) -> list[str]
      :async:


      Embed + store ``chunks``. Returns the assigned ids
      (caller-provided or generated).



   .. py:method:: count() -> int
      :async:


      Number of chunks currently in the store.



   .. py:method:: delete(ids: list[str]) -> None
      :async:


      Remove the named chunks. Unknown ids are silently
      skipped (idempotent).



   .. py:method:: get_by_ids(ids: list[str]) -> list[jeevesagent.loader.base.Chunk]
      :async:


      Fetch chunks by id, in the same order as ``ids``.
      Unknown ids are skipped (the result may be shorter than
      the input).



   .. py:method:: search(query: str, *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[SearchResult]
      :async:


      Embed ``query`` and return the top-``k`` chunks ranked
      by similarity. ``filter`` (optional) restricts candidates
      by metadata. ``diversity`` (optional, 0..1) enables MMR
      reranking for varied results.



   .. py:method:: search_by_vector(vector: list[float], *, k: int = 4, filter: collections.abc.Mapping[str, Any] | None = None, diversity: float | None = None) -> list[SearchResult]
      :async:


      Same as :meth:`search` but with a precomputed query
      vector.



.. py:class:: VoyageEmbedder(model: str = 'voyage-3', *, client: Any | None = None, api_key: str | None = None, input_type: str = 'document')

   Embeddings via Voyage AI's ``voyageai`` SDK.

   Models and dimensions:

   * ``voyage-3`` / ``voyage-3-large`` / ``voyage-code-3`` -> 1024
   * ``voyage-3-lite`` -> 512

   ``input_type`` controls how Voyage encodes the text:

   * ``"document"`` (default) — for corpus / fact-store entries
   * ``"query"`` — for retrieval queries

   Pass an explicit ``input_type=`` if your embedder is dedicated to
   one role; for the agent loop's mixed use (we embed both stored
   triples and recall queries through the same embedder), the
   ``"document"`` default is the safer choice.


   .. 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
      :value: 'voyage-3'



.. py:class:: set_run_context(context: RunContext)

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

   The framework uses this internally inside :meth:`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.


.. py:function:: bash_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'bash', timeout: float = 30.0, allow_pattern: collections.abc.Callable[[str], bool] | None = None, extra_env: dict[str, str] | None = None) -> jeevesagent.tools.registry.Tool

   Build a :class:`Tool` that runs a shell command with the
   workdir as the current working directory.

   Default safety:

   * Commands matching the built-in destructive patterns
     (``rm -rf /``, ``sudo``, ``mkfs``, fork bombs, ...) are
     rejected before being executed.
   * Commands run with a default ``timeout`` of 30 seconds; the
     subprocess is killed on timeout.
   * The shell is invoked via ``/bin/sh -c <command>``, so
     pipelines + redirections work the way you'd expect.

   Knobs:

   * ``allow_pattern`` — a callable that takes the command string
     and returns True if the command should run. When provided, it
     OVERRIDES the default deny list — you take full responsibility.
   * ``extra_env`` — extra environment variables merged into the
     subprocess env.
   * ``timeout`` — seconds before the command is killed.

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with the other built-in tools).


.. py:function:: build_graph(agent: jeevesagent.agent.api.Agent, *, title: str = 'Agent') -> AgentGraph
   :async:


   Walk an :class:`Agent` and return its renderable
   :class:`AgentGraph`.


.. py:function:: classify_model_error(exc: BaseException) -> jeevesagent.core.errors.ModelError | None

   Map an exception from any model SDK to the framework's taxonomy.

   Returns ``None`` when the exception is not recognised as a
   model-call failure — let callers decide whether to wrap it in
   something else or propagate. Returns an instance of one of
   :class:`~jeevesagent.TransientModelError` /
   :class:`~jeevesagent.RateLimitError` /
   :class:`~jeevesagent.AuthenticationError` /
   :class:`~jeevesagent.InvalidRequestError` /
   :class:`~jeevesagent.ContentFilterError` /
   :class:`~jeevesagent.PermanentModelError` otherwise.

   SDK imports are lazy — having e.g. the ``anthropic`` package
   installed is not required for OpenAI classification to work,
   and vice versa.


.. py:function:: default_workdir() -> pathlib.Path

   Return the framework's default workdir for built-in tools,
   creating it lazily on first call.

   The directory is a fresh tempdir under ``$TMPDIR/jeeves_agent_*``,
   created once per process. All built-in tool factories share it
   when called without an explicit ``workdir`` argument, so an
   Agent that registers ``read_tool()`` and ``write_tool()`` (no
   args) sees the same place.

   The directory is NOT auto-cleaned at process exit — leave that
   to the OS's tempdir cleanup so debug data survives a crash.


.. py:function:: deterministic_hash(*parts: Any) -> str

   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)``.


.. py:function:: edit_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'edit') -> jeevesagent.tools.registry.Tool

   Build a :class:`Tool` that does find-and-replace inside an
   existing file under ``workdir``.

   The tool's signature seen by the model:
       ``edit(path: str, old_string: str, new_string: str,
              replace_all: bool = False)``

   Behaviour matches Claude Code's Edit tool:

   * ``old_string`` must be EXACTLY present in the file. Mismatch
     (whitespace, indentation, line breaks) → error.
   * ``old_string`` must appear EXACTLY once in the file unless
     ``replace_all=True`` is passed — forces the model to give
     enough surrounding context for unambiguous matches.
   * ``new_string`` replaces ``old_string`` (or every occurrence
     if ``replace_all=True``).

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with the other built-in tools).


.. py:function:: filesystem_tools(workdir: pathlib.Path | str | None = None) -> list[jeevesagent.tools.registry.Tool]

   Return all three filesystem tools (read + write + edit)
   bound to a single workdir. ``bash_tool`` is excluded — pair
   them only when you want shell access too.

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with bash_tool() called the same way).


.. py:function:: get_run_context() -> RunContext

   Return the :class:`RunContext` for the currently-running agent.

   Inside an active :meth:`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 :class:`RunContext` — never raises.

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

   .. code-block:: python

       @tool
       async def fetch_user_orders() -> str:
           ctx = get_run_context()
           return await db.query("orders", user_id=ctx.user_id)


.. py:function:: new_id(prefix: str = '') -> str

   Return a fresh ULID, optionally prefixed for readability.

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


.. py:function:: read_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'read', line_limit: int = _DEFAULT_READ_LINE_LIMIT) -> jeevesagent.tools.registry.Tool

   Build a :class:`Tool` that reads a text file under ``workdir``.

   The tool's signature seen by the model:
       ``read(path: str, offset: int = 0, limit: int | None = None)``

   Returns the file's text with line numbers prefixed (one
   line per output line), in the same format Claude Code's Read
   tool uses — that lets the ``edit`` tool work without ambiguity
   later. Long files are truncated to ``line_limit`` lines per
   call; pass ``offset`` / ``limit`` to read further chunks.

   Errors (file-not-found, path-escape) are returned as a string
   starting with ``"ERROR: "`` rather than raising — the model
   sees them as a tool result and can adjust.

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with the other built-in tools called without
   a workdir).


.. py:function:: resolve_architecture(spec: jeevesagent.architecture.base.Architecture | str | None) -> jeevesagent.architecture.base.Architecture

   Coerce ``spec`` to a concrete :class:`Architecture`.

   * ``None`` → :class:`ReAct` (the default)
   * ``str`` → looked up in :data:`KNOWN` (only ``"react"`` in v0.3)
   * Architecture instance → returned as-is

   Unknown strings raise :class:`ConfigError` with a list of known
   names.


.. py:function:: run_architecture(architecture: jeevesagent.architecture.Architecture, prompt: str, *, instructions: str = '', model: jeevesagent.core.protocols.Model | str | None = None, memory: jeevesagent.core.protocols.Memory | None = None, runtime: jeevesagent.core.protocols.Runtime | None = None, budget: jeevesagent.core.protocols.Budget | None = None, permissions: jeevesagent.core.protocols.Permissions | None = None, hooks: jeevesagent.security.hooks.HookRegistry | None = None, tools: list[jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object]] | jeevesagent.core.protocols.ToolHost | jeevesagent.tools.registry.Tool | collections.abc.Callable[Ellipsis, object] | None = None, telemetry: jeevesagent.core.protocols.Telemetry | None = None, audit_log: jeevesagent.security.audit.AuditLog | None = None, max_turns: int = DEFAULT_MAX_TURNS, auto_consolidate: bool = False) -> jeevesagent.core.types.RunResult
   :async:


   Run an :class:`Architecture` once with a minimal Agent shell.

   Useful for testing orchestrators in isolation or for one-shot
   scripts where you don't want to construct an Agent yourself.

   The default ``model`` is the framework's resolver default (set
   via ``model=`` or env / config); pass an explicit model or
   string id to override.

   Example::

       sup = Supervisor(workers={"a": agent_a})
       result = await run_architecture(
           sup, "do the thing", model="gpt-4.1-mini"
       )


.. py:function:: tool(fn: collections.abc.Callable[Ellipsis, Any]) -> Tool
                 tool(*, name: str | None = None, description: str | None = None, destructive: bool = False) -> collections.abc.Callable[[collections.abc.Callable[Ellipsis, Any]], Tool]

   Promote a callable to a :class:`Tool`.

   Use as ``@tool`` (bare) or ``@tool(name=..., description=..., destructive=...)``.
   The schema is derived from parameter annotations; primitive types map
   to their JSON-Schema equivalents, anything else falls back to ``string``.


.. py:function:: write_graph(agent: jeevesagent.agent.api.Agent, path: str | pathlib.Path, *, title: str | None = None) -> str
   :async:


   Walk the agent, render to Mermaid, write to ``path``.

   Extension dispatch:

   * ``.mmd`` — raw Mermaid source
   * ``.md``  — Markdown with the diagram in a ``mermaid`` fence
   * ``.png`` / ``.svg`` — fetched from ``mermaid.ink``; on
     network failure, writes ``.mmd`` next to the requested path
     and returns the Mermaid text anyway

   Returns the Mermaid text in every case.


.. py:function:: write_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'write', create_parents: bool = True) -> jeevesagent.tools.registry.Tool

   Build a :class:`Tool` that writes / overwrites a text file
   under ``workdir``.

   The tool's signature seen by the model:
       ``write(path: str, content: str)``

   Overwrites existing files. With ``create_parents=True`` (the
   default), missing parent directories are created automatically.

   Returns a confirmation string with the byte count, or an
   ``"ERROR: "``-prefixed message on failure.

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with the other built-in tools).


