Skip to main content

Dome Python SDK

The official Python SDK for Dome agent governance. Use dome.Client for gateway-backed tools, LLM calls, audit reads, activity correlation, and optional local policy evaluation from one SDK surface.

Install

pip install dome-sdk

Provider SDKs are optional. client.gateway.openai_client() lazy-imports openai; client.gateway.anthropic_client() lazy-imports anthropic. Install those packages only in applications that use those factories.

Gateway Quick Start

import dome

client = dome.Client(
    token="dome_...",
    gateway_url="https://gateway.example.com/vg/<virtual-gateway-id>",
    act_as_method="none",
)
client.connect()

tools = client.gateway.tools.list(
    act_as=dome.PlainActAs(email="alice@example.com"),
)
print([tool.name for tool in tools])

result = client.gateway.tools.call(
    "github/list_issues",
    {"repo": "dome"},
    act_as=dome.PlainActAs(email="alice@example.com"),
)
print(result.content)

response = client.gateway.llm.chat(
    model="prod-gpt",
    messages=[{"role": "user", "content": "Summarize the open incidents"}],
    act_as=dome.PlainActAs(email="alice@example.com"),
)

client.close()

The gateway URL names the one Virtual Gateway this agent is granted (/vg/{uuid}). There is no root endpoint — a bare-root URL (no /vg/{id} segment) fails closed with DomeVirtualGatewayError at connect(). Equivalently, keep the base and id separate and let the SDK compose them:

client = dome.Client(
    token="dome_...",
    gateway_url="https://gateway.example.com",
    virtual_gateway_id="<virtual-gateway-id>",
    act_as_method="none",
)

virtual_gateway_id also composes with a control-plane-discovered base (control_plane_url and no gateway_url), so the only per-environment setting is the Virtual Gateway id.

Gateway calls are authorized, routed, credential-checked, and audited by Dome. The SDK does not require provider API keys in agent code.

Client Configuration

All URLs are named by plane:

Argument Description
token Agent API key or exchanged token.
control_plane_url Dome control-plane URL, used for token exchange, gateway discovery, local policy sync, and audit reads.
gateway_url Dome gateway data-plane URL, including the /vg/{id} prefix naming the agent's Virtual Gateway. Required for raw-key gateway-only use; a bare root URL with no virtual_gateway_id fails closed at connect() (one exception — see Bare-root leniency).
virtual_gateway_id Virtual Gateway UUID to scope gateway calls to. Composed with gateway_url (or the control-plane-discovered base) into the call-ready {root}/vg/{id} URL — idempotent when the URL already names the same VG, an error on a conflicting one.
gateway_auth_mode auto (default), raw, or exchange. With an explicit gateway_url, auto uses the raw dome_* key.
act_as_method Gateway act-as method: none, hmac, oidc, or bound. Required when the SDK cannot discover it from the control plane.
tools_list_cache_ttl In-memory tools/list cache TTL, default 30 seconds.

Bare-root leniency (implicit, not the intended shape)

The documented, intended configuration is always a gateway_url that names a Virtual Gateway — either {root}/vg/{id} directly, or a base plus virtual_gateway_id. connect() enforces that, so misconfiguration surfaces at startup rather than on your first tool call.

There is exactly one place the SDK is deliberately lenient instead: if you configure both control_plane_url and a bare-root gateway_url (no VG named), connect() does not raise. Gateway intent can't be proven for that config — you may only be using client.audit or check() — and the bare physical base is precisely what bootstrap, enrollment, and token exchange return, so it gets wired in without meaning "route gateway calls here".

In that case the SDK logs an ERROR and continues. Every gateway surface still fails closed with DomeVirtualGatewayError when used, so nothing is silently routed anywhere. Treat the log as a bug to fix, not as a supported mode: pass virtual_gateway_id, use a /vg/{id} URL, or drop gateway_url if you don't need the gateway.

A conflicting or non-UUID virtual_gateway_id is never tolerated — those raise at connect() regardless of which surfaces you use.

connect() prepares token/gateway transport state. It does not block on local Cedar bundle sync. Call start_policy_sync() only when you want local self-enforced checks.

Gateway Readiness

client.gateway.wait_ready() is an explicit diagnostic/bootstrap helper, not a startup requirement.

client.gateway.wait_ready(timeout=30)

It polls unauthenticated GET /ready (readiness probes the physical gateway root; data calls always go through the /vg/{id} URL). For newly created raw keys, use dome.wait_for_agent_key(...) or dome.bootstrap.ensure_agent(..., wait_gateway=True) in setup scripts when you need to wait for the gateway's synced API-key snapshot.

Tools

tools/list can do real gateway work: upstream discovery, per-user authorization, audit emission, and credential-link generation. The SDK never does an implicit list-before-call.

catalog = client.gateway.tools.list(refresh=True, act_as=user)
cached = client.gateway.tools.list_cached(act_as=user)
client.gateway.tools.invalidate_cache(act_as=user)

