@lakefrontai/sdk
v0.1.0
Published
Interceptor SDK for AI agents — tamper-evident evidence capture with zero added failure modes
Readme
@lakefrontai/sdk
TypeScript SDK for lakefrontai — tamper-evident evidence capture for AI agents.
Wraps any agent framework and intercepts actions before they execute. Adds <5ms overhead. Fails open.
Install
npm install @lakefrontai/sdk
# With LangChain support
npm install @lakefrontai/sdk @langchain/core
# With OpenAI support
npm install @lakefrontai/sdk openaiQuickstart
import { createInterceptor } from "@lakefrontai/sdk";
const lf = createInterceptor({
apiKey: "lf_your_api_key",
mandate: {
mandateId: "mnd_abc123", // ID from the Lakefront Mandates UI
agentId: "payment-agent-v1",
displayName: "Payment Agent",
authorizedScopes: ["payment.write", "payment.refund"],
expiresAt: 1780000000000, // unix ms — copied from the UI
issuedBy: "[email protected]",
},
});
// Wait for startup mandate validation (optional — first action will surface errors too)
await lf.ready();
// 1. wrap() — drop-in replacement for any async function
const charge = lf.wrap(stripe.charges.create.bind(stripe.charges), {
method: "stripe.charges.create",
category: "payment",
extractArgs: (params) => ({
amount: params.amount,
currency: params.currency,
}),
});
const result = await charge({ amount: 2000, currency: "usd", source: "tok_visa" });
// ↑ The original function runs normally. Before and after are recorded
// as a hash-chained evidence event. If lakefrontai's API is down,
// the charge still completes — evidence is buffered locally.
// 2. @intercept decorator
class PaymentAgent {
@lf.intercept({ method: "refund", category: "refund" })
async refund(chargeId: string, amount: number) {
return stripe.refunds.create({ charge: chargeId, amount });
}
}
// 3. trace() — for streaming / manual flows
const finish = lf.trace("openai.chat", "api_call", { model: "gpt-4" });
const response = await openai.chat.completions.create({ ... });
finish({ result: response });
// Flush before process exit
await lf.flush();
lf.shutdown();Mandate enforcement
Mandates are created in the Lakefront dashboard and enforced by the SDK at three levels:
1 · Startup validation
On createInterceptor(), the SDK fetches the mandate from the server and verifies it:
try {
const lf = createInterceptor({ apiKey, mandate });
await lf.ready(); // throws if revoked or expired
} catch (err) {
if (err instanceof MandateRevokedError) { /* mandate was revoked in the UI */ }
if (err instanceof MandateExpiredError) { /* mandate past its expiry date */ }
}If the API is unreachable at startup, the SDK fails open — the agent starts and uses the locally supplied mandate values.
2 · Per-call scope check
Before every wrap() / intercept() / trace() / wrapSync() call, the SDK checks that the action's category is covered by authorizedScopes:
| Category | Required scope |
|---|---|
| payment | payment.write |
| refund | payment.refund |
| auth | auth.write |
| data_access | data.read |
| api_call | data.read |
| tool_use | data.read |
| decision | data.read |
import { MandateScopeError } from "@lakefrontai/sdk";
// If the mandate only has ["payment.write"] and the agent tries:
lf.wrap(deleteUser, { method: "deleteUser", category: "data_access" });
// → MandateScopeError: Mandate mnd_abc123 does not permit category "data_access".
// Allowed: payment.writeControl what happens on a scope violation:
createInterceptor({
// ...
onScopeViolation: "throw", // default — blocks the action
onScopeViolation: "warn", // logs a warning, action proceeds
onScopeViolation: "allow", // silently permits (audit-only mode)
});3 · Background re-validation
Every 5 minutes (configurable), the SDK re-fetches the mandate from the server. If a compliance officer revokes the mandate in the UI, the running agent will refuse new actions within one refresh interval — no restart required.
createInterceptor({
// ...
mandateRefreshMs: 60_000, // re-validate every 60s
mandateRefreshMs: 0, // disable background refresh
});Error types
import {
MandateExpiredError, // mandate.expiresAt is in the past
MandateRevokedError, // mandate was revoked via the dashboard or API
MandateScopeError, // action category not in authorizedScopes
} from "@lakefrontai/sdk";Getting a mandate ID
- Go to Dashboard → Mandates → New mandate
- Set the agent ID, authorized scopes, spending limit, and expiry
- Click Create mandate
- Expand the row → copy the ready-to-paste SDK snippet
LangChain
import { LakefrontCallbackHandler } from "@lakefrontai/sdk/langchain";
const handler = new LakefrontCallbackHandler({ apiKey, mandate });
const executor = new AgentExecutor({ agent, tools, callbacks: [handler] });
await executor.invoke({ input: "charge $20 to card on file" });OpenAI Assistants
import { wrapOpenAI, pollRunToCompletion } from "@lakefrontai/sdk/openai";
const openai = wrapOpenAI(new OpenAI({ apiKey: "sk-..." }), lfConfig);
// Now every chat.completions.create and runs.create is intercepted
const run = await openai.beta.threads.runs.create(threadId, { assistant_id });
const done = await pollRunToCompletion(openai, lf, threadId, run.id);CrewAI (TypeScript)
import { wrapCrewTools } from "@lakefrontai/sdk/crewai";
const tools = wrapCrewTools([searchTool, calculatorTool], lfConfig);
const crew = new Crew({ agents, tasks, tools });Fail-open behaviour
| Scenario | Agent behaviour | Evidence |
|---|---|---|
| lakefrontai API up | Normal | Written immediately |
| lakefrontai API down | Normal ✓ | Buffered in memory, retried every 2s |
| API down > buffer full | Normal ✓ | Spilled to /tmp/lf_buffer_*.ndjson |
| Filesystem also down | Normal ✓ | onDropped callback fired |
Mandate enforcement uses cached state — if the refresh call fails, the agent keeps running with the last-known mandate until the next successful fetch. Only a hard MandateExpiredError or MandateRevokedError at startup blocks the agent.
Latency budget
| Phase | Time added | |---|---| | Pre-action capture | ~0.5ms | | Enqueue to buffer | ~0.3ms | | Scope check (cached) | <0.1ms | | Total hot-path | < 5ms | | Mandate re-validation | async, never blocks |
Configuration reference
createInterceptor({
apiKey: "lf_...", // required
mandate: { ... }, // required — from Mandates UI
apiUrl: "https://...", // default: http://localhost:3001
sessionId: undefined, // auto-generated as "<pid>-<random>"
bufferSize: 200, // max events before spill to file
flushIntervalMs: 2_000, // background drain frequency
bufferPath: undefined, // file path for crash-safe spill
onDropped: undefined, // called on irrecoverable event drop
attestationToken: undefined, // Ed25519 token for Layer 3 attestation
mandateRefreshMs: 300_000, // how often to re-validate mandate (ms)
onScopeViolation: "throw", // "throw" | "warn" | "allow"
});Horizontal scaling
Rule: one LakefrontInterceptor instance per worker process. Never share a sessionId across workers.
The hash chain is computed locally per interceptor. If two processes write to the same sessionId, their sequence numbers will collide and chain verification will fail.
// ✅ Each worker gets its own session — use transaction_id to group them
const lf = createInterceptor({
apiKey: "lf_...",
mandate,
// sessionId auto-generated as `<pid>-<random>` — unique per process
});
// Link sessions to a payment by passing transaction_id when opening a dispute:
// POST /disputes { session_id: lf.sessionId, transaction_id: "pi_xxx", ... }// ❌ Never do this across workers
const SHARED_SESSION = "order-789";
// Worker 1: createInterceptor({ sessionId: SHARED_SESSION }) ← collision
// Worker 2: createInterceptor({ sessionId: SHARED_SESSION }) ← collisionIf you need to correlate evidence across multiple parallel agents working on the same payment, use transaction_id / payment_intent_id on the dispute — that field exists precisely for this purpose.
