Skip to content
BLACKLAKE

Core Concepts

This document explains how BlackLake works and what each part of the system does.


How BlackLake Works

BlackLake is an oversight layer that sits between an AI agent and the tools it calls. Every time an agent wants to use a tool, it asks BlackLake first. BlackLake checks the request against your policies and returns a decision: allow, deny, or approval_required. The result is recorded as an immutable evaluation. The agent — not BlackLake — is responsible for acting on the decision.

You can run BlackLake entirely on your own machine with no cloud account. The MCP proxy mode makes oversight transparent for any MCP-compatible client (Cursor, Windsurf, Claude Code, Claude Desktop, and others): point your AI tool at the BlackLake proxy instead of your MCP servers directly, and every tool call is evaluated automatically, with no changes to your agent code.


Local Mode

Running npx @blacklake-systems/surface-cli starts BlackLake locally. On first run it creates ~/.blacklake/ and initialises a SQLite database there. Two servers start:

  • API at http://localhost:3100 — handles governance, agents, tools, policies, approvals, and the MCP proxy
  • Console at http://localhost:3200 — the dashboard UI

Everything is stored locally. No data leaves your machine in local mode. There is no signup, no account, no API key required to get started.

The local database holds the full evaluation history. It persists between restarts. When you want that data available across machines or shared with a team, see Cloud Mode.


MCP Proxy

MCP (Model Context Protocol) is the open standard that AI tools use to connect to external services like file systems, APIs, and databases. The MCP proxy is the fastest way to add oversight to your existing AI setup. It works with any MCP-compatible client — Cursor, Windsurf, Claude Code, Claude Desktop, and others.

When you add a server to ~/.blacklake/mcp-config.json, BlackLake:

  1. Starts that MCP server as a child process
  2. Exposes a proxy endpoint at http://localhost:3100/mcp/<server-name>
  3. Intercepts every tool call from the client, runs it through the policy engine, and forwards it to your MCP server only if the decision is allow

The first time a tool is called through a given proxy, BlackLake auto-registers the MCP server as an agent and the tool as a tool record. They appear in the Surface dashboard immediately. You can then write policies against them like any other agent and tool.

Your MCP client does not need to know anything changed — it still speaks standard MCP. Only the URL changes.

The MCP Servers page at http://localhost:3200/mcp shows every proxied server: its status (running, stopped, or error), the number of tools discovered, the last seen timestamp, and any startup error message. The Reconnect button restarts a stopped or errored server without restarting the full CLI.


Agents

An agent represents an AI agent or automated system in your setup. BlackLake tracks governance decisions per agent.

Agents are created in two ways:

  • SDK-registered — your code calls bl.agents.create() or POST /v1/agents to register the agent explicitly, setting its name, environment, and risk classification.
  • MCP-auto-registered — when a new MCP server connects through the proxy and makes its first tool call, BlackLake creates an agent record automatically using the server name.

Auto-registered agents have environment: development and risk_classification: low by default. You can update these in the console or via the API.

FieldTypeDescription
namestringHuman-readable identifier. Used in bl.govern() calls and policy selectors.
environmentdevelopment | staging | productionDeployment environment. Policies can match on this.
risk_classificationlow | medium | high | criticalRisk level. Policies can match on this.
statusactive | suspended | disabledSuspended and disabled agents are denied immediately, before any policy is evaluated.
approval_modeauto_approve | require_approval | blockMetadata for external workflows. Not used by the policy engine directly.

Tools

A tool represents a capability an agent can invoke — a function, an API call, a file write, anything with a side effect.

Tools are created in two ways:

  • SDK-registered — your code calls bl.tools.create() or POST /v1/tools.
  • MCP-discovered — when the MCP proxy intercepts a call to a tool it has not seen before, it creates a tool record automatically.
FieldTypeDescription
namestringHuman-readable identifier. Used in bl.govern() calls and policy selectors.
risk_classificationlow | medium | high | criticalRisk level of the capability. Policies can match on this.

For SDK-registered agents, a binding must exist between the agent and the tool before governance will proceed. The proxy creates bindings automatically as it discovers new tools.


Agent-Tool Bindings

A binding is an explicit allowlist entry. For SDK-integrated agents, an agent can only be governed against tools it is bound to. If no binding exists, Surface returns deny without evaluating any policies.

The MCP proxy creates and manages bindings automatically for MCP-registered agents. When you use the SDK, call bl.agents.bindTool(agentId, toolId) after creating both records.


Policies

A policy is a rule that maps a set of agents and tools to an outcome. Policies are the primary mechanism for expressing what your agents are and are not allowed to do.

FieldTypeDescription
namestringDescriptive label.
priorityintegerEvaluation order. Lower numbers are evaluated first.
agent_selectorobjectKey-value pairs matched against the agent record. An empty object ({}) matches all agents.
tool_selectorobjectKey-value pairs matched against the tool record. An empty object ({}) matches all tools.
outcomeallow | deny | approval_requiredThe decision returned when this policy matches.
enabledbooleanDisabled policies are excluded from evaluation entirely.

First match wins. Policies are evaluated in ascending priority order. The first policy whose selectors both match is applied. Evaluation stops there.

Default deny. If no policy matches, the outcome is default_deny. There is no implicit allow.

