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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Required: base URL of the CommandLayer runtime service.
# Used by api/verify-receipt.js (proxy) and api/health.js.
# Example: https://runtime.commandlayer.org
RUNTIME_BASE_URL=https://runtime.commandlayer.org
20 changes: 0 additions & 20 deletions api/verify-receipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,6 @@ module.exports = async function handler(req, res) {
const schema = qflag(req.query?.schema, '0');
const verifyUrl = `${RUNTIME_BASE}/verify?ens=${ens}&refresh=${refresh}&schema=${schema}`;

console.log('[verify-receipt] runtime verify target', JSON.stringify({ runtime_url: verifyUrl }));
console.log('[verify-receipt] outgoing verify body', JSON.stringify({ body: bareReceipt }));
console.log('[verify-receipt] normalized receipt', JSON.stringify({ normalized_receipt: bareReceipt }));

try {
const upstream = await fetchTextWithTimeout(verifyUrl, {
method: 'POST',
Expand All @@ -165,8 +161,6 @@ module.exports = async function handler(req, res) {
: { ok: false, error: 'Non-JSON response from runtime /verify', raw: String(upstream.text || '').slice(0, 2000) };
const normalizedChecks = normalizeChecks(data, schema === '1');

console.log('[verify-receipt] raw verify response', JSON.stringify({ runtime_url: verifyUrl, body: data }));

return res.status(upstream.status).end(
JSON.stringify(
{
Expand All @@ -180,13 +174,6 @@ module.exports = async function handler(req, res) {
runtime_content_type: upstream.contentType,
normalized_receipt_used: bareReceipt,
},
logs: {
runtime_url: verifyUrl,
outgoing_request_body: bareReceipt,
raw_runtime_response_body: data,
normalized_receipt_chosen: bareReceipt,
canonical_validation_failure_reason: data?.error || null,
},
},
null,
2
Expand All @@ -205,13 +192,6 @@ module.exports = async function handler(req, res) {
verify: { ens, refresh, schema },
normalized_receipt_used: bareReceipt,
},
logs: {
runtime_url: verifyUrl,
outgoing_request_body: bareReceipt,
raw_runtime_response_body: null,
normalized_receipt_chosen: bareReceipt,
canonical_validation_failure_reason: e?.message || String(e),
},
},
null,
2
Expand Down
156 changes: 85 additions & 71 deletions docs/wrap-your-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,19 @@ Turn any agent action into a signed, verifiable receipt.

This is the core primitive of CommandLayer:

**Agents dont make claims — they produce proof.**
**Agents don't make claims — they produce proof.**

---

## Install

```
bash
```bash
npm install @commandlayer/agent-sdk
```


## What this does
When you wrap an agent action:

When you wrap an agent action:

- Your action executes normally
- CommandLayer captures the output
Expand All @@ -29,190 +27,206 @@ When you wrap an agent action:

## Quick start

```
```ts
import { CommandLayer } from "@commandlayer/agent-sdk";

const cl = new CommandLayer({
agent: "runtime.commandlayer.eth",
privateKey: process.env.CL_PRIVATE_KEY_PEM,
keyId: "vC4WbcNoq2znSCiQ",
verifierUrl: "https://www.commandlayer.org/api/verify"});
agent: "runtime.commandlayer.eth",
privateKey: process.env.CL_PRIVATE_KEY_PEM,
keyId: "vC4WbcNoq2znSCiQ",
verifierUrl: "https://www.commandlayer.org/api/verify"
});

const result = await cl.wrap("summarize", async () => {
return { summary: "hello world" };
});

const result = await cl.wrap("summarize", async () => { return { summary: "hello world" };});
console.log(result.output);console.log(result.receipt);
console.log(result.output);
console.log(result.receipt);

const verified = await cl.verify(result.receipt);
console.log(verified.status);
```

## What `wrap()` returns
`wrap()` returns both:

`wrap()` returns both:

- `output` — the value returned by your agent function
- `receipt` — the signed CommandLayer receipt for that action


### Example
```

```ts
const result = await cl.wrap("summarize", async () => {
return { summary: "AI agents need verification" };});
return { summary: "AI agents need verification" };
});

console.log(result.output);
console.log(result.receipt);
```

### Example receipt

```json
{
"signer": "runtime.commandlayer.eth",
"verb": "summarize",
"ts": "2026-05-02T02:53:33.056Z",
"input": {},
"output": {
"summary": "hello world"
},
"execution": {
"status": "ok",
"duration_ms": 1,
"started_at": "2026-05-02T02:53:33.056Z",
"completed_at": "2026-05-02T02:53:33.057Z"
},
"metadata": {
"proof": {
"canonicalization": "json.sorted_keys.v1",
"hash_sha256": "14e559e9454eaba437934220623b95947fdbaf38d45a1d358c327622c8352617"
}
},
"signature": {
"alg": "ed25519",
"kid": "vC4WbcNoq2znSCiQ",
"sig": "..."
}
}
```
{ "signer": "runtime.commandlayer.eth",
"verb": "summarize",
"ts": "2026-05-02T02:53:33.056Z",
"input": {},
"output": {
"summary": "hello world"
},
"execution": {
"status": "ok",
"duration_ms": 1,
"started_at": "2026-05-02T02:53:33.056Z",
"completed_at": "2026-05-02T02:53:33.057Z"
},
"metadata": {
"proof": {
"canonicalization": "json.sorted_keys.v1",
"hash_sha256": "14e559e9454eaba437934220623b95947fdbaf38d45a1d358c327622c8352617"
}
},
"signature": {
"alg": "ed25519",
"kid": "vC4WbcNoq2znSCiQ",
"sig": "..." }}
```

### What verification checks

## What verification checks

The verifier:

- Rebuilds the canonical receipt payload
- Recomputes the SHA-256 hash
- Compares it to `metadata.proof.hash_sha256`
- Resolves signer key metadata from ENS when available
- Validates the Ed25519 signature
- Returns `VERIFIED` or `INVALID`

If the input or output changes after signing, the recomputed hash no longer matches and verification returns `INVALID`.

If the input or output changes after signing, the recomputed hash no longer matches and verification returns `INVALID.`

### ENS signer records
## ENS signer records

The signer should publish key metadata through ENS TXT records.

For `runtime.commandlayer.eth,` the important records are:
For `runtime.commandlayer.eth`, the important records are:

```
cl.sig.kid=vC4WbcNoq2znSCiQ
cl.sig.pub=ed25519:<base64-public-key>
cl.sig.canonical=json.sorted_keys.v1
cl.receipt.signer=runtime.commandlayer.eth
```

The private key stays local. Never commit it, paste it into frontend code, or publish it.

### Verify through the public API
```
## Verify through the public API

```ts
const verified = await cl.verify(result.receipt);
if (verified.status === "VERIFIED") {
console.log("VERIFIED");
console.log("VERIFIED");
} else {
console.log("INVALID");
console.log("INVALID");
}
```

#### Default verifier:
Default verifier:

```
POST https://www.commandlayer.org/api/verify
```

### VerifyAgent.eth callable endpoint
## VerifyAgent.eth callable endpoint

VerifyAgent.eth can also be called directly:

```
POST https://www.commandlayer.org/api/agents/verifyagent
```

#### Request
```
### Request

```json
{
"receipt": {
"...": "CommandLayer receipt"
"receipt": {
"...": "CommandLayer receipt"
}
}
```

VerifyAgent.eth does not execute the original task. It verifies whether a submitted receipt is valid or tampered.

#### Verify in the browser
## Verify in the browser

Paste any receipt into:

```
https://www.commandlayer.org/verify.html

```

Expected behavior:

- valid receipt → VERIFIED
- tampered receipt → INVALID

#### Full proof demo

## Full proof demo

The SDK includes a full proof-loop demo:
```

```bash
npm run example:demo
```

It runs:

```
wrap action → emit receipt → verify → tamper → verify invalid
```

Expected output:

```
Original receipt verification: VERIFIED
Tampered receipt verification: INVALID
```


### Why this matters
## Why this matters

Without CommandLayer:

- agents only claim what happened
- platforms are trusted by default
- outputs can be edited without proof
- verification is not portable


With CommandLayer:


- every action can produce a signed receipt
- signer identity can be resolved through ENS
- receipts can be verified independently
- tampering breaks the proof

### Design principles

## Design principles

- deterministic receipts
- independent verification
- no platform trust required
- composable across agents and apps

### Next steps
## Next steps

- Install the SDK
- Wrap one important agent action
- Emit a signed receipt
- Verify it through VerifyAgent.eth
- Expose receipts anywhere users need proof



### One-line summary
## One-line summary

**Wrap your agent → produce a receipt → prove what actually happened.**
Loading
Loading