> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agents

> Declare an AI agent as a role and a list of tools, and give each conversation a short-lived, scoped session

<Warning>Agents are in beta.</Warning>

An agent is a role of your [access model](/access/access-model) plus a list of
tools. You declare it once; Bijection serves it as an
[MCP endpoint](/mcp/overview) at `/agents/<name>` and handles its grants,
limits and tokens. The agent itself (the model, the conversation, voice) runs
in your own host.

Three things take part:

* **An agent** is code. It names the resource type one session acts for, the
  role a session holds, and its tools.
* **A credential** is a key that can only start sessions. It reads no data.
* **A session** is one conversation. It holds the agent's role on one object,
  such as one customer, until it expires.

Because a session is an ordinary grant of an ordinary role, your access rules
decide everything it does, on its endpoint and on your app's regular API alike.

Use `bijection/mcp` directly instead when your app writes its own access
rules: agents need an access model.

## Declaring an agent

Give the agent a role, and give its credentials a role whose only business
permission is the power to grant that role:

```ts bijection/access.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineAccessModel } from "bijection/server";

export const access = defineAccessModel({
  resources: {
    customer: { permissions: ["see", "read.risk"] },
    refund: { parent: "customer" },
  },
  roles: {
    "root:owner": ["root:*", "customer:*", "refund:*"],
    "root:supportIssuer": ["customer:see", "customer:grant.supportAgent"],
    "customer:supportAgent": {
      permissions: [
        "customer:see",
        "customer:read",
        "refund:read",
        "refund:create",
      ],
      maxDuration: 30 * 60 * 1000,
    },
  },
});
```

`see` covers a customer's identifier alone, so a credential can tell that a
customer exists and reads none of its fields. The agent's role has no
`read.risk`, so the fields behind it never reach the agent. `maxDuration` is
the longest a session can last.

Then declare the agent and export the module:

```ts bijection/agents.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { agentTool, agentsModule, defineAgent } from "bijection/agents";
import { access } from "./access";
import { context, hours, refund } from "./support";

export const support = defineAgent({
  name: "support",
  description: "Customer support for verified customers.",
  actsFor: "customer",
  role: "customer:supportAgent",
  credentialRole: "root:supportIssuer",
  tools: {
    get_context: agentTool.query(
      "support:context",
      context,
      "Read the verified customer's name, tier and remaining refundable amount.",
    ),
    opening_hours: agentTool.query(
      "support:hours",
      hours,
      "Read the support desk's opening hours.",
    ),
    request_refund: agentTool.operation(
      "support:refund",
      refund,
      "Request a refund for the verified customer.",
    ),
    refund_status: agentTool.status(
      "support:refund",
      refund,
      "Check an accepted refund request.",
    ),
  },
  beforeVerification: ["opening_hours"],
  limits: { callsPerMinute: 30 },
});

export default agentsModule({ access, agents: [support] });
```

Each tool takes the definition's `module:export` path, the definition itself
and the description the model reads. A tool must be a public query or
[operation](/operations/overview) with explicit validators: publishing it adds
a description, never a way in.

Keep the default export in `bijection/agents.ts`. To export it from another
file, pass that file's path as `module` to `agentsModule` and to
`agentOperationPolicy`.

Add the tables and the operation policy to your schema, and mount the routes:

```ts bijection/schema.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineSchema, installOperationPolicy } from "bijection/server";
import { agentOperationPolicy, agentTables } from "bijection/agents";
import { access } from "./access";

const schema = defineSchema({
  // ...your tables
  grants: access.protectGrants(),
  ...agentTables(),
});

export default installOperationPolicy(schema, agentOperationPolicy());
```

```ts bijection/http.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { httpRouter } from "bijection/server";
import { mountAgents } from "bijection/agents";
import agents from "./agents";

const http = httpRouter();
mountAgents(http, agents);
export default http;
```

The operation policy lets a session read, preview and invoke exactly the
operations among its tools. It decides for every caller, so if people also
invoke operations in your app, pass `operations: { reads, decide }` to
`agentsModule` to decide for them.

