# Immiscible SDKs

Source: https://immiscible.fly.dev/docs/sdks/overview

# Immiscible SDKs

Immiscible decides what your agents may spend, share and do before they do it, asks a person when it should, and signs a record of everything that crossed. The SDKs are the agent's side of that: ask, wait, act, settle, prove.

Install with `npm install @immiscible/sdk` or `pip install immiscible`, then start with the [quickstart](https://immiscible.fly.dev/docs/sdks/quickstart.md). Each package is published at version 0.1.1, MIT licensed, with its source public at [efr7-7/immiscible-sdks](https://github.com/efr7-7/immiscible-sdks).

| Package | Where | Runtime | Runtime dependencies |
|---|---|---|---|
| `@immiscible/sdk` | [`packages/immiscible-js`](https://github.com/efr7-7/immiscible/blob/main/packages/immiscible-js/README.md) | Node 18+, Deno, Bun, browsers, edge; ESM and CommonJS; typed | none |
| `immiscible` | [`packages/immiscible-py`](https://github.com/efr7-7/immiscible/blob/main/packages/immiscible-py/README.md) | Python 3.9+; type hints | none (standard library, pure-Python Ed25519) |
| `@immiscible/claude-code-hook` | [`packages/immiscible-claude-code`](https://github.com/efr7-7/immiscible/blob/main/packages/immiscible-claude-code/README.md) | Node 18+ | none |

## Integrations

| Framework | Page | TypeScript | Python |
|---|---|---|---|
| OpenAI Agents SDK | [openai-agents.md](https://immiscible.fly.dev/docs/sdks/integrations/openai-agents.md) | `guardOpenAITools`, `openaiToolGuardrail` | `guard_tools`, `guard_function_tool` |
| LangChain and LangGraph | [langchain.md](https://immiscible.fly.dev/docs/sdks/integrations/langchain.md) | `guardLangChainTools` | `guard_langchain_tools` |
| Vercel AI SDK | [vercel-ai.md](https://immiscible.fly.dev/docs/sdks/integrations/vercel-ai.md) | `guardAiTools`, `immiscibleMiddleware`, `gateway.aiSdkOpenAI()` | |
| Claude Code and the Claude Agent SDK | [claude-code.md](https://immiscible.fly.dev/docs/sdks/integrations/claude-code.md) | `@immiscible/claude-code-hook` | |
| MCP clients (Claude Desktop, Cursor, VS Code, Claude Code) | [mcp.md](https://immiscible.fly.dev/docs/sdks/integrations/mcp.md) | configuration only | configuration only |
| Any function a framework turns into a tool | | | `@guarded(...)` |

## The surface, side by side

| | TypeScript | Python |
|---|---|---|
| client | `new Immiscible({ apiKey, baseUrl })` | `Immiscible(api_key, base_url)` |
| a run (trace and session) | `immiscible.run({ sessionId, traceparent, client })` | `immiscible.run(session_id, traceparent=..., client=...)` |
| ask | `authorize(action, { idempotencyKey, signal })` | `authorize(action, idempotency_key=...)` |
| wait | `waitForDecision(id, { timeoutMs, initialDelayMs, maxDelayMs, signal })` | `wait_for_decision(id, timeout=, initial_delay=, max_delay=, cancel=)` |
| ask and wait, or refuse | `decide(action, opts)` | `decide(action, ...)` |
| report | `settle(id, { status, amount })` | `settle(id, status, amount)` |
| all of it | `guard(action, fn, opts)` | `with guard(action) as d:` or `@guard(mapper)` |
| actions | `Immiscible.paymentAction`, `Immiscible.dataAction`, `toolAction` | `Immiscible.payment_action`, `Immiscible.data_action`, `tool_action` |
| verify | `verifyReceipt(token, { issuer, jwks, online, expect })` | `verify_receipt(token, issuer, jwks=, online=, expect=)` |
| pin keys | `fetchJwks(issuer)`, `pinJwks(jwks)` | `fetch_jwks(issuer)`, `pin_jwks(jwks)` |
| gateway | `gateway.openai()`, `gateway.anthropic()`, `gateway.aiSdkOpenAI()`, `gateway.env()` | `gateway.openai()`, `gateway.openai_client()`, `gateway.anthropic_client()`, `gateway.env()` |
| MCP proxy | `mcpProxy(id).callWithApproval(tool, args)` | `mcp_proxy(id).call_with_approval(tool, args)` |
| errors | `ImmiscibleError`, `ImmiscibleDeniedError`, `ImmiscibleApprovalRequiredError`, `ImmiscibleApprovalTimeoutError` | same names |
| test double | `startFakeImmiscible()` from `@immiscible/sdk/testing` | `start_fake()` from `immiscible.testing` |

Environment, every package: `IMMISCIBLE_AGENT_KEY` and `IMMISCIBLE_URL` (the older `ASSAY_AGENT_KEY` and `ASSAY_URL` are read too).

## Runs, traces and sessions

A run is one task. Everything the agent does for it shares:

- **a W3C trace.** Every request carries `traceparent`: the run's trace id and a fresh span id. Continue your own trace by passing the active `traceparent` to `run()`. The server answers with its own span in the same trace, so the gate's decision and the gateway's record land in your trace view, and the evidence ledger carries the trace id.
- **a session.** The gateway records what entered the model's context: a web page, an email, a third-party tool's output. When the agent later asks to act and declares its provenance, the gate compares the two. A web page in the context and a request that claims to be purely the person's idea raises `provenance_mismatch`, and a person decides.

Sessions come in two kinds:

| | How | Header |
|---|---|---|
| issued (default) | leave the id out; the first gateway response carries one, and the run adopts it | `x-immiscible-session: imss_...` |
| chosen | `run({ sessionId: 'my-run-42' })`, or a Claude Code `session_id` | `x-immiscible-client-session: my-run-42` |

An issued id is bound to your key: presented by any other key, the gateway refuses it. A chosen id lives inside your key owner's namespace, so quoting someone else's string joins nothing of theirs. Use one run per task: a web page read for one task should not make the next one look tainted.

## The gateway

The gateway speaks OpenAI Chat Completions at `<base>/v1/chat/completions` and Anthropic Messages at `<base>/anthropic/v1/messages`. The helpers return the options each SDK takes, including a `fetch` (TypeScript) or httpx event hooks (Python) that carry the run's trace and session on every call.

```ts
import OpenAI from 'openai';
import Anthropic from '@anthropic-ai/sdk';

const run = new Immiscible().run();
const openai = new OpenAI(run.gateway.openai({ taskId: 'weekly-shop' }));
const anthropic = new Anthropic(run.gateway.anthropic());
```

```python
run = Immiscible().run()
openai = run.gateway.openai_client(task_id="weekly-shop")          # needs openai installed
anthropic = run.gateway.anthropic_client()                         # needs anthropic installed
# or, with your own client: OpenAI(**run.gateway.openai(), http_client=httpx.Client(event_hooks=run.context.httpx_event_hooks()))
```

The key the gateway meters defaults to the agent key; pass `apiKey` / `api_key` to use a person's inference key instead. `gateway.env()` gives the environment a child process needs (`OPENAI_BASE_URL`, `ANTHROPIC_BASE_URL`, keys, `TRACEPARENT`).

## Writing actions

`tool.call` is the default for every integration: "Run send_invoice: amount 12, customer Acme" as the summary (the tool's name and its arguments in words, never raw JSON), the domain from a `url` argument as the target, and `agent` provenance. Write your own mapping for anything with money or personal data in it, so the gate applies money and data rules:

```ts
mapToAction: ({ name, args }) =>
  name === 'search'
    ? null // read-only: no check
    : Immiscible.paymentAction({
        amount: args.pence,
        currency: 'GBP',
        merchant: args.domain,
        provenance: [{ source: 'user' }],
      })
```

Be honest about provenance. Anything the agent read (a page, an email, a document, a tool's output) belongs in it as `web`, `email`, `document` or `tool`. Missing provenance is treated as untrusted, which is the safe failure.

## Tests

```shell
cd packages/immiscible-js && npm run build && npm test            # unit tests on a fake, CommonJS, and the real server
cd packages/immiscible-py && python3 -m unittest discover -s tests -t .
cd packages/immiscible-claude-code && npm test
```

`packages/immiscible-js/test/integration.test.mjs` boots the real server from `src/server/app.js` in-process. The framework tests (`test/frameworks.test.mjs`, `tests/test_frameworks.py`) run against the real framework packages when they are installed (`IMMISCIBLE_FRAMEWORKS_DIR` points the TypeScript ones at a folder where they are), and skip otherwise.
