Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Product release notes: [docs.proofable.me/changelog](https://docs.proofable.me/c

### Added

- **Your app's users can talk to your agent, and your app pays.** [Talk to your agent from any app](/agents/talk-to-an-agent) shows the sign-in with `ProofableSignIn`, the call as the person (`agentChat` with `agentWallet` runs your public agent in their own profile, with nothing of yours in the conversation), and the billing: `proofable sponsor` turns sponsorship on once, and a client with `appId` and `billingWallet` makes your organization the payer within the daily cap. The refusal table gains `SPONSOR_CAP_EXCEEDED`.

- **Benchmark.** [Benchmark](/benchmark) replaces the Evidence page. It opens on one table of outcomes, each beside the setup it was measured against: 48 of 48 governance scenarios, every out-of-permission action stopped before it runs, 0 of 6 data-theft attempts out of a hijacked agent, 150 of 150 forged records caught, $0.024 against $0.078 per handed-off result, reuse 750 times faster than redoing the work, 42 times fewer tokens for agent memory, a 3.1 times lower worst-case research spend, and one identity check reused by 100 apps for $1.75 against $150. How each result was measured is in one section for reviewers. `/evidence`, `/benchmarks` and `/use-cases/authority-at-dispatch` open it.

- **Sell a service your server runs.** [Your server runs it](/gates/your-server) covers a listing whose orders go to your own URL with the buyer's inputs and repository, and whose result you post back onto the buyer's receipt.
Expand Down
63 changes: 56 additions & 7 deletions agents/talk-to-an-agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,56 @@ turn.credits; // what the turn spent

Sign the client in with an [access key](/deployment/access-keys) on a server, or with the signed-in person's session in a browser. Never ship an access key in browser code.

## Let your app's users talk to your agent

Sign your users in with Proofable, then make the same call as them. Each person talks to your public agent inside their own Proofable profile: their records, and nothing of yours in the conversation. What the agent knows is its instructions and the records you made public. Your app pays.

```javascript
import { ProofableClient, ProofableSignIn } from '@proofable/sdk';

// clientId: what register() returned the first time, kept with your app's settings.
const signIn = new ProofableSignIn({ redirectUri: 'https://your.app/proofable/callback', clientId: settings.proofableClientId });

// 1. Send the person to Proofable. Keep `state` and `verifier` until they are back; a signed cookie works.
const started = await signIn.start();
redirect(started.url);

// 2. They are back at your callback with `code` and `state` in the URL.
const tokens = await signIn.finish({ code: query.code, verifier: kept.verifier, state: query.state, expectedState: kept.state });

// 3. Act as the person, billed to your app.
const person = new ProofableClient({
apiKey: tokens.accessToken,
appId: 'your-app',
billingWallet: '0xYourAccount',
appOrigin: 'https://your.app',
});
const who = await person.me(); // { handle, address, ... }
const turn = await person.agentChat({
agentId: 'my-agent',
agentWallet: '0xYourAccount', // your account: the agent runs in the person's own profile
turnId: `u-${yourUserKey}-${Date.now()}`, // your key for the person; every spend record carries it
messages: [{ role: 'user', content: 'Where does the case start?' }],
});
```

**Sign-in** is the one every MCP host uses ([MCP OAuth](/mcp/oauth)). `start()` with no `clientId` registers your callback and returns the id; store it with your app's settings and pass it to every later instance. Renew with `signIn.refresh(tokens.refreshToken)` before the token expires.

**Who pays.** Your app does. Turn it on once, signed in as yourself:

```bash
npx -y @proofable/sdk sponsor --app your-app --origin https://your.app --credits-per-day 2000
```

From then on a client built with `appId` and `billingWallet` carries your sponsorship: your organization pays for your users' checks and their chats with your agents, within the daily cap you set, and every spend record names your app and your `turnId`, so limits per person are yours to apply. Without `appId` and `billingWallet`, each person spends their own credits. In code, the same step is `client.sponsorApp({ appId, origin, creditsPerDay })`.

**What the agent may do** for the person is fixed: read and add to the person's own records, and search the web. It cannot spend, grant, change keys or run code, and a high-risk action is refused. The agent must be public or link-only on your account. When it keeps words the person confirms, the record is the person's own, and they can open it at `proofable.me/proof/<qHash>`. Keeping it is one credit from the person's own allowance, which every account has; the turns are what your app pays.

## What happens on each turn

1. Proofable finds the [permission](/agents/agent-delegation) you signed for the agent. No active permission from your account, no turn.
2. The turn runs as the agent: its own instructions and skills from its [identity](/agents/agent-identity), and the connected apps you bound to it.
3. Every tool call is checked against the permission before it runs, read again for each call. Revoke the permission and the next call stops, in every app that talks to the agent.
1. Proofable finds the [permission](/agents/agent-delegation) you signed for the agent. With none, the turn runs only if `agentWallet` names someone else's public agent, in your own profile, under the fixed rule above.
2. The turn runs as the agent: its own instructions and skills from its [identity](/agents/agent-identity), and, for your own agent, the connected apps you bound to it.
3. Every tool call is checked before it runs, against the permission or the fixed rule, read again for each call. Revoke the permission and the next call stops, in every app that talks to the agent.
4. The answer comes back with the records the agent read. Each one opens at `proofable.me/proof/<qHash>`, so anyone you share it with can check where the answer came from.

## Over HTTP
Expand All @@ -50,7 +95,7 @@ curl -N https://api.proofable.me/api/v1/agent/chat/turn \
| Field | What it does |
|---|---|
| `agentId` | Run the turn as this agent. Omit it to run as yourself. |
| `agentWallet` | The agent's account, when two of your agents share an id |
| `agentWallet` | The agent's account: when two of your agents share an id, or to talk to someone else's public agent, which then runs in your own profile |
| `messages` | The conversation, `user` and `assistant` turns, ending with the user |
| `systemPrompt` | Context from your app. The agent reads it as data, after its own instructions. |
| `model` | `default` or `advanced`. With `provider`, that provider's model id. |
Expand All @@ -68,18 +113,22 @@ The stream sends `turn.start`, `answer.delta` as the answer arrives, `tool.start
}
```

For someone else's public agent, `delegationQHash` is `null`: there is no permission between you, the agent ran in your own profile.

A request Proofable refuses never opens the stream. It answers JSON with the reason:

| Status | Code | Why |
|---|---|---|
| 401 | `AUTH_REQUIRED` | No session, OAuth token, or access key |
| 402 | `INSUFFICIENT_CREDITS` | Not enough credits for a hosted turn |
| 403 | `AGENT_DELEGATION_REQUIRED` | No active permission from your account to this agent |
| 402 | `INSUFFICIENT_CREDITS` | Not enough credits for a hosted turn, on the account that pays |
| 402 | `SPONSOR_CAP_EXCEEDED` | The sponsoring app has used its daily cap |
| 403 | `AGENT_DELEGATION_REQUIRED` | No active permission from your account to this agent, and no public agent of that id at `agentWallet` |
| 403 | `SPONSOR_GRANT_INVALID` | The app's sponsor grant was sent but not accepted: expired, already used, for another app or origin, or for an agent that is not the sponsoring account's. The person is never charged instead |
| 403 | `ADVANCED_REQUIRES_PRO` | Advanced runs on paid plans |

## What it costs

A hosted turn spends credits from the account that calls it, by the tokens it used and the model that served them, the same rate as a [job](/jobs) step. A turn on your own model key spends no platform credits. See [pricing](/pricing).
A hosted turn spends credits from the account that calls it, by the tokens it used and the model that served them, the same rate as a [job](/jobs) step. A turn your app sponsors spends the app owner's credits instead, within the app's daily cap. A turn on your own model key spends no platform credits. See [pricing](/pricing).

Read your balance and spend at any time, by line and by day:

Expand Down
8 changes: 4 additions & 4 deletions openapi/public-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -4838,7 +4838,7 @@
"post": {
"operationId": "streamAgentChatTurn",
"summary": "Talk to an agent: run one chat turn as it, streamed as Server-Sent Events",
"description": "Runs one turn on the hosted agent runtime. With agentId the turn runs as that agent: its instructions and skills, under the delegation you signed, every tool call checked against it. The final event carries the answer, the records it read as citations, and the credits it spent. Nothing to deploy: call it from any app or runtime with a session, an OAuth token or a profile access key.",
"description": "Runs one turn on the hosted agent runtime. With agentId the turn runs as that agent: its instructions and skills, under the delegation you signed, every tool call checked against it. Name agentWallet to talk to someone else's public agent: it then runs in your own profile, on your records, and nothing of its owner's is part of the conversation; with your app's sponsor grant, your app pays. The final event carries the answer, the records it read as citations, and the credits it spent. Nothing to deploy: call it from any app or runtime with a session, an OAuth token or a profile access key.",
"tags": [
"Agents"
],
Expand All @@ -4859,7 +4859,7 @@
},
"agentWallet": {
"type": "string",
"description": "The agent's account, when two of the caller's agents share an agentId."
"description": "The agent's account: when two of the caller's agents share an agentId, or to talk to someone else's public agent, which then runs in the caller's own profile, with nothing of its owner's in the conversation."
},
"messages": {
"type": "array",
Expand Down Expand Up @@ -4926,10 +4926,10 @@
"description": "Authentication required"
},
"402": {
"description": "Not enough credits for a hosted turn"
"description": "Not enough credits for a hosted turn on the account that pays, or the sponsoring app has used its daily cap (SPONSOR_CAP_EXCEEDED)"
},
"403": {
"description": "No active delegation for this agent, or Advanced without a paid plan"
"description": "No active delegation for this agent and no public agent of that id at agentWallet, Advanced without a paid plan, or a sponsor grant that was not accepted (SPONSOR_GRANT_INVALID)"
},
"404": {
"description": "Not found",
Expand Down
Loading