SDK governance — five-minute quick start
Use the BlackLake SDK when your code calls LLMs or tools directly — backend services, custom AI Actors, batch jobs, anything the MCP gateway doesn't see on the wire. The SDK gives you bl.govern() for the policy decision and bl.cost.record() for self-attributed cost.
This is the right path when you've got code already; the CLI tools quick start is the right path when you've got a Claude Code / Codex / Cursor terminal.
Install#
npm install blacklake
1. Configure the client#
import { BlackLake, suggestActorName, detectSession } from 'blacklake';
const bl = new BlackLake({
apiKey: process.env.BLACKLAKE_API_KEY, // bl_… (workspace) or bl_usr_… (per-user)
baseUrl: process.env.BLACKLAKE_BASE_URL ?? 'https://api.blacklake.systems',
});
Use a workspace key for backend services (no per-user attribution needed) or a user-scoped key when calls represent a specific human.
2. Register the agent + tool + binding#
You only do this once per workspace — the records persist. Either through the console (/agents, /tools) or via the SDK:
const agent = await bl.agents.create({
name: 'expense-bot',
environment: 'production',
risk_classification: 'high',
});
const tool = await bl.tools.create({
name: 'stripe.refund',
risk_classification: 'critical',
});
await bl.agents.bindTool(agent.id, tool.id);
The binding matters. Without it every govern() call denies with reason "Tool is not bound to agent" — see Policy guide → Bind your tools first.
3. Wrap every consequential action with govern()#
const session = detectSession({ tool_client: 'expense-bot', tool_client_version: '1.4.2' });
const decision = await bl.govern({
agent: 'expense-bot',
tool: 'stripe.refund',
action: { customer_id: 'cus_xyz', amount_cents: 4200 },
context: { session },
estimate: {
provider: 'anthropic',
model: 'claude-opus-4-7',
input_tokens: 1200,
output_ceiling_tokens: 800,
estimated_cost_usd: 0.12,
},
});
if (decision.decision === 'allow') {
await stripe.refunds.create({ customer: 'cus_xyz', amount: 4200 });
}
What you get back:
decision.decision—allow/deny/approval_required/default_deny. Branch on this.decision.decision_token— HMAC-signed receipt. Quote it when reporting outcomes;bl.decisions.verify(token)proves the decision was real.decision.budget_headroom_usd— how close the next call would be to denying. Surface in your operator UI.decision.evaluation_id— opens atconsole.blacklake.systems/evaluations/<id>.
4. Record cost the gateway didn't see#
When your code calls the LLM directly (Bedrock / Vertex / your own router), the gateway never saw the wire — feed cost in:
await bl.cost.record({
evaluation_id: decision.evaluation_id,
provider: 'anthropic',
model: 'claude-opus-4-7',
input_tokens: 1200,
output_tokens: 850,
cache_read_tokens: 4000,
cache_write_tokens: 0,
thinking_tokens: 0,
});
The receipt upgrades to v2 (cost-bound) once cost lands, and the Usage page picks it up immediately.
5. Verify a decision later#
A receipt is only worth something if it can be verified independently:
const result = await bl.decisions.verify({
evaluation_id: decision.evaluation_id,
decision_token: decision.decision_token,
});
if (result.valid) {
// result.decision, result.policy_name, result.policy_snapshot, result.action_results
}
Pass this through to anyone receiving an "approved" or "denied" claim from your service — they can verify it without trusting your code.
What's next#
- Cost governance quick start — budgets that deny before the spend, baselines, anomalies.
- Policy guide — selectors, priorities, two-person, approval roles, cost-aware policies.
- SDK reference — every method on every resource.
- Concepts → Session-actor convention — pass
context.sessionso the audit trail attributes work cleanly per developer.