Skip to content

Adapters

Adapters make runeward's governed tools a first-class citizen in the agent frameworks you already use. Instead of hand-rolling HTTP calls to the control plane, you import a small client (or a ready-made set of framework "tools") and hand them to your agent. The agent then calls runeward_shell, runeward_python, runeward_read_file, … exactly as it would any other tool — but every call is routed through the full governance path:

policy check  →  approval gate  →  cost/loop guardrails  →  sandbox exec  →  audit ledger

So work runs in a disposable, deny-by-default sandbox; risky actions are blocked or escalated to a human; and everything is recorded in a tamper-evident ledger.

Which one do I want?

  • Using an MCP client (Claude Desktop, Cursor, VS Code)? You don't need an adapter at all — point the client at the runeward MCP server (runeward mcp). See the REST API and the dist/mcp/ manifest.
  • Building a Python agent with LangChain, CrewAI, LlamaIndex, the OpenAI Agents SDK, or Strands? Use the runeward Python package.
  • Building a TypeScript agent on the Vercel AI SDK, LangChain.js, or Strands? Use the @runeward/sdk package.
  • Rolling your own? Both RunewardClients are thin, typed wrappers over the REST control plane and work standalone with zero third-party runtime dependencies.

All adapters target the same control plane started with runeward serve (default http://localhost:8080) and expose the same method surface, named to match the MCP tools: create_sandbox, shell, python, node, read_file/write_file/list_files/search_files, list_approvals/approve/deny, kill_sandbox, plus audit/verify_audit.

The governance contract every adapter preserves

Every tool call resolves to one of three verdicts. Adapters surface these consistently — as typed exceptions on the raw client, and as short, model-readable strings on the framework tools:

  • allow — the action ran (for shell, still check exit_code).
  • deny — policy refused it. Do not retry the same action; choose an allowed approach or report the block.
  • require-approval — a human must sign off. Pause and surface the approval_id; don't route around the gate.

Authentication

If runeward serve is started with a token (--token / RUNEWARD_API_TOKEN) or RBAC, pass it to the client — RunewardClient(..., token="...") in Python or new RunewardClient({ token: "..." }) in TypeScript.

Python (runeward)

The core client uses only the Python standard library (urllib). Framework helpers are optional extras, imported lazily, so the base client works with nothing else installed.

pip install runeward                    # core client only (no third-party deps)
pip install "runeward[langchain]"       # + LangChain tools
pip install "runeward[crewai]"          # + CrewAI tools
pip install "runeward[llamaindex]"      # + LlamaIndex tools
pip install "runeward[openai-agents]"   # + OpenAI Agents SDK tools
pip install "runeward[strands]"         # + Strands Agents SDK tools

Raw client

from runeward import RunewardClient, RunewardDenied, RunewardApprovalRequired

rw = RunewardClient("http://localhost:8080")
sbx = rw.create_sandbox("dev")
print(rw.shell(sbx["id"], ["python3", "--version"])["stdout"])
rw.kill_sandbox(sbx["id"])

try:
    rw.shell(sbx["id"], ["rm", "-rf", "/"])
except RunewardDenied as e:
    print("blocked by policy:", e.reason)        # do NOT retry
except RunewardApprovalRequired as e:
    print("needs a human:", e.approval_id)        # pause for an operator

Framework tools

Each framework module exposes a single make_runeward_tools(client) factory that returns that framework's native tool objects, all named runeward_*:

from runeward import RunewardClient

client = RunewardClient("http://localhost:8080")

# Pick one, matching your framework:
from runeward.langchain_tools import make_runeward_tools       # LangChain
from runeward.crewai_tools import make_runeward_tools          # CrewAI
from runeward.llamaindex_tools import make_runeward_tools      # LlamaIndex
from runeward.openai_agents_tools import make_runeward_tools   # OpenAI Agents SDK
from runeward.strands_tools import make_runeward_tools         # Strands Agents SDK

tools = make_runeward_tools(client)
# LangChain:     pass `tools` to an AgentExecutor / create_react_agent(...)
# CrewAI:        crewai.Agent(tools=tools, ...)
# LlamaIndex:    FunctionAgent(tools=tools, ...) / ReActAgent
# OpenAI Agents: agents.Agent(name="...", tools=tools)
# Strands:       strands.Agent(tools=tools)

See the Python adapter README for full, per-framework examples.

TypeScript (@runeward/sdk)

The core RunewardClient uses the global fetch and has no runtime dependencies (Node 18+, Deno, Bun, browsers). Tool wrappers require optional peer dependencies, imported lazily.

npm install @runeward/sdk                           # core client only
npm install @runeward/sdk ai zod                    # + Vercel AI SDK tools
npm install @runeward/sdk @langchain/core zod       # + LangChain.js tools
npm install @runeward/sdk @strands-agents/sdk zod   # + Strands Agents SDK tools

Raw client

import { RunewardClient, RunewardDenied } from "@runeward/sdk";

const rw = new RunewardClient({ baseUrl: "http://localhost:8080" });
const sbx = await rw.createSandbox("dev");
console.log((await rw.shell(sbx.id, ["node", "--version"])).stdout);
await rw.killSandbox(sbx.id);

Vercel AI SDK

import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { RunewardClient } from "@runeward/sdk";
import { makeRunewardTools } from "@runeward/sdk/ai-tools";

const tools = await makeRunewardTools(new RunewardClient());
await generateText({ model: openai("gpt-4o"), tools, maxSteps: 8, prompt: "…" });

LangChain.js

import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { RunewardClient } from "@runeward/sdk";
import { makeRunewardTools } from "@runeward/sdk/langchain-tools";

const tools = await makeRunewardTools(new RunewardClient());
const agent = createReactAgent({ llm: new ChatOpenAI({ model: "gpt-4o" }), tools });

Strands Agents SDK

import { Agent } from "@strands-agents/sdk";
import { RunewardClient } from "@runeward/sdk";
import { makeRunewardTools } from "@runeward/sdk/strands-tools";

const tools = await makeRunewardTools(new RunewardClient());
const agent = new Agent({ tools });
await agent.invoke("Create a dev sandbox, run `node --version`, then tear it down.");

See the TypeScript adapter README for more.

Notes

  • deny is a policy decision, not a transient error. Don't retry the same action; pick a different, allowed approach.
  • require-approval is a hard pause. Surface the approval id to a human and wait for the outcome (resolve it via the dashboard, the CLI, or POST /v1/approvals/{id}/{approve,deny}).
  • Prefer the tightest profile that lets the task succeed.