MCP policies. Policies for MCP-auto-registered agents work exactly the same as policies for SDK-registered agents. The mcp-config.json policy field is a shorthand that creates an initial policy when the server is first registered. You can edit or delete it in the console afterwards.

For a full guide to writing policies, see the Policy Guide.


Evaluations

Every governance decision is recorded as an evaluation. Evaluations are immutable audit records. They are never deleted.

FieldDescription
agent_idThe agent that was governed.
tool_idThe tool that was requested.
policy_idThe policy that matched, or null if none matched or the request was denied before policy evaluation.
outcomeThe final decision: allow, deny, approval_required, or default_deny.
action_payloadThe action object passed to bl.govern(), if provided. For MCP calls, this is the tool input.
request_contextThe context object passed to bl.govern(), if provided.
evaluated_atUTC timestamp of the decision.

MCP tool calls appear in the Evaluations page in real time. You can also query evaluations via GET /v1/evaluations or bl.evaluations.list().


Approvals

An approval is created when a governance decision of approval_required is returned. It represents a pending human decision about whether a specific tool invocation should proceed.

Approvals are created only when a matched policy has outcome: 'approval_required'. All other outcomes do not create approvals.

For MCP calls: when an ask-type policy matches, the proxy holds the tool call open and waits for the approval to resolve before responding to the client. The tool either runs or is rejected based on the human decision — the agent sees a normal MCP response either way.

Lifecycle

pending  →  approved
         →  rejected
         →  expired
StatusMeaning
pendingAwaiting a human decision.
approvedThe tool invocation may proceed.
rejectedThe tool invocation should not proceed.
expiredNo decision was made within 24 hours. Treated as a rejection.

Expiry is checked on read, not on a background schedule. An approval past its expires_at is reported as expired.


Webhooks

Webhooks deliver event notifications to a URL you register. When an approval is created, approved, or rejected, BlackLake sends an HTTP POST to your endpoint with a signed JSON payload.

EventFired when
approval.createdA governance call returns approval_required and an approval record is created.
approval.approvedPOST /v1/approvals/:id/approve is called successfully.
approval.rejectedPOST /v1/approvals/:id/reject is called successfully.

Delivery is fire-and-forget with a 5-second timeout. Failures are logged but not retried. Inspect delivery history via GET /v1/webhooks/:id/deliveries.

Every outgoing request is signed with HMAC-SHA256. See the API Reference for the signature format and a verification example.


API Proxy

The API proxy routes your LLM API calls through BlackLake so usage is tracked centrally.

Proxy pathForwards to
/proxy/anthropic/*api.anthropic.com
/proxy/openai/*api.openai.com
/proxy/ollama/*localhost:11434

Point your AI SDK's baseURL at the local proxy. BlackLake forwards the request using your own API key (passed in the Authorization header as normal) and records the model, token counts, and estimated cost from the response. No request or response body is stored — only the usage metadata.

The API proxy does not require authentication against BlackLake. It uses whatever credentials you pass to the upstream API.


Usage Tracking

Every request proxied through the API proxy generates a usage record:

  • Provider and model
  • Input and output token counts
  • Estimated cost in USD (based on the model's published rates)
  • Timestamp

Usage is queryable via GET /v1/usage with optional period and provider filters. The Usage page in the console shows a summary view.


Cloud Mode

The cloud product at console.blacklake.systems adds:

  • Persistence across machines — your agents, tools, policies, and evaluation history are stored centrally
  • Team access — multiple users can view and manage the console
  • Mobile approval UI — approve or reject pending tool calls from your phone
  • Long-term retention — evaluation history is not bounded by local disk space

When you create a cloud account, you get an API key. Point the SDK at https://api.blacklake.systems and pass that key. The cloud API is identical to the local API — the same endpoints, same data model, same policy engine.

The local CLI can sync to the cloud: run npx @blacklake-systems/surface-cli --cloud with your API key set and local data is pushed up on startup.


Governance Flow

Agent wants to use Tool
         |
         v
  Is agent status active?
  (suspended or disabled)
         |
    YES  |  NO --> deny immediately
         |
         v
  Is there a binding between
  this agent and this tool?
  (SDK agents only; MCP agents
  are auto-bound)
         |
    YES  |  NO --> deny (no binding)
         |
         v
  Load enabled policies,
  ordered by priority ASC
         |
         v
  For each policy:
  Does agent_selector match?  AND
  Does tool_selector match?
         |
  First  |  No match for any policy
  match  |
    |    +---> default_deny
    v
  Apply outcome:
  allow / deny / approval_required
         |
         v
  Record evaluation (always)
         |
         v
  Return decision to caller

The caller — your agent runtime or the MCP proxy — is responsible for acting on the decision. Surface records the decision but does not itself execute or block the tool.


Security and Local Mode

BlackLake runs on localhost over HTTP. This is safe for local development — the API and dashboard are only accessible from your machine.

Do not expose ports 3100 or 3200 to your network. If you need remote access to your governance data, use the cloud console at console.blacklake.systems.

Webhook signing secrets and API keys are stored in the local SQLite database at ~/.blacklake/blacklake.db. This file is not encrypted. For production use with sensitive data, connect to the cloud console.