@riskkernel/sdk
v0.9.0
Published
Thin TypeScript client for the RiskKernel reliability runtime (Surface 2).
Maintainers
Readme
@riskkernel/sdk
Thin TypeScript client for the RiskKernel
reliability runtime — Surface 2 (deep control). The Go daemon makes every
deterministic decision (budgets, halts, approval policy); this package just makes
governed runs ergonomic from Node/TypeScript. No runtime dependencies — it uses
the global fetch (Node 20+), the same stdlib-only ethos as the Python SDK.
Status: at parity with the Python SDK — run control, budgets, crash-resume (
resumeRun), the governing proxy, approval gates, and the Vercel AI SDK adapter.
No runtime dependencies: the core client uses only the global
fetch. The Vercel adapter (@riskkernel/sdk/vercel) takes@ai-sdk/provideras an optional peer, used at compile time only — importing the core never pulls it in.
Install
npm install @riskkernel/sdkRequires Node 20+. Zero runtime dependencies; for the Vercel adapter also install
the AI SDK (npm install ai), which brings the @ai-sdk/provider peer.
Use
import { Runtime, BudgetExceeded } from "@riskkernel/sdk";
const rt = new Runtime({ baseUrl: "http://localhost:7070" });
await rt.governedRun(
{ name: "research", budget: { dollars: 1.0, loops: 20, seconds: 600 } },
async (run) => {
// Route your LLM client through the governing proxy — one config change:
const { baseUrl, headers } = run.proxyConfig();
// new OpenAI({ baseURL: baseUrl, defaultHeaders: headers })
for (let i = 0; i < 100; i++) {
await run.step(); // throws BudgetExceeded when loops/time run out
await run.checkpoint("step", { cursor: i });
// ... your agent's work ...
}
},
);A budget halt surfaces as BudgetExceeded (reason is the machine-readable
HaltReason, e.g. dollar_budget_exceeded). The run is cancelled automatically if
the body throws — pass cancelOnError: false to opt out.
Resume after a crash
The daemon reloads non-terminal runs on restart with the budget and usage they had
already spent, so a SIGKILL'd run keeps enforcing without re-spending. Reattach to
it by id with resumeRun and pick your work back up from the last checkpoint:
await rt.resumeRun(runId, async (run) => { // attaches; never creates or cancels
const cp = await run.latestCheckpoint(); // the state you saved before the crash
const start = (cp?.payload?.cursor as number) ?? 0;
for (let i = start; i < total; i++) { // skip the steps you already paid for
await run.step(); // counts against the SAME budget
// ... your work ...
await run.checkpoint("step", { cursor: i + 1 });
}
});The run resumes against whatever budget it had left, so it can't overspend by
restarting — run.step() still throws BudgetExceeded at the original ceiling. The
run id is the only thing to keep across a restart (a file, your job queue, a DB row);
see docs/RESUME.md for the full model.
API
new Runtime(opts)—{ baseUrl, token, approvalPollIntervalMs, approvalTimeoutMs }.rt.governedRun({ name?, budget?, metadata?, cancelOnError? }, async (run) => …).rt.resumeRun(runId, async (run) => …)— re-attach to an existing run after a crash.run.step()·run.checkpoint(name, payload)·run.latestCheckpoint()·run.cancel(reason)·run.status()·run.proxyConfig()·run.approve(tool, opts).RiskKernel— the low-level/v1client, for manual control.- Errors:
BudgetExceeded,ApprovalDenied,ApprovalTimeout,APIError.
The /v1 contract is api/v1/openapi.yaml; the
governance principle is the same as every surface — the LLM proposes, the
deterministic Go core disposes.
Vercel AI SDK adapter
Govern a Vercel AI SDK agent with ~no code change: wrap any
model with governMiddleware(run) and every generateText / streamText ticks one
governed step, so the loop/time budget is enforced and a halt surfaces as
BudgetExceeded (not swallowed).
import { generateText, wrapLanguageModel } from "ai";
import { governMiddleware } from "@riskkernel/sdk/vercel";
await rt.governedRun({ budget: { loops: 20, dollars: 1 } }, async (run) => {
const { baseUrl, headers } = run.proxyConfig();
const openai = createOpenAI({ baseURL: baseUrl, headers }); // cost metered by the proxy
const model = wrapLanguageModel({ model: openai("gpt-4o-mini"), middleware: governMiddleware(run) });
await generateText({ model, prompt }); // loops/time enforced by the middleware
});Pinned and tested against AI SDK v5 (ai@^5 / @ai-sdk/provider@^2, an optional
peer). Runnable example: examples/vercel-ai-sdk.
Develop
npm install
npm run typecheck
npm test # vitest against an in-process mock daemon — no daemon, no keys
npm run build # tsup → dist (ESM + CJS + .d.ts)