Metadata-Version: 2.4
Name: dome-langchain
Version: 0.1.0
Summary: Dome Platform LangChain adapter — govern LangChain tools with Dome authorization
Project-URL: Homepage, https://domesystems.ai
Project-URL: Documentation, https://docs.domesystems.ai
Project-URL: Repository, https://github.com/dome-systems/sdk-dome-python
Project-URL: Issues, https://github.com/dome-systems/sdk-dome-python/issues
Author-email: Dome Systems <eng@domesystems.ai>
License: Proprietary
Keywords: agent-governance,ai-agents,authorization,dome,langchain,langgraph,llm,tools
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: dome-sdk>=0.1.0
Requires-Dist: langchain-core>=0.3
Provides-Extra: anthropic
Requires-Dist: langchain-anthropic>=0.2; extra == 'anthropic'
Provides-Extra: dev
Requires-Dist: langchain-anthropic>=0.2; extra == 'dev'
Requires-Dist: langchain-openai>=0.2; extra == 'dev'
Requires-Dist: langgraph>=0.2; extra == 'dev'
Requires-Dist: mypy>=1.13; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
Provides-Extra: openai
Requires-Dist: langchain-openai>=0.2; extra == 'openai'
Description-Content-Type: text/markdown

# Dome LangChain Adapter

LangChain / LangGraph integration for the [Dome Platform](https://domesystems.ai).
It injects Dome **identity** and **authorization** into the seams of an
existing LangChain app — it is *additive*, not a replacement for LangChain.

There are three surfaces, each opt-in:

| Surface | What it governs | Key symbols |
|---------|-----------------|-------------|
| Governed tools | tool calls | `DomeGovernedTool`, `govern_tools` |
| Governed chat models | LLM calls | `DomeChatOpenAI`, `DomeChatAnthropic`, `broker_chat`, `broker_chat_for` |
| Compose primitives | agent-to-agent handoffs & spawned fleets | `Gate`, `gate_edge`, `gate_command`, `session`, `mint_ephemeral`, `fleet_send`, `causal` |

## Install

```bash
pip install dome-langchain                 # core adapter: tools + gates + spawn
pip install 'dome-langchain[openai]'       # + DomeChatOpenAI (langchain-openai)
pip install 'dome-langchain[anthropic]'    # + DomeChatAnthropic (langchain-anthropic)
pip install 'dome-langchain[langgraph]'    # + the LangGraph shims
```

The core install needs only `langchain-core`. The chat-model surfaces pull in a
provider client; importing `DomeChatOpenAI` / `DomeChatAnthropic` without the
matching extra raises an `ImportError` with the install hint.

## 1. Governed tools

`DomeGovernedTool` wraps any LangChain `BaseTool`. Before each execution it
calls `dome.DomeClient.check()`; on deny it returns a denial message instead of
running, and audit events are emitted automatically.

```python
import dome
from dome_langchain import DomeGovernedTool, govern_tools

client = dome.DomeClient(dome.DomeConfig(base_url="https://api.domesystems.ai", token="dome_..."))
client.start()

governed = DomeGovernedTool(tool=search_tool, dome_client=client)
# or wrap several at once:
tools = govern_tools(client, [search_tool, db_tool, email_tool])

client.close()
```

## 2. Governed chat models

`DomeChatOpenAI` (the name mirrors its `ChatOpenAI` base; the older name
`DomeChatModel` still works as an alias) is a `ChatOpenAI` subclass whose
requests flow through the Dome Broker (the LLM gateway) under a specific
agent's identity. The Broker
authenticates the call as that agent, evaluates Cedar for the model action,
audits it, then forwards to the upstream provider. Because it *is* a
`ChatOpenAI`, streaming, tool-calling, and structured output work unchanged.

```python
from dome_langchain import AgentIdentity, DomeChatOpenAI

# `for_agent` accepts any agent identity (anything with `token` +
# `gateway_endpoint`) — an `EphemeralAgent` from the spawn API, or an
# `AgentIdentity` for an ordinary long-lived agent:
llm = DomeChatOpenAI.for_agent(
    AgentIdentity(token="dome_...", gateway_endpoint="https://gw.example.com")
)
llm.invoke("Summarise this ticket.")          # called as that agent

# End-user delegation (act-as) — per call, or bound once:
llm.invoke("Summarise this ticket.", act_as=user)   # ActAs or OIDC-JWT string
per_user = llm.with_act_as(user)
per_user.invoke("Summarise this ticket.")
```

`act_as` is encoded into an `X-Dome-Act-As` header on that request only; the
Broker exposes it to Cedar as `principal.act_as` and does not forward it
upstream. `broker_chat` / `broker_chat_for` are factory shims that return a
`DomeChatOpenAI` (with a construction-time act-as default).

For Claude-native features (prompt caching, extended thinking, citations) use
the Anthropic ingress instead:

```python
from dome_langchain import DomeChatAnthropic
llm = DomeChatAnthropic.for_agent(agent, model="claude-haiku-4-5")
```

## 3. Compose primitives (chains & spawn)

### Gated handoffs

A `Gate` turns a Dome authorization check into a routing decision. `gate_edge`
adapts it to a LangGraph conditional edge (`(state) -> next_node`); `gate_command`
is the newer node-returns-`Command` idiom. `Gate` is also a `Runnable` **guard**:
in an LCEL chain, `gate | next` forwards the payload on allow and raises
`GateDenied` on deny. (Use `gate.evaluate()` when you want the `GateDecision`
value instead.)

> **What a gate checks — and what it doesn't.** A gate evaluates Cedar with the
> agent backing `dome_client` (typically the orchestrator/parent) as the
> **principal** — *not* the downstream node. The `from_node`/`to_node` labels
> and any extra context are **application-asserted, not verified**: a gate is a
> client-side routing decision, not a cryptographic boundary, so it governs hops
> only insofar as your nodes assert that context honestly. Verified call-chain
> identity is a planned platform feature (S.PRP.008).

```python
from dome_langchain import gate_edge

g.add_conditional_edges(
    "classifier",
    gate_edge(dome_client=client, from_node="classifier",
              to_node="retriever", resource="chain.hop_allowed"),
    {"retriever": "retriever"},
)
```

On allow the hop proceeds; on deny it routes to an `on_deny` node or raises
`GateDenied`.

### Spawned ephemeral fleets

`session` + `mint_ephemeral` register real, short-lived child agents (each with
its own Dome identity and token) and tear them down on exit. `fleet_send` fans
them out across a LangGraph node via `Send`.

```python
from dome_langchain import session, fleet_send, DomeChatOpenAI

with session(platform, parent_agent_id=parent.agent_id, gateway_endpoint=gw) as s:
    fleet = s.spawn(count=4, template="researcher")
    sends = fleet_send(node="research", fleet=fleet,
                       payload_fn=lambda agent, i: {"agent": agent, "task": tasks[i]})
    # inside the "research" node, each worker calls the LLM as itself:
    #   llm = DomeChatOpenAI.for_agent(state["agent"])
# session exit revokes + hard-deletes the whole fleet
```

`mint_ephemeral` / `session` require a `dome.DomeAdminClient` (a workspace-admin
client), not the per-agent `DomeClient`.

## Examples

Runnable end-to-end demos live in [`examples/`](./examples) (chain, spawn,
broker). They require a provisioned Dome workspace — see
[`examples/README.md`](./examples/README.md).

## Documentation

- [Dome Platform Docs](https://docs.domesystems.ai)
- [Core Python SDK](https://github.com/dome-systems/sdk-dome-python)

## License

Proprietary. See LICENSE for details.