The cache is in-memory, bounded, keyed by gateway, agent, act-as method, and an act-as header hash. Cache hits do not call the gateway and do not emit gateway audit. Non-blocking credential advisories from tools/list are returned on the fresh response and are not cached by default.

tools/call distinguishes JSON-RPC errors from successful MCP tool errors. If an upstream tool returns isError=true, the SDK raises DomeToolExecutionError — a failed tool call is not a success-shaped return value. Pass raise_on_tool_error=False to get ToolCallResult(is_error=True) back instead, for callers that want to read the partial content on a failed call; the raised error carries the same payload on .raw.

LLMs

The low-level SDK-owned LLM helpers return provider-shaped response dictionaries and decode structured Dome gateway errors:

model = client.gateway.model("prod-claude", provider="anthropic", act_as=user)
message = model.messages.create(
    messages=[{"role": "user", "content": "Draft a status update"}],
    max_tokens=512,
)

Stock provider clients are available for teams that want provider-native APIs:

openai_client = client.gateway.openai_client(act_as=user)
completion = openai_client.chat.completions.create(
    model="prod-gpt",
    messages=[{"role": "user", "content": "Hello"}],
)

Provider factories return provider-shaped clients and do not promise typed Dome exceptions unless Dome owns the transport/subclass for that path. Use client.gateway.llm.* or client.gateway.model(...) when you want SDK-owned typed error decoding.

Act-As

The gateway act-as trust model is method-driven by Dome configuration, not by the client choosing a header shape.

SDK value Gateway method Header behavior
PlainActAs(...) or legacy ActAs(...) none Canonical JSON, standard-base64 encoded.
HMACActAs(secret=..., ...) hmac Signed, timestamped, base64 encoded.
OIDCActAs(jwt=...) or raw JWT string oidc Raw JWT evidence.
BoundActAs() or no act-as bound No client act-as header; server-bound identity is used.

For bound, the SDK fails closed if caller code tries to send an act-as header.

Activity Correlation

Direct calls carry no activity ID. Use an explicit activity context to correlate a run across gateway calls and control-plane audit reads:

with client.activity(metadata={"case": "incident-123"}) as activity:
    client.gateway.tools.call("github/list_issues", {"repo": "dome"})
    page = client.audit.query(event_types=("mcp.tool_call.completed",))
    print(activity.activity_id)

The ID is an opaque UUID by default. Put human labels in metadata, not in X-Dome-Activity-Id.

Local check() decisions made inside an activity are reported to that activity too, so the agent's own decisions land on the same chain as the gateway calls they guard — including checks made on a worker thread, since the activity is captured when the decision is made rather than when the audit batch flushes. This is the correlator to reach for: it spans gateway calls, control-plane reads, local decisions, the openai/anthropic clients from gateway.openai_client() / anthropic_client(), and the LangChain adapter's chat models. All of them resolve the activity per request, so a client or chat model built once at startup and reused across turns lands each call on the right chain.

dome.current_activity_id() returns the enclosing activity's id (None outside one) for integrations that issue their own requests and need to stamp dome.ACTIVITY_ID_HEADER themselves.

The one boundary the activity does not cross is a thread: contextvars are per-thread, so work handed to a worker thread inside an activity is outside it unless you re-enter the activity there. Asyncio tasks inherit it.

check(trace_id=...) is the other axis, and it points outward. It stamps an id you already own — an HTTP request id, a job id — onto the emitted device.decision, so the decision joins a trace in your system:

with client.activity(metadata={"case": "incident-123"}):
    client.check(tool="github/list_issues", trace_id=http_request_id, ...)

trace_id is correlation only and never reaches rule evaluation. The SDK has no public way to put that same id on a gateway call, so it links the decision to your system rather than to Dome-side gateway events — use the activity for that.

Caller-Surface Attribution

Every request this SDK sends to Dome — control plane, gateway, and the openai/anthropic clients it builds — carries X-Dome-Caller-Surface: sdk, so audit can answer "which application did this?" without guessing. Nothing to configure; first shipped in dome-sdk 0.1.0.

Attribution is telemetry only: it never participates in authentication or authorization, and Dome bounds the value to its own enum, so a wrong or forged one cannot widen access.

Request origin has two independent axes on a returned event:

event = client.audit.query(event_types=("mcp.tool_call.completed",)).events[0]

event.request_surface.surface         # "INITIATOR_SURFACE_GATEWAY_MCP" — verified transport
event.request_surface.caller_surface  # "CALLER_SURFACE_SDK" — asserted calling application

Audit Reads

Gateway-owned audit is the source of truth for gateway calls. Query it through the same gateway-first client when control_plane_url is configured:

page = client.audit.query(
    event_types=("llm.called",),
    results=("EVENT_RESULT_SUCCEEDED",),
    page_size=50,
)

for event in page.events:
    print(event.type, event.correlation.activity_id, event.payload)

Audit read models expose activity_id and activity_trust on event.correlation, and surface / caller_surface on event.request_surface (see Caller-Surface Attribution above).

Local Policy Checks

