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:
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 thedist/mcp/manifest. - Building a Python agent with LangChain, CrewAI, LlamaIndex, the OpenAI
Agents SDK, or Strands? Use the
runewardPython package. - Building a TypeScript agent on the Vercel AI SDK, LangChain.js, or Strands?
Use the
@runeward/sdkpackage. - 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 checkexit_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 theapproval_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¶
denyis a policy decision, not a transient error. Don't retry the same action; pick a different, allowed approach.require-approvalis a hard pause. Surface the approval id to a human and wait for the outcome (resolve it via the dashboard, the CLI, orPOST /v1/approvals/{id}/{approve,deny}).- Prefer the tightest profile that lets the task succeed.