## Reading the session in a tool

A query learns whom the agent acts for from the session, so the model never
names a customer:

```ts bijection/support.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { agentSession } from "bijection/agents";
import { v } from "bijection/values";
import { query } from "./_generated/server";

export const context = query({
  args: {},
  returns: v.object({ name: v.string(), tier: v.string() }),
  handler: async (ctx) => {
    const session = await agentSession(ctx);
    if (!session?.actsFor) throw new Error("Access refused");
    const customer = await ctx.db.get("customers", session.actsFor.key, {
      select: ["name", "tier"],
    });
    return { name: customer.name, tier: customer.tier };
  },
});
```

Select only the fields the agent's role may read. Reading the whole document
asks for every field, and is refused.

## Seeing what an agent may do

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection agent can support
```

prints the role's permissions, the permissions its types declare and it
lacks, and its tools. With a credential, it starts a one-minute session on a
real object and prints what that session holds:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection agent can support --key support.voice-prod.agentkey --for <customer id>
```

`bijection agent try` starts the same kind of session and lists its tools or
calls one, exactly as an agent host would.

## Creating a credential

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection agent credential create support --name voice-prod
```

This does three things, in order: it grants the credential its role as your
verified business identity, so you need the power to grant that role; it
installs the credential's public key on the deployment; and it writes the
private key to `support.voice-prod.agentkey`. Bijection keeps no copy. Store
the file's one line as a secret in your backend, then delete the file.

A credential belongs to one deployment. Create one for each of development,
staging and production.

Use `--on <key>` when the credential's role is granted on one object, such as
one organization, rather than on the whole deployment.

To revoke a credential:

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection agent credential revoke support <credential>
```

Every token the credential signed stops working at its next request.

## Starting a session

In your backend, after you have verified who is calling, start a session and
hand the agent's host its endpoint and token:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { connectAgent } from "bijection/agents/node";

const support = connectAgent({ key: process.env.SUPPORT_AGENT_KEY! });

const session = await support.startSession({
  actsFor: customerId,
  channel: "voice",
  evidence: { method: "otp" },
});

// For a host that takes a URL and a bearer token:
const { url } = session;
const token = session.token();

// For a host that calls operation tools through bijection/mcp/node:
const mcp = session.connect({ store });
```

Bijection records your `evidence` and does not check it: only a credential
holder can start a session, and you decide when a caller is verified.

A session never changes whom it acts for. Before you know who is calling,
start a session with `actsFor: null`: it holds no grant and only the tools in
`beforeVerification`. Once the caller is verified, start another session.

`bijection/agents/node` runs in your own Node.js process. Don't import it from
your `bijection/` folder.

Call `session.end()` when the conversation ends. Otherwise the session ends
when it expires.

## What a session can and cannot do

* It may call the tools it was offered when it started. A tool you add later
  reaches new sessions only.
* It may read and change what its role permits on its one object. Anything
  else is refused, through the endpoint or through your app's API.
* It is never a person: it cannot approve, grant permissions, delegate, or
  take any decision that names who decided.
* Its requests are limited each minute by `limits`.

When you deploy while a conversation is running, the session keeps every tool
whose definition you left unchanged. A tool whose name, description or schema
changed answers `tool_changed` for that session.

When a tool call reads something the session may not, such as another
customer's record, the request fails as a whole rather than returning a tool
error.

## Watching and closing sessions

```sh theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection agent sessions support
bijection agent revoke <session>
```

The console's **Agents** page shows the same: each declared agent, the
installed credentials and recent sessions, with a button to close a live one.

Closing a session ends its endpoint access at once. Its grant, and so its
access through your app's API, lasts until the session expires unless its
credential ends it or is revoked.

## Limits

* Agents need an access model whose grants name identities directly. A model
  that declares a `users` directory is not supported yet.
* A session cannot start on an object under a tenant restriction or a marking.
* Up to 16 agents in a module, 32 tools in an agent and 24 credentials in a
  deployment.
* An agent cannot keep working for a signed-in person after that person's own
  session ends.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.