Let AI act without giving up control.

Canopy is a governed runtime for AI agents. It runs the model loop, connects models and tools from anywhere, enforces exactly what agents can see and do, and records every session as a sealed execution history.

const session = await Canopy.start({
  key:    process.env.CANOPY_KEY!,
  values: { params: { temperature: 0.2 } },
  tools:  {
    time: async () => new Date().toISOString(),
  },
});

session.onReply((turn) => console.log(turn.text));
await session.send("What time is it?");
await session.done();

a key, your tools, and words: the whole surface

the shape of it

You provide the intelligence and the capabilities. Canopy provides the execution environment around them.

Canopy is not watching someone else's agent loop from the outside. It is the thing driving the loop, which is exactly why it can make real guarantees about authority, execution, and what happened.

your app  ·  your models  ·  your tools & MCP servers
canopy
the agent loop  ·  policy & authorization  ·  session runtime
tool routing  ·  observability  ·  audit & sealing
real-world effects
01 · RUN

Canopy runs the agent.

Dial a session with a key and start sending. Canopy owns the model loop, the context, the tool calls, and the retries. Any model, any provider, your keys.

02 · GOVERN

You control what it can do.

Policy decides which tools exist, and with which arguments. Deny by default, denial wins, and nothing reaches an effect without a recorded verdict admitting it.

03 · PROVE

Know what happened.

Every session is one ordered log of inputs, decisions, invocations and results, sealed at close. The record isn't a report about the run. It is the run.

govern

Least privilege isn't middleware. It's the execution model.

Nothing gets implicit authority. Not the caller, not the tool, not Canopy's own internals. Authority is established by identity, compiled from your config, and carried as a capability all the way to the effect, so a denied action never happens rather than being asked nicely not to.

Policy governs the whole surface: which model, which tools, and which arguments those tools will accept. A saved policy lives with the key's configuration, and a session can also carry one inline.

// policy and run, in the same file
const session = await Canopy.start({
  key:   process.env.CANOPY_KEY!,
  tools: { "refunds.issue": issueRefund },
  values: {
    policy: {
      allows: [
        { requestee: "refunds", op: "issue" },
      ],
      tools: [{
        name:      "refunds.issue",
        requestee: "refunds",
        op:        "issue",
        schema:    refundSchema,
      }],
    },
  },
});
// nothing outside this exists for the model
tools from anywhere

Any model.
Tools from anywhere.
One governed runtime.

Capabilities reach an agent from wherever you already keep them. They all arrive at the same governance surface, and none of them get a shortcut around it.

Your application's own functions

Plain functions handed to the session at dial. The callable surface is pinned when the session starts.

A local MCP server

Adapt an in-process server with mcpLocal() and its tools join the same surface.

A remote MCP server

Connect it once through OAuth. Canopy holds the connection and relays every admitted call through one executor.

Any model or provider

Bring your own keys and pick the model per session, within what the configuration permits.

prove

Every run is an execution record.

A Canopy session is not a transcript written alongside the work. It is one totally-ordered log that the work happens through: the inputs, the policy it ran under, every verdict, every invocation and its result. Services can only react to what is on that log.

The arguments land once, addressed to the executor that will run it. The request, the verdict and the completion reference that payload by digest rather than copying it.

Decisions replay off the record afterward, which makes "what could this agent see, and what did it do" a question with one answer instead of an investigation.

// the session's own tail: the records
// the runtime itself ran on
for await (const r of session.records()) {
  console.log(r.cursor, r.kind);
}

// 12  app.policy.configured   session policy
// 13  app.client.user_message "refund 4471"
// 14  inference.responded     wants a tool
// 15  app.invoke.requested    + payload digest
// 16  app.invoke.payload      args to executor
// 17  app.invoke.forward      admitted
// 18  app.invoke.completed    outcome: success
// 19  inference.responded     tries a second
// 20  app.invoke.requested    payments.send
// 21  app.invoke.denied       outside policy
// 22  inference.responded     the model adapts
// 23  app.session.halted      terminal boundary
one runtime

One thing to adopt,
not five.

An LLM gateway, an MCP gateway, a policy layer, a tool registry, an observability stack. Five products with five APIs, five dashboards, and seams nobody owns. Canopy is one runtime, and governance should be cheap enough to leave on.

One SDK

One client, one start, no bundler and no build step.

One console

Configs, policies, providers, connections, sessions, and a dashboard over the same event surface the runtime uses.

One event surface

Session history, causality, tool execution, model activity, policy decisions and failures are all the same records, not five exports.

why not just mcp

MCP gives you connectivity. Not governance.

No verdicts, no argument caps, no identity, no audit trail. Canopy is the layer that adds them, and it embraces MCP rather than fighting it. A server you already run connects once and becomes a governed source of tools, on the same record as everything else.

status · working runtime, taking design partners

Bring us something an agent shouldn't be trusted with.

The runtime runs our own production app today. It isn't packaged for self-serve yet, deliberately. The first partners integrate with us hands-on and shape what gets packaged.

aiden@canopyhq.dev →