Local policy evaluation is available when an agent needs a fast, self-enforced Cedar decision inside its own process.

client = dome.Client(
    token="dome_...",
    control_plane_url="https://api.dome.example.com",
)
client.start_policy_sync()

decision = client.evaluate(
    tool="database/query",
    action="mcp:call",
    connection_id="<connection-uuid>",
)
if decision.allowed:
    run_query()

check() is callback-based for local self-enforcement:

client.check(
    tool="database/query",
    action="mcp:call",
    connection_id="<connection-uuid>",
    on_allow=lambda result: run_query(),
    on_deny=lambda req, reason: log_denial(reason),
)

What a local check is, and is not

A local check is a pre-check, not enforcement. The gateway is authoritative; a local allow is not a promise that the gateway will allow the same call. Two things follow from that:

Address the tool the way the gateway does. Dome stores rules against the MCP connection's UUID — Dome::MCPTool::"<connection-uuid>/<tool>" — so that a connection rename cannot silently change who can call what. Pass connection_id= (the id DomeAdminClient.create_mcp_connection returns, or the connection's id in the dashboard) and the SDK builds the same Cedar entity the gateway does: id <connection-uuid>/<tool>, resource.name the human <connection>/<tool> you passed as tool, plus resource.connection_name and resource.tool_name. Without it the entity is keyed on the name, no stored rule can match, and the SDK logs an ERROR saying so. There is no agent-scoped RPC that maps a tool name to its connection UUID, so an agent has to be told the id it should use.

Some rules cannot be evaluated in-process at all. Rules that gate on facts only the gateway holds — a tool's full Virtual Gateway memberships, an LLM routing pool, customer-defined connection attributes — have no local answer. The SDK stamps resource.virtual_gateways from the client's own configured VG and omits what it cannot prove, so such a rule fails closed locally even where the gateway would permit. Route the call through client.gateway when the decision has to be the platform's.

Bootstrap Helpers

Setup scripts can provision a development agent and issue a fresh key:

agent = await dome.bootstrap.ensure_agent(
    name="incident-bot-dev",
    control_plane_url="https://api.dome.example.com",
    platform_key="dome_pk_...",
    workspace_id="...",
    capabilities=["mcp:call"],
    wait_gateway=True,
    virtual_gateway_id="<vg-uuid>",  # readiness probes a VG surface
)

print(agent.agent_id, agent.token, agent.gateway_url)

# agent.gateway_url is the call-ready {base}/vg/{id} URL, composed from the
# key's physical gateway_endpoint and the virtual_gateway_id passed above.
# When composing manually (e.g. from enrollment or token-exchange values),
# use dome.virtual_gateway_url(base, virtual_gateway_id).

LangChain

dome-langchain stays an adapter over the SDK.

from dome_langchain import DomeGatewayTool, DomeChatOpenAI, govern_tools

# Gateway-backed MCP tool execution.
search = DomeGatewayTool(
    dome_client=client,
    name="github/list_issues",
    description="List GitHub issues",
)

# Local Python tool with local pre-checks.
governed_local_tools = govern_tools(client, [python_tool])

# Provider-compatible chat through the Dome gateway.
llm = DomeChatOpenAI.for_agent(
    agent,
    model="prod-gpt",
    act_as=dome.ActAs(email="alice@example.com"),
)

Error Model

SDK-owned gateway transports raise typed Dome errors from structured wire contracts, including DomeAuthorizationDenied, DomeCredentialRequired, DomePolicyStale, DomeRateLimited, and DomeModelNotFound. Ambiguous legacy or provider-shaped responses remain generic DomeGatewayError or provider-shaped errors rather than being parsed from brittle message strings.

License

Proprietary. See LICENSE for details.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dome_sdk-0.2.0.tar.gz (209.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dome_sdk-0.2.0-py3-none-any.whl (77.3 kB view details)

Uploaded Python 3

File details

Details for the file dome_sdk-0.2.0.tar.gz.

File metadata

  • Download URL: dome_sdk-0.2.0.tar.gz
  • Upload date:
  • Size: 209.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dome_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 0a74a15dedf6c1d81e19a182b555888ef52aefe6e69ccfb128b9652336de7a66
MD5 7862a669c26e6865b16748b4c942a552
BLAKE2b-256 51c135a4e3e3a72c2d03efcf6d5de2ae639c2d8664d1fabae04236213e87fb97

See more details on using hashes here.

Provenance

The following attestation bundles were made for dome_sdk-0.2.0.tar.gz:

Publisher: release.yml on dome-systems/sdk-dome-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dome_sdk-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: dome_sdk-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 77.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dome_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7226026bfb6619d7a27ea295ccf4d8081dd58a48ca2ac2c1d54df618f27aebec
MD5 f732dcefb7df165bcacced8d1a8bf00e
BLAKE2b-256 3efbeffc0842e25635ecea6ab00bba93439dcba834bcf13260a858854835f9e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for dome_sdk-0.2.0-py3-none-any.whl:

Publisher: release.yml on dome-systems/sdk-dome-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page