vacant.substrate¶
LLM / runtime backend abstraction. Substrate is a resource, not
the identity — the same vacant can run on a different substrate
without changing its keypair. Reputation is tracked
per-(vacant, substrate); client-inherited is the load-bearing
deployment for D2 (vacant served via MCP uses the calling client's
LLM).
base
¶
Abstract substrate backend interface.
Concrete implementations (Anthropic / Ollama / Mock / Deterministic) are
filled in by later component PRs. P0 ships only the contract so downstream
code can import SubstrateBackend for type annotations.
SubstrateRequest
¶
Bases: BaseModel
A single inference request handed to a substrate backend.
SubstrateResponse
¶
Bases: BaseModel
A single inference response, plus optional substrate proof material.
SubstrateBackend
¶
Bases: ABC
Backend contract. Implementations must be safe to call from async code.
errors
¶
Substrate-module error hierarchy.
SubstrateError
¶
Bases: CoreError
Base class for vacant.substrate errors (backend / inference failures).
SubstrateUnavailableError
¶
Bases: SubstrateError
The substrate backend cannot be reached (missing SDK, missing API key, no network).
SubstrateRateLimitError
¶
Bases: SubstrateError
The substrate backend rejected with rate-limit; retries exhausted.
mock
¶
Deterministic mock substrate for tests + bit-exact CI runs.
MockSubstrate returns canned text built from the prompt + a seeded
random suffix. Every (seed, system_prompt, user_prompt) tuple produces
the same response, so the integration test asserts exact byte
equality across runs (P7 §"Substrate determinism contract").
MockSubstrate
dataclass
¶
deterministic
¶
Canned-response substrate for reproducible demos.
DeterministicSubstrate looks up responses keyed by a hash of the
prompt. Useful when a scenario needs meaningful canned text (e.g.
"this is a law firm answer") rather than the raw mock prefix.
Falls back to a deterministic synthesised response when the prompt
hash is not in the canned table.
DeterministicSubstrate
dataclass
¶
anthropic
¶
Anthropic Claude substrate.
Default model: claude-sonnet-4-6 (latest Claude 4.6 Sonnet,
per CLAUDE.md tech stack).
API key is read from ANTHROPIC_API_KEY env var. If python-dotenv is
installed (it is — required dep, see pyproject.toml), .env files in
the cwd / parent dirs are auto-loaded the first time the substrate is
asked to infer, so the README workflow `echo ANTHROPIC_API_KEY=...
.env && vacant demo --substrate=anthropic
works withoutexport(F14). Initialisation does NOT create the SDK client until the firstinfer` call (lazy import).
Rate-limit handling: the SDK raises anthropic.RateLimitError; this
wrapper catches it and re-raises as SubstrateRateLimitError after
sleeping for retry_after seconds (header-driven). Retries up to
max_retries times before surfacing the failure.
AnthropicSubstrate
dataclass
¶
AnthropicSubstrate(model: str = DEFAULT_MODEL, api_key_env: str = 'ANTHROPIC_API_KEY', max_retries: int = 3, max_tokens: int = 1024)
Bases: SubstrateBackend
Real-LLM substrate. Used by demo scenarios with --substrate=anthropic.
Tests should NOT use this (they use MockSubstrate); CI cannot
reach the network and bit-exact reproducibility is not possible.
ollama
¶
Ollama substrate (local-LLM, "token-free future" simulation).
Talks to a local Ollama server over HTTP (http://localhost:11434
by default). Useful for the multilingual_translation scenario's
local-ollama-llama3 substrate slot.
Failures (server not running, model not pulled) raise
SubstrateUnavailableError -- callers can catch and degrade.
OllamaSubstrate
dataclass
¶
OllamaSubstrate(model: str = 'llama3', base_url: str = 'http://localhost:11434', timeout_s: float = 60.0)
Bases: SubstrateBackend
Local Ollama backend. Used in demo to simulate the "token-free future" leg of THEORY_V5 §2 substrate diversity.
client_inherited
¶
ClientInheritedSubstrate — borrow the calling client's LLM. (D2)
This is the substrate that closes the "嫁接到客戶端" thesis claim. The
vacant carries no API key and runs no local model. Instead, when an
MCP-aware client calls the vacant, the client lends its own LLM
session for the duration of the call: the vacant asks the client (via
MCP sampling/createMessage) to do an inference on its behalf and
treats the result as the substrate's response.
Architecture:
SubstrateHandle— the small dataclass that travels in the envelope metadata. It names the substrate kind (e.g. "client-inherited"), the model hint the client offers, and an opaque transport callback id.SamplingCallback—async (system_prompt, user_prompt) -> text, i.e. the functionserve.pybuilds at the moment of receiving an MCPtools/calland hands to this substrate.ClientInheritedSubstrate— theSubstrateBackendproper. Itsnamerecords the borrowed identity so reputation per-substrate works (a vacant that always runs under Claude scores its records asclient-inherited:<caller>:claude-sonnet-4-6).
Security model: see ADR D017. The vacant trusts the caller's LLM
output, but signs its own logbook entry. The substrate identity is
recorded as client-inherited:<caller_vacant_id>:<model_hint> so a
reviewer can attribute behaviour to the borrowed brain.
SubstrateHandle
dataclass
¶
SubstrateHandle(substrate_kind: str = 'client-inherited', model_hint: str = 'unknown', transport_callback_id: str = '')
Caller-supplied substrate identifier, carried in envelope metadata.
The values are advisory — the actual inference happens through the
SamplingCallback the serve layer constructs from the MCP session.
transport_callback_id is opaque to the vacant; it lets the serve
layer route the sampling request back to the right MCP session.
borrowed_from
¶
Reputation key: client-inherited:<caller>:<model_hint>.
Used by the logbook attestation so that "this vacant ran on a borrowed Claude session" is auditable post-hoc.
Source code in src/vacant/substrate/client_inherited.py
ClientInheritedSubstrate
dataclass
¶
ClientInheritedSubstrate(callback: SamplingCallback, handle: SubstrateHandle = SubstrateHandle(), caller_vacant_id_hex: str = '')
Bases: SubstrateBackend
Substrate that delegates inference to a caller-supplied callback.
Constructed by serve.py (or mcp_adapter.py) at the moment an
incoming call carries a SubstrateHandle. The instance lives only
for the duration of that one call — the vacant has no LLM state of
its own.
to_logbook_entry
¶
Logbook entry payload describing the borrowed substrate.
Use this when appending a SUBSTRATE_BORROWED log entry so the
chain records "this inference was outsourced to the caller's
LLM at <model_hint> for <caller>".